r7013 - in dumbhippo/trunk/server/src/com/dumbhippo/server: . dm impl views



Author: otaylor
Date: 2007-12-10 19:59:56 -0600 (Mon, 10 Dec 2007)
New Revision: 7013

Added:
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMOKey.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/GroupDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaAlbumThumbnailDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaPersonBlockDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostBlockDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailKey.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailsBlockDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/YouTubeThumbnailDMO.java
Modified:
   dumbhippo/trunk/server/src/com/dumbhippo/server/Stacker.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/DataService.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/dm/UserDMO.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/impl/StackerBean.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/views/AnonymousViewpoint.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/views/SystemViewpoint.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/views/UserViewpoint.java
   dumbhippo/trunk/server/src/com/dumbhippo/server/views/Viewpoint.java
Log:
UserViewpoint Viewpoint SystemViewpoint AnonymousViewpoint: Add
  canSeeBlock() canSeePost()

Stacker StackerBean: Add getStackBlocks(), which returns a user's
  stack in a form convenient for the data model.

UserDMO StackerBean: Add 'stack' property

BlockDMO PostBlockDMO ThumbnailsBlockDMO PicasaPersonBlockDMO
  ThumbnailDMO YouTubeThumbnailDMO PicasaAlbumThumbnailDMO
  PostDMO: Start adding DMO's for the user's stack


Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/Stacker.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/Stacker.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/Stacker.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -81,6 +81,7 @@
 	public void pageStack(Viewpoint viewpoint, User user, Pageable<BlockView> pageable, boolean participantOnly);
 
 	public void pageStack(Viewpoint viewpoint, User user, Pageable<BlockView> pageable, long lastTimestamp, boolean participantOnly);
+	public List<UserBlockData> getStackBlocks(User user, int start, int max, long minTimestamp);
 	
 	public void pageStack(Viewpoint viewpoint, User user, Pageable<BlockView> pageable, long lastTimestamp, 
 			              String filter, boolean participantOnly);	

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,151 @@
+package com.dumbhippo.server.dm;
+
+import javax.ejb.EJB;
+
+import org.slf4j.Logger;
+
+import com.dumbhippo.GlobalSetup;
+import com.dumbhippo.dm.DMObject;
+import com.dumbhippo.dm.DMSession;
+import com.dumbhippo.dm.annotations.DMFilter;
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.dm.annotations.DMProperty;
+import com.dumbhippo.dm.annotations.Inject;
+import com.dumbhippo.dm.annotations.MetaConstruct;
+import com.dumbhippo.dm.store.StoreKey;
+import com.dumbhippo.identity20.Guid;
+import com.dumbhippo.persistence.BlockType;
+import com.dumbhippo.server.NotFoundException;
+import com.dumbhippo.server.Stacker;
+import com.dumbhippo.server.blocks.BlockView;
+import com.dumbhippo.server.blocks.TitleBlockView;
+import com.dumbhippo.server.blocks.TitleDescriptionBlockView;
+import com.dumbhippo.server.views.SystemViewpoint;
+
+ DMO(classId="http://mugshot.org/p/o/block";, resourceBase="/o/block")
+ DMFilter("viewer.canSeeBlock(this)")
+public abstract class BlockDMO extends DMObject<BlockDMOKey> {
+	@SuppressWarnings("unused")
+	private static Logger logger = GlobalSetup.getLogger(BlockDMO.class);
+
+	protected BlockView blockView;
+
+	@Inject
+	DMSession session;
+	
+	@EJB
+	Stacker stacker;
+	
+	protected BlockDMO(BlockDMOKey key) {
+		super(key);
+	}
+	
+	public static Class<? extends BlockDMO> getDMOClass(BlockType blockType) {
+		switch (blockType) {
+		case FLICKR_PERSON:
+		case YOUTUBE_PERSON:
+		case MYSPACE_PERSON:
+		case BLOG_ENTRY:
+		case DELICIOUS_PUBLIC_BOOKMARK:
+		case TWITTER_PERSON:
+		case DIGG_DUGG_ENTRY:
+		case REDDIT_ACTIVITY_ENTRY:
+		case GOOGLE_READER_SHARED_ITEM:
+			return BlockDMO.class;
+
+		case POST:
+			return PostBlockDMO.class;
+			
+		case PICASA_PERSON:
+			return PicasaPersonBlockDMO.class;
+			
+		default:
+		case GROUP_MEMBER: 
+		case GROUP_CHAT:
+		case MUSIC_PERSON:
+		case FACEBOOK_EVENT:
+		case FLICKR_PHOTOSET:
+		case MUSIC_CHAT:
+		case GROUP_REVISION:		
+		case NETFLIX_MOVIE:
+		case ACCOUNT_QUESTION:
+		case AMAZON_REVIEW:
+		case AMAZON_WISH_LIST_ITEM:
+		case OBSOLETE_EXTERNAL_ACCOUNT_UPDATE:
+		case OBSOLETE_EXTERNAL_ACCOUNT_UPDATE_SELF:
+		case OBSOLETE_BLOG_PERSON:
+		case FACEBOOK_PERSON:
+			return null;
+		}
+	}
+	
+	@MetaConstruct
+	public static Class<? extends BlockDMO> getDMOClass(BlockDMOKey key) {
+		return getDMOClass(key.getType());
+	}
+
+	@Override
+	protected void init() throws NotFoundException {
+		BlockView view = stacker.loadBlock(SystemViewpoint.getInstance(), stacker.lookupBlock(getKey().getBlockId()));
+		
+		// This check is important because it prevents someone from bypassing visibility rules
+		// by loading a block as the wrong type of DMO. this.getClass() is the wrapper class
+		// created by the DataModel engine, so the superclass is the real DMO class.
+		
+		Class<? extends BlockDMO> dmoClass = getDMOClass(view.getBlockType());
+		if (!dmoClass.equals(this.getClass().getSuperclass()))
+			throw new NotFoundException("Mismatch between resource type " + this.getClass().getSuperclass() + " and block type " + dmoClass);
+		
+		blockView = view;
+	}
+	
+	@DMProperty(defaultInclude=true)
+	public String getTitle() {
+		if (blockView instanceof TitleBlockView)
+			return ((TitleBlockView)blockView).getTitle();
+		else
+			return null;
+	}
+	
+	@DMProperty(defaultInclude=true)
+	public String getDescription() {
+		if (blockView instanceof TitleDescriptionBlockView)
+			return ((TitleDescriptionBlockView)blockView).getDescription();
+		else
+			return null;
+	}
+	
+	//////////////////////////////////////////////////////////////////////
+	
+	// These properties are here for the implementation of Viewpoint.canSeePrivateBlock(), 
+	// Viewpoint.canSeeBlockDelegate()
+	
+	@DMProperty 
+	public UserDMO getOwner() {
+		switch (blockView.getBlockType().getBlockOwnership()) {
+		case DIRECT_DATA1:
+		case INDIRECT_DATA1:
+			Guid data1 = blockView.getBlock().getData1AsGuid();
+			if (data1 != null)
+				return session.findUnchecked(UserDMO.class, data1);
+			else
+				return null;
+		case DIRECT_DATA2:
+		case INDIRECT_DATA2:
+			Guid data2 = blockView.getBlock().getData2AsGuid();
+			if (data2 != null)
+				return session.findUnchecked(UserDMO.class, data2);
+			else
+				return null;
+		case NONE:
+			return null;
+		}
+		
+		return null;
+	}
+	
+	@DMProperty 
+	public StoreKey<?,?> getVisibilityDelegate() {
+		return null;
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMOKey.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMOKey.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/BlockDMOKey.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,82 @@
+package com.dumbhippo.server.dm;
+
+import com.dumbhippo.dm.BadIdException;
+import com.dumbhippo.dm.DMKey;
+import com.dumbhippo.identity20.Guid;
+import com.dumbhippo.identity20.Guid.ParseException;
+import com.dumbhippo.persistence.Block;
+import com.dumbhippo.persistence.BlockType;
+
+/**
+ * Key for BlockDMO and subclasses; our normal convention would be BlockKey, but
+ * avoid confusion with com.dumbhippo.persistence.BlockKey.
+ * 
+ * We include the type so we know what type of DMObject to create from the key.
+ */
+public class BlockDMOKey implements DMKey {
+	private static final long serialVersionUID = -7356973672026993180L;
+	
+	private Guid blockId;
+	private BlockType type;
+	
+	public BlockDMOKey(String keyString) throws BadIdException {
+		if (keyString.length() > 15 && keyString.charAt(14) == '.') {
+			try {
+				blockId = new Guid(keyString.substring(0, 14));
+			} catch (ParseException e) {
+				throw new BadIdException("Bad GUID type in external account ID", e);
+			}
+			
+			try {
+				type = BlockType.valueOf(keyString.substring(15));
+			} catch (IllegalArgumentException e) {
+				throw new BadIdException("Bad external account type in ID", e);
+			}
+		} else {
+			throw new BadIdException("Bad external account resource ID");
+		}
+	}
+	
+	public BlockDMOKey(Block block) {
+		blockId = block.getGuid();
+		type = block.getBlockType();
+	}
+	
+	public BlockDMOKey(Guid blockId, BlockType type) {
+		this.blockId = blockId;
+		this.type = type;
+	}
+	
+	public BlockType getType() {
+		return type;
+	}
+
+	public Guid getBlockId() {
+		return blockId;
+	}
+	
+	@Override
+	public BlockDMOKey clone() {
+		return this;
+	}
+	
+	@Override
+	public int hashCode() {
+		return blockId.hashCode() + type.ordinal();
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof BlockDMOKey))
+			return false;
+		
+		BlockDMOKey other = (BlockDMOKey)o;
+		
+		return type == other.type && blockId.equals(other.blockId);
+	}
+
+	@Override
+	public String toString() {
+		return blockId.toString() + "." + type.name();
+	}
+}

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/DataService.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/DataService.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/DataService.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -82,13 +82,28 @@
 		String baseUrl = config.getBaseUrl(Site.NONE);
 		
 		model = new DataModel(baseUrl, new DMSessionMapJTA(), emf, new InjectionLookup(), this, Viewpoint.class, SystemViewpoint.getInstance());
+
+		// Keep this list ALPHABETIZED at each level of of the inheritance hierarchy
+		// (It respects the inheritance hierarchy because base classes must be added
+		// before subclasses)
+		model.addDMClass(ApplicationDMO.class);
 		
-		model.addDMClass(ApplicationDMO.class);
+		model.addDMClass(BlockDMO.class);
+		model.addDMClass(ThumbnailsBlockDMO.class);
+		model.addDMClass(PicasaPersonBlockDMO.class);
+		model.addDMClass(PostBlockDMO.class);
+		
+		model.addDMClass(ContactDMO.class);
 		model.addDMClass(DesktopSettingDMO.class);
 		model.addDMClass(ExternalAccountDMO.class);
+		model.addDMClass(PostDMO.class);
+		
+		model.addDMClass(ThumbnailDMO.class);
+		model.addDMClass(PicasaAlbumThumbnailDMO.class);
+		model.addDMClass(YouTubeThumbnailDMO.class);
+		
 		model.addDMClass(TrackDMO.class);
 		model.addDMClass(UserDMO.class);
-		model.addDMClass(ContactDMO.class);
 		
 		model.completeDMClasses();
 		

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/GroupDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/GroupDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/GroupDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,5 @@
+package com.dumbhippo.server.dm;
+
+public class GroupDMO {
+
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaAlbumThumbnailDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaAlbumThumbnailDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaAlbumThumbnailDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,76 @@
+package com.dumbhippo.server.dm;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ejb.EJB;
+
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.persistence.ExternalAccount;
+import com.dumbhippo.persistence.ExternalAccountType;
+import com.dumbhippo.persistence.User;
+import com.dumbhippo.server.ExternalAccountSystem;
+import com.dumbhippo.server.IdentitySpider;
+import com.dumbhippo.server.NotFoundException;
+import com.dumbhippo.server.views.SystemViewpoint;
+import com.dumbhippo.services.PicasaAlbum;
+import com.dumbhippo.services.caches.PicasaAlbumsCache;
+import com.dumbhippo.services.caches.WebServiceCache;
+
+ DMO(classId="http://mugshot.org/p/o/youTubeThumbnail";)
+public abstract class PicasaAlbumThumbnailDMO extends ThumbnailDMO {
+	@EJB
+	private ExternalAccountSystem externalAccountSystem;
+	
+	@EJB
+	private IdentitySpider identitySpider;
+	
+	@WebServiceCache
+	private PicasaAlbumsCache picasaAlbumsCache;
+
+	protected PicasaAlbumThumbnailDMO(ThumbnailKey key) {
+		super(key);
+	}
+	
+	// Although album names can have arbitrary Unicode in them, everything but ascii letters
+	// and digits is stripped out in creating the URL (with upper casing of the beginning
+	// of ascii letter sequences ... "test bourrée" => "TestBourrE")
+	private static Pattern PICASA_URL_PATTERN = Pattern.compile("http://picasaweb.google.com/([A-Za-z0-9]+)/([A-Za-z0-9]+)");
+
+	private static String extractExtra(String url) {
+		Matcher m = PICASA_URL_PATTERN.matcher(url);
+		if (m.matches())
+			return m.group(1) + "-" + m.group(2);
+		else
+			throw new RuntimeException("Cannot extract key from Picasa URL '" + url + "'");
+	}
+	
+	@Override
+	protected void init() throws NotFoundException {
+		super.init();
+		
+		if (thumbnail == null) {
+			User user = identitySpider.lookupUser(getKey().getUserId());
+			ExternalAccount externalAccount = externalAccountSystem.lookupExternalAccount(SystemViewpoint.getInstance(), user, ExternalAccountType.PICASA);
+			if (!externalAccount.isLovedAndEnabled())
+				throw new NotFoundException("Account is not loved and enabled");
+			
+			List<? extends PicasaAlbum> albums = picasaAlbumsCache.getSync(externalAccount.getHandle());
+			
+			String extra = getKey().getExtra();
+			for (PicasaAlbum album : albums) {
+				if (extra.equals(extractExtra(album.getThumbnailHref()))) {
+					thumbnail = album;
+					return;
+				}
+			}
+			
+			throw new NotFoundException("Can't find video");
+		}
+	}
+	
+	public static ThumbnailKey getKey(User user, PicasaAlbum album) {
+		return new ThumbnailKey(user.getGuid(), extractExtra(album.getThumbnailHref()), album); 
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaPersonBlockDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaPersonBlockDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PicasaPersonBlockDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,32 @@
+package com.dumbhippo.server.dm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.dumbhippo.Thumbnail;
+import com.dumbhippo.Thumbnails;
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.persistence.User;
+import com.dumbhippo.server.blocks.PicasaPersonBlockView;
+import com.dumbhippo.services.PicasaAlbum;
+
+ DMO(classId="http://mugshot.org/p/o/picasaPersonBlock";)
+public abstract class PicasaPersonBlockDMO extends ThumbnailsBlockDMO {
+	protected PicasaPersonBlockDMO(BlockDMOKey key) {
+		super(key);
+	}
+
+	@Override
+	public List<ThumbnailDMO> getThumbnails() {
+		PicasaPersonBlockView picasaView = (PicasaPersonBlockView)blockView;
+		
+		Thumbnails thumbnails = picasaView.getThumbnails();
+		User user = picasaView.getPersonSource().getUser(); 
+		
+		List<ThumbnailDMO> result = new ArrayList<ThumbnailDMO>();
+		for (Thumbnail thumbnail : thumbnails.getThumbnails())
+			result.add(session.findUnchecked(PicasaAlbumThumbnailDMO.class, PicasaAlbumThumbnailDMO.getKey(user, (PicasaAlbum)thumbnail)));
+		
+		return result;
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostBlockDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostBlockDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostBlockDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,25 @@
+package com.dumbhippo.server.dm;
+
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.dm.annotations.DMProperty;
+import com.dumbhippo.dm.store.StoreKey;
+import com.dumbhippo.persistence.Post;
+import com.dumbhippo.server.blocks.PostBlockView;
+
+ DMO(classId="http://mugshot.org/p/o/postBlock";)
+public abstract class PostBlockDMO extends BlockDMO {
+	protected PostBlockDMO(BlockDMOKey key) {
+		super(key);
+	}
+
+	@DMProperty(defaultInclude=true, defaultChildren="+")
+	public PostDMO getPost() {
+		Post post = ((PostBlockView)blockView).getPostView().getPost();
+		return session.findUnchecked(PostDMO.class, post.getGuid());
+	}
+	
+	@Override
+	public StoreKey<?,?> getVisibilityDelegate() {
+		return getPost().getStoreKey();
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/PostDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,96 @@
+package com.dumbhippo.server.dm;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.ejb.EJB;
+
+import com.dumbhippo.dm.DMObject;
+import com.dumbhippo.dm.DMSession;
+import com.dumbhippo.dm.annotations.DMFilter;
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.dm.annotations.DMProperty;
+import com.dumbhippo.dm.annotations.Inject;
+import com.dumbhippo.identity20.Guid;
+import com.dumbhippo.persistence.AccountClaim;
+import com.dumbhippo.persistence.Post;
+import com.dumbhippo.persistence.PostVisibility;
+import com.dumbhippo.persistence.Resource;
+import com.dumbhippo.persistence.User;
+import com.dumbhippo.server.NotFoundException;
+import com.dumbhippo.server.PostingBoard;
+import com.dumbhippo.server.views.SystemViewpoint;
+
+ DMO(classId="http://mugshot.org/p/o/post";, resourceBase="/o/post")
+ DMFilter("viewer.canSeePost(this)")
+public abstract class PostDMO extends DMObject<Guid> {
+	@EJB
+	PostingBoard postingBoard; 
+	
+	@Inject
+	DMSession session;
+
+	private Post post;
+	
+	protected PostDMO(Guid key) {
+		super(key);
+	}
+	
+	@Override
+	protected void init() throws NotFoundException {
+		post = postingBoard.loadRawPost(SystemViewpoint.getInstance(), getKey());
+	}
+	
+	@DMProperty(defaultInclude=true)
+	public UserDMO getPoster() {
+		User poster = post.getPoster();
+		if (poster != null)
+			return session.findUnchecked(UserDMO.class, post.getPoster().getGuid());
+		else
+			return null;
+	}
+	
+	@DMProperty(defaultInclude=true)
+	String getTitle() {
+		return post.getTitle();
+	}
+
+	@DMProperty(defaultInclude=true)
+	String getText() {
+		return post.getText();
+	}
+	
+	@DMProperty(defaultInclude=true)
+	@DMFilter("viewer.canSeePrivate(any)")
+	public List<UserDMO> getUserRecipients() {
+		List<UserDMO> result = new ArrayList<UserDMO>();
+		
+		for (Resource resource : post.getPersonRecipients()) {
+			AccountClaim accountClaim = resource.getAccountClaim();
+			if (accountClaim != null)
+				result.add(session.findUnchecked(UserDMO.class, accountClaim.getOwner().getGuid()));
+		}
+		
+		return result;
+	}
+
+	@DMProperty
+	public boolean isPublic() {
+		return post.getVisibility() == PostVisibility.ATTRIBUTED_PUBLIC;
+	}
+
+	@DMProperty
+	public Set<UserDMO> getExpandedRecipients() {
+		Set<UserDMO> result = new HashSet<UserDMO>();
+		
+		for (Resource resource : post.getExpandedRecipients()) {
+			AccountClaim accountClaim = resource.getAccountClaim();
+			if (accountClaim != null)
+				result.add(session.findUnchecked(UserDMO.class, accountClaim.getOwner().getGuid()));
+		}
+		
+		return result;
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,64 @@
+package com.dumbhippo.server.dm;
+
+import com.dumbhippo.Thumbnail;
+import com.dumbhippo.dm.DMObject;
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.dm.annotations.DMProperty;
+import com.dumbhippo.dm.annotations.PropertyType;
+import com.dumbhippo.server.NotFoundException;
+
+ DMO(classId="http://mugshot.org/p/o/thumbnail";, resourceBase="/o/thumbnail")
+public abstract class ThumbnailDMO extends DMObject<ThumbnailKey> {
+	protected Thumbnail thumbnail;
+	
+	protected ThumbnailDMO(ThumbnailKey key) {
+		super(key);
+	}
+
+	@Override
+	protected void init() throws NotFoundException {
+		if (getKey().getObject() != null) {
+			thumbnail = (Thumbnail)getKey().getObject();
+		}
+	}
+	
+	/**
+	 * The URL of the thumbnail image 
+	 */
+	@DMProperty(defaultInclude=true, type=PropertyType.URL)
+	public String getSrc() {
+		return thumbnail.getThumbnailSrc();
+	}
+
+	/**
+	 * The URL that the thumbnail should link to 
+	 */
+	@DMProperty(defaultInclude=true, type=PropertyType.URL)
+	public String getLink() {
+		return thumbnail.getThumbnailHref();
+	}
+	
+	/**
+	 * The tooltip or caption 
+	 */
+	@DMProperty(defaultInclude=true)
+	public String getTitle() {
+		return thumbnail.getThumbnailTitle();
+	}
+	
+	/**
+	 * The width of the thumbnail image in pixels 
+	 */
+	@DMProperty(defaultInclude=true)
+	public int getWidth() {
+		return thumbnail.getThumbnailWidth();
+	}
+	
+	/**
+	 * The height of the thumbnail image in pixels 
+	 */
+	@DMProperty(defaultInclude=true)
+	public int getHeight() {
+		return thumbnail.getThumbnailHeight();
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailKey.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailKey.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailKey.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,92 @@
+package com.dumbhippo.server.dm;
+
+import com.dumbhippo.dm.BadIdException;
+import com.dumbhippo.dm.DMKey;
+import com.dumbhippo.identity20.Guid;
+import com.dumbhippo.identity20.Guid.ParseException;
+
+/**
+ * ThumbnailKey is used for all subclasses of ThumbnailDMO; since 
+ * ThumbnailDMO is an abstract class rather than an interface, all its
+ * subclasses need to have the same key type. (And we don't support
+ * datamodel properties on interfaces currently.) The form of a 
+ * ThumnailKey is a pair of a userId and a string, where the exact
+ * interpretation of the string is up to the subclass.
+ * 
+ * We also support storing the Thumbnail object in the key transiently
+ * within a single session. This is so that converting a list of Thumbnail
+ * objects to a list of ThumbnailDMO doesn't require looking up every
+ * thumbnail from the database again.
+ */
+public class ThumbnailKey implements DMKey {
+	private static final long serialVersionUID = 2258099192938259545L;
+	
+	private transient Object object;
+	private Guid userId;
+	private String extra;
+	
+	public ThumbnailKey(String keyString) throws BadIdException {
+		if (keyString.length() > 15 && keyString.charAt(14) == '.') {
+			try {
+				userId = new Guid(keyString.substring(0, 14));
+			} catch (ParseException e) {
+				throw new BadIdException("Bad GUID type in external account ID", e);
+			}
+			
+			extra = keyString.substring(15);
+		} else {
+			throw new BadIdException("Bad thumbnail resource ID");
+		}
+	}
+	
+	public ThumbnailKey(Guid userId, String extra) {
+		this.userId = userId;
+		this.extra = extra;
+	}
+	
+	public ThumbnailKey(Guid userId, String extra, Object object) {
+		this.userId = userId;
+		this.extra = extra;
+		this.object = object;	
+	}
+	
+	public Object getObject() {
+		return object;
+	}
+
+	public String getExtra() {
+		return extra;
+	}
+
+	public Guid getUserId() {
+		return userId;
+	}
+	
+	@Override
+	public ThumbnailKey clone() {
+		if (object != null)
+			return new ThumbnailKey(userId, extra);
+		else
+			return this;
+	}
+	
+	@Override
+	public int hashCode() {
+		return userId.hashCode() * 11 + extra.hashCode() * 17;
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof ThumbnailKey))
+			return false;
+		
+		ThumbnailKey other = (ThumbnailKey)o;
+		
+		return userId.equals(other.userId) && extra.equals(other.extra);
+	}
+
+	@Override
+	public String toString() {
+		return userId.toString() + "." + extra;
+	}
+}

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailsBlockDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailsBlockDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/ThumbnailsBlockDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,16 @@
+package com.dumbhippo.server.dm;
+
+import java.util.List;
+
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.dm.annotations.DMProperty;
+
+ DMO(classId="http://mugshot.org/p/o/thumbnailsBlock";)
+public abstract class ThumbnailsBlockDMO extends BlockDMO {
+	protected ThumbnailsBlockDMO(BlockDMOKey key) {
+		super(key);
+	}
+
+	@DMProperty(defaultInclude=true, defaultChildren="+")
+	public abstract List<ThumbnailDMO> getThumbnails();
+}

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/UserDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/UserDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/UserDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -4,14 +4,20 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
 import javax.ejb.EJB;
 import javax.persistence.EntityManager;
 
+import org.slf4j.Logger;
+
+import com.dumbhippo.GlobalSetup;
 import com.dumbhippo.Pair;
 import com.dumbhippo.Site;
+import com.dumbhippo.dm.DMFeed;
+import com.dumbhippo.dm.DMFeedItem;
 import com.dumbhippo.dm.DMObject;
 import com.dumbhippo.dm.DMSession;
 import com.dumbhippo.dm.annotations.DMFilter;
@@ -30,12 +36,14 @@
 import com.dumbhippo.persistence.Resource;
 import com.dumbhippo.persistence.TrackHistory;
 import com.dumbhippo.persistence.User;
+import com.dumbhippo.persistence.UserBlockData;
 import com.dumbhippo.persistence.XmppResource;
 import com.dumbhippo.server.DesktopSettings;
 import com.dumbhippo.server.IdentitySpider;
 import com.dumbhippo.server.MusicSystem;
 import com.dumbhippo.server.NotFoundException;
 import com.dumbhippo.server.OnlineDesktopSystem;
+import com.dumbhippo.server.Stacker;
 import com.dumbhippo.server.applications.ApplicationSystem;
 import com.dumbhippo.server.views.AnonymousViewpoint;
 import com.dumbhippo.server.views.UserViewpoint;
@@ -43,6 +51,9 @@
 
 @DMO(classId="http://mugshot.org/p/o/user";, resourceBase="/o/user")
 public abstract class UserDMO extends DMObject<Guid> {
+	@SuppressWarnings("unused")
+	static private final Logger logger = GlobalSetup.getLogger(UserDMO.class);
+	
 	private User user;
 //	private String email;
 //	private String aim;
@@ -87,6 +98,9 @@
 	@EJB
 	private OnlineDesktopSystem onlineDesktopSystem;
 	
+	@EJB
+	private Stacker stacker;
+
 	protected UserDMO(Guid key) {
 		super(key);
 	}
@@ -458,4 +472,28 @@
 		else
 			return -1;
 	}
+
+	@DMProperty
+	public DMFeed<BlockDMO> getStack() {
+		return new BlockFeed();
+	}
+	
+	private class BlockFeed implements DMFeed<BlockDMO> {
+		public Iterator<DMFeedItem<BlockDMO>> iterator(int start, int max, long minTimestamp) {
+			List<UserBlockData> blocks = stacker.getStackBlocks(user, start, max, minTimestamp);
+			
+			List<DMFeedItem<BlockDMO>> items = new ArrayList<DMFeedItem<BlockDMO>>(); 
+			for (UserBlockData ubd : blocks) {
+				Class<? extends BlockDMO> dmoClass = BlockDMO.getDMOClass(ubd.getBlock().getBlockType());
+				if (dmoClass != null) {
+					logger.debug("dmoClass for {} is {}", ubd.getBlock().getBlockType(), dmoClass.getName());
+					
+					BlockDMO blockDMO = session.findUnchecked(dmoClass, new BlockDMOKey(ubd.getBlock()));
+					items.add(new DMFeedItem<BlockDMO>(blockDMO, ubd.getStackTimestampAsLong()));
+				}
+			}
+			
+			return items.iterator();
+		}
+	}
 }

Added: dumbhippo/trunk/server/src/com/dumbhippo/server/dm/YouTubeThumbnailDMO.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/dm/YouTubeThumbnailDMO.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/dm/YouTubeThumbnailDMO.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -0,0 +1,73 @@
+package com.dumbhippo.server.dm;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.ejb.EJB;
+
+import com.dumbhippo.dm.annotations.DMO;
+import com.dumbhippo.persistence.ExternalAccount;
+import com.dumbhippo.persistence.ExternalAccountType;
+import com.dumbhippo.persistence.User;
+import com.dumbhippo.server.ExternalAccountSystem;
+import com.dumbhippo.server.IdentitySpider;
+import com.dumbhippo.server.NotFoundException;
+import com.dumbhippo.server.views.SystemViewpoint;
+import com.dumbhippo.services.YouTubeVideo;
+import com.dumbhippo.services.caches.WebServiceCache;
+import com.dumbhippo.services.caches.YouTubeVideosCache;
+
+ DMO(classId="http://mugshot.org/p/o/youTubeThumbnail";)
+public abstract class YouTubeThumbnailDMO extends ThumbnailDMO {
+	@EJB
+	private ExternalAccountSystem externalAccountSystem;
+	
+	@EJB
+	private IdentitySpider identitySpider;
+	
+	@WebServiceCache
+	private YouTubeVideosCache youTubeVideosCache;
+
+	protected YouTubeThumbnailDMO(ThumbnailKey key) {
+		super(key);
+	}
+	
+	static private Pattern YOUTUBE_URL_PATTERN = Pattern.compile("http://youtube.com/?v=([A-Za-z0-9_-]+)");
+
+	private static String extractExtra(String url) {
+		Matcher m = YOUTUBE_URL_PATTERN.matcher(url);
+		if (m.matches())
+			return m.group(1);
+		else
+			throw new RuntimeException("Cannot extract key from YouTube URL '" + url + "'");
+	}
+	
+	@Override
+	protected void init() throws NotFoundException {
+		super.init();
+		
+		if (thumbnail == null) {
+			User user = identitySpider.lookupUser(getKey().getUserId());
+			ExternalAccount externalAccount = externalAccountSystem.lookupExternalAccount(SystemViewpoint.getInstance(), user, ExternalAccountType.YOUTUBE);
+			if (!externalAccount.isLovedAndEnabled())
+				throw new NotFoundException("Account is not loved and enabled");
+			
+			List<? extends YouTubeVideo> videos = youTubeVideosCache.getSync(externalAccount.getHandle());
+			
+			String extra = getKey().getExtra();
+			for (YouTubeVideo video : videos) {
+				if (extra.equals(extractExtra(video.getThumbnailHref()))) {
+					thumbnail = video;
+					return;
+				}
+			}
+			
+			throw new NotFoundException("Can't find video");
+		}
+	}
+	
+	public static ThumbnailKey getKey(User user, YouTubeVideo video) {
+		return new ThumbnailKey(user.getGuid(), extractExtra(video.getThumbnailHref()), video); 
+	}
+}

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/impl/StackerBean.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/impl/StackerBean.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/impl/StackerBean.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -32,6 +32,7 @@
 import com.dumbhippo.TypeUtils;
 import com.dumbhippo.XmlBuilder;
 import com.dumbhippo.ThreadUtils.DaemonRunnable;
+import com.dumbhippo.dm.ReadWriteSession;
 import com.dumbhippo.identity20.Guid;
 import com.dumbhippo.live.BlockEvent;
 import com.dumbhippo.live.LiveEventListener;
@@ -88,6 +89,8 @@
 import com.dumbhippo.server.blocks.RedditBlockHandler;
 import com.dumbhippo.server.blocks.TwitterPersonBlockHandler;
 import com.dumbhippo.server.blocks.YouTubeBlockHandler;
+import com.dumbhippo.server.dm.DataService;
+import com.dumbhippo.server.dm.UserDMO;
 import com.dumbhippo.server.util.EJBUtil;
 import com.dumbhippo.server.views.GroupMugshotView;
 import com.dumbhippo.server.views.GroupView;
@@ -421,12 +424,16 @@
 					UserBlockData data = new UserBlockData(participant, block, true, reason);
 					em.persist(data);
 				}
+				
+				DataService.currentSessionRW().feedChanged(UserDMO.class, participantId, "stack", block.getTimestampAsLong());
 			}
 		});
 	}
 	
 	// Don't call directly, RetryException is added in the wrapper for readability
 	private void updateUserBlockDatasInternal(Block block, Set<User> desiredUsers, Guid participantId, StackReason reason) {
+		ReadWriteSession session = DataService.getModel().currentSessionRW();
+
 		int addCount;
 		int removeCount;
 		
@@ -473,6 +480,8 @@
 				em.persist(data);
 				addCount += 1;
 			}
+			
+			session.feedChanged(UserDMO.class, u.getGuid(), "stack", block.getTimestampAsLong());
 		}
 		// the rest of "existing" is users who no longer are in the desired set
 		for (User u : existing.keySet()) {
@@ -723,6 +732,8 @@
 		// after commit, so we can do retries when demand-creating {User,Group}BlockData.
 		TxUtils.runInTransactionOnCommit(new TxRunnable() {
 			public void run() throws RetryException {
+				DataService.getModel().initializeReadWriteSession(SystemViewpoint.getInstance());
+				
 				Block attached = em.find(Block.class, block.getId());
 				if (updateAllUserBlockDatas) {
 				    updateUserBlockDatas(attached, (participant != null ? participant.getGuid() : null), reason);
@@ -988,18 +999,27 @@
 			}
 			sb.append(")");
 		}
-		
-		/* Inclusion clause */
-		sb.append(" AND (block.inclusion = ");
-		sb.append(StackInclusion.IN_ALL_STACKS.ordinal());
-		
-		if (viewpoint instanceof UserViewpoint) {
-			sb.append(" OR (block.data1 = :viewpointGuid AND block.inclusion = " + StackInclusion.ONLY_WHEN_VIEWING_SELF.ordinal() + ")");
-			sb.append(" OR (block.data1 != :viewpointGuid AND block.inclusion = " + StackInclusion.ONLY_WHEN_VIEWED_BY_OTHERS.ordinal() + ")");
-		} else {
-			sb.append(" OR block.inclusion = " + StackInclusion.ONLY_WHEN_VIEWED_BY_OTHERS.ordinal());
+
+		// It's not absolutely clear what SystemViewpoint should return, since the system
+		// is neither "the user themself" or "someone else". We don't currently use
+		// StackInclusion.ONLY_WHEN_VIEWED_BY_OTHERS and 
+		// StackInclusion.ONLY_WHEN_VIEWED_BY_SELF is just an optimization of the visibility
+		// rules, so we make SystemViewpoint return everything. If we used
+		// StackInclusion.ONLY_WHEN_VIEWED_BY_SELF, then this could cause duplicate blocks
+		//
+		if (!(viewpoint instanceof SystemViewpoint)) {
+			/* Inclusion clause */
+			sb.append(" AND (block.inclusion = ");
+			sb.append(StackInclusion.IN_ALL_STACKS.ordinal());
+			
+			if (viewpoint instanceof UserViewpoint) {
+				sb.append(" OR (block.data1 = :viewpointGuid AND block.inclusion = " + StackInclusion.ONLY_WHEN_VIEWING_SELF.ordinal() + ")");
+				sb.append(" OR (block.data1 != :viewpointGuid AND block.inclusion = " + StackInclusion.ONLY_WHEN_VIEWED_BY_OTHERS.ordinal() + ")");
+			} else {
+				sb.append(" OR block.inclusion = " + StackInclusion.ONLY_WHEN_VIEWED_BY_OTHERS.ordinal());
+			}
+			sb.append(")");
 		}
-		sb.append(")");
 		
 		/* Ordering clause */
 		
@@ -1029,6 +1049,11 @@
 		return TypeUtils.castList(UserBlockData.class, q.getResultList());		
 	}
 
+	// Note the off-by-one difference betweeb minTimestamp and lastTimestamp
+	public List<UserBlockData> getStackBlocks(User user, int start, int count, long minTimestamp) {
+		return getBlocks(SystemViewpoint.getInstance(), user, minTimestamp - 1, start, count, null, false);
+	}
+	
 	private interface BlockSource<T> {
 		List<Pair<Block, T>> get(int start, int count);
 		BlockView prepareView(Viewpoint viewpoint, Block block, T t) throws BlockNotVisibleException;
@@ -2025,7 +2050,7 @@
 			// facebook external accounts, because not including the user was only implemented them, 
 			// but not music or blog updates)
 			UserBlockData ubd = new UserBlockData(user, block, block.getTimestamp().getTime());
-			em.persist(ubd);						
+			em.persist(ubd);
 		}
 	}
 	

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/views/AnonymousViewpoint.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/views/AnonymousViewpoint.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/views/AnonymousViewpoint.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -7,6 +7,11 @@
 import com.dumbhippo.dm.DMSession;
 import com.dumbhippo.identity20.Guid;
 import com.dumbhippo.persistence.User;
+import com.dumbhippo.persistence.BlockType.BlockVisibility;
+import com.dumbhippo.server.NotFoundException;
+import com.dumbhippo.server.dm.BlockDMOKey;
+import com.dumbhippo.server.dm.DataService;
+import com.dumbhippo.server.dm.PostDMO;
 
 /**
  * AnonymousViewpoint represents a anonymous public view onto
@@ -68,8 +73,24 @@
 		return false;
 	}	
 	
+
 	@Override
+	public boolean canSeeBlock(BlockDMOKey blockKey) {
+		return blockKey.getType().getBlockVisibility() == BlockVisibility.PUBLIC;
+	}
+	
+	@Override
 	public Site getSite() {
 		return site;
 	}
+
+	@Override
+	public boolean canSeePost(Guid postId) {
+		try {
+			DMSession session = DataService.getModel().currentSession();
+			return (Boolean)session.getRawProperty(PostDMO.class, postId, "public");
+		} catch (NotFoundException e) {
+			return false;
+		}
+	}
 }

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/views/SystemViewpoint.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/views/SystemViewpoint.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/views/SystemViewpoint.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -4,6 +4,7 @@
 import com.dumbhippo.dm.DMSession;
 import com.dumbhippo.identity20.Guid;
 import com.dumbhippo.persistence.User;
+import com.dumbhippo.server.dm.BlockDMOKey;
 
 /**
  * SystemViewpoint represents the systems view of the database.
@@ -63,6 +64,16 @@
 		return true;
 	}	
 	
+	@Override
+	public boolean canSeeBlock(BlockDMOKey blockKey) {
+		return true;
+	}
+	
+	@Override
+	public boolean canSeePost(Guid postId) {
+		return true;
+	}
+	
 	// the SystemViewpoint is never relative to Site.GNOME or Site.MUGSHOT
 	@Override
 	public Site getSite() {

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/views/UserViewpoint.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/views/UserViewpoint.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/views/UserViewpoint.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -4,11 +4,15 @@
 
 import com.dumbhippo.Site;
 import com.dumbhippo.dm.DMSession;
+import com.dumbhippo.dm.store.StoreKey;
 import com.dumbhippo.identity20.Guid;
 import com.dumbhippo.persistence.User;
 import com.dumbhippo.server.IdentitySpider;
 import com.dumbhippo.server.NotFoundException;
+import com.dumbhippo.server.dm.BlockDMO;
+import com.dumbhippo.server.dm.BlockDMOKey;
 import com.dumbhippo.server.dm.ContactDMO;
+import com.dumbhippo.server.dm.PostDMO;
 import com.dumbhippo.server.dm.UserDMO;
 import com.dumbhippo.server.util.EJBUtil;
 
@@ -123,4 +127,52 @@
 			return false;
 		}	
 	}
+	
+	@Override
+	public boolean canSeeBlock(BlockDMOKey blockKey) {
+		try {
+			switch (blockKey.getType().getBlockVisibility()) {
+			case PUBLIC:
+				return true;
+			case OWNER:
+				Guid ownerGuid = (Guid)session.getRawProperty(BlockDMO.class, blockKey, "owner");
+				return viewerId.equals(ownerGuid);
+			case DELEGATE:
+				StoreKey<?,?> delegateKey = (StoreKey<?,?>)session.getRawProperty(BlockDMO.class, blockKey, "visibilityDelegate");
+				return delegateKey.isVisible(this);
+			case NOBODY:
+				return false;
+			}
+		} catch (NotFoundException e) {
+			return false;
+		}
+		
+		return false;
+	}
+
+	@Override
+	public boolean canSeePost(Guid postId) {
+		try {
+			boolean isPublic = (Boolean)session.getRawProperty(PostDMO.class, postId, "public");
+			if (isPublic)
+				return true;
+			//  This check is a little too restrictive, in that it doesn't handle
+			//
+			// A) posts sent to private groups before the viewer joined the group
+			// B) posts sent to a resource when someone later claims the resource, since we don't
+			//    invalidate the cached expandedRecipients, which are users, resource
+			//
+			// B) is especially problematical, but is probably best handled by adding the
+			// invalidation, rather than trying to cache resources (that is, find all posts
+			// where the newly claimed resource is an expandedRecipient, and then invalidate
+			// their expandedRecipients)
+			
+			@SuppressWarnings("unchecked")
+			Set<Guid> expandedRecipients = (Set<Guid>)session.getRawProperty(PostDMO.class, postId, "expandedRecipients");
+			return expandedRecipients.contains(viewerId);
+			
+		} catch (NotFoundException e) {
+			return false;
+		}
+	}
 }

Modified: dumbhippo/trunk/server/src/com/dumbhippo/server/views/Viewpoint.java
===================================================================
--- dumbhippo/trunk/server/src/com/dumbhippo/server/views/Viewpoint.java	2007-12-11 01:48:01 UTC (rev 7012)
+++ dumbhippo/trunk/server/src/com/dumbhippo/server/views/Viewpoint.java	2007-12-11 01:59:56 UTC (rev 7013)
@@ -4,6 +4,7 @@
 import com.dumbhippo.dm.DMViewpoint;
 import com.dumbhippo.identity20.Guid;
 import com.dumbhippo.persistence.User;
+import com.dumbhippo.server.dm.BlockDMOKey;
 
 /**
  * The Viewpoint class represents the concept of "current user". 
@@ -46,6 +47,8 @@
 	public abstract boolean canSeeFriendsOnly(Guid userId);
 	public abstract boolean canSeePrivate(Guid userId);
 	public abstract boolean canSeeContact(Guid contactId);
+	public abstract boolean canSeeBlock(BlockDMOKey blockKey);
+	public abstract boolean canSeePost(Guid postId);
 	
 	public abstract Site getSite();
 	



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]