PageRenderTime 86ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/dumbhippo/trunk/server/src/com/dumbhippo/server/impl/IdentitySpiderBean.java

https://gitlab.com/manoj-makkuboy/magnetism
Java | 1418 lines | 1042 code | 223 blank | 153 comment | 210 complexity | 2ca58a2e56f9185994873b7666830625 MD5 | raw file
  1. package com.dumbhippo.server.impl;
  2. import java.net.URL;
  3. import java.util.ArrayList;
  4. import java.util.Collection;
  5. import java.util.Collections;
  6. import java.util.Date;
  7. import java.util.HashSet;
  8. import java.util.Iterator;
  9. import java.util.List;
  10. import java.util.Set;
  11. import javax.ejb.EJB;
  12. import javax.ejb.Stateless;
  13. import javax.persistence.EntityManager;
  14. import javax.persistence.NoResultException;
  15. import javax.persistence.PersistenceContext;
  16. import javax.persistence.Query;
  17. import org.jboss.annotation.IgnoreDependency;
  18. import org.slf4j.Logger;
  19. import com.dumbhippo.GlobalSetup;
  20. import com.dumbhippo.Pair;
  21. import com.dumbhippo.Site;
  22. import com.dumbhippo.TypeUtils;
  23. import com.dumbhippo.dm.ReadWriteSession;
  24. import com.dumbhippo.identity20.Guid;
  25. import com.dumbhippo.identity20.Guid.ParseException;
  26. import com.dumbhippo.live.LiveState;
  27. import com.dumbhippo.live.LiveUser;
  28. import com.dumbhippo.live.UserChangedEvent;
  29. import com.dumbhippo.live.UserPrefChangedEvent;
  30. import com.dumbhippo.persistence.Account;
  31. import com.dumbhippo.persistence.AccountClaim;
  32. import com.dumbhippo.persistence.AccountType;
  33. import com.dumbhippo.persistence.Administrator;
  34. import com.dumbhippo.persistence.AimResource;
  35. import com.dumbhippo.persistence.Contact;
  36. import com.dumbhippo.persistence.ContactClaim;
  37. import com.dumbhippo.persistence.ContactStatus;
  38. import com.dumbhippo.persistence.EmailDetails;
  39. import com.dumbhippo.persistence.EmailResource;
  40. import com.dumbhippo.persistence.ExternalAccount;
  41. import com.dumbhippo.persistence.ExternalAccountType;
  42. import com.dumbhippo.persistence.FacebookResource;
  43. import com.dumbhippo.persistence.Group;
  44. import com.dumbhippo.persistence.GuidPersistable;
  45. import com.dumbhippo.persistence.LinkResource;
  46. import com.dumbhippo.persistence.Person;
  47. import com.dumbhippo.persistence.Post;
  48. import com.dumbhippo.persistence.Resource;
  49. import com.dumbhippo.persistence.Sentiment;
  50. import com.dumbhippo.persistence.User;
  51. import com.dumbhippo.persistence.UserBioChangedRevision;
  52. import com.dumbhippo.persistence.ValidationException;
  53. import com.dumbhippo.persistence.Validators;
  54. import com.dumbhippo.persistence.XmppResource;
  55. import com.dumbhippo.server.AccountSystem;
  56. import com.dumbhippo.server.ClaimVerifier;
  57. import com.dumbhippo.server.Configuration;
  58. import com.dumbhippo.server.ContactConflictException;
  59. import com.dumbhippo.server.Enabled;
  60. import com.dumbhippo.server.ExternalAccountSystem;
  61. import com.dumbhippo.server.GroupSystem;
  62. import com.dumbhippo.server.HippoProperty;
  63. import com.dumbhippo.server.HumanVisibleException;
  64. import com.dumbhippo.server.IdentitySpider;
  65. import com.dumbhippo.server.IdentitySpiderRemote;
  66. import com.dumbhippo.server.NotFoundException;
  67. import com.dumbhippo.server.Notifier;
  68. import com.dumbhippo.server.OnlineDesktopSystem;
  69. import com.dumbhippo.server.PermissionDeniedException;
  70. import com.dumbhippo.server.PostingBoard;
  71. import com.dumbhippo.server.RevisionControl;
  72. import com.dumbhippo.server.dm.ContactDMO;
  73. import com.dumbhippo.server.dm.DataService;
  74. import com.dumbhippo.server.dm.UserClientMatcher;
  75. import com.dumbhippo.server.dm.UserDMO;
  76. import com.dumbhippo.server.util.EJBUtil;
  77. import com.dumbhippo.server.views.SystemViewpoint;
  78. import com.dumbhippo.server.views.UserViewpoint;
  79. import com.dumbhippo.server.views.Viewpoint;
  80. import com.dumbhippo.tx.RetryException;
  81. import com.dumbhippo.tx.TxCallable;
  82. import com.dumbhippo.tx.TxUtils;
  83. /*
  84. * An implementation of the Identity Spider. It sucks your blood.
  85. * @author walters
  86. */
  87. @Stateless
  88. public class IdentitySpiderBean implements IdentitySpider, IdentitySpiderRemote {
  89. static private final Logger logger = GlobalSetup
  90. .getLogger(IdentitySpider.class);
  91. @PersistenceContext(unitName = "dumbhippo")
  92. private EntityManager em;
  93. @EJB
  94. @IgnoreDependency
  95. private ClaimVerifier claimVerifier;
  96. @EJB
  97. @IgnoreDependency
  98. private GroupSystem groupSystem;
  99. @EJB
  100. @IgnoreDependency
  101. private ExternalAccountSystem externalAccounts;
  102. @EJB
  103. private Configuration config;
  104. @EJB
  105. private Notifier notifier;
  106. @EJB
  107. private RevisionControl revisionControl;
  108. @EJB
  109. private OnlineDesktopSystem onlineDesktop;
  110. @EJB
  111. @IgnoreDependency
  112. private PostingBoard postingBoard;
  113. public User lookupUserByEmail(Viewpoint viewpoint, String email) throws NotFoundException {
  114. EmailResource res = lookupEmail(email);
  115. // lookupEmail will normally throw a NotFoundException if the resource is not found,
  116. // so it's not clear in what situation res will be null
  117. if (res == null)
  118. throw new NotFoundException("Resource was null");
  119. return lookupUserByResource(viewpoint, res);
  120. }
  121. public User lookupUserByAim(Viewpoint viewpoint, String aim) throws NotFoundException {
  122. AimResource res = lookupAim(aim);
  123. if (res == null)
  124. throw new NotFoundException("Resource was null");
  125. return lookupUserByResource(viewpoint, res);
  126. }
  127. public User lookupUserByFacebookUserId(Viewpoint viewpoint, String facebookUserId) throws NotFoundException {
  128. FacebookResource res = lookupFacebook(facebookUserId);
  129. if (res == null)
  130. throw new NotFoundException("Resource was null");
  131. return lookupUserByResource(viewpoint, res);
  132. }
  133. public User lookupUserByResource(Viewpoint viewpoint, Resource resource) {
  134. return getUser(resource);
  135. }
  136. public User lookupUser(LiveUser luser) {
  137. return em.find(User.class, luser.getGuid().toString());
  138. }
  139. public User lookupUser(Guid guid) {
  140. return em.find(User.class, guid.toString());
  141. }
  142. public Contact lookupContact(Guid guid) {
  143. return em.find(Contact.class, guid.toString());
  144. }
  145. public <T extends GuidPersistable> T lookupGuidString(Class<T> klass,
  146. String id) throws ParseException, NotFoundException {
  147. if (klass.equals(Post.class) || klass.equals(Group.class))
  148. logger.error("Probable bug: looking up Post/Group should use GroupSystem/PostingBoard to get access controls");
  149. return EJBUtil.lookupGuidString(em, klass, id);
  150. }
  151. public <T extends GuidPersistable> T lookupGuid(Class<T> klass, Guid id)
  152. throws NotFoundException {
  153. if (klass.equals(Post.class) || klass.equals(Group.class))
  154. logger.error("Probable bug: looking up Post/Group should use GroupSystem/PostingBoard to get access controls");
  155. return EJBUtil.lookupGuid(em, klass, id);
  156. }
  157. public <T extends GuidPersistable> Set<T> lookupGuidStrings(Class<T> klass,
  158. Set<String> ids) throws ParseException, NotFoundException {
  159. Set<T> ret = new HashSet<T>();
  160. for (String s : ids) {
  161. T obj = lookupGuidString(klass, s);
  162. ret.add(obj);
  163. }
  164. return ret;
  165. }
  166. public <T extends GuidPersistable> Set<T> lookupGuids(Class<T> klass,
  167. Set<Guid> ids) throws NotFoundException {
  168. Set<T> ret = new HashSet<T>();
  169. for (Guid id : ids) {
  170. T obj = lookupGuid(klass, id);
  171. ret.add(obj);
  172. }
  173. return ret;
  174. }
  175. public EmailResource getEmail(final String emailRaw) throws ValidationException, RetryException {
  176. // well, we could do a little better here with the validation...
  177. final String email = EmailResource.canonicalize(emailRaw);
  178. return TxUtils.runNeedsRetry(new TxCallable<EmailResource>() {
  179. public EmailResource call() {
  180. Query q;
  181. q = em.createQuery("from EmailResource e where e.email = :email");
  182. q.setParameter("email", email);
  183. EmailResource res;
  184. try {
  185. res = (EmailResource) q.getSingleResult();
  186. } catch (NoResultException e) {
  187. res = new EmailResource(email);
  188. em.persist(res);
  189. if (res.isGmail()) {
  190. try {
  191. onlineDesktop.setGoogleServicedEmail(SystemViewpoint.getInstance(), null, res, true);
  192. } catch (RetryException e1) {
  193. logger.error("Failed to create a Google serviced e-mail for " + email, e1);
  194. }
  195. }
  196. }
  197. return res;
  198. }
  199. });
  200. }
  201. public AimResource getAim(final String screenNameRaw) throws ValidationException, RetryException {
  202. final String screenName = AimResource.canonicalize(screenNameRaw);
  203. return TxUtils.runNeedsRetry(new TxCallable<AimResource>() {
  204. public AimResource call() {
  205. Query q;
  206. q = em.createQuery("from AimResource a where a.screenName = :name");
  207. q.setParameter("name", screenName);
  208. AimResource res;
  209. try {
  210. res = (AimResource) q.getSingleResult();
  211. } catch (NoResultException e) {
  212. try {
  213. res = new AimResource(screenName);
  214. } catch (ValidationException v) {
  215. throw new RuntimeException(v); // Already validated above
  216. }
  217. em.persist(res);
  218. }
  219. return res;
  220. }
  221. });
  222. }
  223. public XmppResource getXmpp(final String jidRaw) throws ValidationException, RetryException {
  224. final String jid = XmppResource.canonicalize(jidRaw);
  225. return TxUtils.runNeedsRetry(new TxCallable<XmppResource>() {
  226. public XmppResource call() {
  227. Query q;
  228. q = em.createQuery("from XmppResource a where a.jid = :name");
  229. q.setParameter("name", jid);
  230. XmppResource res;
  231. try {
  232. res = (XmppResource) q.getSingleResult();
  233. } catch (NoResultException e) {
  234. try {
  235. res = new XmppResource(jid);
  236. } catch (ValidationException v) {
  237. throw new RuntimeException(v); // Already validated above
  238. }
  239. em.persist(res);
  240. }
  241. return res;
  242. }
  243. });
  244. }
  245. private <T extends Resource> T lookupResourceByName(Class<T> klass, String identifier, String name) throws NotFoundException {
  246. Query q;
  247. String className = klass.getName();
  248. q = em.createQuery("SELECT a FROM " + className + " a WHERE a." + identifier
  249. + " = :name");
  250. q.setParameter("name", name);
  251. T res = null;
  252. try {
  253. res = klass.cast(q.getSingleResult());
  254. } catch (NoResultException e) {
  255. throw new NotFoundException("No such resource");
  256. }
  257. return res;
  258. }
  259. public AimResource lookupAim(String screenName) throws NotFoundException {
  260. try {
  261. screenName = AimResource.canonicalize(screenName);
  262. } catch (ValidationException e) {
  263. throw new NotFoundException("Not a valid AIM address", e);
  264. }
  265. return lookupResourceByName(AimResource.class, "screenName", screenName);
  266. }
  267. public EmailResource lookupEmail(String email) throws NotFoundException {
  268. try {
  269. email = EmailResource.canonicalize(email);
  270. } catch (ValidationException e) {
  271. throw new NotFoundException("Not a valid email address", e);
  272. }
  273. return lookupResourceByName(EmailResource.class, "email", email);
  274. }
  275. public XmppResource lookupXmpp(String jid) throws NotFoundException {
  276. try {
  277. jid = XmppResource.canonicalize(jid);
  278. } catch (ValidationException e) {
  279. throw new NotFoundException("Not a valid XMPP address", e);
  280. }
  281. return lookupResourceByName(XmppResource.class, "jid", jid);
  282. }
  283. public FacebookResource lookupFacebook(String facebookUserId) throws NotFoundException {
  284. try {
  285. facebookUserId = FacebookResource.canonicalize(facebookUserId);
  286. } catch (ValidationException e) {
  287. throw new NotFoundException("Not a valid Facebook user id", e);
  288. }
  289. return lookupResourceByName(FacebookResource.class, "facebookUserId", facebookUserId);
  290. }
  291. public LinkResource lookupLink(final URL url) throws NotFoundException {
  292. return lookupResourceByName(LinkResource.class, "url", url.toExternalForm());
  293. }
  294. public LinkResource getLink(final URL url) {
  295. // Retrying here causes side effects all over the code; the right solution
  296. // is to get rid of LinkResource instead.
  297. // return TxUtils.runNeedsRetry(new TxCallable<LinkResource>() {
  298. // public LinkResource call() {
  299. Query q;
  300. q = em.createQuery("SELECT l FROM LinkResource l WHERE l.url = :url");
  301. q.setParameter("url", url.toExternalForm());
  302. LinkResource res;
  303. try {
  304. res = (LinkResource) q.getSingleResult();
  305. } catch (NoResultException e) {
  306. res = new LinkResource(url.toExternalForm());
  307. em.persist(res);
  308. }
  309. return res;
  310. // }
  311. //
  312. // });
  313. }
  314. private User getUserForContact(Contact contact) {
  315. for (ContactClaim cc : contact.getResources()) {
  316. Resource resource = cc.getResource();
  317. if (resource instanceof Account)
  318. return ((Account) resource).getOwner();
  319. else {
  320. AccountClaim ac = resource.getAccountClaim();
  321. if (ac != null) {
  322. return ac.getOwner();
  323. }
  324. }
  325. }
  326. return null;
  327. }
  328. public User getUser(Person person) {
  329. if (person == null)
  330. throw new IllegalArgumentException("null person in getUser()");
  331. if (person instanceof User)
  332. return (User) person;
  333. else {
  334. // logger.debug("getUser: contact = {}", person);
  335. User user = getUserForContact((Contact) person);
  336. // logger.debug("getUserForContact: user = {}", user);
  337. return user;
  338. }
  339. }
  340. public User getUser(Resource resource) {
  341. if (resource instanceof Account)
  342. return ((Account) resource).getOwner();
  343. else {
  344. AccountClaim ac = resource.getAccountClaim();
  345. if (ac != null)
  346. return ac.getOwner();
  347. return null;
  348. }
  349. }
  350. private void invalidateContacters(User user) {
  351. ReadWriteSession session = DataService.currentSessionRW();
  352. Guid userId = user.getGuid();
  353. session.changed(UserDMO.class, userId, "contacters");
  354. session.changed(UserDMO.class, userId, "blockingContacters");
  355. session.changed(UserDMO.class, userId, "coldContacters");
  356. session.changed(UserDMO.class, userId, "hotContacters");
  357. // Legacy contacters cache
  358. LiveState.getInstance().invalidateContacters(user.getGuid());
  359. }
  360. private void invalidateUserContacts(User user) {
  361. ReadWriteSession session = DataService.currentSessionRW();
  362. Guid userId = user.getGuid();
  363. session.changed(UserDMO.class, userId, "userContacts");
  364. // Legacy user contact cache
  365. LiveState.getInstance().invalidateContacts(userId);
  366. }
  367. private void invalidateContacts(User user) {
  368. ReadWriteSession session = DataService.currentSessionRW();
  369. Guid userId = user.getGuid();
  370. session.changed(UserDMO.class, userId, "contacts");
  371. }
  372. private void invalidateContactStatus(User contacter, User contactUser) {
  373. DataService.currentSessionRW().changed(UserDMO.class, contactUser.getGuid(), "contactStatus",
  374. new UserClientMatcher(contacter.getGuid()));
  375. }
  376. public void invalidateContactUser(Contact contact) {
  377. DataService.currentSessionRW().changed(ContactDMO.class, contact.getGuid(), "user");
  378. }
  379. public void invalidateUserResource(User user, Resource resource) {
  380. if (resource instanceof EmailResource) {
  381. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "email");
  382. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "emails");
  383. } else if (resource instanceof AimResource) {
  384. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "aim");
  385. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "aims");
  386. } else if (resource instanceof XmppResource) {
  387. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "xmpp");
  388. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "xmpps");
  389. } else if (resource instanceof FacebookResource) {
  390. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "facebook");
  391. }
  392. }
  393. public void invalidateContactResource(Contact contact, Resource resource) {
  394. if (resource instanceof EmailResource) {
  395. DataService.currentSessionRW().changed(ContactDMO.class, contact.getGuid(), "emails");
  396. } else if (resource instanceof AimResource) {
  397. DataService.currentSessionRW().changed(ContactDMO.class, contact.getGuid(), "aims");
  398. } else if (resource instanceof XmppResource) {
  399. DataService.currentSessionRW().changed(ContactDMO.class, contact.getGuid(), "xmpps");
  400. }
  401. }
  402. public void addVerifiedOwnershipClaim(User claimedOwner, Resource res) {
  403. // first be sure it isn't a dup - the db constraints check this too,
  404. // but it's more user friendly to no-op here than to throw a db
  405. // exception
  406. Set<AccountClaim> claims = claimedOwner.getAccountClaims();
  407. for (AccountClaim claim : claims) {
  408. if (claim.getResource().equals(res)) {
  409. logger.debug(
  410. "Found existing claim for {} on {}, not adding again",
  411. claimedOwner, res);
  412. return;
  413. }
  414. }
  415. // People may have listed the newly claimed resource as a contact
  416. //
  417. // Various possibilities:
  418. //
  419. // - The same Contact object also already listed another resource pointing
  420. // to the same user, nothing to do.
  421. // - A different Contact object also already another resource for the
  422. // the same contact. We need to merge the contacts
  423. // - The same Contact object listed a resource owned by a different
  424. // user, we need to split the contact
  425. // - The owner was not already a contact, we need to invalidate
  426. // user <=> contacter relationships
  427. //
  428. // (Plus certain combinations of the above)
  429. //
  430. // We handle these things *before* creating the account claim, since we
  431. // logically have the invariants that each (contactOwner,resourceOwner)
  432. // pair has no more than one Contact and each Contact has no more than
  433. // one resourceOwner
  434. //
  435. Collection<Contact> newContacts = findResourceContacts(res);
  436. if (!newContacts.isEmpty()) {
  437. // We'll keep things simple and just always invalidate the contacters
  438. // for the user
  439. invalidateContacters(claimedOwner);
  440. for (Contact contact : newContacts) {
  441. User contacter = contact.getAccount().getOwner();
  442. Contact oldContact;
  443. try {
  444. oldContact = findContactByUser(contacter, claimedOwner);
  445. } catch (NotFoundException e) {
  446. oldContact = null;
  447. }
  448. if (oldContact != null && oldContact != contact) {
  449. // The contacter had another Contact pointing to the new
  450. // owner, move the resource
  451. removeContactResource(contact, res);
  452. try {
  453. addContactResource(contacter, oldContact, res);
  454. } catch (ContactConflictException e) {
  455. throw new RuntimeException("Unexpected ContactConflict", e);
  456. }
  457. contact = oldContact;
  458. }
  459. User oldContactUser = getUserForContact(contact);
  460. if (oldContactUser == null) {
  461. // User is a contact for the first time
  462. User contactOwner = contact.getAccount().getOwner();
  463. invalidateContactStatus(contactOwner, claimedOwner);
  464. invalidateUserContacts(contactOwner);
  465. invalidateContactUser(contact);
  466. } else if (oldContactUser != claimedOwner) {
  467. // Ooops, our newly added account claim caused a conflict and a single
  468. // contact points to two resources pointing to two different users.
  469. // Split the contact.
  470. ContactStatus status = contact.getStatus();
  471. removeContactResource(contact, res);
  472. Contact newContact = createContact(contact.getAccount().getOwner(), res);
  473. newContact.setStatus(status);
  474. }
  475. }
  476. }
  477. // Now create the new db claim
  478. res.prepareToSetAccountClaim();
  479. AccountClaim ac = new AccountClaim(claimedOwner, res);
  480. em.persist(ac);
  481. // Update inverse mappings
  482. res.setAccountClaim(ac);
  483. claimedOwner.getAccountClaims().add(ac);
  484. // fix up group memberships
  485. groupSystem.fixupGroupMemberships(claimedOwner);
  486. invalidateUserResource(claimedOwner, res);
  487. if (res instanceof EmailResource) {
  488. EmailDetails emailDetails = onlineDesktop.lookupEmailDetails((EmailResource)res);
  489. if (emailDetails != null && emailDetails.getGoogleServicesEnabled()) {
  490. // we want to send out a notification for the claimedOwner about a new Google serviced e-mail
  491. onlineDesktop.onGoogleServicedEmailChange(SystemViewpoint.getInstance(), claimedOwner, (EmailResource)res);
  492. }
  493. }
  494. }
  495. public void removeVerifiedOwnershipClaim(UserViewpoint viewpoint,
  496. User owner, Resource res) throws HumanVisibleException {
  497. Collection<Contact> oldContacts = findResourceContacts(res);
  498. if (!viewpoint.isOfUser(owner)) {
  499. throw new RuntimeException(
  500. "can only remove your own ownership claims");
  501. }
  502. Set<AccountClaim> claims = owner.getAccountClaims();
  503. // normally, any user will claim their Account resource, up to one FacebookResource,
  504. // and multiple email, aim, and xmpp resources
  505. // it's possible to not have a single EmailResource if a user has a FacebookResource,
  506. // but once an EmailResource has been added, we don't allow deleting the last EmailResource
  507. // we are currently never deleting a FacebookResource claim
  508. if (res instanceof EmailResource) {
  509. int emailClaimsCounter = 0;
  510. for (AccountClaim claim : claims) {
  511. if (claim.getResource() instanceof EmailResource) {
  512. emailClaimsCounter++;
  513. if (emailClaimsCounter > 1)
  514. break;
  515. }
  516. }
  517. if (emailClaimsCounter < 2) {
  518. // UI shouldn't let this happen, but we double-check here
  519. throw new HumanVisibleException(
  520. "You have to keep at least one e-mail address on your user account.");
  521. }
  522. }
  523. for (AccountClaim claim : claims) {
  524. if (claim.getResource().equals(res)) {
  525. logger.debug("Found claim for {} on {}, removing it", owner,
  526. res);
  527. res.setAccountClaim(null);
  528. claims.remove(claim);
  529. em.remove(claim);
  530. invalidateUserResource(owner, res);
  531. // People may have listed resource as a contact
  532. if (!oldContacts.isEmpty()) {
  533. invalidateContacters(owner);
  534. for (Contact contact : oldContacts) {
  535. User contactOwner = contact.getAccount().getOwner();
  536. invalidateUserContacts(contactOwner);
  537. invalidateContactStatus(contactOwner, owner);
  538. invalidateContactUser(contact);
  539. }
  540. }
  541. if (res instanceof EmailResource) {
  542. EmailDetails emailDetails = onlineDesktop.lookupEmailDetails((EmailResource)res);
  543. if (emailDetails != null && emailDetails.getGoogleServicesEnabled()) {
  544. // we want to send out a notification for the claimedOwner about a removed Google serviced e-mail
  545. onlineDesktop.onGoogleServicedEmailChange(SystemViewpoint.getInstance(), owner, (EmailResource)res);
  546. }
  547. }
  548. return;
  549. }
  550. }
  551. logger.warn("No current claims for {} on {}, cancelling pending claims", owner, res);
  552. claimVerifier.cancelClaimToken(owner, res);
  553. }
  554. private Collection<Contact> findResourceContacts(Resource res) {
  555. Query q = em.createQuery("SELECT cc.contact " +
  556. " FROM ContactClaim cc " +
  557. " WHERE cc.resource = :resource");
  558. q.setParameter("resource", res);
  559. return TypeUtils.castList(Contact.class, q.getResultList());
  560. }
  561. private Contact findContactByUser(User owner, User contactUser) throws NotFoundException {
  562. Query q = em.createQuery("SELECT cc.contact " +
  563. " FROM ContactClaim cc, AccountClaim ac " +
  564. " WHERE cc.resource = ac.resource " +
  565. " AND cc.account = :account " +
  566. " AND ac.owner = :contact");
  567. q.setParameter("account", owner.getAccount());
  568. q.setParameter("contact", contactUser);
  569. q.setMaxResults(1);
  570. try {
  571. return (Contact)q.getSingleResult();
  572. } catch (NoResultException e) {
  573. throw new NotFoundException("No contact for user");
  574. }
  575. }
  576. public Contact findContactByResource(User owner, Resource resource) throws NotFoundException {
  577. Query q = em.createQuery("SELECT cc.contact " +
  578. " FROM ContactClaim cc " +
  579. " WHERE cc.account = :account" +
  580. " AND cc.resource = :contact");
  581. q.setParameter("account", owner.getAccount());
  582. q.setParameter("contact", resource);
  583. q.setMaxResults(1);
  584. try {
  585. return (Contact)q.getSingleResult();
  586. } catch (NoResultException e) {
  587. throw new NotFoundException("No contact for user");
  588. }
  589. }
  590. public Contact createContact(User user, Resource resource) {
  591. try {
  592. return addContactResource(user, null, resource);
  593. } catch (ContactConflictException e) {
  594. throw new RuntimeException("ContactConflict exception not expected when contact not passed in", e);
  595. }
  596. }
  597. private Contact addContactResource(User contacter, Contact contact, Resource resource) throws ContactConflictException {
  598. // This is the central place where we handle adding contact claims; we put
  599. // everything through here to avoid duplication of (somewhat complex) logic
  600. if (contacter == null)
  601. throw new IllegalArgumentException("null contact owner");
  602. if (resource == null)
  603. throw new IllegalArgumentException("null contact resource");
  604. if (contact != null && contact.getAccount() != contacter.getAccount())
  605. throw new IllegalArgumentException("Contact does not match owner");
  606. if (contact != null)
  607. logger.debug("Adding resource {} to existing contact {}", resource, contact);
  608. else
  609. logger.debug("Adding contact resource {} for user {}", resource, contacter);
  610. Contact oldContact = null;
  611. // If adding the resource would cause conflicts (two contacts pointing to the
  612. // same user, a single contact pointing to multiple users) then we raise a
  613. // ContactConflictException, but maybe it would be better to just merge/split
  614. // contacts and do a best-effort jobs? (We do that when adding AccountClaims,
  615. // since the person adding the AccountClaim has no control over what contacts
  616. // exist.)
  617. //
  618. // Certainly merge/splits would make for a confusing user interface, but that
  619. // basically has to be handled with pre-checks, since we only provide a
  620. // text string back to the client side.
  621. //
  622. // FIXME: At a minimum, make the exception more user friendly
  623. // First check if we already have a contact that claims this resource
  624. try {
  625. oldContact = findContactByResource(contacter, resource);
  626. if (contact == null || contact == oldContact) {
  627. logger.debug("Contact resource was already present, nothing to do");
  628. return oldContact;
  629. }
  630. throw new ContactConflictException("Resource " + resource.getId() + " already part of another contact");
  631. } catch (NotFoundException e) {
  632. }
  633. // Now check if the contact already points to a different user or we have a
  634. // contact that claims another resource with the same owner
  635. User resourceOwner = getUser(resource);
  636. if (resourceOwner != null) {
  637. if (contact != null) {
  638. User contactUser = getUserForContact(contact);
  639. if (contactUser != null && contactUser != resourceOwner) {
  640. throw new ContactConflictException("Contact already points to user " + contactUser.getId() + " but resource is owned by user " + resourceOwner.getGuid());
  641. }
  642. }
  643. try {
  644. oldContact = findContactByUser(contacter, resourceOwner);
  645. if (contact == null) {
  646. contact = oldContact;
  647. logger.debug("Found existing contact {} pointing to resource's owner {}",
  648. contact, resourceOwner);
  649. } else if (contact != oldContact) {
  650. throw new ContactConflictException("Owner " + contacter.getGuid() + " of resource " + resource.getId() + " already part of another contact");
  651. }
  652. } catch (NotFoundException e) {
  653. }
  654. }
  655. if (contact == null) {
  656. Account account = contacter.getAccount();
  657. contact = new Contact(account);
  658. em.persist(contact);
  659. invalidateContacts(contacter);
  660. logger.debug("Created new contact {}", contact);
  661. }
  662. boolean foundAccount = false;
  663. for (ContactClaim cc : contact.getResources()) {
  664. if (resourceOwner != null && cc.getResource().equals(resourceOwner.getAccount()))
  665. foundAccount = true;
  666. }
  667. // Updating the inverse mappings is essential since they are cached;
  668. // if we don't update them the second-level cache will contain stale
  669. // data.
  670. // Updating them won't actually update the data in the second-level
  671. // cache for a non-transactional cache; rather it will flag the data to be
  672. // reloaded from the database.
  673. //
  674. ContactClaim cc = new ContactClaim(contact, resource);
  675. em.persist(cc);
  676. contact.getResources().add(cc);
  677. logger.debug("Added resource to contact");
  678. // Things work better (especially for now, when we don't fully
  679. // implement spidering) if contacts own the account resource for
  680. // users, and not just the EmailResource
  681. if (resourceOwner != null && !(resource instanceof Account) && !foundAccount) {
  682. cc = new ContactClaim(contact, resourceOwner.getAccount());
  683. em.persist(cc);
  684. contact.getResources().add(cc);
  685. logger.debug("Also added resource owner's account resource {} to contact", resourceOwner.getAccount());
  686. }
  687. if (resourceOwner != null && oldContact == null) {
  688. // If the resource is owned by somebody, and we didn't already have a contact
  689. // for them, then cached contacter <=> contactee relationships need to
  690. // be invalidated.
  691. invalidateUserContacts(contacter);
  692. invalidateContactStatus(contacter, resourceOwner);
  693. invalidateContacters(resourceOwner);
  694. invalidateContactUser(contact);
  695. }
  696. invalidateContactResource(contact, resource);
  697. return contact;
  698. }
  699. public void addContactResource(Contact contact, Resource resource) throws ContactConflictException {
  700. addContactResource(contact.getAccount().getOwner(), contact, resource);
  701. }
  702. public void removeContactResource(Contact contact, Resource resource) {
  703. User contacter = contact.getAccount().getOwner();
  704. User removedUser = null;
  705. for (ContactClaim cc : contact.getResources()) {
  706. if (cc.getResource().equals(resource)) {
  707. logger.debug("removing contact claim {}", cc);
  708. removedUser = getUser(cc.getResource());
  709. contact.getResources().remove(cc);
  710. em.remove(cc);
  711. break;
  712. }
  713. }
  714. if (removedUser != null) {
  715. invalidateUserContacts(contacter);
  716. invalidateContactStatus(contacter, removedUser);
  717. invalidateContacters(removedUser);
  718. invalidateContactUser(contact);
  719. }
  720. invalidateContactResource(contact, resource);
  721. // allowing a 'bare' contact with no resources might make sense in
  722. // some circumstances (explicitly removing the last address from a
  723. // contact), and in other circumstances (automatic merge of contacts)
  724. // would be strange. It might make sense to pass in whether to keep
  725. // the contact as an option. Also, whether the contact has been
  726. // customized (if we allowed notes, setting a headshot, etc) would
  727. // matter.
  728. //
  729. // For now, we always delete empty resources
  730. if (contact.getResources().isEmpty()) {
  731. em.remove(contact);
  732. DataService.currentSessionRW().removed(ContactDMO.class, contact.getGuid());
  733. invalidateContacts(contacter);
  734. }
  735. }
  736. public void addContactPerson(User user, Person contactPerson) {
  737. logger.debug("adding contact " + contactPerson + " to account "
  738. + user.getAccount());
  739. if (contactPerson instanceof Contact) {
  740. // Must be a contact of user, so nothing to do
  741. assert ((Contact) contactPerson).getAccount() == user.getAccount();
  742. } else {
  743. User contactUser = (User) contactPerson;
  744. try {
  745. findContactByUser(user, contactUser);
  746. } catch (NotFoundException e) {
  747. createContact(user, contactUser.getAccount());
  748. }
  749. }
  750. }
  751. public void deleteContactByPerson(User user, Person contactPerson) {
  752. logger.debug("removing contact {} from account {}", contactPerson, user.getAccount());
  753. Contact contact;
  754. if (contactPerson instanceof Contact) {
  755. contact = (Contact) contactPerson;
  756. } else {
  757. try {
  758. contact = findContactByUser(user, (User)contactPerson);
  759. } catch (NotFoundException e) {
  760. logger.debug("User {} not found as a contact", contactPerson);
  761. return;
  762. }
  763. }
  764. deleteContact(user, contact);
  765. }
  766. public void deleteContactByResource(User user, Resource contactResource) {
  767. logger.debug("removing contact {} from account {}", contactResource, user.getAccount());
  768. Contact contact;
  769. try {
  770. contact = findContactByResource(user, contactResource);
  771. deleteContact(user, contact);
  772. } catch (NotFoundException e) {
  773. logger.debug("Resource {} not found as a contact", contactResource);
  774. return;
  775. }
  776. }
  777. public void deleteContact(User user, Contact contact) {
  778. Set<User> removedUsers = new HashSet<User>();
  779. for (ContactClaim cc : contact.getResources()) {
  780. User resourceUser = getUser(cc.getResource());
  781. if (resourceUser != null)
  782. removedUsers.add(resourceUser);
  783. }
  784. em.remove(contact);
  785. logger.debug("contact deleted");
  786. DataService.currentSessionRW().removed(ContactDMO.class, contact.getGuid());
  787. invalidateContacts(user);
  788. if (!removedUsers.isEmpty()) {
  789. invalidateUserContacts(user);
  790. for (User removedUser : removedUsers) {
  791. invalidateContactStatus(user, removedUser);
  792. invalidateContacters(removedUser);
  793. }
  794. }
  795. }
  796. public void setContactStatus(UserViewpoint viewpoint, User contactUser, ContactStatus status) {
  797. User viewer = viewpoint.getViewer();
  798. if (status == ContactStatus.NONCONTACT) {
  799. deleteContactByPerson(viewer, contactUser);
  800. } else {
  801. Contact contact;
  802. try {
  803. contact = findContactByUser(viewer, contactUser);
  804. } catch (NotFoundException e) {
  805. contact = createContact(viewer, contactUser.getAccount());
  806. }
  807. setContactStatus(viewpoint, contact, status);
  808. }
  809. }
  810. public void setContactStatus(UserViewpoint viewpoint, Contact contact, ContactStatus status) {
  811. User viewer = viewpoint.getViewer();
  812. if (status == ContactStatus.NONCONTACT) {
  813. deleteContactByPerson(viewer, contact);
  814. } else {
  815. if (contact.getStatus() != status) {
  816. contact.setStatus(status);
  817. DataService.currentSessionRW().changed(ContactDMO.class, contact.getGuid(), "status");
  818. User contactUser = getUser(contact);
  819. if (contactUser != null) {
  820. invalidateContactStatus(viewer, contactUser);
  821. invalidateContacters(contactUser);
  822. }
  823. }
  824. }
  825. }
  826. public void setContactName(UserViewpoint viewpoint, Contact contact, String name) throws PermissionDeniedException {
  827. if (!contact.getAccount().getOwner().equals(viewpoint.getViewer())) {
  828. throw new PermissionDeniedException("Can't change someone else's contact's name");
  829. }
  830. name = name.trim();
  831. contact.setNickname(name);
  832. DataService.currentSessionRW().changed(ContactDMO.class, contact.getGuid(), "name");
  833. }
  834. // get all contacts, even if they have no user
  835. public Set<Guid> computeContacts(Guid userId) {
  836. User user = em.find(User.class, userId.toString());
  837. Query q = em.createQuery("SELECT c.id " +
  838. " FROM Contact c " +
  839. " WHERE c.account = :account");
  840. q.setParameter("account", user.getAccount());
  841. Set<Guid> result = new HashSet<Guid>();
  842. for (String s : TypeUtils.castList(String.class, q.getResultList())) {
  843. try {
  844. result.add(new Guid(s));
  845. } catch (ParseException e) {
  846. throw new RuntimeException("Bad GUID in database");
  847. }
  848. }
  849. return result;
  850. }
  851. // get only contacts that point to a User
  852. public Set<Guid> computeUserContacts(Guid userId) {
  853. User user = em.find(User.class, userId.toString());
  854. Query q = em.createQuery("SELECT ac.owner.id " +
  855. " FROM ContactClaim cc, AccountClaim ac " +
  856. " WHERE cc.resource = ac.resource " +
  857. " AND cc.account = :account");
  858. q.setParameter("account", user.getAccount());
  859. Set<Guid> result = new HashSet<Guid>();
  860. for (String s : TypeUtils.castList(String.class, q.getResultList())) {
  861. try {
  862. result.add(new Guid(s));
  863. } catch (ParseException e) {
  864. throw new RuntimeException("Bad GUID in database");
  865. }
  866. }
  867. return result;
  868. }
  869. // computes which user GUIDs have a contact which points to a resource
  870. // which is proven-owned (AccountClaim) by the passed-in user.
  871. // "People who say they have a ContactStatus with respect to userId"
  872. public Set<Guid> computeContacters(Guid userId) {
  873. Query q = em.createQuery("SELECT cc.account.owner.id " +
  874. " FROM ContactClaim cc, AccountClaim ac " +
  875. " WHERE cc.resource = ac.resource " +
  876. " AND ac.owner.id = :userId");
  877. q.setParameter("userId", userId.toString());
  878. Set<Guid> result = new HashSet<Guid>();
  879. for (String s : TypeUtils.castList(String.class, q.getResultList())) {
  880. try {
  881. result.add(new Guid(s));
  882. } catch (ParseException e) {
  883. throw new RuntimeException("Bad GUID in database");
  884. }
  885. }
  886. return result;
  887. }
  888. // like computeContacters(), but also returns the status that someone has
  889. // assigned to userId. i.e. returns people who have assigned userId a ContactStatus,
  890. // plus that ContactStatus they have assigned.
  891. public List<Pair<Guid,ContactStatus>> computeContactersWithStatus(Guid userId) {
  892. Query q = em.createQuery("SELECT cc.account.owner.id, cc.contact.status " +
  893. " FROM ContactClaim cc, AccountClaim ac " +
  894. " WHERE cc.resource = ac.resource " +
  895. " AND ac.owner.id = :userId");
  896. q.setParameter("userId", userId.toString());
  897. List<Pair<Guid,ContactStatus>> result = new ArrayList<Pair<Guid,ContactStatus>>();
  898. for (Object o : q.getResultList()) {
  899. Object[] os = (Object[])o;
  900. Guid guid;
  901. try {
  902. guid = new Guid((String)os[0]);
  903. } catch (ParseException e) {
  904. throw new RuntimeException("Bad GUID in database");
  905. }
  906. ContactStatus status = (ContactStatus)os[1];
  907. result.add(new Pair<Guid, ContactStatus>(guid, status));
  908. }
  909. return result;
  910. }
  911. public int computeContactsCount(User user) {
  912. Query q = em.createQuery("SELECT COUNT(*) FROM Contact c WHERE c.account = :account");
  913. q.setParameter("account", user.getAccount());
  914. return ((Number)q.getSingleResult()).intValue();
  915. }
  916. public Set<User> getRawUserContacts(Viewpoint viewpoint, User user) {
  917. if (!isViewerSystemOrFriendOf(viewpoint, user))
  918. return Collections.emptySet();
  919. Guid viewedUserId = user.getGuid();
  920. Set<User> ret = new HashSet<User>();
  921. for (Guid guid : LiveState.getInstance().getContacts(viewedUserId)) {
  922. // The user might be listed as their own contact, but avoid revealing that
  923. if (viewedUserId.equals(guid))
  924. continue;
  925. ret.add(em.find(User.class, guid.toString()));
  926. }
  927. return ret;
  928. }
  929. public Set<User> getUsersWhoHaveUserAsContact(Viewpoint viewpoint, User user) {
  930. if (!isViewerSystemOrFriendOf(viewpoint, user))
  931. return Collections.emptySet();
  932. Guid viewedUserId = user.getGuid();
  933. Set<User> ret = new HashSet<User>();
  934. for (Guid guid : LiveState.getInstance().getContacters(viewedUserId)) {
  935. // The user might be listed as their own contact, but avoid revealing that
  936. if (viewedUserId.equals(guid))
  937. continue;
  938. ret.add(em.find(User.class, guid.toString()));
  939. }
  940. return ret;
  941. }
  942. /**
  943. * @param user
  944. * the user we're looking at the contacts of
  945. * @param contactUser
  946. * is this person a contact of user?
  947. * @return true if contactUser is a contact of user
  948. */
  949. private boolean userHasContact(Viewpoint viewpoint, User user, User contactUser) {
  950. LiveState liveState = LiveState.getInstance();
  951. // See if we have either contacts or contacters cached for the relationship
  952. Set<Guid> contacters = liveState.peekContacters(contactUser.getGuid());
  953. if (contacters != null) {
  954. return contacters.contains(user.getGuid());
  955. }
  956. Set<Guid> contacts = liveState.peekContacts(user.getGuid());
  957. if (contacts != null) {
  958. return contacts.contains(contactUser.getGuid());
  959. }
  960. // If neither is cached, we compute one of them; we prefer to cache
  961. // information for the viewer; when neither the user or contactUser is
  962. // the viewer we prefer the contact side of the relationship (somewhat
  963. // abitrarily)
  964. if (viewpoint.isOfUser(contactUser)) {
  965. contacters = liveState.getContacters(contactUser.getGuid());
  966. return contacters.contains(user.getGuid());
  967. } else {
  968. contacts = liveState.getContacts(user.getGuid());
  969. return contacts.contains(contactUser.getGuid());
  970. }
  971. }
  972. public boolean isContact(Viewpoint viewpoint, User user, User contactUser) {
  973. // see if we're allowed to look at who user's contacts are
  974. if (!isViewerSystemOrFriendOf(viewpoint, user))
  975. return false;
  976. // if we can see their contacts, return whether this person is one of them
  977. return userHasContact(viewpoint, user, contactUser);
  978. }
  979. public boolean isViewerSystemOrFriendOf(Viewpoint viewpoint, User user) {
  980. if (viewpoint instanceof SystemViewpoint) {
  981. return true;
  982. } else if (viewpoint instanceof UserViewpoint) {
  983. UserViewpoint userViewpoint = (UserViewpoint) viewpoint;
  984. if (user.equals(userViewpoint.getViewer()))
  985. return true;
  986. return userHasContact(viewpoint, user, userViewpoint.getViewer());
  987. } else {
  988. return false;
  989. }
  990. }
  991. static final String GET_CONTACT_RESOURCES_QUERY = "SELECT cc.resource FROM ContactClaim cc WHERE cc.contact = :contact";
  992. private Resource getFirstContactResource(Contact contact) {
  993. // An invariant we retain in the database is that every contact
  994. // has at least one resource, so we don't need to check for
  995. // NoResultException
  996. return (Resource) em.createQuery(GET_CONTACT_RESOURCES_QUERY)
  997. .setParameter("contact", contact).setMaxResults(1)
  998. .getSingleResult();
  999. }
  1000. public Resource getBestResource(Person person) {
  1001. User user = getUser(person);
  1002. if (user != null)
  1003. return user.getAccount();
  1004. return getFirstContactResource((Contact) person);
  1005. }
  1006. private Account getAttachedAccount(User user) {
  1007. if (!em.contains(user))
  1008. user = lookupUser(user.getGuid());
  1009. return user.getAccount();
  1010. }
  1011. public boolean getAccountDisabled(User user) {
  1012. return user.getAccount().isDisabled();
  1013. }
  1014. public void setAccountDisabled(User user, Site site, boolean disabled) throws RetryException {
  1015. Account account = getAttachedAccount(user);
  1016. if (site == Site.MUGSHOT && account.getAccountType() == AccountType.GNOME) {
  1017. if (account.isPublicPage() == disabled) {
  1018. setPublicPage(new UserViewpoint(user, site), !disabled);
  1019. logger.debug("Public page flag toggled to {} on account {}", !disabled,
  1020. account);
  1021. }
  1022. } else if (account.isDisabled() != disabled) {
  1023. account.setDisabled(disabled);
  1024. logger.debug("Disabled flag toggled to {} on account {}", disabled,
  1025. account);
  1026. notifier.onAccountDisabledToggled(account);
  1027. }
  1028. }
  1029. public boolean getAccountAdminDisabled(User user) {
  1030. return user.getAccount().isAdminDisabled();
  1031. }
  1032. public void setAccountAdminDisabled(User user, boolean disabled) {
  1033. Account account = getAttachedAccount(user);
  1034. if (account.isAdminDisabled() != disabled) {
  1035. account.setAdminDisabled(disabled);
  1036. logger.debug("adminDisabled flag toggled to {} on account {}", disabled,
  1037. account);
  1038. notifier.onAccountAdminDisabledToggled(account);
  1039. }
  1040. }
  1041. static final String GET_ADMIN_QUERY = "SELECT adm FROM Administrator adm WHERE adm.account = :acct";
  1042. public boolean isAdministrator(User user) {
  1043. Account acct = getAttachedAccount(user);
  1044. if (acct == null)
  1045. return false;
  1046. boolean noAuthentication = config.getProperty(
  1047. HippoProperty.DISABLE_AUTHENTICATION).equals("true");
  1048. if (noAuthentication) {
  1049. logger
  1050. .debug("auth disabled - everyone gets to be an administrator!");
  1051. return true;
  1052. }
  1053. try {
  1054. Administrator adm = (Administrator) em.createQuery(GET_ADMIN_QUERY)
  1055. .setParameter("acct", acct).getSingleResult();
  1056. return adm != null;
  1057. } catch (NoResultException e) {
  1058. return false;
  1059. }
  1060. }
  1061. public boolean getMusicSharingEnabled(User user, Enabled enabled) {
  1062. // we only share your music if your account is enabled, AND music
  1063. // sharing is enabled.
  1064. // but we return only the music sharing flag here since the two settings
  1065. // are distinct
  1066. // in the UI. The pref we send to the client is a composite of the two.
  1067. Account account = user.getAccount();
  1068. Boolean musicSharingEnabled = account.isMusicSharingEnabled();
  1069. if (musicSharingEnabled == null)
  1070. musicSharingEnabled = AccountSystem.DEFAULT_ENABLE_MUSIC_SHARING;
  1071. switch (enabled) {
  1072. case RAW_PREFERENCE_ONLY:
  1073. return musicSharingEnabled;
  1074. case AND_ACCOUNT_IS_ACTIVE:
  1075. return account.isActive() && musicSharingEnabled;
  1076. }
  1077. throw new IllegalArgumentException(
  1078. "invalid value for enabled param to getMusicSharingEnabled");
  1079. }
  1080. public void setMusicSharingEnabled(UserViewpoint viewpoint, boolean enabled) {
  1081. Account account = getAttachedAccount(viewpoint.getViewer());
  1082. if (account.isMusicSharingEnabled() == null || account.isMusicSharingEnabled() != enabled) {
  1083. account.setMusicSharingEnabled(enabled);
  1084. notifier.onMusicSharingToggled(viewpoint);
  1085. LiveState.getInstance().queueUpdate(new UserPrefChangedEvent(viewpoint.getViewer().getGuid(), "musicSharingEnabled", Boolean.toString(enabled)));
  1086. DataService.currentSessionRW().changed(UserDMO.class, account.getOwner().getGuid(), "musicSharingEnabled");
  1087. }
  1088. }
  1089. public boolean getMusicSharingPrimed(User user) {
  1090. Account account = getAttachedAccount(user);
  1091. return account.isMusicSharingPrimed();
  1092. }
  1093. public void setMusicSharingPrimed(User user, boolean primed) {
  1094. Account account = getAttachedAccount(user);
  1095. if (account.isMusicSharingPrimed() != primed) {
  1096. account.setMusicSharingPrimed(primed);
  1097. LiveState.getInstance().queueUpdate(new UserPrefChangedEvent(user.getGuid(), "musicSharingPrimed", Boolean.toString(primed)));
  1098. DataService.currentSessionRW().changed(UserDMO.class, account.getOwner().getGuid(), "musicSharingPrimed");
  1099. }
  1100. }
  1101. public boolean getApplicationUsageEnabled(User user) {
  1102. Boolean enabled = user.getAccount().isApplicationUsageEnabled();
  1103. return enabled != null ? enabled : AccountSystem.DEFAULT_APPLICATION_USAGE_ENABLED;
  1104. }
  1105. public void setApplicationUsageEnabled(UserViewpoint viewpoint, boolean enabled) {
  1106. Account account = viewpoint.getViewer().getAccount();
  1107. boolean wasSet = account.isApplicationUsageEnabled() != null;
  1108. boolean wasEnabled = getApplicationUsageEnabled(viewpoint.getViewer());
  1109. account.setApplicationUsageEnabled(enabled);
  1110. if (enabled != wasEnabled) {
  1111. LiveState.getInstance().queueUpdate(new UserPrefChangedEvent(viewpoint.getViewer().getGuid(), "applicationUsageEnabled", Boolean.toString(enabled)));
  1112. DataService.currentSessionRW().changed(UserDMO.class, account.getOwner().getGuid(), "applicationUsageEnabled");
  1113. }
  1114. if (enabled != wasEnabled || !wasSet)
  1115. notifier.onApplicationUsageToggled(viewpoint);
  1116. }
  1117. public void incrementUserVersion(final User user) {
  1118. // While it isn't a big deal in practice, the implementation below is
  1119. // slightly
  1120. // racy. The following would be better, but triggers a hibernate bug.
  1121. //
  1122. // em.createQuery("UPDATE User u set u.version = u.version + 1 WHERE
  1123. // u.id = :id")
  1124. // .setParameter("id", userId)
  1125. // .executeUpdate();
  1126. //
  1127. // em.refresh(user);
  1128. user.setVersion(user.getVersion() + 1);
  1129. }
  1130. public void setBio(UserViewpoint viewpoint, User user, String bio) {
  1131. if (!viewpoint.isOfUser(user))
  1132. throw new RuntimeException("can only set one's own bio");
  1133. if (!em.contains(user))
  1134. throw new RuntimeException("user not attached");
  1135. Account acct = user.getAccount();
  1136. acct.setBio(bio);
  1137. revisionControl.persistRevision(new UserBioChangedRevision(viewpoint.getViewer(), new Date(), bio));
  1138. }
  1139. public void setMusicBio(UserViewpoint viewpoint, User user, String bio) {
  1140. if (!viewpoint.isOfUser(user))
  1141. throw new RuntimeException("can only set one's own music bio");
  1142. if (!em.contains(user))
  1143. throw new RuntimeException("user not attached");
  1144. Account acct = user.getAccount();
  1145. acct.setMusicBio(bio);
  1146. }
  1147. /**
  1148. * Note that the photo CAN be null, which means to use the uploaded photo
  1149. * for the user instead of a photo filename.
  1150. */
  1151. public void setStockPhoto(UserViewpoint viewpoint, User user, String photo) {
  1152. if (!viewpoint.isOfUser(user))
  1153. throw new RuntimeException("can only set one's own photo");
  1154. if (photo != null && !Validators.validateStockPhoto(photo))
  1155. throw new RuntimeException("invalid stock photo name");
  1156. user.setStockPhoto(photo);
  1157. DataService.currentSessionRW().changed(UserDMO.class, user.getGuid(), "photoUrl");
  1158. LiveState.getInstance().queueUpdate(new UserChangedEvent(user.getGuid(), UserChangedEvent.Detail.PHOTO));
  1159. }
  1160. public Set<User> getMySpaceContacts(UserViewpoint viewpoint) {
  1161. Set<User> contacts = getRawUserContacts(viewpoint, viewpoint.getViewer());
  1162. // filter out anyone with no myspace
  1163. Iterator<User> iterator = contacts.iterator();
  1164. while (iterator.hasNext()) {
  1165. User user = iterator.next();
  1166. ExternalAccount external;
  1167. try {
  1168. // not using externalAccounts.getMySpaceName() because we
  1169. // also want to check we have the friend id
  1170. external = externalAccounts.lookupExternalAccount(
  1171. viewpoint, user, ExternalAccountType.MYSPACE);
  1172. if (external.getSentiment() == Sentiment.LOVE
  1173. && external.getHandle() != null
  1174. && external.getExtra() != null) {
  1175. // we have myspace name AND friend ID
  1176. continue;
  1177. }
  1178. } catch (NotFoundException e) {
  1179. // nothing
  1180. }
  1181. // remove - did not have a myspace name
  1182. iterator.remove();
  1183. }
  1184. return contacts;
  1185. }
  1186. public void setPublicPage(UserViewpoint view, boolean enabled) throws RetryException {
  1187. User viewer = view.getViewer();
  1188. Account account = viewer.getAccount();
  1189. account.setPublicPage(enabled);
  1190. DataService.currentSessionRW().changed(UserDMO.class, viewer.getGuid(), "mugshotPublicPage");
  1191. if (enabled && !account.getWasSentShareLinkTutorial()) {
  1192. postingBoard.doInitialShare(view);
  1193. }
  1194. }
  1195. }