PageRenderTime 59ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/dumbhippo/branches/linux-client-1-1-32-bugfix/server/src/com/dumbhippo/server/impl/IdentitySpiderBean.java

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