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.Message;
import org.sakaiproject.api.app.messageforums.OpenForum;
import org.sakaiproject.api.app.messageforums.PrivateForum;
import org.sakaiproject.api.app.messageforums.Topic;
import org.sakaiproject.api.app.messageforums.ui.DiscussionForumManager;
import org.sakaiproject.api.app.messageforums.ui.UIPermissionsManager;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.entitybroker.EntityView;
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.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;
import org.sakaiproject.tool.messageforums.entityproviders.sparsepojos.*;
import org.sakaiproject.tool.messageforums.entityproviders.utils.MessageUtils;
/**
* Provides the forums entity provider.
*
* @author Adrian Fish <adrian.r.fish@gmail.com>
*/
public class ForumsEntityProviderImpl extends AbstractEntityProvider implements Outputable, AutoRegisterEntityProvider, ActionsExecutable, Describeable {
private static final Log LOG = LogFactory.getLog(ForumsEntityProviderImpl.class);
public final static String ENTITY_PREFIX = "forums";
@Setter
protected DiscussionForumManager forumManager;
@Setter
protected UIPermissionsManager uiPermissionsManager;
@Setter
protected SiteService siteService;
@Setter
protected ToolManager toolManager;
@Setter
private SecurityService securityService;
public String getEntityPrefix() {
return ENTITY_PREFIX;
}
public String[] getHandledOutputFormats() {
return new String[] {Formats.JSON,Formats.XML};
}
/**
* This handles the paths:
*
* /direct/forums/site/SITEID.json
* /direct/forums/site/SITEID/forum/FORUMID.json
* /direct/forums/site/SITEID/forum/FORUMID/topic/TOPICID.json
* /direct/forums/site/SITEID/forum/FORUMID/topic/TOPICID/message/MESSAGEID.json
*
* @param view
* @param params
* @return
*/
@EntityCustomAction(action="site",viewKey=EntityView.VIEW_LIST)
public Object handleSite(EntityView view, Map<String, Object> params) {
if(LOG.isDebugEnabled()) {
LOG.debug("handleSite");
}
String userId = developerHelperService.getCurrentUserId();
if(userId == null) {
LOG.error("Not logged in");
throw new EntityException("You must be logged in to retrieve fora.","",HttpServletResponse.SC_UNAUTHORIZED);
}
String siteId = view.getPathSegment(2);
if(siteId == null) {
LOG.error("Bad request. No SITEID supplied on path.");
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);
String[] pathSegments = view.getPathSegments();
if(pathSegments.length == 3) {
// This is a request for all the fora in the site
return getAllForaForSite(siteId,userId);
} else if(pathSegments.length == 5) {
// This is a request for a particular forum in the site
Long forumId = -1L;
try {
forumId = Long.parseLong(view.getPathSegment(4));
} catch(NumberFormatException nfe) {
LOG.error("Bad request. FORUMID must be an integer.");
throw new EntityException("The forum id must be an integer.","",HttpServletResponse.SC_BAD_REQUEST);
}
return getForum(forumId,siteId,userId);
} else if(pathSegments.length == 7) {
// This is a request for a particular topic in the forum
Long topicId = -1L;
try {
topicId = Long.parseLong(view.getPathSegment(6));
} catch(NumberFormatException nfe) {
LOG.error("Bad request. TOPICID must be an integer.");
throw new EntityException("The topic id must be an integer.","",HttpServletResponse.SC_BAD_REQUEST);
}
return getTopic(topicId,siteId,userId);
} else if(pathSegments.length == 9) {
Long messageId = -1L;
try {
messageId = Long.parseLong(view.getPathSegment(8));
} catch(NumberFormatException nfe) {
LOG.error("Bad request. MESSAGEID must be an integer.");
throw new EntityException("The message id must be an integer.","",HttpServletResponse.SC_BAD_REQUEST);
}
return getMessage(messageId,siteId,userId);
} else {
return null;
}
}
private List<?> getAllForaForSite(String siteId,String userId) {
if(LOG.isDebugEnabled()) {
LOG.debug("getAllForaForSite(" + siteId + "," + userId + ")");
}
List<SparsestForum> sparseFora = new ArrayList<SparsestForum>();
List<DiscussionForum> fatFora = forumManager.getDiscussionForumsWithTopics(siteId);
for(DiscussionForum fatForum : fatFora) {
if( ! checkAccess(fatForum,userId)) {
LOG.warn("Access denied for user id '" + userId + "' to forum '" + fatForum.getId()
+ "'. This forum will not be returned.");
continue;
}
List<Long> topicIds = new ArrayList<Long>();
for(Topic topic : (List<Topic>) fatForum.getTopics()) {
topicIds.add(topic.getId());
}
List<Object[]> topicTotals = forumManager.getMessageCountsForMainPage(topicIds);
List<Object[]> 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;
}
/**
* This will return a SparseForum populated down to the topics with their
* attachments.
*/
private Object getForum(Long forumId, String siteId, String userId) {
if(LOG.isDebugEnabled()) {
LOG.debug("getForum(" + forumId + "," + siteId + "," + userId + ")");
}
DiscussionForum fatForum = forumManager.getForumByIdWithTopicsAttachmentsAndMessages(forumId);
if(checkAccess(fatForum,userId)) {
SparseForum sparseForum = new SparseForum(fatForum,developerHelperService);
List<DiscussionTopic> fatTopics = (List<DiscussionTopic>) fatForum.getTopics();
// Gather all the topic ids so we can make the minimum number
// of calls for the message counts.
List<Long> topicIds = new ArrayList<Long>();
for(DiscussionTopic topic : fatTopics) {
topicIds.add(topic.getId());
}
List<Object[]> topicTotals = forumManager.getMessageCountsForMainPage(topicIds);
List<Object[]> 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<SparsestTopic> sparseTopics = new ArrayList<SparsestTopic>();
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<SparseAttachment> attachments = new ArrayList<SparseAttachment>();
for(Attachment attachment : (List<Attachment>) 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 {
LOG.error("Not authorised to access forum '" + forumId + "'");
throw new EntityException("You are not authorised to access this forum.","",HttpServletResponse.SC_UNAUTHORIZED);
}
}
private Object getTopic(Long topicId,String siteId,String userId) {
if(LOG.isDebugEnabled()) {
LOG.debug("getTopic(" + topicId + "," + siteId + "," + userId + ")");
}
// This call gets the attachments for the messages but not the topic. Unexpected, yes. Cool, not.
Topic fatTopic = forumManager.getTopicByIdWithMessagesAndAttachments(topicId);
if(!uiPermissionsManager.isRead(topicId,((DiscussionTopic)fatTopic).getDraft(),false,userId,forumManager.getContextForTopicById(topicId))) {
LOG.error("'" + userId + "' is not authorised to read topic '" + 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<Long> topicIds = new ArrayList<Long>();
topicIds.add(fatTopic.getId());
List<Object[]> totalCounts = forumManager.getMessageCountsForMainPage(topicIds);
if(totalCounts.size() > 0) {
sparseTopic.setTotalMessages((Integer) totalCounts.get(0)[1]);
} else {
sparseTopic.setTotalMessages(0);
}
List<Object[]> readCounts = forumManager.getReadMessageCountsForMainPage(topicIds);
if(readCounts.size() > 0) {
sparseTopic.setReadMessages((Integer) readCounts.get(0)[1]);
} else {
sparseTopic.setReadMessages(0);
}
List<SparseMessage> messages = new ArrayList<SparseMessage>();
for(Message fatMessage : (List<Message>) fatTopic.getMessages()) {
SparseMessage sparseMessage = new SparseMessage(fatMessage,/* readStatus = */ false,/* addAttachments = */ true,developerHelperService.getServerURL());
messages.add(sparseMessage);
}
List<SparseThread> threads = new MessageUtils().getThreadsWithCounts(messages, forumManager, userId);
sparseTopic.setThreads(threads);
return sparseTopic;
}
private Object getMessage(Long messageId,String siteId,String userId) {
if(LOG.isDebugEnabled()) {
LOG.debug("getMessage(" + messageId + "," + siteId + "," + userId + ")");
}
Message fatMessage = forumManager.getMessageById(messageId);
Topic fatTopic = forumManager.getTopicByIdWithMessagesAndAttachments(fatMessage.getTopic().getId());
// 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()))) {
LOG.error("'" + userId + "' is not authorised to read message '" + messageId + "'.");
throw new EntityException("You are not authorised to read this message.","",HttpServletResponse.SC_UNAUTHORIZED);
}
List<SparseMessage> messages = new ArrayList<SparseMessage>();
for(Message fm : (List<Message>) 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;
}
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;
}
/**
* 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<Message> populatedMessages) {
for(Message populatedMessage : populatedMessages) {
if(populatedMessage.getId().equals(unPopulatedMessage.getId())
&& populatedMessage.getHasAttachments()) {
unPopulatedMessage.setAttachments(populatedMessage.getAttachments());
break;
}
}
}
/**
* Checks whether the current user can access this site and whether they can
* see the forums tool.
*
* @param siteId
* @throws EntityException
*/
private 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);
}
}
}