@Stateless
@LocalBinding(jndiBinding = "com.dumbhippo.live.PostCreatedEventProcessor")
public class PostCreatedEventProcessor implements LiveEventProcessor {
  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(LiveEventProcessor.class);

  @EJB LiveUserUpdater userUpdater;

  public void process(LiveState state, LiveEvent abstractEvent) {
    PostCreatedEvent event = (PostCreatedEvent) abstractEvent;
    userUpdater.handlePostCreated(event.getPosterId());
  }
}
Exemple #2
0
public class WantsInServlet extends AbstractServlet {

  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(WantsInServlet.class);

  private static final long serialVersionUID = 1L;

  private WantsInSystem wantsInSystem;

  @Override
  public void init() {
    wantsInSystem = WebEJBUtil.defaultLookup(WantsInSystem.class);
  }

  @Override
  protected String wrappedDoPost(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException, HttpException, HumanVisibleException, RetryException {
    String address = request.getParameter("address");
    if (address != null) address = address.trim();
    if (address == null
        || address.length() == 0
        || address.indexOf('@') < 1
        || address.endsWith("example.com")) {
      throw new HumanVisibleException("You have to put in an email address")
          .setHtmlSuggestion("<a href=\"/\">Try again</a>");
    }

    try {
      wantsInSystem.addWantsIn(address);
    } catch (ValidationException e) {
      throw new HumanVisibleException(
              "Something was wrong with that email address (" + e.getMessage() + ")")
          .setHtmlSuggestion("<a href=\"/\">Try again</a>");
    }

    response.setContentType("text/html");
    OutputStream out = response.getOutputStream();
    out.write(
        ("<head><title>Saved your address</title></head>\n"
                + " <body><p>We saved your address; we'll let you know when we have room for more.</p><p>Thanks!</p></body>\n")
            .getBytes());
    out.flush();

    return null;
  }

  @Override
  protected boolean requiresTransaction(HttpServletRequest request) {
    return true;
  }
}
Exemple #3
0
public class MySpaceScraper {

  private static final Logger logger = GlobalSetup.getLogger(MySpaceScraper.class);

  private static final int REQUEST_TIMEOUT = 1000 * 12;

  private static String scrapeFriendID(String html) {
    Pattern p = Pattern.compile("/profileHitCounter.cfm\\?friendID=([0-9]+)");
    Matcher m = p.matcher(html);
    if (m.find()) return m.group(1);
    p = Pattern.compile("\\?fuseaction=user.viewfriends&friendID=([0-9]+)");
    m = p.matcher(html);
    if (!m.find()) return null;
    return m.group(1);
  }

  public static String getFriendId(String mySpaceName) throws IOException {
    URL u;
    try {
      u = new URL("http://myspace.com/" + mySpaceName);
    } catch (MalformedURLException e) {
      throw new RuntimeException(e);
    }
    URLConnection connection;
    logger.debug("opening connection to {}", u);
    connection = u.openConnection();
    connection.setConnectTimeout(REQUEST_TIMEOUT);
    connection.setReadTimeout(REQUEST_TIMEOUT);
    connection.setAllowUserInteraction(false);

    String html = StreamUtils.readStreamUTF8(connection.getInputStream(), StreamUtils.ONE_MEGACHAR);
    return scrapeFriendID(html);
  }

  public static final void main(String[] args) throws IOException {
    String friendID = MySpaceScraper.getFriendId("cgwalters");
    System.out.println(friendID);
  }
}
@Stateless
public class ExternalAccountSystemBean implements ExternalAccountSystem {

  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(ExternalAccountSystemBean.class);

  @EJB @IgnoreDependency private FacebookSystem facebookSystem;

  @EJB @IgnoreDependency private YouTubeUpdater youTubeUpdater;

  @EJB private Notifier notifier;

  @PersistenceContext(unitName = "dumbhippo")
  private EntityManager em;

  @WebServiceCache private FlickrUserPhotosCache flickrUserPhotosCache;

  @WebServiceCache private YouTubeVideosCache youTubeVideosCache;

  @EJB private CacheFactory cacheFactory;

  @PostConstruct
  public void init() {
    cacheFactory.injectCaches(this);
  }

  public ExternalAccount getOrCreateExternalAccount(
      UserViewpoint viewpoint, ExternalAccountType type) {
    Account a = viewpoint.getViewer().getAccount();
    if (!em.contains(a))
      throw new RuntimeException("detached account in getOrCreateExternalAccount");

    ExternalAccount external = a.getExternalAccount(type);
    if (external == null) {
      external = new ExternalAccount(type);
      external.setAccount(a);
      em.persist(external);
      a.getExternalAccounts().add(external);

      notifier.onExternalAccountCreated(a.getOwner(), external);
    }
    return external;
  }

  public ExternalAccount lookupExternalAccount(
      Viewpoint viewpoint, User user, ExternalAccountType type) throws NotFoundException {
    if (!em.contains(user.getAccount()))
      throw new RuntimeException("detached account in lookupExternalAccount()");

    // Right now, external accounts are public, unlike email/aim resources which are friends only...
    // so we don't need to use the viewpoint. But here in case we want to add it later.
    ExternalAccount external = user.getAccount().getExternalAccount(type);
    if (external == null)
      throw new NotFoundException("No external account of type " + type + " for user " + user);
    else return external;
  }

  public Set<ExternalAccountView> getExternalAccountViews(Viewpoint viewpoint, User user) {
    // Right now we ignore the viewpoint, so this method is pretty pointless.
    // but if people use it, future code will work properly.

    // be sure the account is attached... the external accounts are lazy-loaded
    if (!em.contains(user.getAccount()))
      throw new RuntimeException("detached account in getExternalAccounts()");

    Set<ExternalAccount> accounts = user.getAccount().getExternalAccounts();
    // logger.debug("{} external accounts for user {}", accounts.size(), user);

    Set<ExternalAccountView> accountViews = new HashSet<ExternalAccountView>();
    for (ExternalAccount account : accounts) {
      if (account.getAccountType() == ExternalAccountType.FACEBOOK) {
        accountViews.add(new ExternalAccountView(account, facebookSystem.getProfileLink(account)));
      } else {
        accountViews.add(new ExternalAccountView(account));
      }
    }

    return accountViews;
  }

  public ExternalAccountView getExternalAccountView(
      Viewpoint viewpoint, ExternalAccount externalAccount) {
    ExternalAccountView view;
    if (externalAccount.getAccountType() == ExternalAccountType.FACEBOOK) {
      view =
          new ExternalAccountView(externalAccount, facebookSystem.getProfileLink(externalAccount));
    } else {
      view = new ExternalAccountView(externalAccount);
    }

    loadThumbnails(viewpoint, view);

    return view;
  }

  public ExternalAccountView getExternalAccountView(
      Viewpoint viewpoint, User user, ExternalAccountType externalAccountType)
      throws NotFoundException {
    ExternalAccount externalAccount = lookupExternalAccount(viewpoint, user, externalAccountType);
    return getExternalAccountView(viewpoint, externalAccount);
  }

  private void loadFlickrThumbnails(Viewpoint viewpoint, ExternalAccountView accountView) {
    ExternalAccount account = accountView.getExternalAccount();

    if (account.getAccountType() != ExternalAccountType.FLICKR)
      throw new IllegalArgumentException("should be a flickr account here");

    if (account.getSentiment() != Sentiment.LOVE)
      throw new IllegalArgumentException("Flickr account is unloved");

    if (account.getHandle() == null) return;

    FlickrPhotosView photos = flickrUserPhotosCache.getSync(account.getHandle());
    if (photos == null) {
      logger.debug("No public photos for {}", account);
      return;
    }

    accountView.setThumbnailsData(
        TypeUtils.castList(Thumbnail.class, photos.getPhotos()),
        photos.getTotal(),
        FlickrPhotoSize.SMALL_SQUARE.getPixels(),
        FlickrPhotoSize.SMALL_SQUARE.getPixels());
  }

  private void loadYouTubeThumbnails(Viewpoint viewpoint, ExternalAccountView accountView) {
    ExternalAccount account = accountView.getExternalAccount();

    if (account.getAccountType() != ExternalAccountType.YOUTUBE)
      throw new IllegalArgumentException("should be a YouTube account here");

    if (account.getSentiment() != Sentiment.LOVE)
      throw new IllegalArgumentException("YouTube account is unloved =(");

    if (account.getHandle() == null) return;

    try {
      youTubeUpdater.getCachedStatus(account);
    } catch (NotFoundException e) {
      logger.debug("No cached YouTube status for {}", account);
      return;
    }

    List<? extends YouTubeVideo> videos = youTubeVideosCache.getSync(account.getHandle());
    if (videos.isEmpty()) {
      logger.debug("Empty list of videos for {}", account);
      return;
    }

    accountView.setThumbnailsData(
        TypeUtils.castList(Thumbnail.class, videos),
        videos.size(),
        videos.get(0).getThumbnailWidth(),
        videos.get(0).getThumbnailHeight());
  }

  private void loadThumbnails(Viewpoint viewpoint, ExternalAccountView externalAccountView) {
    ExternalAccount externalAccount = externalAccountView.getExternalAccount();
    ExternalAccountType type = externalAccount.getAccountType();
    // you only have thumbnails for accounts you like
    if (externalAccount.getSentiment() != Sentiment.LOVE) return;

    switch (type) {
      case FLICKR:
        loadFlickrThumbnails(viewpoint, externalAccountView);
        break;
      case YOUTUBE:
        loadYouTubeThumbnails(viewpoint, externalAccountView);
        break;
      default:
        // most accounts lack thumbnails
        break;
    }
  }

  public void loadThumbnails(Viewpoint viewpoint, Set<ExternalAccountView> accountViews) {
    for (ExternalAccountView externalView : accountViews) {
      loadThumbnails(viewpoint, externalView);
    }
  }

  public void setSentiment(ExternalAccount externalAccount, Sentiment sentiment) {
    if ((sentiment == Sentiment.LOVE) && !externalAccount.hasAccountInfo()) {
      throw new RuntimeException(
          "Trying to set a love sentiment on account with no valid account info");
    }

    externalAccount.setSentiment(sentiment);
    notifier.onExternalAccountLovedAndEnabledMaybeChanged(
        externalAccount.getAccount().getOwner(), externalAccount);
  }

  public boolean getExternalAccountExistsLovedAndEnabled(
      Viewpoint viewpoint, User user, ExternalAccountType accountType) {
    try {
      ExternalAccount external = lookupExternalAccount(viewpoint, user, accountType);
      return external.isLovedAndEnabled();
    } catch (NotFoundException e) {
      return false;
    }
  }

  public void onAccountDisabledToggled(Account account) {
    for (ExternalAccount external : account.getExternalAccounts()) {
      // this is why we have "maybe changed" since we really don't know.
      notifier.onExternalAccountLovedAndEnabledMaybeChanged(account.getOwner(), external);
    }
  }

  public void onAccountAdminDisabledToggled(Account account) {
    for (ExternalAccount external : account.getExternalAccounts()) {
      // this is why we have "maybe changed" since we really don't know.
      notifier.onExternalAccountLovedAndEnabledMaybeChanged(account.getOwner(), external);
    }
  }

  public void onMusicSharingToggled(Account account) {
    // We aren't interested in this, just part of a listener iface we're using
  }

  public void validateAll() {
    logger.info("Administrator kicked off validation of all ExternalAccount rows in the database");
    Query q =
        em.createQuery("SELECT ea.id, ea.accountType, ea.handle, ea.extra FROM ExternalAccount ea");
    List<Object[]> results = TypeUtils.castList(Object[].class, q.getResultList());

    logger.info("There are {} ExternalAccount rows to validate", results.size());

    List<Long> invalidHandles = new ArrayList<Long>();
    List<Long> invalidExtras = new ArrayList<Long>();

    for (Object[] row : results) {
      // logger.debug("row is: {}", Arrays.toString(row));
      Long id = (Long) row[0];
      ExternalAccountType accountType = (ExternalAccountType) row[1];
      String handle = (String) row[2];
      String extra = (String) row[3];
      if (accountType == null) {
        logger.info("Row has null accountType: {}", Arrays.toString(row));
      } else {
        try {
          String c = accountType.canonicalizeHandle(handle);
          if (handle != null && !c.equals(handle)) {
            logger.info(
                "Row is not canonicalized: {}: '{}' vs. '{}'",
                new Object[] {Arrays.toString(row), handle, c});
          }
        } catch (ValidationException e) {
          logger.info("Row had invalid 'handle': {}: {}", Arrays.toString(row), e.getMessage());
          invalidHandles.add(id);
        }
        try {
          String c = accountType.canonicalizeExtra(extra);
          if (extra != null && !c.equals(extra)) {
            logger.info(
                "Row is not canonicalized: {}: '{}' vs. '{}'",
                new Object[] {Arrays.toString(row), extra, c});
          }
        } catch (ValidationException e) {
          logger.info("Row had invalid 'extra': {}: {}", Arrays.toString(row), e.getMessage());
          invalidExtras.add(id);
        }
      }
    }

    if (!invalidHandles.isEmpty()) {
      StringBuilder sb = new StringBuilder();
      sb.append("UPDATE ExternalAccount SET handle = NULL WHERE id IN (");
      for (Long id : invalidHandles) {
        sb.append(id);
        sb.append(",");
      }
      sb.setLength(sb.length() - 1); // chop comma
      sb.append(");");
      logger.info("Possible query to null invalid handles: {}", sb);
    }

    if (!invalidExtras.isEmpty()) {
      StringBuilder sb = new StringBuilder();
      sb.append("UPDATE ExternalAccount SET extra = NULL WHERE id IN (");
      for (Long id : invalidHandles) {
        sb.append(id);
        sb.append(",");
      }
      sb.setLength(sb.length() - 1); // chop comma
      sb.append(");");
      logger.info("Possible query to null invalid extras: {}", sb);
    }

    logger.info(
        "ExternalAccount validation complete, {} invalid handles {} invalid extras",
        invalidHandles.size(),
        invalidExtras.size());
  }
}
Exemple #5
0
/**
 * This is only truly @Stateless if the same mailSession gets injected into all instances, I guess,
 * since a MimeMessage points back to the session. Though as long as sendMessage() doesn't refer to
 * mailSession it would be OK either way.
 *
 * @author hp
 */
@Stateless
public class MailerBean implements Mailer {
  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(MailerBean.class);

  @EJB private Configuration configuration;

  @javax.annotation.Resource(mappedName = "java:/Mail")
  private Session mailSession;

  @EJB private PersonViewer personViewer;

  private MimeMessage createMessage(InternetAddress fromAddress, InternetAddress toAddress) {
    MimeMessage msg;

    msg = new MimeMessage(mailSession);

    try {
      // sender the recipient will see
      msg.setFrom(fromAddress);
      // sender the mail system will verify against etc.
      msg.setSender(new InternetAddress(SpecialSender.NOBODY.toString()));
      msg.setRecipient(Message.RecipientType.TO, toAddress);
    } catch (MessagingException e) {
      throw new RuntimeException(e);
    }

    return msg;
  }

  private MimeMessage createMessage(
      InternetAddress fromAddress, InternetAddress replyToAddress, InternetAddress toAddress) {
    MimeMessage msg = createMessage(fromAddress, toAddress);

    try {
      msg.setReplyTo(new InternetAddress[] {replyToAddress});
    } catch (MessagingException e) {
      throw new RuntimeException(e);
    }

    return msg;
  }

  private InternetAddress createAddressFromViewpoint(
      UserViewpoint viewpoint, SpecialSender fallbackAddress) {
    PersonView fromViewedBySelf =
        personViewer.getPersonView(viewpoint, viewpoint.getViewer(), PersonViewExtra.PRIMARY_EMAIL);

    InternetAddress internetAddress;

    try {
      if ((fromViewedBySelf.getEmail() != null)
          && (fromViewedBySelf.getEmail().getEmail() != null)) {
        String niceName = fromViewedBySelf.getName();
        String address = fromViewedBySelf.getEmail().getEmail();
        internetAddress = new InternetAddress(address, niceName);
      } else {
        // theoretically, we might have users in the system who do not have an e-mail, but
        // have an AIM, when we allow such users in practice, we can change this to possibly
        // use users's @aol.com address, though it's quite possible that the person does not
        // really use this address
        internetAddress = new InternetAddress(fallbackAddress.toString());
      }
    } catch (AddressException e) {
      throw new RuntimeException(e);
    } catch (UnsupportedEncodingException e) {
      throw new RuntimeException(e);
    }

    return internetAddress;
  }

  private InternetAddress createAddressFromString(String address) {
    try {
      InternetAddress internetAddress = new InternetAddress(address);
      return internetAddress;
    } catch (AddressException e) {
      throw new RuntimeException(e);
    }
  }

  public MimeMessage createMessage(
      UserViewpoint viewpoint, SpecialSender viewpointFallbackAddress, String to) {

    InternetAddress fromAddress = createAddressFromViewpoint(viewpoint, viewpointFallbackAddress);
    InternetAddress toAddress = createAddressFromString(to);

    return createMessage(fromAddress, toAddress);
  }

  public MimeMessage createMessage(UserViewpoint viewpoint, String to) {
    return createMessage(viewpoint, SpecialSender.MUGSHOT, to);
  }

  public MimeMessage createMessage(SpecialSender from, String to) {

    InternetAddress fromAddress = createAddressFromString(from.toString());
    InternetAddress toAddress = createAddressFromString(to);

    return createMessage(fromAddress, toAddress);
  }

  public MimeMessage createMessage(
      SpecialSender from,
      UserViewpoint viewpointReplyTo,
      SpecialSender viewpointFallbackAddress,
      String to) {

    InternetAddress fromAddress = createAddressFromString(from.toString());
    InternetAddress replyToAddress =
        createAddressFromViewpoint(viewpointReplyTo, viewpointFallbackAddress);
    InternetAddress toAddress = createAddressFromString(to);

    return createMessage(fromAddress, replyToAddress, toAddress);
  }

  public void setMessageContent(
      MimeMessage message, String subject, String bodyText, String bodyHtml) {
    try {
      message.setSubject(subject);

      MimeBodyPart textPart = new MimeBodyPart();
      textPart.setText(bodyText.toString(), "UTF-8");

      MimeBodyPart htmlPart = new MimeBodyPart();
      htmlPart.setContent(bodyHtml.toString(), "text/html; charset=UTF-8");

      MimeMultipart multiPart = new MimeMultipart();
      // "alternative" means display only one or the other, "mixed" means both
      multiPart.setSubType("alternative");

      // I read something on the internet saying to put the text part first
      // so sucktastic mail clients see it first
      multiPart.addBodyPart(textPart);
      multiPart.addBodyPart(htmlPart);

      message.setContent(multiPart);
    } catch (MessagingException e) {
      throw new RuntimeException("failed to put together MIME message", e);
    }
  }

  public void sendMessage(MimeMessage message) {
    // The primary reason for DISABLE_EMAIL is for test configurations, so
    // we check only at the end of the process to catch bugs earlier
    // in the process.
    if (!configuration.getProperty(HippoProperty.DISABLE_EMAIL).equals("true")) {
      try {
        logger.debug("Sending email...");
        Transport.send(message);
      } catch (MessagingException e) {
        throw new RuntimeException(e);
      }
    }
  }
}
Exemple #6
0
@Entity
@Table(
    name = "NoteRepository",
    uniqueConstraints = {@UniqueConstraint(columnNames = {"uuid"})})
public class NoteRepository extends EmbeddedUuidPersistable {
  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(NoteRepository.class);

  private static final long serialVersionUID = 0L;

  private User owner;
  private int revision;
  private Set<NoteRevision> noteRevisions;

  protected NoteRepository() {
    this(null);
  }

  public NoteRepository(User owner) {
    revision = 0;
    noteRevisions = new HashSet<NoteRevision>();
    if (owner != null) {
      this.owner = owner;

      // we don't do a back-link since usually when loading a User we aren't
      // doing anything with Tomboy
      // owner.setNoteRepository(this);
    }
  }

  @OneToOne
  @JoinColumn(nullable = false)
  public User getOwner() {
    return owner;
  }

  /**
   * This is protected because calling it is probably a bad idea. (Give someone else your
   * repository?)
   *
   * @param owner The owner to set.
   */
  protected void setOwner(User owner) {
    this.owner = owner;
  }

  @Column(nullable = false)
  public int getRevision() {
    return revision;
  }

  public void setRevision(int revision) {
    this.revision = revision;
  }

  @OneToMany(mappedBy = "repository", fetch = FetchType.EAGER)
  @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
  public Set<NoteRevision> getNoteRevisions() {
    if (noteRevisions == null) throw new RuntimeException("no note revisions set???");
    return noteRevisions;
  }

  /**
   * This is protected because only Hibernate probably needs to call it.
   *
   * @param noteRevisions
   */
  protected void setNoteRevisions(Set<NoteRevision> noteRevisions) {
    if (noteRevisions == null) throw new IllegalArgumentException("null note revisions");
    this.noteRevisions = noteRevisions;
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append("{NoteRepository " + getId() + " owner = ");
    if (owner != null) builder.append(owner.toString());
    else builder.append("null");

    builder.append(" uuid = " + getUuid());

    builder.append("}");

    return builder.toString();
  }
}
public class ListCacheStorage<KeyType, ResultType, EntityType extends CachedListItem>
    extends AbstractCacheStorage<KeyType, List<? extends ResultType>, EntityType> {

  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(ListCacheStorage.class);

  private ListCacheStorageMapper<KeyType, ResultType, EntityType> mapper;
  // unused for now
  // private Class<ResultType> resultClass;

  protected ListCacheStorage(
      EntityManager em,
      long expirationTime,
      Class<ResultType> resultClass,
      ListCacheStorageMapper<KeyType, ResultType, EntityType> mapper) {
    super(em, expirationTime);
    this.mapper = mapper;
    // this.resultClass = resultClass;
  }

  public List<? extends ResultType> checkCache(KeyType key) throws NotCachedException {
    List<EntityType> oldItems = mapper.queryExisting(key);

    if (oldItems.isEmpty()) throw new NotCachedException();

    long now = System.currentTimeMillis();
    boolean outdated = false;
    boolean haveNoResultsMarker = false;
    for (EntityType d : oldItems) {
      if ((d.getLastUpdated().getTime() + getExpirationTime()) < now) {
        outdated = true;
      }
      if (d.isNoResultsMarker()) {
        haveNoResultsMarker = true;
      }
    }

    if (outdated) {
      logger.debug("Cache appears outdated for key {}", key);
      throw new ExpiredCacheException();
    }

    if (haveNoResultsMarker) {
      logger.debug("Negative result cached for key {}", key);
      return Collections.emptyList();
    }

    sort(oldItems);
    return formResultTypeList(oldItems);
  }

  private List<ResultType> formResultTypeList(List<EntityType> list) {
    List<ResultType> results = new ArrayList<ResultType>();
    for (EntityType e : list) {
      results.add(mapper.resultFromEntity(e));
    }
    return results;
  }

  @Override
  public void expireCache(KeyType key) {
    mapper.setAllLastUpdatedToZero(key);
  }

  // why Eclipse warns about this I do not understand
  @SuppressWarnings("unchecked")
  private void sort(List<EntityType> list) {
    Collections.sort(list);
  }

  // null means that we could not get the updated results, so leave the old results
  // empty list results means that we should save a no results marker
  public List<? extends ResultType> saveInCacheInsideExistingTransaction(
      KeyType key,
      List<? extends ResultType> newItems,
      Date now,
      boolean refetchedWithoutCheckingCache) {
    TxUtils.assertHaveTransaction();

    logger.debug("Saving new results in cache");

    List<EntityType> oldItems = mapper.queryExisting(key);

    // since we always load all items, this is as good as letting the database do it, and doesn't
    // require
    // hacking each individual oldItems
    sort(oldItems);

    if (newItems == null) return formResultTypeList(oldItems);

    // Delete old items if any, the noResultsMarker if none
    deleteCache(key);

    // This is perhaps superstitious, but we do have an ordering constraint that we must
    // remove the old items then insert the new, or it will cause a constraint violation
    em.flush();

    // save new results
    if (newItems.isEmpty()) {
      EntityType e = mapper.newNoResultsMarker(key);
      if (!e.isNoResultsMarker()) throw new RuntimeException("new no results marker isn't: " + e);
      e.setLastUpdated(now);
      em.persist(e);
      logger.debug("cached a no results marker for key {}", key);
    } else {
      // Note that we are relying on getting database ID ordering that matches
      // the order of newItems, so when we load from the cache we can get things
      // back in the same order.
      for (ResultType r : newItems) {
        EntityType e = mapper.entityFromResult(key, r);
        e.setLastUpdated(now);
        em.persist(e);
      }
      logger.debug("cached {} items for key {}", newItems.size(), key);
    }

    return newItems;
  }

  public void deleteCache(KeyType key) {
    List<EntityType> oldItems = mapper.queryExisting(key);
    for (EntityType d : oldItems) {
      em.remove(d);
    }
  }
}
public abstract class AbstractBlockHandlerBean<BlockViewSubType extends BlockView>
    implements BlockHandler {

  private static final Logger logger = GlobalSetup.getLogger(AbstractBlockHandlerBean.class);

  @PersistenceContext(unitName = "dumbhippo")
  protected EntityManager em;

  @EJB protected ChatSystem chatSystem;

  @EJB protected IdentitySpider identitySpider;

  @EJB protected GroupSystem groupSystem;

  @EJB protected ExternalAccountSystem externalAccountSystem;

  @EJB protected PersonViewer personViewer;

  @EJB @IgnoreDependency protected Stacker stacker;

  private Class<? extends BlockViewSubType> viewClass;
  private Constructor<? extends BlockViewSubType> viewClassConstructorUser;
  private Constructor<? extends BlockViewSubType> viewClassConstructorGroup;

  protected AbstractBlockHandlerBean(Class<? extends BlockViewSubType> viewClass) {
    this.viewClass = viewClass;
    try {
      this.viewClassConstructorUser =
          viewClass.getConstructor(
              Viewpoint.class, Block.class, UserBlockData.class, boolean.class);
    } catch (SecurityException e) {
    } catch (NoSuchMethodException e) {
    }
    try {
      this.viewClassConstructorGroup =
          viewClass.getConstructor(
              Viewpoint.class, Block.class, GroupBlockData.class, boolean.class);
    } catch (SecurityException e) {
    } catch (NoSuchMethodException e) {
    }
    if (this.viewClassConstructorUser == null || this.viewClassConstructorGroup == null)
      logger.debug(
          "{} must override createBlockView since it lacks the expected constructors for its view class",
          this.getClass().getName());
  }

  private void checkCreationInvariants(Viewpoint viewpoint, Block block)
      throws BlockNotVisibleException {
    UserViewpoint userview = null;
    if (viewpoint instanceof UserViewpoint) userview = (UserViewpoint) viewpoint;

    if (block.getInclusion() == StackInclusion.ONLY_WHEN_VIEWING_SELF) {
      User user = identitySpider.lookupUser(block.getData1AsGuid());
      if (userview == null || !userview.getViewer().equals(user)) {
        // FIXME should this be a RuntimeException? logger.warn is here so we can investigate
        // that if the exception ever really happens
        logger.warn(
            "Trying to view an ONLY_WHEN_VIEWING_SELF block from a different viewpoint: {} block={}",
            userview,
            block);
        throw new BlockNotVisibleException(
            "ONLY_WHEN_VIEWING_SELF block is not visible to non-self viewpoint");
      }
    }
  }

  public BlockViewSubType getUnpopulatedBlockView(
      Viewpoint viewpoint, Block block, UserBlockData ubd, boolean participated)
      throws BlockNotVisibleException {

    checkCreationInvariants(viewpoint, block);
    BlockViewSubType blockView = createBlockView(viewpoint, block, ubd, participated);

    checkBlockViewVisible(blockView);

    return blockView;
  }

  public BlockViewSubType getUnpopulatedBlockView(
      Viewpoint viewpoint, Block block, GroupBlockData gbd, boolean participated)
      throws BlockNotVisibleException {

    checkCreationInvariants(viewpoint, block);
    BlockViewSubType blockView = createBlockView(viewpoint, block, gbd, participated);

    checkBlockViewVisible(blockView);

    return blockView;
  }

  /**
   * Creates a new block view. This should just create the object, not fill it in at all. There are
   * two later stages for filling it in; checkBlockViewVisible() will be called to see if the
   * viewpoint can view the block view, and then populateBlockViewImpl() will be called to
   * initialize any additional details on the block view if it is visible.
   *
   * <p>The default implementation just constructs an instance of viewClass.
   *
   * @param viewpoint
   * @param block
   * @param ubd
   * @return new block view
   */
  protected BlockViewSubType createBlockView(
      Viewpoint viewpoint, Block block, UserBlockData ubd, boolean participated) {
    if (viewClassConstructorUser == null)
      throw new IllegalStateException(
          "Must override createBlockView if your block view type doesn't have the right constructor");

    try {
      return viewClassConstructorUser.newInstance(viewpoint, block, ubd, participated);
    } catch (IllegalArgumentException e) {
      throw new RuntimeException(e);
    } catch (InstantiationException e) {
      throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException(e);
    }
  }

  protected BlockViewSubType createBlockView(
      Viewpoint viewpoint, Block block, GroupBlockData gbd, boolean participated) {
    if (viewClassConstructorUser == null)
      throw new IllegalStateException(
          "Must override createBlockView if your block view type doesn't have the right constructor");

    try {
      return viewClassConstructorGroup.newInstance(viewpoint, block, gbd, participated);
    } catch (IllegalArgumentException e) {
      throw new RuntimeException(e);
    } catch (InstantiationException e) {
      throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * The default implementation of this returns immediately if block.isPublicBlock(), otherwise
   * tries to call populateBlockViewImpl() and lets that throw BlockNotVisibleException if
   * appropriate. Subclasses may be able to do something more efficient than a full
   * populateBlockViewImpl().
   *
   * @param blockView the block view which may or may not be visible to its viewpoint
   * @throws BlockNotVisibleException if blockView's viewpoint can't see this blockView
   */
  protected void checkBlockViewVisible(BlockViewSubType blockView) throws BlockNotVisibleException {
    Block block = blockView.getBlock();
    if (!block.isPublicBlock()) {
      populateBlockViewImpl(blockView); // throws if we can't see the block contents
    }
  }

  /**
   * Fill in a block view. Called by the default implementation of checkBlockViewVisible() and also
   * by the default implementation of populateBlockView() - thus may be called twice. The second
   * call will be skipped if blockView.isPopulated(), so set that flag once you fully populate.
   *
   * @param blockView a block view
   * @throws BlockNotVisibleException if the block view is not visible to its viewpoint
   */
  protected abstract void populateBlockViewImpl(BlockViewSubType blockView)
      throws BlockNotVisibleException;

  // this is final because you need to override populateBlockViewImpl instead.
  public final void populateBlockView(BlockView blockView) {
    try {
      if (!blockView.isPopulated()) populateBlockViewImpl(viewClass.cast(blockView));
    } catch (BlockNotVisibleException e) {
      logger.warn(
          "populateBlockView() should not run into a BlockNotVisibleException because prepareBlockView() should have done it {}",
          blockView);
      throw new RuntimeException("prepareBlockView missed a visibility check", e);
    }
  }

  private Set<User> getUsersWhoCareAboutData1User(Block block, User user) {
    Set<User> peopleWhoCare = null;

    switch (block.getInclusion()) {
      case IN_ALL_STACKS:
      case ONLY_WHEN_VIEWED_BY_OTHERS:
        peopleWhoCare =
            identitySpider.getUsersWhoHaveUserAsContact(SystemViewpoint.getInstance(), user);

        // we also show each block to its "owner" when it is IN_ALL_STACKS;
        // we include the "owner" for the ONLY_WHEN_VIEWED_BY_OTHERS block, so
        // that the block is displayed in the owner's mugshot when it is viewed
        // by another person
        peopleWhoCare.add(user);
        return peopleWhoCare;
      case ONLY_WHEN_VIEWING_SELF:
        peopleWhoCare = Collections.singleton(user);
        return peopleWhoCare;
        // no default, it hides bugs
    }

    throw new RuntimeException("invalid inclusion " + block);
  }

  protected User getData1User(Block block) {
    User user;
    try {
      user = EJBUtil.lookupGuid(em, User.class, block.getData1AsGuid());
    } catch (NotFoundException e) {
      throw new RuntimeException("invalid user in data1 of " + block, e);
    }
    return user;
  }

  protected PersonView getData1UserView(Viewpoint viewpoint, Block block) {
    User user = getData1User(block);
    // No PersonViewExtra are needed since we know this isn't a contact so we have a name
    // without having to get any resources.
    PersonView userView = personViewer.getPersonView(viewpoint, user);
    return userView;
  }

  /**
   * Utility helper for implementing getInterestedUsers() that gets everyone with the user id in
   * data1 listed in their contacts.
   *
   * <p>Returns only the person themselves in the set if block.getInclusion() ==
   * ONLY_WHEN_VIEWING_SELF.
   *
   * @param block
   * @return
   */
  protected final Set<User> getUsersWhoCareAboutData1User(Block block) {
    return getUsersWhoCareAboutData1User(block, getData1User(block));
  }

  protected final Set<User> getUsersWhoCareAboutData1UserAndExternalAccount(
      Block block, ExternalAccountType accountType) {
    User user = getData1User(block);
    if (externalAccountSystem.getExternalAccountExistsLovedAndEnabled(
        SystemViewpoint.getInstance(), user, accountType)) {
      return getUsersWhoCareAboutData1User(block, user);
    } else {
      return Collections.emptySet();
    }
  }

  /**
   * Utility helper for implementing getInterestedUsers() that gets everyone in the group identified
   * by the group id in data1.
   *
   * @param block
   * @return
   */
  protected final Set<User> getUsersWhoCareAboutData1Group(Block block) {
    Group group;
    try {
      group = EJBUtil.lookupGuid(em, Group.class, block.getData1AsGuid());
    } catch (NotFoundException e) {
      throw new RuntimeException("invalid group in data1 of " + block, e);
    }
    Set<User> groupMembers = groupSystem.getUserMembers(SystemViewpoint.getInstance(), group);
    return groupMembers;
  }

  /**
   * Utility helper for implementing getInterestedGroups() that returns a single-member set with the
   * group stored in data1.
   *
   * @param block
   * @return
   */
  protected Set<Group> getData1GroupAsSet(Block block) {
    Group group;
    try {
      group = EJBUtil.lookupGuid(em, Group.class, block.getData1AsGuid());
    } catch (NotFoundException e) {
      throw new RuntimeException(e);
    }
    return Collections.singleton(group);
  }

  private Set<Group> getGroupsData1UserIsIn(Block block, User user) {
    switch (block.getInclusion()) {
      case IN_ALL_STACKS:
      case ONLY_WHEN_VIEWED_BY_OTHERS:
        return groupSystem.findRawGroups(SystemViewpoint.getInstance(), user);
      case ONLY_WHEN_VIEWING_SELF:
        return Collections.emptySet();
        // no default, hides bugs
    }
    throw new RuntimeException("invalid inclusion " + block);
  }

  /**
   * Utility helper for implementing getInterestedGroups() that returns the set of groups the user
   * id stored in data1 is a member of.
   *
   * <p>Returns an empty set of groups if the block's inclusion is ONLY_WHEN_VIEWING_SELF.
   *
   * @param block
   * @return
   */
  protected final Set<Group> getGroupsData1UserIsIn(Block block) {
    User user = getData1User(block);

    return getGroupsData1UserIsIn(block, user);
  }

  protected final Set<Group> getGroupsData1UserIsInIfExternalAccount(
      Block block, ExternalAccountType accountType) {
    User user = getData1User(block);

    if (externalAccountSystem.getExternalAccountExistsLovedAndEnabled(
        SystemViewpoint.getInstance(), user, accountType)) {
      return getGroupsData1UserIsIn(block, user);
    } else {
      return Collections.emptySet();
    }
  }
}
/**
 * ChangeNotification represents pending notifications for a single resource.
 *
 * <p>Note that this class must be serializable, because we send it over JMS when broadcasting
 * changes. That's why we store Class<T> rather than DMClassHolder<K,T>, even though it means we
 * have to look up the DMClassHolder each time.
 *
 * @param <K>
 * @param <T>
 */
public class ChangeNotification<K, T extends DMObject<K>> implements Serializable {
  private static final long serialVersionUID = 3460039660076438219L;

  private static Logger logger = GlobalSetup.getLogger(ChangeNotification.class);

  private Class<T> clazz;
  private K key;
  private long propertyMask; // bitset
  private ClientMatcher matcher;

  private long[] feedTimestamps;

  /**
   * DO NOT USE THIS CONSTRUCTOR DIRECTLY. Instead use model.makeChangeNotification(), which
   * properly handles subclassing.
   */
  public ChangeNotification(Class<T> clazz, K key, ClientMatcher matcher) {
    this.clazz = clazz;
    this.key = key;
    this.matcher = matcher;
  }

  public void addProperty(int propertyIndex) {
    this.propertyMask |= 1 << propertyIndex;
  }

  public void addProperty(DataModel model, String propertyName) {
    @SuppressWarnings("unchecked")
    DMClassHolder<K, T> classHolder = (DMClassHolder<K, T>) model.getClassHolder(clazz);

    int propertyIndex = classHolder.getPropertyIndex(propertyName);
    if (propertyIndex < 0)
      throw new RuntimeException(
          "Class " + classHolder.getDMOClass().getName() + " has no property " + propertyName);

    DMPropertyHolder<K, T, ?> property = classHolder.getProperty(propertyIndex);
    if (property instanceof FeedPropertyHolder)
      throw new RuntimeException("For feed-valued-properties, you must use session.feedChanged()");

    addProperty(propertyIndex);
  }

  public void addFeedProperty(DataModel model, String propertyName, long itemTimestamp) {
    @SuppressWarnings("unchecked")
    DMClassHolder<K, T> classHolder = (DMClassHolder<K, T>) model.getClassHolder(clazz);

    int propertyIndex = classHolder.getPropertyIndex(propertyName);
    if (propertyIndex < 0)
      throw new RuntimeException(
          "Class " + classHolder.getDMOClass().getName() + " has no property " + propertyName);

    addProperty(propertyIndex);

    DMPropertyHolder<K, T, ?> property = classHolder.getProperty(propertyIndex);
    if (!(property instanceof FeedPropertyHolder))
      throw new RuntimeException("session.feedChanged() for a non-feed-valued property");

    int feedPropertyIndex = classHolder.getFeedPropertyIndex(property.getName());

    if (feedTimestamps == null) {
      feedTimestamps = new long[classHolder.getFeedPropertiesCount()];
      for (int i = 0; i < feedTimestamps.length; i++) feedTimestamps[i] = Long.MAX_VALUE;
    }

    if (feedTimestamps[feedPropertyIndex] > itemTimestamp)
      feedTimestamps[feedPropertyIndex] = itemTimestamp;
  }

  public void invalidate(DataModel model, long timestamp) {
    @SuppressWarnings("unchecked")
    DMClassHolder<K, T> classHolder = (DMClassHolder<K, T>) model.getClassHolder(clazz);

    long v = propertyMask;
    int propertyIndex = 0;
    while (v != 0) {
      if ((v & 1) != 0) {
        DMPropertyHolder<K, T, ?> property = classHolder.getProperty(propertyIndex);

        if (property instanceof FeedPropertyHolder) {
          int feedPropertyIndex = classHolder.getFeedPropertyIndex(property.getName());
          model
              .getStore()
              .invalidateFeed(
                  classHolder, key, propertyIndex, timestamp, feedTimestamps[feedPropertyIndex]);

          logger.debug(
              "Invalidated {}#{}.{}, feedTimestamp={}",
              new Object[] {
                classHolder.getDMOClass().getSimpleName(),
                key,
                property.getName(),
                feedTimestamps[feedPropertyIndex]
              });
        } else {
          model.getStore().invalidate(classHolder, key, propertyIndex, timestamp);

          logger.debug(
              "Invalidated {}#{}.{}",
              new Object[] {classHolder.getDMOClass().getSimpleName(), key, property.getName()});
        }
      }

      propertyIndex++;
      v >>= 1;
    }
  }

  public void resolveNotifications(DataModel model, ClientNotificationSet result) {
    @SuppressWarnings("unchecked")
    DMClassHolder<K, T> classHolder = (DMClassHolder<K, T>) model.getClassHolder(clazz);

    model.getStore().resolveNotifications(classHolder, key, propertyMask, result, matcher);
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof ChangeNotification)) return false;

    ChangeNotification<?, ?> other = (ChangeNotification<?, ?>) o;

    return clazz == other.clazz && key.equals(other.key);
  }

  @Override
  public int hashCode() {
    return 11 * clazz.hashCode() + 17 * key.hashCode();
  }

  @Override
  public String toString() {
    if (matcher != null)
      return clazz.getSimpleName() + "#" + key.toString() + "; matcher=" + matcher;
    else return clazz.getSimpleName() + "#" + key.toString();
  }
}
/**
 * Base class used for beans that implement a cached web service lookup.
 *
 * <p>Use one of the subclasses (AbstractBasicCacheWithStorageBean,
 * AbstractListCacheWithStorageBean, AbstractBasicCacheBean, AbstractListCacheBean) if possible,
 * they have more stuff implemented by default.
 */
@TransactionAttribute(
    TransactionAttributeType.SUPPORTS) // hackaround for bug with method tx attr on generic methods
public abstract class AbstractCacheBean<KeyType, ResultType, EjbIfaceType>
    implements Cache<KeyType, ResultType> {
  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(AbstractCacheBean.class);

  // how long to wait on the search API call
  protected static final int REQUEST_TIMEOUT = 1000 * 12;
  // 2 days, shared by yahoo-related subclasses
  protected static final int YAHOO_EXPIRATION_TIMEOUT = 1000 * 60 * 60 * 24 * 2;
  // hour timeout to retry on failure
  protected static final int FAILED_QUERY_TIMEOUT = 1000 * 60 * 60;

  protected enum Request {
    AMAZON_ALBUM,
    RHAPSODY_DOWNLOAD,
    YAHOO_ALBUM,
    YAHOO_ALBUM_SONGS,
    YAHOO_ARTIST_ALBUMS,
    YAHOO_ARTIST,
    YAHOO_ARTIST_BY_NAME,
    YAHOO_SONG,
    YAHOO_SONG_DOWNLOAD,
    FLICKR_USER_PHOTOS,
    FLICKR_PHOTOSET_PHOTOS,
    FLICKR_USER_PHOTOSETS,
    FACEBOOK_PHOTO_DATA,
    YOUTUBE_VIDEOS,
    NETFLIX_QUEUE_MOVIES
  }

  private static EnumMap<Request, UniqueTaskExecutor> executors;
  private static boolean shutdown = false;

  private Request defaultRequest;

  private Class<? extends EjbIfaceType> ejbIface;
  private long expirationTime; // in milliseconds until we expire the cache

  @PersistenceContext(unitName = "dumbhippo")
  protected EntityManager em;

  @EJB protected TransactionRunner runner;

  @EJB protected Configuration config;

  @EJB protected CacheFactory cacheFactory;

  private static synchronized UniqueTaskExecutor getExecutorInternal(Request request) {
    if (shutdown) throw new RuntimeException("getExecutor() called after shutdown");

    if (executors == null) executors = new EnumMap<Request, UniqueTaskExecutor>(Request.class);

    UniqueTaskExecutor executor = executors.get(request);
    if (executor == null) {
      executor = new CacheTaskExecutor(request.name().toLowerCase() + " pool");
      executors.put(request, executor);
    }
    return executor;
  }

  @SuppressWarnings("unchecked")
  public static void shutdown() {
    synchronized (AbstractCacheBean.class) {
      shutdown = true;

      // executors is null if we've never called getExecutorInternal
      if (executors != null) {
        for (UniqueTaskExecutor executor : executors.values()) {
          executor.shutdownAndAwaitTermination();
        }
        executors.clear();
        executors = null;
      }
    }
  }

  protected AbstractCacheBean(
      Request defaultRequest, Class<? extends EjbIfaceType> ejbIface, long expirationTime) {
    this.defaultRequest = defaultRequest;
    this.ejbIface = ejbIface;
    this.expirationTime = expirationTime;
  }

  @SuppressWarnings("unchecked")
  protected UniqueTaskExecutor<KeyType, ResultType> getExecutor() {
    return getExecutorInternal(defaultRequest);
  }

  @SuppressWarnings("unchecked")
  protected UniqueTaskExecutor<KeyType, ResultType> getExecutor(Request request) {
    return getExecutorInternal(request);
  }

  protected Class<? extends EjbIfaceType> getEjbIface() {
    return ejbIface;
  }

  protected long getExpirationTime() {
    return expirationTime;
  }

  @TransactionAttribute(
      TransactionAttributeType.REQUIRED) // would be the default, but we changed the class default
  public void expireCache(KeyType key) {
    throw new UnsupportedOperationException("This cache bean doesn't implement cache expiration");
  }

  protected abstract ResultType fetchFromNetImpl(KeyType key);

  @TransactionAttribute(TransactionAttributeType.NEVER)
  public ResultType fetchFromNet(KeyType key) {
    EJBUtil.assertNoTransaction();
    return fetchFromNetImpl(key);
  }

  /**
   * This is final since you should override saveInCacheInsideExistingTransaction instead, generally
   */
  @TransactionAttribute(TransactionAttributeType.NEVER)
  public final ResultType saveInCache(
      final KeyType key, final ResultType data, final boolean refetchedWithoutCheckingCache) {
    EJBUtil.assertNoTransaction();
    try {
      return runner.runTaskRetryingOnConstraintViolation(
          new Callable<ResultType>() {
            public ResultType call() {
              return saveInCacheInsideExistingTransaction(
                  key, data, new Date(), refetchedWithoutCheckingCache);
            }
          });
    } catch (Exception e) {
      if (EJBUtil.isDatabaseException(e)) {
        logger.warn("Ignoring database exception {}: {}", e.getClass().getName(), e.getMessage());
        return data;
      } else {
        ExceptionUtils.throwAsRuntimeException(e);
        throw new RuntimeException(e); // not reached
      }
    }
  }

  @TransactionAttribute(TransactionAttributeType.SUPPORTS)
  public ResultType getSync(KeyType key) {
    return getSync(key, false);
  }

  @TransactionAttribute(TransactionAttributeType.SUPPORTS)
  public Future<ResultType> getAsync(KeyType key) {
    return getAsync(key, false);
  }
}
Exemple #11
0
/**
 * Test basic object-management functions of the DMCache
 *
 * @author otaylor
 */
public class BasicTests extends AbstractSupportedTests {
  private static final Logger logger = GlobalSetup.getLogger(BasicTests.class);

  // Test whether properties can be retrieved from the session and global caches
  public void testCaching() throws NotFoundException {
    TestGroup group;
    TestGroupDMO groupDMO;
    EntityManager em;
    ReadOnlySession session;
    Guid guid;
    TestViewpoint viewpoint = new TestViewpoint(Guid.createNew());

    em = support.beginSessionRW(viewpoint);

    group = new TestGroup("Hippos");
    guid = group.getGuid();
    em.persist(group);

    em.getTransaction().commit();

    //////////////////////////////////////

    em = support.beginSessionRO(viewpoint);

    session = support.currentSessionRO();

    // First time stores in session-local and global caches
    groupDMO = session.find(TestGroupDMO.class, guid);
    assertTrue(groupDMO != null);
    assertTrue(groupDMO.getKey().equals(guid));
    assertTrue(groupDMO.getName().equals("Hippos"));

    // Second time within session finds the existing DMO in
    // the session-local cache. The property value is cached
    // in the DMO.
    groupDMO = session.find(TestGroupDMO.class, guid);
    assertTrue(groupDMO != null);
    assertTrue(groupDMO.getKey().equals(guid));
    assertTrue(groupDMO.getName().equals("Hippos"));

    em.getTransaction().commit();

    //////////////////////////////////////////////////

    em = support.beginSessionRO(viewpoint);

    session = support.currentSessionRO();

    // Creates a new GroupDMO. The property value will be found
    // from the global cache
    groupDMO = session.find(TestGroupDMO.class, guid);
    assertTrue(groupDMO != null);
    assertTrue(groupDMO.getKey().equals(guid));
    assertTrue(groupDMO.getName().equals("Hippos"));

    em.getTransaction().commit();
  }

  // Test multi-valued properties and custom keys
  public void testMultiValued() throws Exception {
    EntityManager em;

    TestViewpoint viewpoint = new TestViewpoint(Guid.createNew());

    /////////////////////////////////////////////////
    // Setup

    em = support.beginSessionRW(viewpoint);

    TestUser bob = new TestUser("Bob");
    Guid bobId = bob.getGuid();
    em.persist(bob);

    TestUser jane = new TestUser("Jane");
    Guid janeId = jane.getGuid();
    em.persist(jane);

    TestGroup group = new TestGroup("BobAndJane");
    Guid groupId = group.getGuid();
    em.persist(group);

    TestGroupMember groupMember;

    groupMember = new TestGroupMember(group, bob);
    em.persist(groupMember);
    group.getMembers().add(groupMember);

    groupMember = new TestGroupMember(group, jane);
    em.persist(groupMember);
    group.getMembers().add(groupMember);

    em.getTransaction().commit();

    /////////////////////////////////////////////////

    em = support.beginSessionRO(viewpoint);

    // Check that the group looks OK read as DMO's from a new transaction.
    logger.debug("===== Checking the group in a new transaction ====\n");
    checkGroupValidity(groupId, bobId, janeId);

    // Check again with everything cached in the sesssion
    logger.debug("===== Checking the group again in the same transaction ====\n");
    checkGroupValidity(groupId, bobId, janeId);

    em.getTransaction().commit();

    em = support.beginSessionRO(viewpoint);

    // Now check once again in another new transaction to test reading
    // back from the global cache
    logger.debug("===== Checking the group again in a different new transaction ====\n");
    checkGroupValidity(groupId, bobId, janeId);

    em.getTransaction().commit();
  }

  // Test grouping
  public void testGrouping() throws Exception {
    EntityManager em;

    TestViewpoint viewpoint = new TestViewpoint(Guid.createNew());

    /////////////////////////////////////////////////
    // Setup

    em = support.beginSessionRW(viewpoint);

    TestUser bob = new TestUser("Bob");
    Guid bobId = bob.getGuid();
    em.persist(bob);

    em.getTransaction().commit();

    /////////////////////////////////////////////////

    em = support.beginSessionRO(viewpoint);

    ReadOnlySession session = support.currentSessionRO();

    TestUserDMO bobDMO = session.find(TestUserDMO.class, bobId);

    assertEquals("initializedA", bobDMO.getGroupedA());
    assertEquals("initializedB", bobDMO.getGroupedB());

    em.getTransaction().commit();
  }

  // Test looking up objects by String resource ID
  public void testStringResourceId() throws NotFoundException {
    EntityManager em;

    TestViewpoint viewpoint = new TestViewpoint(Guid.createNew());

    /////////////////////////////////////////////////
    // Setup

    em = support.beginSessionRW(viewpoint);

    TestUser bob = new TestUser("Bob");
    em.persist(bob);

    TestGroup group = new TestGroup("BobOnly");
    Guid groupId = group.getGuid();
    em.persist(group);

    TestGroupMember groupMember;

    groupMember = new TestGroupMember(group, bob);
    em.persist(groupMember);
    group.getMembers().add(groupMember);

    em.getTransaction().commit();

    /////////////////////////////////////////////////

    em = support.beginSessionRO(viewpoint);

    ReadOnlySession session = support.currentSessionRO();

    // Test for a GUID key
    TestGroupDMO groupDMO = session.find(TestGroupDMO.class, groupId);
    assertEquals(groupDMO, session.find(groupDMO.getResourceId()));

    // Test for a custom key
    TestGroupMemberDMO groupMemberDMO = groupDMO.getMembers().get(0);
    assertEquals(groupMemberDMO, session.find(groupMemberDMO.getResourceId()));

    em.getTransaction().commit();
  }

  private void checkGroupValidity(Guid groupId, Guid bobId, Guid janeId) throws NotFoundException {
    ReadOnlySession session = support.currentSessionRO();

    TestGroupDMO groupDMO = session.find(TestGroupDMO.class, groupId);
    assertNotNull(groupDMO);
    assertEquals(groupId, groupDMO.getKey());

    boolean seenBob = false;
    boolean seenJane = false;

    assertEquals(2, groupDMO.getMembers().size());

    for (TestGroupMemberDMO groupMemberDMO : groupDMO.getMembers()) {
      TestUserDMO memberDMO = groupMemberDMO.getMember();
      if (memberDMO.getKey().equals(bobId)) {
        seenBob = true;
        assertEquals("Bob", memberDMO.getName());
      }
      if (memberDMO.getKey().equals(janeId)) {
        seenJane = true;
        assertEquals("Jane", memberDMO.getName());
      }
    }

    assertTrue(seenBob && seenJane);
  }
}
// @Stateless // for now, these cache beans are our own special kind of bean and not EJBs due to a
// jboss bug
public class FacebookPhotoDataCacheBean
    extends AbstractListCacheWithStorageBean<String, FacebookPhotoDataView, CachedFacebookPhotoData>
    implements FacebookPhotoDataCache {

  private static final Logger logger = GlobalSetup.getLogger(FacebookPhotoDataCacheBean.class);

  @EJB protected FacebookSystem facebookSystem;

  // Facebook no longer specifies a maximum time for which we can keep the data that is "not
  // storable", so we should delete the data whenever the session in which we obtained it expires.
  // We can make the expiration period here arbitrarily long to cut back on the number of requests
  // we make to Facebook, even though having it at 11 1/2 hours as it used to be was still
  // insignificant, it resulted in additional ~3 requests per person in 24 hours vs. ~393 requests
  // for updates that we currently make per person per day. (11 1/2 hour number was designed for 24
  // hour
  // sessions.)
  // Let's make it 14 days like for many other types of cached data. When we believe the data has
  // changed
  // we explicitly expire it. (This might catch some new photos if the same number of photos was
  // added
  // and deleted between our checks, which are every 11 minutes.)
  private static final long FACEBOOK_PHOTO_DATA_EXPIRATION = 1000 * 60 * 60 * 24 * 14;

  public FacebookPhotoDataCacheBean() {
    super(
        Request.FACEBOOK_PHOTO_DATA,
        FacebookPhotoDataCache.class,
        FACEBOOK_PHOTO_DATA_EXPIRATION,
        FacebookPhotoDataView.class);
  }

  @Override
  public List<CachedFacebookPhotoData> queryExisting(String key) {
    Query q =
        em.createQuery(
            "SELECT photo FROM CachedFacebookPhotoData photo WHERE photo.userId = :userId");
    q.setParameter("userId", key);
    List<CachedFacebookPhotoData> results =
        TypeUtils.castList(CachedFacebookPhotoData.class, q.getResultList());
    return results;
  }

  public List<FacebookPhotoDataView> queryExisting(
      String key, Set<FacebookPhotoDataStatus> photos) {
    List<FacebookPhotoDataView> results = new ArrayList<FacebookPhotoDataView>();
    StringBuilder sb = new StringBuilder();
    sb.append(
        "SELECT photo FROM CachedFacebookPhotoData photo WHERE photo.userId = :userId"
            + " AND photo.facebookPhotoId IN (");
    boolean facebookPhotoIdsAvailable = false;
    for (FacebookPhotoDataStatus photoStatus : photos) {
      if (photoStatus.getFacebookPhotoId() != null) {
        sb.append(photoStatus.getFacebookPhotoId());
        sb.append(",");
        facebookPhotoIdsAvailable = true;
      }
    }

    // no need to do the query if we don't have a single id
    if (!facebookPhotoIdsAvailable) {
      return results;
    }

    sb.setLength(sb.length() - 1); // chop comma
    sb.append(")");
    Query q = em.createQuery(sb.toString());
    q.setParameter("userId", key);
    List<CachedFacebookPhotoData> entities =
        TypeUtils.castList(CachedFacebookPhotoData.class, q.getResultList());
    for (CachedFacebookPhotoData entity : entities) {
      results.add(resultFromEntity(entity));
    }
    return results;
  }

  @Override
  public void setAllLastUpdatedToZero(String key) {
    EJBUtil.prepareUpdate(em, CachedFacebookPhotoData.class);

    Query q =
        em.createQuery(
            "UPDATE CachedFacebookPhotoData c"
                + " SET c.lastUpdated = '1970-01-01 00:00:00' "
                + " WHERE c.userId = :userId");
    q.setParameter("userId", key);
    int updated = q.executeUpdate();
    logger.debug("{} cached items expired for key {}", updated, key);
  }

  @Override
  public FacebookPhotoDataView resultFromEntity(CachedFacebookPhotoData entity) {
    return entity.toPhotoData();
  }

  @Override
  public CachedFacebookPhotoData entityFromResult(String key, FacebookPhotoDataView result) {
    return new CachedFacebookPhotoData(key, result);
  }

  @Override
  protected List<FacebookPhotoDataView> fetchFromNetImpl(String key) {
    FacebookWebServices ws = new FacebookWebServices(REQUEST_TIMEOUT, config);
    FacebookAccount facebookAccount = lookupFacebookAccount(key);
    // this facebookAccount is detached
    List<FacebookPhotoData> photos = ws.getTaggedPhotos(facebookAccount);

    // we don't want to call facebookTracker.handleExpiredSessionKey(facebookAccount.getId());
    // here because we'll handle that when we are updating tagged photos and we also
    // need to drop the photos cache for the user in that case, which we do there
    if (photos == null) return null;
    else return TypeUtils.castList(FacebookPhotoDataView.class, photos);
  }

  @Override
  public CachedFacebookPhotoData newNoResultsMarker(String key) {
    return CachedFacebookPhotoData.newNoResultsMarker(key);
  }

  private FacebookAccount lookupFacebookAccount(String key) {
    try {
      FacebookAccount facebookAccount =
          facebookSystem.lookupFacebookAccount(SystemViewpoint.getInstance(), key);
      return facebookAccount;
    } catch (ParseException e) {
      throw new RuntimeException("Could not parse the key " + key + " as a user guid", e);
    } catch (NotFoundException e) {
      throw new RuntimeException(
          "Could not find the user or facebook account for the user with guid " + key, e);
    }
  }
}
Exemple #13
0
public class PersonMusicPage extends AbstractPersonPage {
  @SuppressWarnings("unused")
  private static final Logger logger = GlobalSetup.getLogger(PersonMusicPage.class);

  private static final int LIST_SIZE = 5;

  private Configuration configuration;
  private NowPlayingThemeSystem nowPlayingSystem;
  private ListBean<TrackView> latestTracks;
  private ListBean<TrackView> frequentTracks;
  private ListBean<TrackView> popularTracks;
  private boolean musicSharingEnabled;
  private int selfInvitations;
  private ListBean<NowPlayingTheme> exampleThemes;

  public PersonMusicPage() {
    selfInvitations = -1;
    configuration = WebEJBUtil.defaultLookup(Configuration.class);
    nowPlayingSystem = WebEJBUtil.defaultLookup(NowPlayingThemeSystem.class);
  }

  public ListBean<TrackView> getFrequentTracks() {
    if (frequentTracks == null) {
      frequentTracks =
          new ListBean<TrackView>(
              getMusicSystem()
                  .getFrequentTrackViews(getSignin().getViewpoint(), getViewedUser(), LIST_SIZE));
    }

    return frequentTracks;
  }

  public ListBean<TrackView> getLatestTracks() {
    if (latestTracks == null) {
      latestTracks =
          new ListBean<TrackView>(
              getMusicSystem()
                  .getLatestTrackViews(getSignin().getViewpoint(), getViewedUser(), LIST_SIZE));
    }

    return latestTracks;
  }

  public ListBean<TrackView> getPopularTracks() {
    if (popularTracks == null) {
      popularTracks =
          new ListBean<TrackView>(
              getMusicSystem().getPopularTrackViews(getSignin().getViewpoint(), LIST_SIZE));
    }

    return popularTracks;
  }

  public boolean isMusicSharingEnabled() {
    return musicSharingEnabled;
  }

  public String getDownloadUrlWindows() {
    return configuration.getProperty(HippoProperty.DOWNLOADURL_WINDOWS);
  }

  @Override
  public void setViewedUser(User user) {
    super.setViewedUser(user);
    musicSharingEnabled =
        getIdentitySpider().getMusicSharingEnabled(user, Enabled.RAW_PREFERENCE_ONLY);
  }

  public int getSelfInvitations() {
    if (selfInvitations < 0) {
      selfInvitations =
          invitationSystem.getInvitations(getAccountSystem().getCharacter(Character.MUSIC_GEEK));
    }
    return selfInvitations;
  }

  public String getPromotion() {
    return PromotionCode.MUSIC_INVITE_PAGE_200602.getCode();
  }

  public ListBean<NowPlayingTheme> getExampleThemes() {
    if (exampleThemes == null) {
      exampleThemes =
          new ListBean<NowPlayingTheme>(
              nowPlayingSystem.getExampleNowPlayingThemes(getSignin().getViewpoint(), 5));
    }
    return exampleThemes;
  }
}
Exemple #14
0
/**
 * Represents a particular region within the given JBossCache TreeCache.
 *
 * @author Gavin King
 */
public class TreeCache implements Cache {

  private static final Logger log = GlobalSetup.getLogger(TreeCache.class);

  private static final String ITEM = "item";

  private org.jboss.cache.TreeCache cache;
  private final String regionName;
  private final Fqn regionFqn;
  private final TransactionManager transactionManager;

  public TreeCache(
      org.jboss.cache.TreeCache cache, String regionName, TransactionManager transactionManager)
      throws CacheException {
    this.cache = cache;
    this.regionName = regionName;
    this.regionFqn = Fqn.fromString(regionName.replace('.', '/'));
    this.transactionManager = transactionManager;
  }

  public Object get(Object key) throws CacheException {
    Transaction tx = suspend();
    try {
      return read(key);
    } finally {
      resume(tx);
    }
  }

  public Object read(Object key) throws CacheException {
    try {
      return cache.get(new Fqn(regionFqn, key), ITEM);
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void update(Object key, Object value) throws CacheException {
    try {
      cache.put(new Fqn(regionFqn, key), ITEM, value);
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  @SuppressWarnings("deprecation")
  public void put(Object key, Object value) throws CacheException {
    Transaction tx = suspend();
    try {
      // Workaround for JBCACHE-785
      cache.getInvocationContext().setTransaction(null);
      cache.getInvocationContext().setGlobalTransaction(null);

      // do the failfast put outside the scope of the JTA txn
      cache.putFailFast(new Fqn(regionFqn, key), ITEM, value, 0);
    } catch (TimeoutException te) {
      // ignore!
      log.debug("ignoring write lock acquisition failure");
    } catch (Exception e) {
      throw new CacheException(e);
    } finally {
      resume(tx);
    }
  }

  private void resume(Transaction tx) {
    try {
      if (tx != null) transactionManager.resume(tx);
    } catch (Exception e) {
      throw new CacheException("Could not resume transaction", e);
    }
  }

  private Transaction suspend() {
    Transaction tx = null;
    try {
      if (transactionManager != null) {
        tx = transactionManager.suspend();
      }
    } catch (SystemException se) {
      throw new CacheException("Could not suspend transaction", se);
    }
    return tx;
  }

  public void remove(Object key) throws CacheException {
    try {
      cache.remove(new Fqn(regionFqn, key));
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void clear() throws CacheException {
    try {
      cache.remove(regionFqn);
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void destroy() throws CacheException {
    try {
      // NOTE : evict() operates locally only (i.e., does not propogate
      // to any other nodes in the potential cluster).  This is
      // exactly what is needed when we destroy() here; destroy() is used
      // as part of the process of shutting down a SessionFactory; thus
      // these removals should not be propogated
      cache.evict(regionFqn);
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public void lock(Object key) throws CacheException {
    throw new UnsupportedOperationException(
        "TreeCache is a fully transactional cache" + regionName);
  }

  public void unlock(Object key) throws CacheException {
    throw new UnsupportedOperationException(
        "TreeCache is a fully transactional cache: " + regionName);
  }

  public long nextTimestamp() {
    return System.currentTimeMillis() / 100;
  }

  public int getTimeout() {
    return 600; // 60 seconds
  }

  public String getRegionName() {
    return regionName;
  }

  public long getSizeInMemory() {
    return -1;
  }

  public long getElementCountInMemory() {
    try {
      Set children = cache.getChildrenNames(regionFqn);
      return children == null ? 0 : children.size();
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  public long getElementCountOnDisk() {
    return 0;
  }

  @SuppressWarnings("unchecked")
  public Map toMap() {
    try {
      Map result = new HashMap();
      Set childrenNames = cache.getChildrenNames(regionFqn);
      if (childrenNames != null) {
        Iterator iter = childrenNames.iterator();
        while (iter.hasNext()) {
          Object key = iter.next();
          result.put(key, cache.get(new Fqn(regionFqn, key), ITEM));
        }
      }
      return result;
    } catch (Exception e) {
      throw new CacheException(e);
    }
  }

  @Override
  public String toString() {
    return "TreeCache(" + regionName + ')';
  }
}