org.sakaiproject.nakamura.files.pool.ContentPoolCommentServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.files.pool.ContentPoolCommentServlet.java

Source

/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.sakaiproject.nakamura.files.pool;

import static org.sakaiproject.nakamura.api.files.FilesConstants.POOLED_CONTENT_USER_MANAGER;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.OptingServlet;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.commons.json.JSONException;
import org.sakaiproject.nakamura.api.doc.BindingType;
import org.sakaiproject.nakamura.api.doc.ServiceBinding;
import org.sakaiproject.nakamura.api.doc.ServiceDocumentation;
import org.sakaiproject.nakamura.api.doc.ServiceExtension;
import org.sakaiproject.nakamura.api.doc.ServiceMethod;
import org.sakaiproject.nakamura.api.doc.ServiceParameter;
import org.sakaiproject.nakamura.api.doc.ServiceResponse;
import org.sakaiproject.nakamura.api.lite.ClientPoolException;
import org.sakaiproject.nakamura.api.lite.Repository;
import org.sakaiproject.nakamura.api.lite.Session;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.authorizable.Authorizable;
import org.sakaiproject.nakamura.api.lite.authorizable.AuthorizableManager;
import org.sakaiproject.nakamura.api.lite.authorizable.Group;
import org.sakaiproject.nakamura.api.lite.authorizable.User;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.api.lite.content.ContentManager;
import org.sakaiproject.nakamura.api.user.BasicUserInfoService;
import org.sakaiproject.nakamura.util.ExtendedJSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.jcr.RepositoryException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet endpoint for comments associated to a piece of pooled content.
 */
@SlingServlet(methods = { "GET", "POST", "DELETE" }, extensions = "comments", resourceTypes = {
        "sakai/pooled-content" })
@ServiceDocumentation(name = "ContentPoolCommentServlet", okForVersion = "1.2", shortDescription = "Read/Write/Delete comments on a content item.", description = "Read, write, and delete comments associated to a piece of pooled content.", bindings = @ServiceBinding(type = BindingType.TYPE, bindings = "sakai/pooled-content", extensions = @ServiceExtension(name = "comments", description = "Accesses the comments on a piece of content.")), methods = {
        @ServiceMethod(name = "GET", description = "Retrieve the list of comments associated to a content item.", response = {
                @ServiceResponse(code = 200, description = "Successfully retrieved the comments for this content item."),
                @ServiceResponse(code = 500, description = "Failure, explanation is in the HTML.") }),
        @ServiceMethod(name = "POST", description = "Add a new comment to a content item. <br />", parameters = {
                @ServiceParameter(name = "comment", description = "The text of the comment to add to the content item."),
                @ServiceParameter(name = "_charset_", description = "Must be utf-8") }, response = {
                        @ServiceResponse(code = 201, description = "Comment successfully added."),
                        @ServiceResponse(code = 400, description = "Bad request: if there is no comment parameter or if user is anonymous."),
                        @ServiceResponse(code = 500, description = "Failure, explanation is in the HTML.") }),
        @ServiceMethod(name = "DELETE", description = "Remove the specified comment from this content item.", parameters = {
                @ServiceParameter(name = "commentId", description = "The unique ID of the comment to delete.") }, response = {
                        @ServiceResponse(code = 204, description = "The comment was successfully removed."),
                        @ServiceResponse(code = 403, description = "Forbidden. Must be a manager of the pooled content item or author of the comment to delete a comment."),
                        @ServiceResponse(code = 400, description = "Bad request. The commentId parameter was missing, and is not optional."),
                        @ServiceResponse(code = 500, description = "Failure, explanation is in the HTML.") }) })
public class ContentPoolCommentServlet extends SlingAllMethodsServlet implements OptingServlet {
    private static final Logger LOGGER = LoggerFactory.getLogger(ContentPoolCommentServlet.class);
    private static final long serialVersionUID = 1L;
    private static final String COMMENT = "comment";
    private static final String COMMENTS = "comments";
    private static final String COMMENT_ID = "commentId";
    private static final String AUTHOR = "author";
    private static final String CREATED = "_created";

    @Reference
    private Repository repository;
    @Reference
    private BasicUserInfoService basicUserInfoService;

    /**
     * Determine if we will accept this request. Had to add this because something is
     * triggering this servlet to take GET requests even though the extension != comments.
     * 
     * {@inheritDoc}
     * 
     * @see org.apache.sling.api.servlets.OptingServlet#accepts(org.apache.sling.api.SlingHttpServletRequest)
     */
    public boolean accepts(SlingHttpServletRequest request) {
        RequestPathInfo rpi = request.getRequestPathInfo();
        boolean willAccept = "comments".equals(rpi.getExtension());
        return willAccept;
    }

    @Override
    protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        Resource resource = request.getResource();
        Content poolContent = resource.adaptTo(Content.class);

        boolean isTidy = false;
        String[] selectors = request.getRequestPathInfo().getSelectors();
        if (selectors != null) {
            for (int i = 0; i < selectors.length; i++) {
                String selector = selectors[i];
                if ("tidy".equals(selector)) {
                    isTidy = true;
                }
            }
        }

        try {
            ContentManager contentManager = resource.adaptTo(ContentManager.class);
            Session session = resource.adaptTo(Session.class);
            AuthorizableManager authorizableManager = session.getAuthorizableManager();
            Content comments = contentManager.get(poolContent.getPath() + "/" + COMMENTS);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");

            ExtendedJSONWriter w = new ExtendedJSONWriter(response.getWriter());
            w.setTidy(isTidy);
            w.object();
            w.key(COMMENTS);
            w.array();

            if (comments != null) {
                for (Content comment : comments.listChildren()) {
                    Map<String, Object> properties = comment.getProperties();
                    String authorId = (String) properties.get(AUTHOR);
                    w.object();

                    try {
                        Authorizable author = authorizableManager.findAuthorizable(authorId);
                        ValueMap profile = new ValueMapDecorator(basicUserInfoService.getProperties(author));
                        w.valueMapInternals(profile);
                    } catch (StorageClientException e) {
                        w.key(AUTHOR);
                        w.value(authorId);
                    } catch (AccessDeniedException e) {
                        w.key(AUTHOR);
                        w.value(authorId);
                    }

                    w.key(COMMENT);
                    w.value(properties.get(COMMENT));

                    w.key(COMMENT_ID);
                    w.value(comment.getPath());

                    w.key(CREATED);
                    w.value(properties.get(CREATED));

                    w.endObject();
                }
            }

            w.endArray();
            w.endObject();
        } catch (JSONException e) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
            LOGGER.error(e.getMessage(), e);
        } catch (StorageClientException e) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
            LOGGER.error(e.getMessage(), e);
        } catch (AccessDeniedException e) {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
            LOGGER.error(e.getMessage(), e);
        }
    }

    @Override
    protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {

        String user = request.getRemoteUser();
        String body = request.getParameter(COMMENT);
        int statusCode;

        // stop now if user is not logged in
        if ("anonymous".equals(user)) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Must be logged in to post a comment.");
            return;
        }

        Resource resource = request.getResource();
        Session adminSession = null;
        try {
            adminSession = repository.loginAdministrative();
            ContentManager contentManager = adminSession.getContentManager();
            AuthorizableManager authorizableManager = adminSession.getAuthorizableManager();
            User currentUser = (User) authorizableManager.findAuthorizable(user);
            Content poolContent = resource.adaptTo(Content.class);
            String path = poolContent.getPath() + "/" + COMMENTS;

            Content comments = contentManager.get(path);
            if (comments == null) {
                comments = new Content(path, new HashMap<String, Object>());
                contentManager.update(comments);
            }

            String commentId = request.getParameter("commentId");
            if (commentId != null) {
                // we're going to edit an existing comment
                Content existingComment = contentManager.get(commentId);
                if (existingComment == null) {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                            "Attempting to update non-existent content: " + commentId);
                    return;
                }
                if (!isManager(poolContent, currentUser, authorizableManager)
                        && !currentUser.getId().equals(existingComment.getProperty("author"))) {
                    response.sendError(HttpServletResponse.SC_FORBIDDEN,
                            "Must be a manager of the pooled content item or author of the comment to edit a comment.");
                    return;
                }
                path = commentId;
                statusCode = HttpServletResponse.SC_OK;
            } else {
                // stop now if no comment provided
                if (StringUtils.isBlank(body)) {
                    response.sendError(HttpServletResponse.SC_BAD_REQUEST, "'comment' must be provided.");
                    return;
                }
                // have the node name be the current time in milliseconds since the epoch
                Calendar cal = Calendar.getInstance();
                String newNodeName = Long.toString(cal.getTimeInMillis());
                path = path + "/" + newNodeName;
                statusCode = HttpServletResponse.SC_CREATED;
            }
            ImmutableMap.Builder<String, Object> commentPropertiesBuilder = ImmutableMap.builder();
            commentPropertiesBuilder.put(AUTHOR, user);
            // KERN-1536 allow arbitrary properties to be stored on a comment
            for (String parameterName : request.getRequestParameterMap().keySet()) {
                // commentId is not meant to be stored
                if ("commentId".equals(parameterName)) {
                    continue;
                }
                String[] parameterValues = request.getParameterValues(parameterName);
                if (parameterValues.length == 1) {
                    commentPropertiesBuilder.put(parameterName, parameterValues[0]);
                } else {
                    commentPropertiesBuilder.put(parameterName, parameterValues);
                }
            }

            Content comment = new Content(path, commentPropertiesBuilder.build());

            contentManager.update(comment);

            response.setStatus(statusCode);
            // return the comment id created for this comment
            response.setContentType("text/plain");
            response.setCharacterEncoding("UTF-8");
            ExtendedJSONWriter w = new ExtendedJSONWriter(response.getWriter());
            w.object();
            w.key(COMMENT_ID);
            w.value(path);
            w.endObject();
        } catch (JSONException e) {
            LOGGER.warn(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        } catch (ClientPoolException e) {
            LOGGER.warn(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        } catch (StorageClientException e) {
            LOGGER.warn(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        } catch (AccessDeniedException e) {
            LOGGER.warn(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        } finally {
            try {
                if (adminSession != null) {
                    adminSession.logout();
                }
            } catch (ClientPoolException e) {
                LOGGER.warn(e.getMessage(), e);
            }
        }
    }

    @Override
    protected void doDelete(SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws ServletException, IOException {
        String commentId = request.getParameter("commentId");

        Session adminSession = null;
        try {
            // check that user is a manager of the content item
            adminSession = repository.loginAdministrative();
            Resource resource = request.getResource();
            Content poolItem = resource.adaptTo(Content.class);
            AuthorizableManager authorizableManager = adminSession.getAuthorizableManager();
            User user = (User) authorizableManager.findAuthorizable(request.getRemoteUser());

            // stop now if no comment ID is provided
            if (StringUtils.isBlank(commentId)) {
                response.sendError(HttpServletResponse.SC_BAD_REQUEST, "'commentId' must be provided.");
                return;
            }

            String path = poolItem.getPath() + "/" + COMMENTS + "/" + commentId;
            ContentManager contentManager = adminSession.getContentManager();
            Content comment = contentManager.get(path);
            if (!isManager(poolItem, user, authorizableManager)
                    && !user.getId().equals(comment.getProperty("author"))) {
                response.sendError(HttpServletResponse.SC_FORBIDDEN,
                        "Must be a manager of the pooled content item or author of the comment to delete a comment.");
                return;
            }
            contentManager.delete(path);
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
        } catch (AccessDeniedException e) {
            LOGGER.warn(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        } catch (StorageClientException e) {
            LOGGER.warn(e.getMessage(), e);
            throw new ServletException(e.getMessage(), e);
        } finally {
            if (adminSession != null) {
                try {
                    adminSession.logout();
                } catch (ClientPoolException e) {
                    LOGGER.warn(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Gets all the "members" for a file.
     * @param authorizableManager 
     * 
     * @param node
     *          The node that represents the file.
     * @return true if the user is a manager of the pooled item.
     * @throws RepositoryException
     */
    private boolean isManager(Content poolItem, User user, AuthorizableManager authorizableManager) {
        Map<String, Object> properties = poolItem.getProperties();
        String[] managers = (String[]) properties.get(POOLED_CONTENT_USER_MANAGER);
        Set<String> principals = Sets.newHashSet();
        principals.add(user.getId());
        if (!User.ANON_USER.equals(user.getId())) {
            principals.add(Group.EVERYONE);
        }
        // direct membership
        for (String p : user.getPrincipals()) {
            principals.add(p);
        }
        // indirect
        for (Iterator<Group> gi = user.memberOf(authorizableManager); gi.hasNext();) {
            principals.add(gi.next().getId());
        }
        if (managers != null) {
            for (String m : managers) {
                if (principals.contains(m)) {
                    return true;
                }
            }
        }
        return false;
    }
}