org.nuxeo.ecm.social.workspace.gadgets.webengine.SocialWebEngineRoot.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.social.workspace.gadgets.webengine.SocialWebEngineRoot.java

Source

/*
 * (C) Copyright 2012 Nuxeo SA (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     eugen
 *     vpasquier <vpasquier@nuxeo.com>
 */
package org.nuxeo.ecm.social.workspace.gadgets.webengine;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.nuxeo.ecm.core.api.LifeCycleConstants.DELETE_TRANSITION;
import static org.nuxeo.ecm.core.api.security.SecurityConstants.ADD_CHILDREN;
import static org.nuxeo.ecm.core.api.security.SecurityConstants.REMOVE;
import static org.nuxeo.ecm.core.api.security.SecurityConstants.REMOVE_CHILDREN;
import static org.nuxeo.ecm.social.workspace.SocialConstants.SOCIAL_WORKSPACE_FACET;
import static org.nuxeo.ecm.social.workspace.SocialConstants.SOCIAL_WORKSPACE_IS_PUBLIC_PROPERTY;
import static org.nuxeo.ecm.social.workspace.helper.SocialWorkspaceHelper.toSocialDocument;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;

import org.apache.commons.httpclient.util.URIUtil;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.common.utils.i18n.I18NUtils;
import org.nuxeo.ecm.automation.AutomationService;
import org.nuxeo.ecm.automation.OperationChain;
import org.nuxeo.ecm.automation.OperationContext;
import org.nuxeo.ecm.automation.core.operations.services.DocumentPageProviderOperation;
import org.nuxeo.ecm.automation.core.util.PaginableDocumentModelList;
import org.nuxeo.ecm.core.api.Blob;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentLocation;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.DocumentRef;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.api.PathRef;
import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
import org.nuxeo.ecm.core.api.impl.DocumentLocationImpl;
import org.nuxeo.ecm.core.schema.DocumentType;
import org.nuxeo.ecm.core.schema.TypeService;
import org.nuxeo.ecm.platform.comment.api.CommentableDocument;
import org.nuxeo.ecm.platform.comment.workflow.utils.CommentsConstants;
import org.nuxeo.ecm.platform.comment.workflow.utils.FollowTransitionUnrestricted;
import org.nuxeo.ecm.platform.query.nxql.NXQLQueryBuilder;
import org.nuxeo.ecm.platform.types.Type;
import org.nuxeo.ecm.platform.types.TypeManager;
import org.nuxeo.ecm.platform.types.TypeView;
import org.nuxeo.ecm.platform.types.adapter.TypeInfo;
import org.nuxeo.ecm.platform.ui.web.tag.fn.DocumentModelFunctions;
import org.nuxeo.ecm.platform.url.DocumentViewImpl;
import org.nuxeo.ecm.platform.url.api.DocumentView;
import org.nuxeo.ecm.platform.url.api.DocumentViewCodecManager;
import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
import org.nuxeo.ecm.rating.api.LikeService;
import org.nuxeo.ecm.social.workspace.adapters.SocialDocument;
import org.nuxeo.ecm.user.center.profile.UserProfileService;
import org.nuxeo.ecm.webengine.forms.FormData;
import org.nuxeo.ecm.webengine.model.Template;
import org.nuxeo.ecm.webengine.model.WebObject;
import org.nuxeo.ecm.webengine.model.impl.ModuleRoot;
import org.nuxeo.runtime.api.Framework;

/**
 * WebEngine handler for gadgets requests.
 *
 * @author <a href="mailto:ei@nuxeo.com">Eugen Ionica</a>
 */
/**
 * @author rlegall
 *
 */
@Path("/social")
@WebObject(type = "social")
@Produces("text/html; charset=UTF-8")
public class SocialWebEngineRoot extends ModuleRoot {

    private static final Log log = LogFactory.getLog(SocialWebEngineRoot.class);

    static AutomationService automationService;

    protected static final String AVATAR_PROPERTY = "userprofile:avatar";

    @GET
    public Object index(@Context HttpServletRequest request) throws Exception {
        FormData formData = new FormData(request);

        String lang = formData.getString("lang");
        setLanguage(lang);
        setDocumentLinkBuilder(formData.getString("documentLinkBuilder"));

        // get the arguments
        String ref = formData.getString("docRef");
        String queryText = formData.getString("queryText");
        int pageSize = getIntFromString(formData.getString("limit"));
        int page = getIntFromString(formData.getString("page"));
        return buildDocumentList(ref, pageSize, page, queryText).arg("renderFullHtml", true);
    }

    /**
     * Main method that return a html snipped with the list of documents
     * specified in request.
     * <p>
     * parameters:
     * <ul>
     * <li>docRef: parent document identifier (may be a path or an id)
     * <li>limit: number of documents per page</li>
     * <li>page: index of the current page</li>
     * <li>queryText (optional): search term</li>
     * </ul>
     */
    @POST
    @Path("documentList")
    public Object documentList(@Context HttpServletRequest request) throws Exception {
        FormData formData = new FormData(request);

        String lang = formData.getString("lang");
        setLanguage(lang);
        setDocumentLinkBuilder(formData.getString("documentLinkBuilder"));

        // get the arguments
        String ref = formData.getString("docRef");
        int pageSize = getIntFromString(formData.getString("limit"));
        int page = getIntFromString(formData.getString("page"));
        return buildDocumentList(ref, pageSize, page, null);
    }

    @GET
    @Path("documentListGet")
    public Object documentListGet(@Context HttpServletRequest request) throws Exception {
        FormData formData = new FormData(request);

        String lang = formData.getString("lang");
        setLanguage(lang);
        setDocumentLinkBuilder(formData.getString("documentLinkBuilder"));

        // get the arguments
        String ref = formData.getString("docRef");
        int pageSize = getIntFromString(formData.getString("limit"));
        int page = getIntFromString(formData.getString("page"));
        return buildDocumentList(ref, pageSize, page, null);
    }

    /**
     * @since 5.6
     */
    @POST
    @Path("search")
    public Object search(@Context HttpServletRequest request) throws Exception {
        FormData formData = new FormData(request);

        String lang = formData.getString("lang");
        setLanguage(lang);
        setDocumentLinkBuilder(formData.getString("documentLinkBuilder"));

        // get the arguments
        String ref = formData.getString("docRef");
        int pageSize = getIntFromString(formData.getString("limit"));
        int page = getIntFromString(formData.getString("page"));
        String queryText = formData.getString("queryText");
        return buildDocumentList(ref, pageSize, page, queryText);
    }

    Template buildDocumentList(String ref, int pageSize, int page, String queryText) throws Exception {
        DocumentRef docRef = getDocumentRef(ref);
        return buildDocumentList(docRef, pageSize, page, queryText);
    }

    Template buildDocumentList(DocumentRef docRef, int pageSize, int page, String queryText) throws Exception {
        boolean isSearch = queryText != null && queryText.trim().length() > 0;

        // build freemarker arguments map
        Map<String, Object> args = new HashMap<String, Object>();

        CoreSession session = ctx.getCoreSession();

        DocumentModel doc = session.getDocument(docRef);
        args.put("currentDoc", doc);

        PaginableDocumentModelList docs;
        DocumentModel socialWorkspace = null;
        if (isSearch) {
            docs = search(doc, pageSize, page, queryText);
            socialWorkspace = doc;
            args.put("queryText", queryText);
        } else {
            docs = getChildren(doc, pageSize, page);
            List<DocumentModel> ancestors = getAncestors(doc);

            DocumentModel parent = null;
            if (ancestors.size() > 1) {
                parent = ancestors.get(ancestors.size() - 2);
            }
            args.put("parent", parent);

            if (!ancestors.isEmpty() && isSocialWorkspace(ancestors.get(0))) {
                socialWorkspace = ancestors.remove(0);
            }
            args.put("ancestors", ancestors);

            args.put("queryText", "");
        }

        args.put("socialWorkspace", socialWorkspace);
        args.put("docs", docs);

        args.put("publishablePublic", getPublishableDocs(socialWorkspace, docs, true));
        args.put("publishablePrivate", getPublishableDocs(socialWorkspace, docs, false));
        args.put("removable", getDocsWithDeleteRight(docs));
        args.put("isPublicSocialWorkspace", socialWorkspace.getPropertyValue(SOCIAL_WORKSPACE_IS_PUBLIC_PROPERTY));
        args.put("collaboration_views", getCollaborationViews(docs));

        // add navigation arguments
        args.put("page", docs.getCurrentPageIndex());
        args.put("maxPage", docs.getNumberOfPages());
        args.put("nextPage", page < docs.getNumberOfPages() - 1 ? page + 1 : page);
        args.put("prevPage", page > 0 ? page - 1 : page);

        if (isSearch) {
            return getView("search_result").args(args);
        } else {
            return getView("children_list").args(args);
        }
    }

    @POST
    @Path("publishDocument")
    public Object publishDocument(@Context HttpServletRequest request) throws Exception {
        FormData formData = new FormData(request);
        CoreSession session = ctx.getCoreSession();
        DocumentRef docRef = getDocumentRef(formData.getString("targetRef"));

        DocumentModel target = session.getDocument(docRef);
        SocialDocument socialDocument = toSocialDocument(target);

        if (socialDocument == null) {
            throw new ClientException("Can't fetch social document.");
        }

        boolean isPublic = "true".equals(formData.getString("public"));
        if (isPublic) {
            socialDocument.makePublic();
        } else {
            socialDocument.restrictToMembers();
        }
        return documentList(request);
    }

    /**
     * Remove the document specified in request
     */
    @POST
    @Path("deleteDocument")
    public Object deleteDocument(@Context HttpServletRequest request) throws Exception {
        FormData formData = new FormData(request);
        String target = formData.getString("targetRef");
        DocumentRef docRef = getDocumentRef(target);
        CoreSession session = ctx.getCoreSession();

        if (session.getAllowedStateTransitions(docRef).contains(DELETE_TRANSITION)) {
            session.followTransition(docRef, DELETE_TRANSITION);
        } else {
            session.removeDocument(docRef);
        }
        return documentList(request);
    }

    /**
     * Return the html form for creation of a document
     */
    @GET
    @Path("createDocumentForm")
    public Object createDocumentForm(@QueryParam("docRef") String ref, @QueryParam("doctype") String docTypeId,
            @QueryParam("lang") String lang) throws Exception {
        setLanguage(lang);
        DocumentRef docRef = getDocumentRef(ref);
        CoreSession session = ctx.getCoreSession();
        DocumentModel currentDoc = session.getDocument(docRef);

        DocumentType coreType = TypeService.getSchemaManager().getDocumentType(docTypeId);

        return getView("create_document_form").arg("currentDoc", currentDoc)
                .arg("docType", getTypeService().getType(docTypeId)).arg("coreType", coreType);
    }

    /**
     * return a form to select a doctype the user wants to create
     */
    @GET
    @Path("selectDocTypeToCreate")
    public Object selectDocTypeToCreate(@QueryParam("docRef") String ref, @QueryParam("lang") String lang)
            throws ClientException {
        setLanguage(lang);
        DocumentRef docRef = getDocumentRef(ref);
        CoreSession session = ctx.getCoreSession();
        DocumentModel currentDoc = session.getDocument(docRef);
        TypeManager typeService = getTypeService();
        Map<String, List<Type>> types = typeService.getTypeMapForDocumentType(currentDoc.getType(), currentDoc);
        filterAllowedTypes(types);

        return getView("select_doc_type").arg("currentDoc", currentDoc).arg("docTypes", types)
                .arg("categories", types.keySet()).arg("lang", lang);
    }

    protected void filterAllowedTypes(Map<String, List<Type>> typesMap) {
        for (String key : typesMap.keySet()) {
            List<Type> toRemoved = new ArrayList<Type>();
            List<Type> types = typesMap.get(key);

            for (Type type : types) {
                if (!isAllowedLibraryType(type)) {
                    toRemoved.add(type);
                }
            }
            types.removeAll(toRemoved);
        }
    }

    protected boolean isAllowedLibraryType(Type type) {
        return !type.getId().equals("VEVENT");
    }

    protected static TypeManager getTypeService() throws ClientException {
        TypeManager typeService;
        try {
            typeService = Framework.getService(TypeManager.class);
        } catch (Exception e) {
            throw new ClientException(e.getMessage(), e);
        }
        if (typeService == null) {
            throw new ClientException("Can't fetch the typeService, please contact your administrator");
        }
        return typeService;
    }

    /**
     * Create the document given a post request where input item name is the
     * field name ("dc:title", ...) and "docRef" value is the target document
     * where to create the given document
     */
    @POST
    @Path("createDocument")
    public Object createDocument(@Context HttpServletRequest request) throws Exception {
        CoreSession session = ctx.getCoreSession();
        FormData formData = new FormData(request);
        String type = formData.getDocumentType();
        String title = formData.getDocumentTitle();
        DocumentRef docRef = getDocumentRef(formData.getString("docRef"));
        DocumentModel parent = session.getDocument(docRef);
        DocumentModel newDoc = session.createDocumentModel(parent.getPathAsString(), title, type);
        formData.fillDocument(newDoc);
        newDoc = session.createDocument(newDoc);
        session.save();
        if (newDoc.isFolder()) {
            return buildDocumentList(newDoc.getId(), 0, 0, null);
        } else {
            return buildDocumentList(parent.getId(), 0, 0, null);
        }
    }

    protected static PaginableDocumentModelList getChildren(DocumentModel doc, int pageSize, int page)
            throws Exception {
        CoreSession session = doc.getCoreSession();

        OperationContext ctx = new OperationContext(session);
        OperationChain chain = new OperationChain("getChildren");

        String query = "SELECT * FROM Document " + "WHERE ecm:mixinType != 'HiddenInNavigation' "
                + "AND ecm:isCheckedInVersion = 0 " + "AND ecm:currentLifeCycleState != 'deleted'"
                + "AND ecm:parentId = '" + doc.getId() + "' " + "ORDER BY dc:title";

        chain.add(DocumentPageProviderOperation.ID).set("query", query).set("page", page).set("pageSize",
                pageSize > 0 ? pageSize : 5);

        return (PaginableDocumentModelList) getAutomationService().run(ctx, chain);
    }

    protected static PaginableDocumentModelList search(DocumentModel doc, int pageSize, int page, String queryText)
            throws Exception {
        CoreSession session = doc.getCoreSession();

        OperationContext ctx = new OperationContext(session);
        OperationChain chain = new OperationChain("search");

        String escapedQueryText = NXQLQueryBuilder.prepareStringLiteral(queryText.trim(), false, true);

        String query = "SELECT * FROM Document " + "WHERE ecm:mixinType != 'HiddenInNavigation' "
                + "AND ecm:isCheckedInVersion = 0 " + "AND ecm:isProxy = 0 "
                + "AND ecm:currentLifeCycleState != 'deleted' " + "AND ecm:fulltext = '" + escapedQueryText + "%' "
                + "AND ecm:path STARTSWITH '" + doc.getPathAsString() + "' " + "ORDER BY dc:title";
        chain.add(DocumentPageProviderOperation.ID).set("query", query).set("page", page).set("pageSize",
                pageSize > 0 ? pageSize : 5);

        return (PaginableDocumentModelList) getAutomationService().run(ctx, chain);
    }

    /**
     * Computes a list with the ancestors of the document specified within the
     * SocialWorkspace.
     */
    protected static List<DocumentModel> getAncestors(DocumentModel doc) throws ClientException {
        List<DocumentModel> list = new ArrayList<DocumentModel>();
        CoreSession session = doc.getCoreSession();
        list.add(doc);
        while (doc != null && !isSocialWorkspace(doc)) {
            doc = session.getParentDocument(doc.getRef());
            list.add(0, doc);
        }
        return list;
    }

    protected static boolean isSocialWorkspace(DocumentModel doc) {
        return doc != null && doc.hasFacet(SOCIAL_WORKSPACE_FACET);
    }

    protected static DocumentRef getDocumentRef(String ref) {
        DocumentRef docRef;
        if (ref != null && ref.startsWith("/")) { // doc identified by absolute
            // path
            docRef = new PathRef(ref);
        } else { // // doc identified by id
            docRef = new IdRef(ref);
        }
        return docRef;
    }

    protected void setLanguage(String lang) {
        if (lang != null && lang.trim().length() > 0) {
            Locale locale = new Locale(lang);
            ctx.setLocale(locale);
        }
    }

    protected void setDocumentLinkBuilder(String documentLinkBuilder) {
        if (!StringUtils.isBlank(documentLinkBuilder)) {
            ctx.setProperty("documentLinkBuilder", documentLinkBuilder);
        }
    }

    protected static int getIntFromString(String value) {
        if (value != null) {
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException e) {
                // nothing to do => return 0
            }
        }
        return 0;
    }

    private static AutomationService getAutomationService() throws Exception {
        if (automationService == null) {
            automationService = Framework.getService(AutomationService.class);
        }
        return automationService;
    }

    protected static List<String> getPublishableDocs(DocumentModel socialWorkspace, DocumentModelList docs,
            boolean isPublic) throws ClientException {
        boolean isPublicSocialWorkspace = (Boolean) socialWorkspace
                .getPropertyValue(SOCIAL_WORKSPACE_IS_PUBLIC_PROPERTY);

        List<String> list = new ArrayList<String>();

        if (isPublicSocialWorkspace) {
            if (isPublic) { // is public publication
                for (DocumentModel doc : docs) {
                    SocialDocument socialDocument = toSocialDocument(doc);
                    if (socialDocument != null && socialDocument.isRestrictedToMembers()) {
                        list.add(doc.getId());
                    }
                }
            } else { // is private publication
                for (DocumentModel doc : docs) {
                    SocialDocument socialDocument = toSocialDocument(doc);
                    if (socialDocument != null && socialDocument.isPublic()) {
                        list.add(doc.getId());
                    }
                }
            }
        }
        return list;
    }

    protected static List<String> getDocsWithDeleteRight(DocumentModelList docs) throws ClientException {
        List<String> docsIdResult = new ArrayList<String>();
        if (docs.isEmpty()) {
            return docsIdResult;
        }

        CoreSession session = docs.get(0).getCoreSession();
        for (DocumentModel doc : docs) {
            if (session.hasPermission(doc.getRef(), REMOVE)
                    && session.hasPermission(session.getParentDocumentRef(doc.getRef()), REMOVE_CHILDREN)) {
                docsIdResult.add(doc.getId());
            }
        }

        return docsIdResult;
    }

    protected static Map<String, String> getCollaborationViews(DocumentModelList docs) throws ClientException {
        Map<String, String> viewResults = new HashMap<String, String>();
        if (docs.isEmpty()) {
            return viewResults;
        }

        TypeManager typeManager;
        try {
            typeManager = Framework.getService(TypeManager.class);
        } catch (Exception e) {
            throw new ClientException(e.getMessage(), e);
        }

        for (DocumentModel doc : docs) {
            Type type = typeManager.getType(doc.getType());
            TypeView view = type.getView("collaboration");
            if (view != null) {
                viewResults.put(doc.getId(), view.getValue());
            } else {
                viewResults.put(doc.getId(), type.getDefaultView());
            }
        }

        return viewResults;
    }

    public String getTranslatedLabel(String label) {
        String newLabel = I18NUtils.getMessageString("messages", label, null, ctx.getLocale());
        if (newLabel == null) {
            return label;
        }
        return newLabel;
    }

    /**
     * Returns the main blob for the given {@code doc}, {@code null} if there is
     * no main file available.
     */
    public Blob getAttachment(DocumentModel doc) throws ClientException {
        BlobHolder bh = doc.getAdapter(BlobHolder.class);
        return bh != null ? bh.getBlob() : null;
    }

    public String escape(String text) {
        text = StringEscapeUtils.escapeJavaScript(text);
        text = StringEscapeUtils.escapeHtml(text);
        text = StringEscapeUtils.escapeXml(text);
        return text;
    }

    public String escapePath(String text) {
        // text = text.replaceAll("\"", Matcher.quoteReplacement("\\\\\""));
        text = text.replaceAll("'", Matcher.quoteReplacement("\\\'"));
        try {
            text = URIUtil.encodePath(text);
        } catch (Exception e) {
            log.debug("failed to encode:" + text, e);
        }
        return text;
    }

    /**
     * Indicates if the current user has the right to Add Children to the
     * current Document
     *
     * @param docId the reference of the document
     * @return true if the current user has the right to Add Children to the
     *         current Document and false otherwise
     */
    public boolean hasAddChildrenRight(String docId) {
        try {
            IdRef docRef = new IdRef(docId);
            boolean hasPermission = ctx.getCoreSession().hasPermission(docRef, ADD_CHILDREN);
            return hasPermission;
        } catch (Exception e) {
            return false;
        }
    }

    public List<DocumentModel> getComments(DocumentModel doc) throws ClientException {
        // Load document comments if exist
        List<DocumentModel> comments = null;
        CommentableDocument commentableDoc = doc.getAdapter(CommentableDocument.class);
        if (commentableDoc != null) {
            comments = commentableDoc.getComments();
        }
        return comments;
    }

    @GET
    @Path("documentCommentList")
    public Object documentCommentList(@QueryParam("docRef") String ref) throws Exception {
        // build freemarker arguments map
        Map<String, Object> args = new HashMap<String, Object>();
        CoreSession session = ctx.getCoreSession();
        IdRef docRef = new IdRef(ref);
        DocumentModel doc = session.getDocument(docRef);
        args.put("doc", doc);
        return Response.ok(getView("document_comments_template").args(args)).header("docRef", ref).build();
    }

    public List<DocumentModel> getCommentChildren(DocumentModel doc, DocumentModel parent) throws ClientException {
        // Load all comment children of the document doc
        List<DocumentModel> comments = null;
        CommentableDocument commentableDoc = doc.getAdapter(CommentableDocument.class);
        if (commentableDoc != null) {
            comments = commentableDoc.getComments(parent);
        }
        return comments;
    }

    /**
     * Add comment to the related document and parent comment
     */
    @POST
    @Path("addComment")
    public Object addComment() throws ClientException {
        try {
            HttpServletRequest request = ctx.getRequest();
            CoreSession session = ctx.getCoreSession();
            // Create pending comment
            DocumentModel myComment = session.createDocumentModel("Comment");
            // Set comment properties
            myComment.setProperty("comment", "author", ctx.getPrincipal().getName());
            myComment.setProperty("comment", "text", request.getParameter("commentContent"));
            myComment.setProperty("comment", "creationDate", Calendar.getInstance());
            // Retrieve document to comment
            String docToCommentRef = request.getParameter("docToCommentRef");
            DocumentModel docToComment = session.getDocument(new IdRef(docToCommentRef));
            String commentParentRef = request.getParameter("commentParentRef");
            // Create comment
            CommentableDocument commentableDoc = null;
            if (docToComment != null) {
                commentableDoc = docToComment.getAdapter(CommentableDocument.class);
            }
            DocumentModel newComment;
            if (commentParentRef != null) {
                // if exists retrieve comment parent
                DocumentModel commentParent = session.getDocument(new IdRef(commentParentRef));
                newComment = commentableDoc.addComment(commentParent, myComment);
            } else {
                newComment = commentableDoc.addComment(myComment);
            }
            // automatically validate the comments
            if (CommentsConstants.COMMENT_LIFECYCLE.equals(newComment.getLifeCyclePolicy())) {
                new FollowTransitionUnrestricted(ctx.getCoreSession(), newComment.getRef(),
                        CommentsConstants.TRANSITION_TO_PUBLISHED_STATE).runUnrestricted();
            }
            // Return the new comment view
            Map<String, Object> args = new HashMap<String, Object>();
            args.put("doc", docToComment);
            args.put("comment", newComment);
            return Response.ok(getView("bricks/document_comments").args(args)).header("docRef", docToCommentRef)
                    .header("parentCommentRef", commentParentRef).build();
        } catch (Throwable t) {
            log.error("failed to add comment", t);
            throw ClientException.wrap(t);
        }
    }

    /**
     * Like/Unlike the related document
     */
    @POST
    @Path("docLike")
    public Object docLike(@FormParam("docRef") String docRef) throws Exception {
        // Get document
        CoreSession session = ctx.getCoreSession();
        DocumentModel docToLike = session.getDocument(new IdRef(docRef));
        // Get Like Services
        LikeService likeService = Framework.getLocalService(LikeService.class);
        // Get user name
        String userName = ctx.getPrincipal().getName();
        if (likeService.hasUserLiked(userName, docToLike)) {
            likeService.dislike(userName, docToLike);
        } else {
            likeService.like(userName, docToLike);
        }
        return Response.ok(getView("bricks/document_like").arg("doc", docToLike)).header("docRef", docRef).build();
    }

    public boolean hasUserLiked(DocumentModel doc) {
        LikeService likeService = Framework.getLocalService(LikeService.class);
        String userName = ctx.getPrincipal().getName();
        return likeService.hasUserLiked(userName, doc);
    }

    public long getLikesCount(DocumentModel doc) {
        LikeService likeService = Framework.getLocalService(LikeService.class);
        return likeService.getLikesCount(doc);
    }

    /**
     * Get the related user avatar to display in the UI comment
     */
    public String getAvatarURL(String commentUser) throws ClientException {
        String url = VirtualHostHelper.getContextPathProperty() + "/icons/missing_avatar.png";
        UserProfileService userProfileService = Framework.getLocalService(UserProfileService.class);
        DocumentModel userProfileDoc = userProfileService.getUserProfileDocument(commentUser, ctx.getCoreSession());
        if (userProfileDoc == null) {
            return url;
        }

        if (userProfileDoc.getPropertyValue(AVATAR_PROPERTY) != null) {
            url = VirtualHostHelper.getContextPathProperty() + "/"
                    + DocumentModelFunctions.fileUrl("downloadFile", userProfileDoc, AVATAR_PROPERTY, "avatar");
        }
        return url;
    }

    /**
     * Computes and returns the URL for the given {@code doc}, using the
     * document link builder sets in the
     * {@link org.nuxeo.ecm.webengine.model.WebContext}.
     */
    public String computeDocumentURL(DocumentModel doc) {
        DocumentLocation docLoc = new DocumentLocationImpl(doc.getRepositoryName(), new IdRef(doc.getId()));
        DocumentView docView = new DocumentViewImpl(docLoc, doc.getAdapter(TypeInfo.class).getDefaultView());
        DocumentViewCodecManager documentViewCodecManager = Framework
                .getLocalService(DocumentViewCodecManager.class);
        String documentLinkBuilder = (String) ctx.getProperty("documentLinkBuilder");
        String codecName = isBlank(documentLinkBuilder) ? documentViewCodecManager.getDefaultCodecName()
                : documentLinkBuilder;
        String baseURL = VirtualHostHelper.getBaseURL(ctx.getRequest());
        return documentViewCodecManager.getUrlFromDocumentView(codecName, docView, true, baseURL);
    }
}