Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/utils/MessageUtils.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/utils/MessageUtils.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/utils/MessageUtils.java (revision 0) @@ -0,0 +1,164 @@ +package org.sakaiproject.tool.messageforums.entityproviders.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.sakaiproject.api.app.messageforums.ui.DiscussionForumManager; +import org.sakaiproject.tool.messageforums.entityproviders.sparsepojos.SparseMessage; +import org.sakaiproject.tool.messageforums.entityproviders.sparsepojos.SparseThread; + + +/** + * A helper class for arranging messages into their proper parent child relationships. + * + * @author Adrian Fish + */ +public class MessageUtils { + + /** + * Extracts the threads (messages with no parent) from the supplied messages and sets + * the message totals up on each of them. + * + * @param sparseForum The SparseForum to extract the threads from + * @param forumManager We need this to the get the read stati for the accumulated message ids. + * @param userId We need this to the get the read stati for the accumulated message ids. + */ + public List getThreadsWithCounts(List messages,DiscussionForumManager forumManager, String userId) { + + List messageIds = new ArrayList(); + + // Find the top level threads, the messages with no parent, basically. + List threads = new ArrayList(); + for (SparseMessage message : messages) { + + if(message.isDraft() || message.isDeleted()) { + continue; + } + + messageIds.add(message.getMessageId()); + if(message.getReplyTo() == null) { + threads.add(new SparseThread(message)); + } + } + + Map readStati = forumManager.getReadStatusForMessagesWithId(messageIds, userId); + + for(SparseMessage thread : threads) { + boolean read = readStati.get(thread.getMessageId()); + Counts counts = new Counts(1,(read) ? 1 : 0); + thread.setRead(read); + // We don't want to add the messages and bulk up the resulting JSON feed. We + // still want the total and unread messages counts for this thread though. + setupCounts(thread,messages,readStati,counts); + thread.setTotalMessages(counts.total); + thread.setReadMessages(counts.read); + } + + return threads; + } + + /** + * Sets up the message hierarchy for topMessage. + * + * @param topMessage The topmost message that we want to setup the message graph for + * @param messages The flat list of messages that we want to insert into the hierarchy + * @param forumManager We need this to the get the read stati for the accumulated message ids. + * @param userId We need this to the get the read stati for the accumulated message ids. + */ + public void attachReplies(SparseMessage topMessage, List messages,DiscussionForumManager forumManager, String userId) { + + List messageIds = new ArrayList(); + + for (SparseMessage message : messages) { + messageIds.add(message.getMessageId()); + } + + Map readStati = forumManager.getReadStatusForMessagesWithId(messageIds, userId); + boolean read = readStati.get(topMessage.getMessageId()); + topMessage.setRead(read); + Counts counts = new Counts(1,(read) ? 1 : 0); + addReplies(topMessage,messages,readStati,counts); + topMessage.setTotalMessages(counts.total); + topMessage.setReadMessages(counts.read); + } + + /** + * Does a depth first recursion into the messages list, looking for replies + * to the specified parent. + * + * @param parent + * @param messages + */ + private void addReplies(SparseMessage parent,List messages, Map readStati,Counts counts) { + + for (SparseMessage message : messages) { + + if(message.isDraft() || message.isDeleted()) { + continue; + } + + if(message.getReplyTo() != null + && message.getReplyTo().equals(parent.getMessageId())) { + + counts.total = counts.total + 1; + + boolean read = readStati.get(message.getMessageId()); + + message.setRead(read); + + if(read) { + counts.read = counts.read + 1; + } + + parent.addReply(message); + + // Recurse + addReplies(message,messages, readStati,counts); + } + } + } + + /** + * Recursively iterates over the messages and increments the counts accumulator if any of them + * are a reply to the parent. The idea is to finally exit with a set of totals for an ancestor. + * + * @param parent The message for which to look for replies. + * @param messages The flatted list of messages to search. + * @param readStati The pre-retrieved list of read stati for the messsage list. + * @param counts An accumulator of message counts + */ + private void setupCounts(SparseMessage parent,List messages, Map readStati,Counts counts) { + + for (SparseMessage message : messages) { + + if(message.isDraft() || message.isDeleted()) { + continue; + } + + if(message.getReplyTo() != null + && message.getReplyTo().equals(parent.getMessageId())) { + + counts.total = counts.total + 1; + + if(readStati.get(message.getMessageId())) { + counts.read = counts.read + 1; + } + + // Recurse + setupCounts(message,messages, readStati,counts); + } + } + } + + public class Counts { + + public int total = 0; + public int read = 0; + + public Counts(int total,int read) { + this.total = total; + this.read = read; + } + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/BaseEntityProvider.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/BaseEntityProvider.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/BaseEntityProvider.java (revision 0) @@ -0,0 +1,58 @@ +package org.sakaiproject.tool.messageforums.entityproviders; + +import javax.servlet.http.HttpServletResponse; + +import lombok.Setter; + +import org.sakaiproject.api.app.messageforums.ui.DiscussionForumManager; +import org.sakaiproject.api.app.messageforums.ui.UIPermissionsManager; +import org.sakaiproject.entitybroker.exception.EntityException; +import org.sakaiproject.entitybroker.util.AbstractEntityProvider; +import org.sakaiproject.exception.IdUnusedException; +import org.sakaiproject.exception.PermissionException; +import org.sakaiproject.site.api.Site; +import org.sakaiproject.site.api.SiteService; +import org.sakaiproject.site.api.ToolConfiguration; +import org.sakaiproject.tool.api.ToolManager; + +public abstract class BaseEntityProvider extends AbstractEntityProvider { + + @Setter + protected DiscussionForumManager forumManager; + + @Setter + protected UIPermissionsManager uiPermissionsManager; + + @Setter + protected SiteService siteService; + + @Setter + protected ToolManager toolManager; + + /** + * Checks whether the current user can access this site and whether they can + * see the forums tool. + * + * @param siteId + * @throws EntityException + */ + protected void checkSiteAndToolAccess(String siteId) throws EntityException { + + //check user can access this site + Site site; + try { + site = siteService.getSiteVisit(siteId); + } catch (IdUnusedException e) { + throw new EntityException("Invalid siteId: " + siteId,"", HttpServletResponse.SC_BAD_REQUEST); + } catch (PermissionException e) { + throw new EntityException("No access to site: " + siteId,"",HttpServletResponse.SC_UNAUTHORIZED); + } + + //check user can access the tool, it might be hidden + ToolConfiguration toolConfig = site.getToolForCommonId("sakai.forums"); + if(!toolManager.isVisible(site, toolConfig)) { + throw new EntityException("No access to tool in site: " + siteId, "",HttpServletResponse.SC_UNAUTHORIZED); + } + + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsTopicEntityProviderImpl.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsTopicEntityProviderImpl.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsTopicEntityProviderImpl.java (revision 0) @@ -0,0 +1,105 @@ +package org.sakaiproject.tool.messageforums.entityproviders; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.api.app.messageforums.DiscussionTopic; +import org.sakaiproject.api.app.messageforums.Message; +import org.sakaiproject.api.app.messageforums.Topic; +import org.sakaiproject.entitybroker.EntityReference; +import org.sakaiproject.entitybroker.entityprovider.CoreEntityProvider; +import org.sakaiproject.entitybroker.entityprovider.capabilities.*; +import org.sakaiproject.entitybroker.entityprovider.extension.Formats; +import org.sakaiproject.entitybroker.exception.EntityException; +import org.sakaiproject.tool.messageforums.entityproviders.sparsepojos.*; +import org.sakaiproject.tool.messageforums.entityproviders.utils.MessageUtils; + +public class ForumsTopicEntityProviderImpl extends BaseEntityProvider implements CoreEntityProvider, Outputable, Resolvable, AutoRegisterEntityProvider, ActionsExecutable, Describeable { + + private static final Log LOG = LogFactory.getLog(ForumsTopicEntityProviderImpl.class); + + public final static String ENTITY_PREFIX = "forums-topic"; + + public String getEntityPrefix() { + return ENTITY_PREFIX; + } + + public boolean entityExists(String id) { + + Topic topic = null; + try { + topic = forumManager.getTopicById(Long.valueOf(id)); + } catch (Exception e) { + LOG.error("Failed to get topic with id '" + id +"'",e); + } + return (topic != null); + } + + public Object getEntity(EntityReference ref) { + + String userId = developerHelperService.getCurrentUserId(); + + if(userId == null) { + throw new EntityException("You must be logged in to retrieve topics.","",HttpServletResponse.SC_UNAUTHORIZED); + } + + Long topicId = -1L; + + try { + topicId = Long.parseLong(ref.getId()); + } catch(NumberFormatException nfe) { + throw new EntityException("The topic id must be an integer.","",HttpServletResponse.SC_BAD_REQUEST); + } + + // This call gets the attachments for the messages but not the topic. Unexpected, yes. Cool, not. + Topic fatTopic = forumManager.getTopicByIdWithMessagesAndAttachments(topicId); + + String siteId = forumManager.getContextForTopicById(topicId); + + checkSiteAndToolAccess(siteId); + + if(!uiPermissionsManager.isRead(topicId,((DiscussionTopic)fatTopic).getDraft(),false,userId,forumManager.getContextForTopicById(topicId))) { + throw new EntityException("You are not authorised to read this topic.","",HttpServletResponse.SC_UNAUTHORIZED); + } + + SparseTopic sparseTopic = new SparseTopic(fatTopic); + + // Setup the total and read message counts on the topic + List topicIds = new ArrayList(); + topicIds.add(fatTopic.getId()); + + List totalCounts = forumManager.getMessageCountsForMainPage(topicIds); + if(totalCounts.size() > 0) { + sparseTopic.setTotalMessages((Integer) totalCounts.get(0)[1]); + } else { + sparseTopic.setTotalMessages(0); + } + + List readCounts = forumManager.getReadMessageCountsForMainPage(topicIds); + if(readCounts.size() > 0) { + sparseTopic.setReadMessages((Integer) readCounts.get(0)[1]); + } else { + sparseTopic.setReadMessages(0); + } + + List messages = new ArrayList(); + for(Message fatMessage : (List) fatTopic.getMessages()) { + SparseMessage sparseMessage = new SparseMessage(fatMessage,/* readStatus = */ false,/* addAttachments = */ true,developerHelperService.getServerURL()); + messages.add(sparseMessage); + } + + List threads = new MessageUtils().getThreadsWithCounts(messages, forumManager, userId); + + sparseTopic.setThreads(threads); + + return sparseTopic; + } + + public String[] getHandledOutputFormats() { + return new String[] {Formats.JSON,Formats.XML}; + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseMessage.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseMessage.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseMessage.java (revision 0) @@ -0,0 +1,124 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import java.util.ArrayList; +import java.util.List; + +import org.sakaiproject.api.app.messageforums.Attachment; +import org.sakaiproject.api.app.messageforums.Message; + +import lombok.Getter; +import lombok.Setter; + +public class SparseMessage{ + + @Getter + private Long messageId; + + @Getter + private Long topicId; + + @Getter + private String title; + + @Getter + private String body; + + @Getter + private Long lastModified; + + @Getter @Setter + private List attachments = new ArrayList(); + + @Getter @Setter + private List replies = new ArrayList(); + + @Getter + private String authoredBy; + + @Getter + private String authorId; + + @Getter @Setter + private int indentIndex = 0; + + @Getter + private Long replyTo; + + @Getter + private Long createdOn; + + @Getter @Setter + private boolean read; + + @Getter @Setter + private Integer totalMessages = 0; + + @Getter @Setter + private Integer readMessages = 0; + + @Getter + private boolean isDraft; + + @Getter + private boolean isDeleted; + + public SparseMessage(Message fatMessage, Boolean readStatus, boolean addAttachments, String serverUrl) { + + super(); + + this.messageId = fatMessage.getId(); + this.topicId = fatMessage.getTopic().getId(); + this.title = fatMessage.getTitle(); + this.body = fatMessage.getBody(); + this.lastModified = fatMessage.getModified().getTime()/1000; + this.authoredBy = fatMessage.getAuthor(); + this.authorId = fatMessage.getAuthorId(); + this.isDraft = fatMessage.getDraft(); + this.isDeleted = fatMessage.getDeleted(); + + Message parent = fatMessage.getInReplyTo(); + if(parent != null) { + this.replyTo = parent.getId(); + } + + this.createdOn = fatMessage.getCreated().getTime()/1000; + this.read = readStatus; + + if(addAttachments && fatMessage.getHasAttachments()) { + List sparseAttachments = new ArrayList(); + for(Attachment fatAttachment : (List)fatMessage.getAttachments()) { + String url = serverUrl + "/access/content" + fatAttachment.getAttachmentId(); + attachments.add(new SparseAttachment(fatAttachment.getAttachmentName(),url)); + } + } + } + + public SparseMessage(SparseMessage that) { + + super(); + + this.messageId = that.getMessageId(); + this.topicId = that.getTopicId(); + this.title = that.getTitle(); + this.body = that.getBody(); + this.lastModified = that.getLastModified(); + this.authoredBy = that.getAuthoredBy(); + this.authorId = that.getAuthorId(); + this.isDraft = that.isDraft(); + this.isDeleted = that.isDeleted(); + + this.replyTo = that.getReplyTo(); + + this.createdOn = that.getCreatedOn(); + this.read = that.isRead(); + this.attachments = that.getAttachments(); + } + + public void addReply(SparseMessage reply) { + + if(replies == null) { + replies = new ArrayList(); + } + replies.add(reply); + } +} \ No newline at end of file Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparsestTopic.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparsestTopic.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparsestTopic.java (revision 0) @@ -0,0 +1,53 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +import org.sakaiproject.api.app.messageforums.Topic; + +public class SparsestTopic { + + @Getter + private Long id; + + @Getter + private String title; + + @Getter + private Long createdDate; + + @Getter + private String creator; + + @Getter + private Long modifiedDate; + + @Getter + private String modifier; + + @Getter + private Boolean isAutoMarkThreadsRead; + + @Getter @Setter + private Integer totalMessages = 0; + + @Getter @Setter + private Integer readMessages = 0; + + @Getter @Setter + private List attachments = new ArrayList(); + + public SparsestTopic(Topic fatTopic) { + + this.id = fatTopic.getId(); + this.title = fatTopic.getTitle(); + this.createdDate = fatTopic.getCreated().getTime()/1000; + this.creator = fatTopic.getCreatedBy(); + this.modifiedDate = fatTopic.getModified().getTime()/1000; + this.modifier = fatTopic.getModifiedBy(); + this.isAutoMarkThreadsRead = fatTopic.getAutoMarkThreadsRead(); + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseThread.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseThread.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseThread.java (revision 0) @@ -0,0 +1,18 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import lombok.Getter; +import lombok.Setter; + +public class SparseThread extends SparseMessage { + + /** + * This is only set when this is a top level message, a.k.a. a thread. + */ + @Getter @Setter + private Long threadId; + + public SparseThread(SparseMessage sparseMessage) { + super(sparseMessage); + this.threadId = sparseMessage.getMessageId(); + } +} \ No newline at end of file Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseAttachment.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseAttachment.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseAttachment.java (revision 0) @@ -0,0 +1,18 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import lombok.Getter; + +public class SparseAttachment { + + @Getter + private String name; + + @Getter + private String url; + + public SparseAttachment(String name, String url) { + this.name = name; + this.url = url; + } + +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseForum.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseForum.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseForum.java (revision 0) @@ -0,0 +1,25 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +import org.sakaiproject.api.app.messageforums.DiscussionForum; +import org.sakaiproject.entitybroker.DeveloperHelperService; + +/** + * A json friendly representation of a DiscussionForum. This one adds the topic list to the json. + * + * @author Adrian Fish + */ +public class SparseForum extends SparsestForum { + + @Getter @Setter + private List topics; + + public SparseForum(DiscussionForum fatForum, DeveloperHelperService dhs) { + + super(fatForum,dhs); + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseTopic.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseTopic.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparseTopic.java (revision 0) @@ -0,0 +1,19 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +import org.sakaiproject.api.app.messageforums.Topic; + +public class SparseTopic extends SparsestTopic { + + @Getter @Setter + private List threads; + + public SparseTopic(Topic fatTopic) { + + super(fatTopic); + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparsestForum.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparsestForum.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/sparsepojos/SparsestForum.java (revision 0) @@ -0,0 +1,84 @@ +package org.sakaiproject.tool.messageforums.entityproviders.sparsepojos; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +import org.sakaiproject.api.app.messageforums.Attachment; +import org.sakaiproject.api.app.messageforums.DiscussionForum; +import org.sakaiproject.entitybroker.DeveloperHelperService; + +/** + * A json friendly representation of a DiscussionForum. Just the stuff you need, hopefully. + * + * @author Adrian Fish + */ +public class SparsestForum { + + @Getter + private Long id; + + @Getter + private String title; + + /** + * An epoch date in seconds. NOT milliseconds. + */ + @Getter + private Long createdDate; + + @Getter + private String creator; + + @Getter + private String extendedDescription; + + @Getter + private String shortDescription; + + @Getter + private Boolean isModerated; + + /** + * An epoch date in seconds. NOT milliseconds. + */ + @Getter + private Long modifiedDate; + + @Getter + private String modifier; + + @Getter @Setter + private Integer totalMessages; + + @Getter @Setter + private Integer readMessages; + + @Getter + private Boolean isDraft; + + @Getter + private List attachments = new ArrayList(); + + public SparsestForum(DiscussionForum fatForum, DeveloperHelperService dhs) { + + this.id = fatForum.getId(); + this.title = fatForum.getTitle(); + // Epoch time in seconds for the created date + this.createdDate = fatForum.getCreated().getTime()/1000; + this.creator = fatForum.getCreatedBy(); + this.extendedDescription = fatForum.getExtendedDescription(); + this.shortDescription = fatForum.getShortDescription(); + this.isModerated = fatForum.getModerated(); + this.modifiedDate = fatForum.getModified().getTime()/1000; + this.modifier = fatForum.getModifiedBy(); + this.isDraft = fatForum.getDraft(); + + for(Attachment attachment : (List) fatForum.getAttachments()) { + String url = dhs.getServerURL() + "/access/content" + attachment.getAttachmentId(); + attachments.add(new SparseAttachment(attachment.getAttachmentName(),url)); + } + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsMessageEntityProviderImpl.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsMessageEntityProviderImpl.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsMessageEntityProviderImpl.java (revision 0) @@ -0,0 +1,111 @@ +package org.sakaiproject.tool.messageforums.entityproviders; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.api.app.messageforums.DiscussionTopic; +import org.sakaiproject.api.app.messageforums.Message; +import org.sakaiproject.api.app.messageforums.Topic; +import org.sakaiproject.entitybroker.EntityReference; +import org.sakaiproject.entitybroker.entityprovider.CoreEntityProvider; +import org.sakaiproject.entitybroker.entityprovider.capabilities.*; +import org.sakaiproject.entitybroker.entityprovider.extension.Formats; +import org.sakaiproject.entitybroker.exception.EntityException; +import org.sakaiproject.tool.messageforums.entityproviders.sparsepojos.*; +import org.sakaiproject.tool.messageforums.entityproviders.utils.MessageUtils; + +public class ForumsMessageEntityProviderImpl extends BaseEntityProvider implements CoreEntityProvider, Outputable, Resolvable, AutoRegisterEntityProvider, ActionsExecutable, Describeable { + + private static final Log LOG = LogFactory.getLog(ForumsMessageEntityProviderImpl.class); + + public final static String ENTITY_PREFIX = "forums-message"; + + public String getEntityPrefix() { + return ENTITY_PREFIX; + } + + public boolean entityExists(String id) { + + Message message = null; + try { + message = forumManager.getMessageById(Long.valueOf(id)); + } catch (Exception e) { + LOG.error("Failed to get message with id '" + id +"'",e); + } + return (message != null); + } + + /** + * Implements the functionality for getting a particular message and its replies. + */ + public Object getEntity(EntityReference ref) { + + String userId = developerHelperService.getCurrentUserId(); + + if(userId == null) { + throw new EntityException("You must be logged in to retrieve messages.","",HttpServletResponse.SC_UNAUTHORIZED); + } + + Long messageId = -1L; + + try { + messageId = Long.parseLong(ref.getId()); + } catch(NumberFormatException nfe) { + throw new EntityException("The thread id must be an integer.","",HttpServletResponse.SC_BAD_REQUEST); + } + + Message fatMessage = forumManager.getMessageById(messageId); + + Topic fatTopic = forumManager.getTopicByIdWithMessagesAndAttachments(fatMessage.getTopic().getId()); + + String siteId = forumManager.getContextForTopicById(fatTopic.getId()); + checkSiteAndToolAccess(siteId); + + // This sets the attachments on the message.We have to do this as + // getMessageById doesn't populate the attachments. + setAttachments(fatMessage,fatTopic.getMessages()); + + if(!uiPermissionsManager.isRead(fatTopic.getId(),((DiscussionTopic)fatTopic).getDraft(),false,userId,forumManager.getContextForTopicById(fatTopic.getId()))) { + throw new EntityException("You are not authorised to read this message.","",HttpServletResponse.SC_UNAUTHORIZED); + } + + List messages = new ArrayList(); + + for(Message fm : (List) fatTopic.getMessages()) { + messages.add(new SparseMessage(fm,/* readStatus =*/ false,/* addAttachments =*/ true, developerHelperService.getServerURL())); + } + + SparseMessage sparseThread = new SparseMessage(fatMessage,false,/* readStatus =*/ true,developerHelperService.getServerURL()); + + new MessageUtils().attachReplies(sparseThread,messages, forumManager, userId); + + return sparseThread; + } + + public String[] getHandledOutputFormats() { + return new String[] {Formats.JSON,Formats.XML}; + } + + /** + * This is a dirty hack to set the attachments on the message. There doesn't seem + * to be an api for getting a single message with all attachments. If you try and retrieve + * them after, hibernate, wonderful framework that it is, throws a lazy exception. + * + * @param unPopulatedMessage The message we want to set attachments on + * @param populatedMessages The list of populated messages retrieved from the forum manager + */ + private void setAttachments(Message unPopulatedMessage, List populatedMessages) { + + for(Message populatedMessage : populatedMessages) { + if(populatedMessage.getId().equals(unPopulatedMessage.getId()) + && populatedMessage.getHasAttachments()) { + unPopulatedMessage.setAttachments(populatedMessage.getAttachments()); + break; + } + } + } +} Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsForumEntityProviderImpl.java =================================================================== --- messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsForumEntityProviderImpl.java (revision 0) +++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/entityproviders/ForumsForumEntityProviderImpl.java (revision 0) @@ -0,0 +1,251 @@ +package org.sakaiproject.tool.messageforums.entityproviders; + +import java.util.*; + +import javax.servlet.http.HttpServletResponse; + +import lombok.Setter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.sakaiproject.api.app.messageforums.Attachment; +import org.sakaiproject.api.app.messageforums.BaseForum; +import org.sakaiproject.api.app.messageforums.DiscussionForum; +import org.sakaiproject.api.app.messageforums.DiscussionTopic; +import org.sakaiproject.api.app.messageforums.OpenForum; +import org.sakaiproject.api.app.messageforums.PrivateForum; +import org.sakaiproject.api.app.messageforums.Topic; +import org.sakaiproject.authz.api.SecurityService; +import org.sakaiproject.entitybroker.EntityReference; +import org.sakaiproject.entitybroker.EntityView; +import org.sakaiproject.entitybroker.entityprovider.CoreEntityProvider; +import org.sakaiproject.entitybroker.entityprovider.annotations.EntityCustomAction; +import org.sakaiproject.entitybroker.entityprovider.capabilities.*; +import org.sakaiproject.entitybroker.entityprovider.extension.Formats; +import org.sakaiproject.entitybroker.exception.EntityException; +import org.sakaiproject.tool.messageforums.entityproviders.sparsepojos.*; + +/** + * Provides the forums entity provider. + * + * @author Adrian Fish + */ +public class ForumsForumEntityProviderImpl extends BaseEntityProvider implements CoreEntityProvider, Outputable, Resolvable, AutoRegisterEntityProvider, ActionsExecutable, Describeable { + + private static final Log LOG = LogFactory.getLog(ForumsForumEntityProviderImpl.class); + + public final static String ENTITY_PREFIX = "forums-forum"; + + @Setter + private SecurityService securityService; + + public String getEntityPrefix() { + return ENTITY_PREFIX; + } + + public boolean entityExists(String id) { + + DiscussionForum forum = null; + try { + forum = forumManager.getForumById(Long.valueOf(id)); + } catch (Exception e) { + LOG.error("Failed to get forum with id '" + id +"'",e); + } + return (forum != null); + } + + /** + * This will return a SparseForum populated down to the topics with their + * attachments. + */ + public Object getEntity(EntityReference ref) { + + String userId = developerHelperService.getCurrentUserId(); + + if(userId == null) { + throw new EntityException("You must be logged in to retrieve fora.","",HttpServletResponse.SC_UNAUTHORIZED); + } + + Long forumId = -1L; + + try { + forumId = Long.parseLong(ref.getId()); + } catch(NumberFormatException nfe) { + throw new EntityException("The forum id must be an integer.","",HttpServletResponse.SC_BAD_REQUEST); + } + + String siteId = forumManager.getContextForForumById(forumId); + + checkSiteAndToolAccess(siteId); + + DiscussionForum fatForum = forumManager.getForumByIdWithTopicsAttachmentsAndMessages(forumId); + + if(checkAccess(fatForum,userId)) { + + SparseForum sparseForum = new SparseForum(fatForum,developerHelperService); + + List fatTopics = (List) fatForum.getTopics(); + + // Gather all the topic ids so we can make the minimum number + // of calls for the message counts. + List topicIds = new ArrayList(); + for(DiscussionTopic topic : fatTopics) { + topicIds.add(topic.getId()); + } + + List topicTotals = forumManager.getMessageCountsForMainPage(topicIds); + List topicReadTotals = forumManager.getReadMessageCountsForMainPage(topicIds); + + int totalForumMessages = 0; + for(Object[] topicTotal : topicTotals) { + totalForumMessages += (Integer) topicTotal[1]; + } + sparseForum.setTotalMessages(totalForumMessages); + + int totalForumReadMessages = 0; + for(Object[] topicReadTotal : topicReadTotals) { + totalForumReadMessages += (Integer) topicReadTotal[1]; + } + sparseForum.setReadMessages(totalForumReadMessages); + + // Reduce the fat topics to sparse topics while setting the total and read + // counts. A SparseTopic will only be created if the currrent user has read access. + List sparseTopics = new ArrayList(); + for(DiscussionTopic fatTopic : fatTopics) { + + // Only add this topic to the list if the current user has read permission + if( ! uiPermissionsManager.isRead(fatTopic,fatForum,userId,siteId)) { + // No read permission, skip this topic. + continue; + } + + SparsestTopic sparseTopic = new SparsestTopic(fatTopic); + for(Object[] topicTotal : topicTotals) { + if(topicTotal[0].equals(sparseTopic.getId())) { + sparseTopic.setTotalMessages((Integer)topicTotal[1]); + } + } + for(Object[] topicReadTotal : topicReadTotals) { + if(topicReadTotal[0].equals(sparseTopic.getId())) { + sparseTopic.setReadMessages((Integer)topicReadTotal[1]); + } + } + + List attachments = new ArrayList(); + for(Attachment attachment : (List) fatTopic.getAttachments()) { + String url = developerHelperService.getServerURL() + "/access/content" + attachment.getAttachmentId(); + attachments.add(new SparseAttachment(attachment.getAttachmentName(),url)); + } + sparseTopic.setAttachments(attachments); + + sparseTopics.add(sparseTopic); + } + + sparseForum.setTopics(sparseTopics); + + return sparseForum; + } else { + throw new EntityException("You are not authorised to access this forum.","",HttpServletResponse.SC_UNAUTHORIZED); + } + } + + /** + * This will return a list of SparseForum populated just to forum level, but with counts setup. + * + * @param view + * @param params + * @return + */ + @EntityCustomAction(action="site",viewKey=EntityView.VIEW_LIST) + public List getForumsInSite(EntityView view, Map params) { + + String userId = developerHelperService.getCurrentUserId(); + + if(userId == null) { + throw new EntityException("You must be logged in to retrieve fora.","",HttpServletResponse.SC_UNAUTHORIZED); + } + + String siteId = view.getPathSegment(2); + + if(siteId == null) { + throw new EntityException("Bad request: To get the fora in a site you need a url like '/direct/forum/site/SITEID.json'" + ,"",HttpServletResponse.SC_BAD_REQUEST); + } + + checkSiteAndToolAccess(siteId); + + List sparseFora = new ArrayList(); + + List fatFora = forumManager.getDiscussionForumsWithTopics(siteId); + + for(DiscussionForum fatForum : fatFora) { + + if( ! checkAccess(fatForum,userId)) { + // TODO: Log this rejected access attempt + continue; + } + + List topicIds = new ArrayList(); + for(Topic topic : (List) fatForum.getTopics()) { + topicIds.add(topic.getId()); + } + + List topicTotals = forumManager.getMessageCountsForMainPage(topicIds); + List topicReadTotals = forumManager.getReadMessageCountsForMainPage(topicIds); + + SparsestForum sparseForum = new SparsestForum(fatForum,developerHelperService); + + int totalForumMessages = 0; + for(Object[] topicTotal : topicTotals) { + totalForumMessages += (Integer) topicTotal[1]; + } + sparseForum.setTotalMessages(totalForumMessages); + + int totalForumReadMessages = 0; + for(Object[] topicReadTotal : topicReadTotals) { + totalForumReadMessages += (Integer) topicReadTotal[1]; + } + sparseForum.setReadMessages(totalForumReadMessages); + + sparseFora.add(sparseForum); + } + + return sparseFora; + } + + public String[] getHandledOutputFormats() { + return new String[] {Formats.JSON,Formats.XML}; + } + + private boolean checkAccess(BaseForum baseForum, String userId) { + + if(baseForum instanceof OpenForum) { + + // If the supplied user is the super user, return true. + if(securityService.isSuperUser(userId)) { + return true; + } + + OpenForum of = (OpenForum) baseForum; + + // If this is not a draft and is available, return true. + if(!of.getDraft() && of.getAvailability()) { + return true; + } + + // If this is a draft/unavailable forum AND was authored by the current user, return true. + if((of.getDraft() || !of.getAvailability()) && of.getCreatedBy().equals(userId)) { + return true; + } + } + else if(baseForum instanceof PrivateForum) { + PrivateForum pf = (PrivateForum) baseForum; + // If the current user is the creator, return true. + if(pf.getCreatedBy().equals(userId)) { + return true; + } + } + + return false; + } +} Index: messageforums-app/src/bundle/forums-topic.properties =================================================================== --- messageforums-app/src/bundle/forums-topic.properties (revision 0) +++ messageforums-app/src/bundle/forums-topic.properties (revision 0) @@ -0,0 +1,8 @@ +forums-topic = Provides a RESTful endpoint for retrieving a topic. This provider is designed to \ +be used with the forums-forum provider. Call /direct/forums-forum/FORUMID.json to get a json feed of topics for a particular \ +forum, then /direct/forums-topic/TOPICID.json to get the threads for a topic. You may then want to call the forums-message \ +provider to get the full set of messages for a thread. + +forums-topic.view.show = (GET) Retrieves the topic specified by the appended id. All the threads (top level messages) \ +are included, but not the child messages.
\ +Example URL: /direct/forums-topic/TOPICID.json Index: messageforums-app/src/bundle/forums-message.properties =================================================================== --- messageforums-app/src/bundle/forums-message.properties (revision 0) +++ messageforums-app/src/bundle/forums-message.properties (revision 0) @@ -0,0 +1,6 @@ +forums-message = Provides a RESTful endpoint for retrieving a message with all its children. \ +You would use this in conjunction with the forums-forum and forums-topic providers. + +forums-message.view.show = (GET) Retrieves the message specified by the appended id. All the child messages \ +are included.
\ +Example URL: /direct/forums-message/MESSAGEID.json Index: messageforums-app/src/bundle/forums-forum.properties =================================================================== --- messageforums-app/src/bundle/forums-forum.properties (revision 0) +++ messageforums-app/src/bundle/forums-forum.properties (revision 0) @@ -0,0 +1,13 @@ +# this defines the entity description for the forum provider +forums-forum = Provides a set of RESTful endpoints for retrieving a single forum or a collection of fora for a \ +given site id. An example workflow would be to call the site action with /direct/forums-forum/site/SITEID.json for \ +the top level fora in a site, then to use /direct/forums-forum/FORUMID.json to get an individual forum to the topic level. \ +After that you would use the forums-topic provider to get a list of threads for a particular topic. + +forums-forum.view.show = (GET) Retrieves the forum specified by the appended id. All the topics are included, but \ +not the messages.
\ +Example URL: /direct/forums-forum/FORUMID.json + +forums-forum.action.site = (GET) Retrieves a list of forums for a given site id, as specified in the path. Topics \ +and messages ARE NOT included.
\ +Example URL: /direct/forums-forum/site/SITEID.json Index: messageforums-app/src/webapp/WEB-INF/local.xml =================================================================== --- messageforums-app/src/webapp/WEB-INF/local.xml (revision 121242) +++ messageforums-app/src/webapp/WEB-INF/local.xml (working copy) @@ -2,4 +2,27 @@ + + + + + + + + + + + + + + + + + + Index: messageforums-app/pom.xml =================================================================== --- messageforums-app/pom.xml (revision 121242) +++ messageforums-app/pom.xml (working copy) @@ -62,6 +62,10 @@ entitybroker-api + org.sakaiproject.entitybroker + entitybroker-utils + + org.sakaiproject.edu-services.gradebook gradebook-service-api @@ -92,6 +96,11 @@ javax.servlet.jsp jsp-api + + org.projectlombok + lombok + 0.11.6 +