@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()); } }
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; } }
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()); } }
/** * 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); } } } }
@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); } }
/** * 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); } } }
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; } }
/** * 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 + ')'; } }