PageRenderTime 116ms CodeModel.GetById 5ms RepoModel.GetById 1ms app.codeStats 0ms

/dumbhippo/branches/production/server/src/com/dumbhippo/server/impl/IdentitySpiderBean.java

https://gitlab.com/manoj-makkuboy/magnetism
Java | 932 lines | 702 code | 149 blank | 81 comment | 115 complexity | 89528981d5097ecd2b644ea641997503 MD5 | raw file
  1. package com.dumbhippo.server.impl;
  2. import java.net.URL;
  3. import java.util.Collection;
  4. import java.util.Collections;
  5. import java.util.Date;
  6. import java.util.HashSet;
  7. import java.util.Iterator;
  8. import java.util.Set;
  9. import javax.ejb.EJB;
  10. import javax.ejb.Stateless;
  11. import javax.persistence.EntityManager;
  12. import javax.persistence.NoResultException;
  13. import javax.persistence.PersistenceContext;
  14. import javax.persistence.Query;
  15. import org.jboss.annotation.IgnoreDependency;
  16. import org.slf4j.Logger;
  17. import com.dumbhippo.GlobalSetup;
  18. import com.dumbhippo.TypeUtils;
  19. import com.dumbhippo.identity20.Guid;
  20. import com.dumbhippo.identity20.Guid.ParseException;
  21. import com.dumbhippo.live.LiveState;
  22. import com.dumbhippo.live.LiveUser;
  23. import com.dumbhippo.live.UserChangedEvent;
  24. import com.dumbhippo.live.UserPrefChangedEvent;
  25. import com.dumbhippo.persistence.Account;
  26. import com.dumbhippo.persistence.AccountClaim;
  27. import com.dumbhippo.persistence.Administrator;
  28. import com.dumbhippo.persistence.AimResource;
  29. import com.dumbhippo.persistence.Contact;
  30. import com.dumbhippo.persistence.ContactClaim;
  31. import com.dumbhippo.persistence.EmailResource;
  32. import com.dumbhippo.persistence.ExternalAccount;
  33. import com.dumbhippo.persistence.ExternalAccountType;
  34. import com.dumbhippo.persistence.Group;
  35. import com.dumbhippo.persistence.GuidPersistable;
  36. import com.dumbhippo.persistence.LinkResource;
  37. import com.dumbhippo.persistence.Person;
  38. import com.dumbhippo.persistence.Post;
  39. import com.dumbhippo.persistence.Resource;
  40. import com.dumbhippo.persistence.Sentiment;
  41. import com.dumbhippo.persistence.User;
  42. import com.dumbhippo.persistence.UserBioChangedRevision;
  43. import com.dumbhippo.persistence.ValidationException;
  44. import com.dumbhippo.persistence.Validators;
  45. import com.dumbhippo.server.AccountSystem;
  46. import com.dumbhippo.server.Configuration;
  47. import com.dumbhippo.server.Enabled;
  48. import com.dumbhippo.server.ExternalAccountSystem;
  49. import com.dumbhippo.server.GroupSystem;
  50. import com.dumbhippo.server.HippoProperty;
  51. import com.dumbhippo.server.IdentitySpider;
  52. import com.dumbhippo.server.IdentitySpiderRemote;
  53. import com.dumbhippo.server.NotFoundException;
  54. import com.dumbhippo.server.Notifier;
  55. import com.dumbhippo.server.RevisionControl;
  56. import com.dumbhippo.server.dm.DataService;
  57. import com.dumbhippo.server.dm.UserDMO;
  58. import com.dumbhippo.server.util.EJBUtil;
  59. import com.dumbhippo.server.views.SystemViewpoint;
  60. import com.dumbhippo.server.views.UserViewpoint;
  61. import com.dumbhippo.server.views.Viewpoint;
  62. import com.dumbhippo.tx.RetryException;
  63. import com.dumbhippo.tx.TxCallable;
  64. import com.dumbhippo.tx.TxUtils;
  65. /*
  66. * An implementation of the Identity Spider. It sucks your blood.
  67. * @author walters
  68. */
  69. @Stateless
  70. public class IdentitySpiderBean implements IdentitySpider, IdentitySpiderRemote {
  71. static private final Logger logger = GlobalSetup
  72. .getLogger(IdentitySpider.class);
  73. @PersistenceContext(unitName = "dumbhippo")
  74. private EntityManager em;
  75. @EJB
  76. @IgnoreDependency
  77. private GroupSystem groupSystem;
  78. @EJB
  79. @IgnoreDependency
  80. private ExternalAccountSystem externalAccounts;
  81. @EJB
  82. private Configuration config;
  83. @EJB
  84. private Notifier notifier;
  85. @EJB
  86. private RevisionControl revisionControl;
  87. public User lookupUserByEmail(Viewpoint viewpoint, String email) throws NotFoundException {
  88. EmailResource res = lookupEmail(email);
  89. if (res == null)
  90. return null;
  91. return lookupUserByResource(viewpoint, res);
  92. }
  93. public User lookupUserByAim(Viewpoint viewpoint, String aim) throws NotFoundException {
  94. AimResource res = lookupAim(aim);
  95. if (res == null)
  96. return null;
  97. return lookupUserByResource(viewpoint, res);
  98. }
  99. public User lookupUserByResource(Viewpoint viewpoint, Resource resource) {
  100. return getUser(resource);
  101. }
  102. public User lookupUser(LiveUser luser) {
  103. return em.find(User.class, luser.getGuid().toString());
  104. }
  105. public User lookupUser(Guid guid) {
  106. return em.find(User.class, guid.toString());
  107. }
  108. public <T extends GuidPersistable> T lookupGuidString(Class<T> klass,
  109. String id) throws ParseException, NotFoundException {
  110. if (klass.equals(Post.class) || klass.equals(Group.class))
  111. logger.error("Probable bug: looking up Post/Group should use GroupSystem/PostingBoard to get access controls");
  112. return EJBUtil.lookupGuidString(em, klass, id);
  113. }
  114. public <T extends GuidPersistable> T lookupGuid(Class<T> klass, Guid id)
  115. throws NotFoundException {
  116. if (klass.equals(Post.class) || klass.equals(Group.class))
  117. logger.error("Probable bug: looking up Post/Group should use GroupSystem/PostingBoard to get access controls");
  118. return EJBUtil.lookupGuid(em, klass, id);
  119. }
  120. public <T extends GuidPersistable> Set<T> lookupGuidStrings(Class<T> klass,
  121. Set<String> ids) throws ParseException, NotFoundException {
  122. Set<T> ret = new HashSet<T>();
  123. for (String s : ids) {
  124. T obj = lookupGuidString(klass, s);
  125. ret.add(obj);
  126. }
  127. return ret;
  128. }
  129. public <T extends GuidPersistable> Set<T> lookupGuids(Class<T> klass,
  130. Set<Guid> ids) throws NotFoundException {
  131. Set<T> ret = new HashSet<T>();
  132. for (Guid id : ids) {
  133. T obj = lookupGuid(klass, id);
  134. ret.add(obj);
  135. }
  136. return ret;
  137. }
  138. public EmailResource getEmail(final String emailRaw) throws ValidationException, RetryException {
  139. // well, we could do a little better here with the validation...
  140. final String email = EmailResource.canonicalize(emailRaw);
  141. return TxUtils.runNeedsRetry(new TxCallable<EmailResource>() {
  142. public EmailResource call() {
  143. Query q;
  144. q = em.createQuery("from EmailResource e where e.email = :email");
  145. q.setParameter("email", email);
  146. EmailResource res;
  147. try {
  148. res = (EmailResource) q.getSingleResult();
  149. } catch (NoResultException e) {
  150. res = new EmailResource(email);
  151. em.persist(res);
  152. }
  153. return res;
  154. }
  155. });
  156. }
  157. public AimResource getAim(final String screenNameRaw) throws ValidationException, RetryException {
  158. final String screenName = AimResource.canonicalize(screenNameRaw);
  159. return TxUtils.runNeedsRetry(new TxCallable<AimResource>() {
  160. public AimResource call() {
  161. Query q;
  162. q = em
  163. .createQuery("from AimResource a where a.screenName = :name");
  164. q.setParameter("name", screenName);
  165. AimResource res;
  166. try {
  167. res = (AimResource) q.getSingleResult();
  168. } catch (NoResultException e) {
  169. try {
  170. res = new AimResource(screenName);
  171. } catch (ValidationException v) {
  172. throw new RuntimeException(v); // Already validated above
  173. }
  174. em.persist(res);
  175. }
  176. return res;
  177. }
  178. });
  179. }
  180. private <T extends Resource> T lookupResourceByName(Class<T> klass, String identifier, String name) throws NotFoundException {
  181. Query q;
  182. String className = klass.getName();
  183. q = em.createQuery("SELECT a FROM " + className + " a WHERE a." + identifier
  184. + " = :name");
  185. q.setParameter("name", name);
  186. T res = null;
  187. try {
  188. res = klass.cast(q.getSingleResult());
  189. } catch (NoResultException e) {
  190. throw new NotFoundException("No such resource");
  191. }
  192. return res;
  193. }
  194. public AimResource lookupAim(String screenName) throws NotFoundException {
  195. try {
  196. screenName = AimResource.canonicalize(screenName);
  197. } catch (ValidationException e) {
  198. throw new NotFoundException("Not a valid AIM address", e);
  199. }
  200. return lookupResourceByName(AimResource.class, "screenName", screenName);
  201. }
  202. public EmailResource lookupEmail(String email) throws NotFoundException {
  203. try {
  204. email = EmailResource.canonicalize(email);
  205. } catch (ValidationException e) {
  206. throw new NotFoundException("Not a valid email address", e);
  207. }
  208. return lookupResourceByName(EmailResource.class, "email", email);
  209. }
  210. public LinkResource lookupLink(final URL url) throws NotFoundException {
  211. return lookupResourceByName(LinkResource.class, "url", url.toExternalForm());
  212. }
  213. public LinkResource getLink(final URL url) {
  214. // Retrying here causes side effects all over the code; the right solution
  215. // is to get rid of LinkResource instead.
  216. // return TxUtils.runNeedsRetry(new TxCallable<LinkResource>() {
  217. // public LinkResource call() {
  218. Query q;
  219. q = em.createQuery("SELECT l FROM LinkResource l WHERE l.url = :url");
  220. q.setParameter("url", url.toExternalForm());
  221. LinkResource res;
  222. try {
  223. res = (LinkResource) q.getSingleResult();
  224. } catch (NoResultException e) {
  225. res = new LinkResource(url.toExternalForm());
  226. em.persist(res);
  227. }
  228. return res;
  229. // }
  230. //
  231. // });
  232. }
  233. private User getUserForContact(Contact contact) {
  234. for (ContactClaim cc : contact.getResources()) {
  235. Resource resource = cc.getResource();
  236. if (resource instanceof Account)
  237. return ((Account) resource).getOwner();
  238. else {
  239. AccountClaim ac = resource.getAccountClaim();
  240. if (ac != null) {
  241. return ac.getOwner();
  242. }
  243. }
  244. }
  245. return null;
  246. }
  247. public User getUser(Person person) {
  248. if (person == null)
  249. throw new IllegalArgumentException("null person in getUser()");
  250. if (person instanceof User)
  251. return (User) person;
  252. else {
  253. // logger.debug("getUser: contact = {}", person);
  254. User user = getUserForContact((Contact) person);
  255. // logger.debug("getUserForContact: user = {}", user);
  256. return user;
  257. }
  258. }
  259. public User getUser(Resource resource) {
  260. if (resource instanceof Account)
  261. return ((Account) resource).getOwner();
  262. else {
  263. AccountClaim ac = resource.getAccountClaim();
  264. if (ac != null)
  265. return ac.getOwner();
  266. return null;
  267. }
  268. }
  269. public void addVerifiedOwnershipClaim(User claimedOwner, Resource res) {
  270. // first be sure it isn't a dup - the db constraints check this too,
  271. // but it's more user friendly to no-op here than to throw a db
  272. // exception
  273. Set<AccountClaim> claims = claimedOwner.getAccountClaims();
  274. for (AccountClaim claim : claims) {
  275. if (claim.getResource().equals(res)) {
  276. logger.debug(
  277. "Found existing claim for {} on {}, not adding again",
  278. claimedOwner, res);
  279. return;
  280. }
  281. }
  282. // Now create the new db claim
  283. res.prepareToSetAccountClaim();
  284. AccountClaim ac = new AccountClaim(claimedOwner, res);
  285. em.persist(ac);
  286. // Update inverse mappings
  287. res.setAccountClaim(ac);
  288. claimedOwner.getAccountClaims().add(ac);
  289. // fix up group memberships
  290. groupSystem.fixupGroupMemberships(claimedOwner);
  291. // People may have listed the newly claimed resource as a contact
  292. Collection<Guid> newContacters = findResourceContacters(res);
  293. if (!newContacters.isEmpty()) {
  294. LiveState.getInstance().invalidateContacters(claimedOwner.getGuid());
  295. for (Guid contacter : newContacters)
  296. LiveState.getInstance().invalidateContacts(contacter);
  297. }
  298. }
  299. public void removeVerifiedOwnershipClaim(UserViewpoint viewpoint,
  300. User owner, Resource res) {
  301. Collection<Guid> oldContacters = findResourceContacters(res);
  302. if (!viewpoint.isOfUser(owner)) {
  303. throw new RuntimeException(
  304. "can only remove your own ownership claims");
  305. }
  306. Set<AccountClaim> claims = owner.getAccountClaims();
  307. if (claims.size() < 2) {
  308. // UI shouldn't let this happen, but we double-check here
  309. throw new RuntimeException(
  310. "you have to keep at least one address on an account");
  311. }
  312. for (AccountClaim claim : claims) {
  313. if (claim.getResource().equals(res)) {
  314. logger.debug("Found claim for {} on {}, removing it", owner,
  315. res);
  316. res.setAccountClaim(null);
  317. claims.remove(claim);
  318. em.remove(claim);
  319. // People may have listed resource as a contact
  320. if (!oldContacters.isEmpty()) {
  321. LiveState.getInstance().invalidateContacters(owner.getGuid());
  322. for (Guid contacter : oldContacters)
  323. LiveState.getInstance().invalidateContacts(contacter);
  324. }
  325. return;
  326. }
  327. }
  328. logger.warn("Tried but failed to remove claim for {} on {}", owner, res);
  329. }
  330. private Collection<Guid> findResourceContacters(Resource res) {
  331. Query q = em.createQuery("SELECT cc.account.owner.id " +
  332. " FROM ContactClaim cc " +
  333. " WHERE cc.resource = :resource");
  334. q.setParameter("resource", res);
  335. Set<Guid> result = new HashSet<Guid>();
  336. for (String id : TypeUtils.castList(String.class, q.getResultList())) {
  337. try {
  338. result.add(new Guid(id));
  339. } catch (ParseException e) {
  340. logger.error("Bad GUID in database: {}", id);
  341. }
  342. }
  343. return result;
  344. }
  345. private Contact findContactByUser(User owner, User contactUser) throws NotFoundException {
  346. Query q = em.createQuery("SELECT cc.contact " +
  347. " FROM ContactClaim cc, AccountClaim ac " +
  348. " WHERE cc.resource = ac.resource " +
  349. " AND cc.account = :account " +
  350. " AND ac.owner = :contact");
  351. q.setParameter("account", owner.getAccount());
  352. q.setParameter("contact", contactUser);
  353. q.setMaxResults(1);
  354. try {
  355. return (Contact)q.getSingleResult();
  356. } catch (NoResultException e) {
  357. throw new NotFoundException("No contact for user");
  358. }
  359. }
  360. public Contact findContactByResource(User owner, Resource resource) throws NotFoundException {
  361. Query q = em.createQuery("SELECT cc.contact " +
  362. " FROM ContactClaim cc " +
  363. " WHERE cc.account = :account" +
  364. " AND cc.resource = :contact");
  365. q.setParameter("account", owner.getAccount());
  366. q.setParameter("contact", resource);
  367. q.setMaxResults(1);
  368. try {
  369. return (Contact)q.getSingleResult();
  370. } catch (NoResultException e) {
  371. throw new NotFoundException("No contact for user");
  372. }
  373. }
  374. private Contact doCreateContact(User user, Resource resource) {
  375. logger.debug("Creating contact for user {} with resource {}", user,
  376. resource);
  377. Account account = user.getAccount();
  378. Contact contact = new Contact(account);
  379. // FIXME we don't want contacts to have nicknames, so leave it null for
  380. // now, but eventually we should change the db schema
  381. // contact.setNickname(resource.getHumanReadableString());
  382. em.persist(contact);
  383. // Updating the inverse mappings is essential since they are cached;
  384. // if we don't update them the second-level cache will contain stale
  385. // data.
  386. // Updating them won't actually update the data in the second-level
  387. // cache for a non-transactional cache; rather it will flag the data to be
  388. // reloaded from the database.
  389. //
  390. ContactClaim cc = new ContactClaim(contact, resource);
  391. em.persist(cc);
  392. contact.getResources().add(cc);
  393. User contactUser = getUser(resource);
  394. LiveState liveState = LiveState.getInstance();
  395. liveState.invalidateContacts(user.getGuid());
  396. if (contactUser != null)
  397. liveState.invalidateContacters(contactUser.getGuid());
  398. return contact;
  399. }
  400. public Contact createContact(User user, Resource resource) {
  401. if (user == null)
  402. throw new IllegalArgumentException("null contact owner");
  403. if (resource == null)
  404. throw new IllegalArgumentException("null contact resource");
  405. Contact contact;
  406. try {
  407. contact = findContactByResource(user, resource);
  408. } catch (NotFoundException e) {
  409. contact = doCreateContact(user, resource);
  410. // Things work better (especially for now, when we don't fully
  411. // implement spidering) if contacts own the account resource for
  412. // users, and not just the EmailResource
  413. if (!(resource instanceof Account)) {
  414. User contactUser = lookupUserByResource(SystemViewpoint
  415. .getInstance(), resource);
  416. if (contactUser != null) {
  417. ContactClaim cc = new ContactClaim(contact, contactUser
  418. .getAccount());
  419. em.persist(cc);
  420. contact.getResources().add(cc);
  421. logger.debug(
  422. "Added contact resource {} pointing to account {}",
  423. cc.getContact(), cc.getAccount());
  424. }
  425. }
  426. }
  427. return contact;
  428. }
  429. public void addContactPerson(User user, Person contactPerson) {
  430. logger.debug("adding contact " + contactPerson + " to account "
  431. + user.getAccount());
  432. if (contactPerson instanceof Contact) {
  433. // Must be a contact of user, so nothing to do
  434. assert ((Contact) contactPerson).getAccount() == user.getAccount();
  435. } else {
  436. User contactUser = (User) contactPerson;
  437. try {
  438. findContactByUser(user, contactUser);
  439. } catch (NotFoundException e) {
  440. doCreateContact(user, contactUser.getAccount());
  441. }
  442. }
  443. }
  444. public void removeContactPerson(User user, Person contactPerson) {
  445. logger.debug("removing contact {} from account {}", contactPerson, user.getAccount());
  446. Contact contact;
  447. if (contactPerson instanceof Contact) {
  448. contact = (Contact) contactPerson;
  449. } else {
  450. try {
  451. contact = findContactByUser(user, (User)contactPerson);
  452. } catch (NotFoundException e) {
  453. logger.debug("User {} not found as a contact", contactPerson);
  454. return;
  455. }
  456. }
  457. removeContact(user, contact);
  458. }
  459. public void removeContactResource(User user, Resource contactResource) {
  460. logger.debug("removing contact {} from account {}", contactResource, user.getAccount());
  461. Contact contact;
  462. try {
  463. contact = findContactByResource(user, contactResource);
  464. removeContact(user, contact);
  465. } catch (NotFoundException e) {
  466. logger.debug("Resource {} not found as a contact", contactResource);
  467. return;
  468. }
  469. }
  470. public void removeContact(User user, Contact contact) {
  471. Set<User> removedUsers = new HashSet<User>();
  472. for (ContactClaim cc : contact.getResources()) {
  473. User resourceUser = getUser(cc.getResource());
  474. if (resourceUser != null)
  475. removedUsers.add(resourceUser);
  476. }
  477. em.remove(contact);
  478. logger.debug("contact deleted");
  479. LiveState liveState = LiveState.getInstance();
  480. liveState.invalidateContacts(user.getGuid());
  481. for (User removedUser : removedUsers) {
  482. liveState.invalidateContacters(removedUser.getGuid());
  483. }
  484. }
  485. public Set<Guid> computeContacts(Guid userId) {
  486. User user = em.find(User.class, userId.toString());
  487. Query q = em.createQuery("SELECT ac.owner.id " +
  488. " FROM ContactClaim cc, AccountClaim ac " +
  489. " WHERE cc.resource = ac.resource " +
  490. " AND cc.account = :account");
  491. q.setParameter("account", user.getAccount());
  492. Set<Guid> result = new HashSet<Guid>();
  493. for (String s : TypeUtils.castList(String.class, q.getResultList())) {
  494. try {
  495. result.add(new Guid(s));
  496. } catch (ParseException e) {
  497. throw new RuntimeException("Bad GUID in database");
  498. }
  499. }
  500. return result;
  501. }
  502. public Set<Guid> computeContacters(Guid userId) {
  503. Query q = em.createQuery("SELECT cc.account.owner.id " +
  504. " FROM ContactClaim cc, AccountClaim ac " +
  505. " WHERE cc.resource = ac.resource " +
  506. " AND ac.owner.id = :userId");
  507. q.setParameter("userId", userId.toString());
  508. Set<Guid> result = new HashSet<Guid>();
  509. for (String s : TypeUtils.castList(String.class, q.getResultList())) {
  510. try {
  511. result.add(new Guid(s));
  512. } catch (ParseException e) {
  513. throw new RuntimeException("Bad GUID in database");
  514. }
  515. }
  516. return result;
  517. }
  518. public int computeContactsCount(User user) {
  519. Query q = em.createQuery("SELECT COUNT(*) FROM Contact c WHERE c.account = :account");
  520. q.setParameter("account", user.getAccount());
  521. return ((Number)q.getSingleResult()).intValue();
  522. }
  523. public Set<User> getRawUserContacts(Viewpoint viewpoint, User user) {
  524. if (!isViewerSystemOrFriendOf(viewpoint, user))
  525. return Collections.emptySet();
  526. Guid viewedUserId = user.getGuid();
  527. Set<User> ret = new HashSet<User>();
  528. for (Guid guid : LiveState.getInstance().getContacts(viewedUserId)) {
  529. // The user might be listed as their own contact, but avoid revealing that
  530. if (viewedUserId.equals(guid))
  531. continue;
  532. ret.add(em.find(User.class, guid.toString()));
  533. }
  534. return ret;
  535. }
  536. public Set<User> getUsersWhoHaveUserAsContact(Viewpoint viewpoint, User user) {
  537. if (!isViewerSystemOrFriendOf(viewpoint, user))
  538. return Collections.emptySet();
  539. Guid viewedUserId = user.getGuid();
  540. Set<User> ret = new HashSet<User>();
  541. for (Guid guid : LiveState.getInstance().getContacters(viewedUserId)) {
  542. // The user might be listed as their own contact, but avoid revealing that
  543. if (viewedUserId.equals(guid))
  544. continue;
  545. ret.add(em.find(User.class, guid.toString()));
  546. }
  547. return ret;
  548. }
  549. /**
  550. * @param user
  551. * the user we're looking at the contacts of
  552. * @param contactUser
  553. * is this person a contact of user?
  554. * @return true if contactUser is a contact of user
  555. */
  556. private boolean userHasContact(Viewpoint viewpoint, User user, User contactUser) {
  557. LiveState liveState = LiveState.getInstance();
  558. // See if we have either contacts or contacters cached for the relationship
  559. Set<Guid> contacters = liveState.peekContacters(contactUser.getGuid());
  560. if (contacters != null) {
  561. return contacters.contains(user.getGuid());
  562. }
  563. Set<Guid> contacts = liveState.peekContacts(user.getGuid());
  564. if (contacts != null) {
  565. return contacts.contains(contactUser.getGuid());
  566. }
  567. // If neither is cached, we compute one of them; we prefer to cache
  568. // information for the viewer; when neither the user or contactUser is
  569. // the viewer we prefer the contact side of the relationship (somewhat
  570. // abitrarily)
  571. if (viewpoint.isOfUser(contactUser)) {
  572. contacters = liveState.getContacters(contactUser.getGuid());
  573. return contacters.contains(user.getGuid());
  574. } else {
  575. contacts = liveState.getContacts(user.getGuid());
  576. return contacts.contains(contactUser.getGuid());
  577. }
  578. }
  579. public boolean isContact(Viewpoint viewpoint, User user, User contactUser) {
  580. // see if we're allowed to look at who user's contacts are
  581. if (!isViewerSystemOrFriendOf(viewpoint, user))
  582. return false;
  583. // if we can see their contacts, return whether this person is one of them
  584. return userHasContact(viewpoint, user, contactUser);
  585. }
  586. public boolean isViewerSystemOrFriendOf(Viewpoint viewpoint, User user) {
  587. if (viewpoint instanceof SystemViewpoint) {
  588. return true;
  589. } else if (viewpoint instanceof UserViewpoint) {
  590. UserViewpoint userViewpoint = (UserViewpoint) viewpoint;
  591. if (user.equals(userViewpoint.getViewer()))
  592. return true;
  593. return userHasContact(viewpoint, user, userViewpoint.getViewer());
  594. } else {
  595. return false;
  596. }
  597. }
  598. static final String GET_CONTACT_RESOURCES_QUERY = "SELECT cc.resource FROM ContactClaim cc WHERE cc.contact = :contact";
  599. private Resource getFirstContactResource(Contact contact) {
  600. // An invariant we retain in the database is that every contact
  601. // has at least one resource, so we don't need to check for
  602. // NoResultException
  603. return (Resource) em.createQuery(GET_CONTACT_RESOURCES_QUERY)
  604. .setParameter("contact", contact).setMaxResults(1)
  605. .getSingleResult();
  606. }
  607. public Resource getBestResource(Person person) {
  608. User user = getUser(person);
  609. if (user != null)
  610. return user.getAccount();
  611. return getFirstContactResource((Contact) person);
  612. }
  613. private Account getAttachedAccount(User user) {
  614. if (!em.contains(user))
  615. user = lookupUser(user.getGuid());
  616. return user.getAccount();
  617. }
  618. public boolean getAccountDisabled(User user) {
  619. return user.getAccount().isDisabled();
  620. }
  621. public void setAccountDisabled(User user, boolean disabled) {
  622. Account account = getAttachedAccount(user);
  623. if (account.isDisabled() != disabled) {
  624. account.setDisabled(disabled);
  625. logger.debug("Disabled flag toggled to {} on account {}", disabled,
  626. account);
  627. notifier.onAccountDisabledToggled(account);
  628. }
  629. }
  630. public boolean getAccountAdminDisabled(User user) {
  631. return user.getAccount().isAdminDisabled();
  632. }
  633. public void setAccountAdminDisabled(User user, boolean disabled) {
  634. Account account = getAttachedAccount(user);
  635. if (account.isAdminDisabled() != disabled) {
  636. account.setAdminDisabled(disabled);
  637. logger.debug("adminDisabled flag toggled to {} on account {}", disabled,
  638. account);
  639. notifier.onAccountAdminDisabledToggled(account);
  640. }
  641. }
  642. static final String GET_ADMIN_QUERY = "SELECT adm FROM Administrator adm WHERE adm.account = :acct";
  643. public boolean isAdministrator(User user) {
  644. Account acct = getAttachedAccount(user);
  645. if (acct == null)
  646. return false;
  647. boolean noAuthentication = config.getProperty(
  648. HippoProperty.DISABLE_AUTHENTICATION).equals("true");
  649. if (noAuthentication) {
  650. logger
  651. .debug("auth disabled - everyone gets to be an administrator!");
  652. return true;
  653. }
  654. try {
  655. Administrator adm = (Administrator) em.createQuery(GET_ADMIN_QUERY)
  656. .setParameter("acct", acct).getSingleResult();
  657. return adm != null;
  658. } catch (NoResultException e) {
  659. return false;
  660. }
  661. }
  662. public boolean getMusicSharingEnabled(User user, Enabled enabled) {
  663. // we only share your music if your account is enabled, AND music
  664. // sharing is enabled.
  665. // but we return only the music sharing flag here since the two settings
  666. // are distinct
  667. // in the UI. The pref we send to the client is a composite of the two.
  668. Account account = user.getAccount();
  669. Boolean musicSharingEnabled = account.isMusicSharingEnabled();
  670. if (musicSharingEnabled == null)
  671. musicSharingEnabled = AccountSystem.DEFAULT_ENABLE_MUSIC_SHARING;
  672. switch (enabled) {
  673. case RAW_PREFERENCE_ONLY:
  674. return musicSharingEnabled;
  675. case AND_ACCOUNT_IS_ACTIVE:
  676. return account.isActive() && musicSharingEnabled;
  677. }
  678. throw new IllegalArgumentException(
  679. "invalid value for enabled param to getMusicSharingEnabled");
  680. }
  681. public void setMusicSharingEnabled(User user, boolean enabled) {
  682. Account account = getAttachedAccount(user);
  683. if (account.isMusicSharingEnabled() == null || account.isMusicSharingEnabled() != enabled) {
  684. account.setMusicSharingEnabled(enabled);
  685. notifier.onMusicSharingToggled(account);
  686. LiveState.getInstance().queueUpdate(new UserPrefChangedEvent(user.getGuid(), "musicSharingEnabled", Boolean.toString(enabled)));
  687. }
  688. }
  689. public boolean getMusicSharingPrimed(User user) {
  690. Account account = getAttachedAccount(user);
  691. return account.isMusicSharingPrimed();
  692. }
  693. public void setMusicSharingPrimed(User user, boolean primed) {
  694. Account account = getAttachedAccount(user);
  695. if (account.isMusicSharingPrimed() != primed) {
  696. account.setMusicSharingPrimed(primed);
  697. LiveState.getInstance().queueUpdate(new UserPrefChangedEvent(user.getGuid(), "musicSharingPrimed", Boolean.toString(primed)));
  698. }
  699. }
  700. public boolean getApplicationUsageEnabled(User user) {
  701. Boolean enabled = user.getAccount().isApplicationUsageEnabled();
  702. return enabled != null ? enabled : AccountSystem.DEFAULT_APPLICATION_USAGE_ENABLED;
  703. }
  704. public void setApplicationUsageEnabled(User user, boolean enabled) {
  705. Account account = user.getAccount();
  706. boolean wasSet = account.isApplicationUsageEnabled() != null;
  707. boolean wasEnabled = getApplicationUsageEnabled(user);
  708. account.setApplicationUsageEnabled(enabled);
  709. if (enabled != wasEnabled)
  710. LiveState.getInstance().queueUpdate(new UserPrefChangedEvent(user.getGuid(), "applicationUsageEnabled", Boolean.toString(enabled)));
  711. if (enabled != wasEnabled || !wasSet)
  712. notifier.onApplicationUsageToggled(account);
  713. }
  714. public void incrementUserVersion(final User user) {
  715. // While it isn't a big deal in practice, the implementation below is
  716. // slightly
  717. // racy. The following would be better, but triggers a hibernate bug.
  718. //
  719. // em.createQuery("UPDATE User u set u.version = u.version + 1 WHERE
  720. // u.id = :id")
  721. // .setParameter("id", userId)
  722. // .executeUpdate();
  723. //
  724. // em.refresh(user);
  725. user.setVersion(user.getVersion() + 1);
  726. }
  727. public void setBio(UserViewpoint viewpoint, User user, String bio) {
  728. if (!viewpoint.isOfUser(user))
  729. throw new RuntimeException("can only set one's own bio");
  730. if (!em.contains(user))
  731. throw new RuntimeException("user not attached");
  732. Account acct = user.getAccount();
  733. acct.setBio(bio);
  734. revisionControl.persistRevision(new UserBioChangedRevision(viewpoint.getViewer(), new Date(), bio));
  735. }
  736. public void setMusicBio(UserViewpoint viewpoint, User user, String bio) {
  737. if (!viewpoint.isOfUser(user))
  738. throw new RuntimeException("can only set one's own music bio");
  739. if (!em.contains(user))
  740. throw new RuntimeException("user not attached");
  741. Account acct = user.getAccount();
  742. acct.setMusicBio(bio);
  743. }
  744. /**
  745. * Note that the photo CAN be null, which means to use the uploaded photo
  746. * for the user instead of a photo filename.
  747. */
  748. public void setStockPhoto(UserViewpoint viewpoint, User user, String photo) {
  749. if (!viewpoint.isOfUser(user))
  750. throw new RuntimeException("can only set one's own photo");
  751. if (photo != null && !Validators.validateStockPhoto(photo))
  752. throw new RuntimeException("invalid stock photo name");
  753. user.setStockPhoto(photo);
  754. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "photoUrl");
  755. LiveState.getInstance().queueUpdate(new UserChangedEvent(user.getGuid(), UserChangedEvent.Detail.PHOTO));
  756. }
  757. public Set<User> getMySpaceContacts(UserViewpoint viewpoint) {
  758. Set<User> contacts = getRawUserContacts(viewpoint, viewpoint.getViewer());
  759. // filter out anyone with no myspace
  760. Iterator<User> iterator = contacts.iterator();
  761. while (iterator.hasNext()) {
  762. User user = iterator.next();
  763. ExternalAccount external;
  764. try {
  765. // not using externalAccounts.getMySpaceName() because we
  766. // also want to check we have the friend id
  767. external = externalAccounts.lookupExternalAccount(
  768. viewpoint, user, ExternalAccountType.MYSPACE);
  769. if (external.getSentiment() == Sentiment.LOVE
  770. && external.getHandle() != null
  771. && external.getExtra() != null) {
  772. // we have myspace name AND friend ID
  773. continue;
  774. }
  775. } catch (NotFoundException e) {
  776. // nothing
  777. }
  778. // remove - did not have a myspace name
  779. iterator.remove();
  780. }
  781. return contacts;
  782. }
  783. }