package uk.ac.cam.caret.sakai.rwiki.tool.entityproviders; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; import lombok.Setter; import org.sakaiproject.entitybroker.EntityView; import org.sakaiproject.entitybroker.entityprovider.annotations.EntityCustomAction; import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable; import org.sakaiproject.entitybroker.entityprovider.capabilities.AutoRegisterEntityProvider; import org.sakaiproject.entitybroker.entityprovider.capabilities.Describeable; import org.sakaiproject.entitybroker.entityprovider.capabilities.Outputable; import org.sakaiproject.entitybroker.entityprovider.extension.Formats; import org.sakaiproject.entitybroker.exception.EntityException; import org.sakaiproject.entitybroker.util.AbstractEntityProvider; import org.sakaiproject.user.api.UserDirectoryService; import org.sakaiproject.user.api.UserNotDefinedException; import uk.ac.cam.caret.sakai.rwiki.service.api.RWikiObjectService; import uk.ac.cam.caret.sakai.rwiki.service.api.RenderService; import uk.ac.cam.caret.sakai.rwiki.service.api.model.RWikiObject; import uk.ac.cam.caret.sakai.rwiki.utils.NameHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Provides the /direct/wiki REST endpoint. * * GET a URL like this /direct/wiki/site/SITEID.json and you'll get a graph of wiki * pages for the site specified. The graph contains page urls as well as names. * * GET a URL like this /direct/wiki/site/SITEID/page/PAGENAME.json and you'll get the * specified page as a JSON object. * * @author Adrian Fish <adrian.r.fish@gmail.com> */ public class RWikiEntityProvider extends AbstractEntityProvider implements AutoRegisterEntityProvider, ActionsExecutable, Outputable,Describeable { private static Log log = LogFactory.getLog(RWikiEntityProvider.class); public final static String ENTITY_PREFIX = "wiki"; @Setter private RWikiObjectService objectService; @Setter private RenderService renderService; @Setter private UserDirectoryService userDirectoryService; public String getEntityPrefix() { return ENTITY_PREFIX; } public String[] getHandledOutputFormats() { return new String[] { Formats.JSON, Formats.XML }; } @EntityCustomAction(action = "site", viewKey = EntityView.VIEW_LIST) public Object handleSite(EntityView view, Map<String, Object> params) { String userId = developerHelperService.getCurrentUserId(); if(userId == null) { throw new EntityException("You must be logged in to retrieve pages","",HttpServletResponse.SC_UNAUTHORIZED); } String format = view.getFormat(); if(view.getPathSegments().length == 3) { // This is a request for the site's page graph String siteId = view.getPathSegment(2); SparserPage homePage = new SparserPage("home",siteId, format); getSubPages(homePage, "/site/" + siteId, siteId, userId, format); return homePage; } else if(view.getPathSegments().length == 5) { // This is a request for a page String siteId = view.getPathSegment(2); String pageName = view.getPathSegment(4); // Construct the page name and get the content. String defaultRealm = "/site/" + siteId; if(objectService.exists(pageName,defaultRealm)) { RWikiObject page = objectService.getRWikiObject(pageName, defaultRealm); if(!objectService.checkRead(page)) { log.warn("User '" + userId + "' does not have read permissions for page '" + page.getName() + "'. This page will not be returned in the JSON."); throw new EntityException("Forbidden: You do not have permission to read this page","",HttpServletResponse.SC_FORBIDDEN); } SparsePage sparsePage = new SparsePage(pageName,siteId, format); String localSpace = NameHelper.localizeSpace(page.getName(),defaultRealm); DirectServletPageLinkRenderer plr = new DirectServletPageLinkRenderer(localSpace, defaultRealm, format); String rendered = renderService.renderPage(page, localSpace, plr); sparsePage.setHtml(rendered); addComments(page,sparsePage); return sparsePage; } else { log.warn("Bad request '" + view.getOriginalEntityUrl() + "'"); throw new EntityException("Bad request: You must supply a valid page name" ,"",HttpServletResponse.SC_BAD_REQUEST); } } else { log.warn("Bad request '" + view.getOriginalEntityUrl() + "'"); throw new EntityException("Bad request: To get the pages in a site you need a url like " + "'/direct/wiki/site/SITEID.json' or '/direct/wiki/site/SITEID/page/PAGENAME.json'" ,"",HttpServletResponse.SC_BAD_REQUEST); } } private void getSubPages(SparserPage parent, String realm,String siteId, String userId, String format) { RWikiObject page = objectService.getRWikiObject(parent.getName(), realm); // If the current user cannot read this page it will NOT be added to the returned graph if(!objectService.checkRead(page)) { log.warn("User '" + userId + "' does not have read permissions for page '" + parent.getName() + "'. This page will not be returned in the JSON."); return; } String referenced = page.getReferenced(); if(referenced != null) { String[] parts = referenced.split("::"); List<String> children = Arrays.asList(parts); for(String childName : children) { if(!"".equals(childName)) { childName = childName.substring(childName.lastIndexOf("/") + 1); if(objectService.exists(childName,realm)) { SparserPage child = new SparserPage(childName,siteId,format); parent.addChildPage(child); getSubPages(child,realm,siteId,userId,format); } } } } // Now get the comment count for the parent page int commentCount = objectService.findRWikiSubPages(page.getName() + ".").size(); parent.setNumberOfComments(commentCount); } /** * Be warned: this method only works if the DAO code continues to order by page name ascending. * * @param page The page to get the comments for. * @param sparsePage The sparse page which will be returned as JSON. The comments get added to this. */ private void addComments(RWikiObject page, SparsePage sparsePage) { List<RWikiObject> comments = objectService.findRWikiSubPages(page.getName() + "."); String realm = page.getRealm(); Map<String,SparseComment> commentMap = new HashMap<String,SparseComment>(); for(RWikiObject comment : comments) { String userDisplayName = comment.getUser(); try { userDisplayName = userDirectoryService.getUser(userDisplayName).getDisplayName(); } catch (UserNotDefinedException unde) { log.warn("No user for id '" + userDisplayName + "'. The user id will be returned instead."); } // Each comment object name is suffixed by a series of three digit strings, separated // by full stops. These represent the nesting of the comments. String name = comment.getName(); String localName = NameHelper.localizeName(name, NameHelper.localizeSpace(name, realm)); String dottyBit = localName.substring(localName.indexOf(".", 0)); String[] commentIds = dottyBit.split("\\."); if(commentIds.length == 2) { // This is a top level comment. SparseComment topLevelComment = new SparseComment(userDisplayName,comment.getVersion().getTime(),comment.getContent()); commentMap.put(commentIds[1], topLevelComment); sparsePage.addComment(topLevelComment); } else if(commentIds.length > 2) { // This is a descendant comment. The parent should already be in the // comment map. Get the parent and add the child to it. THIS DEPENDS // ON THE ORDERING FROM DAO BEING ASCENDING BY NAME. SparseComment parent = commentMap.get(commentIds[commentIds.length - 2]); if(parent == null) { // This is terribly bad news. It means the ordering was messed up // as the parent should be in the map at this point. log.warn("No comment in the map for " + commentIds[commentIds.length - 2] + ". Skipping ..."); continue; } SparseComment child = new SparseComment(userDisplayName,comment.getVersion().getTime(),comment.getContent()); parent.addChildComment(child); commentMap.put(commentIds[commentIds.length - 1], child); } } } }