org.ambraproject.wombat.controller.CommentController.java Source code

Java tutorial

Introduction

Here is the source code for org.ambraproject.wombat.controller.CommentController.java

Source

/*
 * Copyright (c) 2017 Public Library of Science
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package org.ambraproject.wombat.controller;

import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import org.ambraproject.wombat.config.RuntimeConfiguration;
import org.ambraproject.wombat.config.site.Site;
import org.ambraproject.wombat.config.site.SiteParam;
import org.ambraproject.wombat.identity.RequestedDoiVersion;
import org.ambraproject.wombat.model.ArticleComment;
import org.ambraproject.wombat.model.ArticleCommentFlag;
import org.ambraproject.wombat.service.CommentService;
import org.ambraproject.wombat.service.CommentValidationService;
import org.ambraproject.wombat.service.HoneypotService;
import org.ambraproject.wombat.service.remote.ApiAddress;
import org.ambraproject.wombat.service.remote.ArticleApi;
import org.ambraproject.wombat.service.remote.UserApi;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Controller for submitting and rendering an article's comments.
 */
@Controller
public class CommentController extends WombatController {

    private static final Logger log = LoggerFactory.getLogger(CommentController.class);

    @Autowired
    private UserApi userApi;
    @Autowired
    private ArticleApi articleApi;
    @Autowired
    private HoneypotService honeypotService;
    @Autowired
    private CommentValidationService commentValidationService;
    @Autowired
    private CommentService commentService;
    @Autowired
    private ArticleMetadata.Factory articleMetadataFactory;
    @Autowired
    private Gson gson;
    @Autowired
    private RuntimeConfiguration runtimeConfiguration;

    private void addCommentAvailability(Model model) {
        model.addAttribute("areCommentsDisabled", runtimeConfiguration.areCommentsDisabled());
    }

    /**
     * Serves a request for a list of all the root-level comments associated with an article.
     *
     * @param model     data to pass to the view
     * @param site      current site
     * @param articleId specifies the article
     * @return path to the template
     * @throws IOException
     */
    @RequestMapping(name = "articleComments", value = "/article/comments")
    public String renderArticleComments(HttpServletRequest request, Model model, @SiteParam Site site,
            RequestedDoiVersion articleId) throws IOException {
        articleMetadataFactory.get(site, articleId).validateVisibility("articleComments").populate(request, model);

        try {
            model.addAttribute("articleComments", commentService.getArticleComments(articleId, site));
        } catch (UserApi.UserApiException e) {
            log.error(e.getMessage(), e);
            model.addAttribute("userApiError", e);
        }

        addCommentAvailability(model);

        return site + "/ftl/article/comment/comments";
    }

    @RequestMapping(name = "articleCommentForm", value = "/article/comments/new")
    public String renderNewCommentForm(HttpServletRequest request, Model model, @SiteParam Site site,
            RequestedDoiVersion articleId) throws IOException {
        articleMetadataFactory.get(site, articleId).validateVisibility("articleCommentForm").populate(request,
                model);

        addCommentAvailability(model);
        return site + "/ftl/article/comment/newComment";
    }

    /**
     * @param comment Json representing a comment
     * @return the parent article's doi (latest revision)
     */
    private static String getParentArticleDoiFromComment(Map<String, Object> comment) {
        return (String) (((Map<String, Object>) comment.get("parentArticle")).get("doi"));
    }

    /**
     * @param commentDoi
     * @return Json representing a comment (no user data included)
     * @throws IOException
     */
    private Map<String, Object> getComment(String commentDoi) throws IOException {
        return articleApi.requestObject(ApiAddress.builder("comments").embedDoi(commentDoi).build(), Map.class);
    }

    /**
     * Serves a request for an expanded view of a single comment and any replies.
     *
     * @param model      data to pass to the view
     * @param site       current site
     * @param commentDoi specifies the comment
     * @return path to the template
     * @throws IOException
     */
    @RequestMapping(name = "articleCommentTree", value = "/article/comment")
    public String renderArticleCommentTree(HttpServletRequest request, Model model, @SiteParam Site site,
            @RequestParam("id") String commentDoi) throws IOException {
        requireNonemptyParameter(commentDoi);
        Map<String, Object> comment;
        try {
            comment = commentService.getComment(commentDoi, site);
        } catch (CommentService.CommentNotFoundException e) {
            throw new NotFoundException(e);
        } catch (UserApi.UserApiException e) {
            log.error(e.getMessage(), e);
            model.addAttribute("userApiError", e);

            // Get a copy of the comment that is not populated with userApi data.
            // This articleApi call is redundant to one that commentService.getComment would have made before throwing.
            // TODO: Prevent extra articleApi call
            comment = getComment(commentDoi);
        }

        RequestedDoiVersion doi = RequestedDoiVersion.of(getParentArticleDoiFromComment(comment));

        articleMetadataFactory.get(site, doi).validateVisibility("articleCommentTree").populate(request, model);

        model.addAttribute("comment", comment);
        addCommentAvailability(model);
        return site + "/ftl/article/comment/comment";
    }

    private void checkCommentsAreEnabled() {
        if (runtimeConfiguration.areCommentsDisabled()) {
            // TODO: Need a special exception and handler to produce a 400-series response instead of 500?
            throw new RuntimeException("Posting of comments is disabled");
        }
    }

    /**
     * @param parentArticleDoi null if a reply to another comment
     * @param parentCommentUri null if a direct reply to an article
     */
    @RequestMapping(name = "postComment", method = RequestMethod.POST, value = "/article/comments/new")
    @ResponseBody
    public Object receiveNewComment(HttpServletRequest request, @SiteParam Site site,
            @RequestParam("commentTitle") String commentTitle, @RequestParam("comment") String commentBody,
            @RequestParam("isCompetingInterest") boolean hasCompetingInterest,
            @RequestParam(value = "authorEmailAddress", required = false) String authorEmailAddress,
            @RequestParam(value = "authorName", required = false) String authorName,
            @RequestParam(value = "authorPhone", required = false) String authorPhone,
            @RequestParam(value = "authorAffiliation", required = false) String authorAffiliation,
            @RequestParam(value = "ciStatement", required = false) String ciStatement,
            @RequestParam(value = "target", required = false) String parentArticleDoi,
            @RequestParam(value = "inReplyTo", required = false) String parentCommentUri) throws IOException {

        if (honeypotService.checkHoneypot(request, authorPhone, authorAffiliation)) {
            return ImmutableMap.of("status", "success");
        }

        checkCommentsAreEnabled();

        Map<String, Object> validationErrors = commentValidationService.validateComment(site, commentTitle,
                commentBody, hasCompetingInterest, ciStatement);

        if (!validationErrors.isEmpty()) {
            return ImmutableMap.of("validationErrors", validationErrors);
        }

        if (parentArticleDoi == null) {
            Map<String, Object> comment = getComment(parentCommentUri);
            parentArticleDoi = getParentArticleDoiFromComment(comment);
        }

        ApiAddress address = ApiAddress.builder("articles").embedDoi(parentArticleDoi).addToken("comments").build();

        String authId = request.getRemoteUser();
        final String creatorUserId = authId == null ? null : userApi.getUserIdFromAuthId(authId);
        ArticleComment comment = new ArticleComment(parentArticleDoi, creatorUserId, parentCommentUri, commentTitle,
                commentBody, ciStatement, authorEmailAddress, authorName);

        HttpResponse response = articleApi.postObject(address, comment);
        String responseJson = EntityUtils.toString(response.getEntity());
        Map<String, Object> commentJson = gson.fromJson(responseJson, HashMap.class);
        return ImmutableMap.of("createdCommentUri", commentJson.get("commentUri"));
    }

    @RequestMapping(name = "postCommentFlag", method = RequestMethod.POST, value = "/article/comments/flag")
    @ResponseBody
    public Object receiveCommentFlag(HttpServletRequest request, @RequestParam("reasonCode") String reasonCode,
            @RequestParam("comment") String flagCommentBody, @RequestParam("target") String targetCommentDoi)
            throws IOException {
        checkCommentsAreEnabled();

        Map<String, Object> validationErrors = commentValidationService.validateFlag(flagCommentBody);
        if (!validationErrors.isEmpty()) {
            return ImmutableMap.of("validationErrors", validationErrors);
        }

        String authId = request.getRemoteUser();
        final String creatorUserId = authId == null ? null : userApi.getUserIdFromAuthId(authId);
        ArticleCommentFlag flag = new ArticleCommentFlag(creatorUserId, flagCommentBody, reasonCode);

        Map<String, Object> comment = getComment(targetCommentDoi);
        String parentArticleDoi = getParentArticleDoiFromComment(comment);

        ApiAddress address = ApiAddress.builder("articles").embedDoi(parentArticleDoi).addToken("comments")
                .embedDoi(targetCommentDoi).addToken("flags").build();

        articleApi.postObject(address, flag);
        return ImmutableMap.of(); // the "201 CREATED" status is all the AJAX client needs
    }

}