Merged 5.1.N (5.1.1) to HEAD (5.2)

123426 amorarasu: Merged 5.0.N (5.0.4) to 5.1.N (5.1.1)
      123344 cturlica: Merged V4.2-BUG-FIX (4.2.7) to 5.0.N (5.0.4)
         123045 rmunteanu: Merged V4.2.6 (4.2.6) to V4.2-BUG-FIX (4.2.7)
            122858 rneamtu: MNT-15679: Delete comment updates the "Modifier" and "Modified date" properties of a file
               - Java-backed webscript CommentDelete added
               - Auditable behavior is disabled around deleting comment-node
               - Added unit test for case: CommentsApiTest.testDeleteCommentDoesNotChangeModifiedDate


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@123701 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2016-03-11 22:34:24 +00:00
parent b3c0766c82
commit 6da9f1acc7
6 changed files with 708 additions and 218 deletions

View File

@@ -0,0 +1,327 @@
/*
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.web.scripts.comments;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONStringer;
import org.json.JSONWriter;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* This class is the abstract controller for the comments web scripts (delete
* and post)
*
* @author Ramona Popa
* @since 4.2.6
*/
public abstract class AbstractCommentsWebScript extends DeclarativeWebScript
{
protected final static String COMMENTS_TOPIC_NAME = "Comments";
private static Log logger = LogFactory.getLog(CommentsPost.class);
protected final static String JSON_KEY_SITE = "site";
protected final static String JSON_KEY_SITE_ID = "siteid";
protected final static String JSON_KEY_ITEM_TITLE = "itemTitle";
protected final static String JSON_KEY_PAGE = "page";
protected final static String JSON_KEY_TITLE = "title";
protected final static String JSON_KEY_PAGE_PARAMS = "pageParams";
protected final static String JSON_KEY_NODEREF = "nodeRef";
protected final static String JSON_KEY_CONTENT = "content";
protected final static String COMMENT_CREATED_ACTIVITY = "org.alfresco.comments.comment-created";
protected final static String COMMENT_DELETED_ACTIVITY = "org.alfresco.comments.comment-deleted";
protected ServiceRegistry serviceRegistry;
protected NodeService nodeService;
protected ContentService contentService;
protected PersonService personService;
protected SiteService siteService;
protected PermissionService permissionService;
protected ActivityService activityService;
protected BehaviourFilter behaviourFilter;
protected static final String PARAM_MESSAGE = "message";
protected static final String PARAM_NODE = "node";
protected static final String PARAM_ITEM = "item";
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
this.serviceRegistry = serviceRegistry;
this.nodeService = serviceRegistry.getNodeService();
this.siteService = serviceRegistry.getSiteService();
this.contentService = serviceRegistry.getContentService();
this.personService = serviceRegistry.getPersonService();
this.permissionService = serviceRegistry.getPermissionService();
}
public void setBehaviourFilter(BehaviourFilter behaviourFilter)
{
this.behaviourFilter = behaviourFilter;
}
public void setActivityService(ActivityService activityService)
{
this.activityService = activityService;
}
/**
* returns the nodeRef from web script request
* @param req
* @return
*/
protected NodeRef parseRequestForNodeRef(WebScriptRequest req)
{
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
String storeType = templateVars.get("store_type");
String storeId = templateVars.get("store_id");
String nodeId = templateVars.get("id");
// create the NodeRef and ensure it is valid
StoreRef storeRef = new StoreRef(storeType, storeId);
return new NodeRef(storeRef, nodeId);
}
/**
* get the value from JSON for given key if exists
* @param json
* @param key
* @return
*/
protected String getOrNull(JSONObject json, String key)
{
if (json != null && json.containsKey(key))
{
return (String) json.get(key);
}
return null;
}
/**
* parse JSON from request
* @param req
* @return
*/
protected JSONObject parseJSON(WebScriptRequest req)
{
JSONObject json = null;
String contentType = req.getContentType();
if (contentType != null && contentType.indexOf(';') != -1)
{
contentType = contentType.substring(0, contentType.indexOf(';'));
}
if (MimetypeMap.MIMETYPE_JSON.equals(contentType))
{
JSONParser parser = new JSONParser();
try
{
json = (JSONObject) parser.parse(req.getContent().getContent());
}
catch (IOException io)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + io.getMessage());
}
catch (ParseException pe)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + pe.getMessage());
}
}
return json;
}
/**
* parse JSON for a given input string
* @param input
* @return
*/
protected JSONObject parseJSONFromString(String input)
{
JSONObject json = null;
JSONParser parser = new JSONParser();
try
{
json = (JSONObject) parser.parse(input);
}
catch (ParseException pe)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + pe.getMessage());
}
return json;
}
/**
* Post an activity entry for the comment added or deleted
*
* @param json
* - is not sent null with this activity type - only for delete
* @param req
* @param nodeRef
* @param activityType
*/
protected void postActivity(JSONObject json, WebScriptRequest req, NodeRef nodeRef, String activityType)
{
String jsonActivityData = "";
String siteId = "";
String page = "";
String title = "";
String strNodeRef = nodeRef.toString();
SiteInfo siteInfo = getSiteInfo(req, COMMENT_CREATED_ACTIVITY.equals(activityType));
// post an activity item, but only if we've got a site
if (siteInfo != null)
{
siteId = siteInfo.getShortName();
if (siteId == null || siteId.length() == 0)
{
return;
}
}
// json is not sent null with this activity type - only for delete
if (COMMENT_CREATED_ACTIVITY.equals(activityType))
{
try
{
org.json.JSONObject params = new org.json.JSONObject(getOrNull(json, JSON_KEY_PAGE_PARAMS));
String strParams = "";
Iterator<?> itr = params.keys();
while (itr.hasNext())
{
String strParam = itr.next().toString();
strParams += strParam + "=" + params.getString(strParam) + "&";
}
page = getOrNull(json, JSON_KEY_PAGE) + "?" + (strParams != "" ? strParams.substring(0, strParams.length() - 1) : "");
title = getOrNull(json, JSON_KEY_ITEM_TITLE);
}
catch (Exception e)
{
logger.warn("Error parsing JSON", e);
}
}
else
{
// COMMENT_DELETED_ACTIVITY
title = req.getParameter(JSON_KEY_ITEM_TITLE);
page = req.getParameter(JSON_KEY_PAGE) + "?" + JSON_KEY_NODEREF + "=" + strNodeRef;
}
try
{
JSONWriter jsonWriter = new JSONStringer().object();
jsonWriter.key(JSON_KEY_TITLE).value(title);
jsonWriter.key(JSON_KEY_PAGE).value(page);
jsonWriter.key(JSON_KEY_NODEREF).value(strNodeRef);
jsonActivityData = jsonWriter.endObject().toString();
activityService.postActivity(activityType, siteId, COMMENTS_TOPIC_NAME, jsonActivityData);
}
catch (Exception e)
{
logger.warn("Error adding comment to activities feed", e);
}
}
/**
* returns SiteInfo needed for post activity
* @param req
* @return
*/
protected SiteInfo getSiteInfo(WebScriptRequest req, boolean searchForSiteInJSON)
{
String siteName = req.getParameter(JSON_KEY_SITE);
if (siteName == null && searchForSiteInJSON )
{
JSONObject json = parseJSON(req);
if (json != null){
if (json.containsKey(JSON_KEY_SITE))
{
siteName = (String) json.get(JSON_KEY_SITE);
}
else if (json.containsKey(JSON_KEY_SITE_ID))
{
siteName = (String) json.get(JSON_KEY_SITE_ID);
}
}
}
if (siteName != null)
{
SiteInfo site = siteService.getSite(siteName);
return site;
}
return null;
}
/**
* Overrides DeclarativeWebScript with parse request for nodeRef
*/
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
{
// get requested node
NodeRef nodeRef = parseRequestForNodeRef(req);
// Have the real work done
return executeImpl(nodeRef, req, status, cache);
}
/**
*
* @param nodeRef
* @param req
* @param status
* @param cache
* @return
*/
protected abstract Map<String, Object> executeImpl(NodeRef nodeRef, WebScriptRequest req, Status status, Cache cache);
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.web.scripts.comments;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ForumModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONObject;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* This class is the controller for the comment.delete web script.
*
* @author Ramona Popa
* @since 4.2.6
*/
public class CommentDelete extends AbstractCommentsWebScript
{
private static Log logger = LogFactory.getLog(CommentDelete.class);
/**
* Overrides AbstractCommentsWebScript to delete comment
*/
@Override
protected Map<String, Object> executeImpl(NodeRef nodeRef, WebScriptRequest req, Status status, Cache cache)
{
String pageParams = req.getParameter(JSON_KEY_PAGE_PARAMS);
JSONObject jsonPageParams = parseJSONFromString(pageParams);
NodeRef parentNodeRef = new NodeRef((String) getOrNull(jsonPageParams, JSON_KEY_NODEREF));
this.behaviourFilter.disableBehaviour(parentNodeRef, ContentModel.ASPECT_AUDITABLE);
try
{
// delete node
deleteComment(nodeRef);
if (nodeService.exists(nodeRef))
{
// comment was not removed
status.setCode(Status.STATUS_INTERNAL_SERVER_ERROR, "Unable to delete node: " + nodeRef);
return null;
}
// generate response model for a comment node
Map<String, Object> model = generateModel(nodeRef);
// post an activity item - it is ok to send json as null since the
// infos that we need are as parameters on request
postActivity(null, req, parentNodeRef, COMMENT_DELETED_ACTIVITY);
status.setCode(Status.STATUS_OK);
return model;
}
finally
{
this.behaviourFilter.enableBehaviour(parentNodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
/**
* deletes comment node
*
* @param commentNodeRef
*/
private void deleteComment(NodeRef commentNodeRef)
{
QName nodeType = nodeService.getType(commentNodeRef);
if (!nodeType.equals(ForumModel.TYPE_POST))
{
throw new IllegalArgumentException("Node to delete is not a comment node.");
}
nodeService.deleteNode(commentNodeRef);
}
/**
* generates model for delete comment script
*
* @param commentNodeRef
* @return
*/
private Map<String, Object> generateModel(NodeRef commentNodeRef)
{
Map<String, Object> model = new HashMap<String, Object>(2, 1.0f);
model.put(PARAM_MESSAGE, "Node " + commentNodeRef + " deleted");
return model;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,47 +18,28 @@
*/
package org.alfresco.repo.web.scripts.comments;
import java.io.IOException;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.model.ForumModel;
import org.alfresco.repo.Client;
import org.alfresco.repo.Client.ClientType;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONStringer;
import org.json.JSONWriter;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
@@ -68,33 +49,14 @@ import org.springframework.extensions.webscripts.WebScriptRequest;
* @since 4.1.7.1
*/
public class CommentsPost extends DeclarativeWebScript {
private final static String COMMENTS_TOPIC_NAME = "Comments";
private static Log logger = LogFactory.getLog(CommentsPost.class);
private final static String JSON_KEY_SITE = "site";
private final static String JSON_KEY_ITEM_TITLE = "itemTitle";
private final static String JSON_KEY_PAGE = "page";
private final static String JSON_KEY_TITLE = "title";
private final static String JSON_KEY_PAGE_PARAMS = "pageParams";
private final static String JSON_KEY_NODEREF = "nodeRef";
private final static String JSON_KEY_CONTENT = "content";
private ServiceRegistry serviceRegistry;
private NodeService nodeService;
private ContentService contentService;
private PersonService personService;
private PermissionService permissionService;
private ActivityService activityService;
private BehaviourFilter behaviourFilter;
public class CommentsPost extends AbstractCommentsWebScript
{
/**
* Overrides AbstractCommentsWebScript to add comment
*/
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
protected Map<String, Object> executeImpl(NodeRef nodeRef, WebScriptRequest req, Status status, Cache cache)
{
// get requested node
NodeRef nodeRef = parseRequestForNodeRef(req);
// get json object from request
JSONObject json = parseJSON(req);
@@ -110,7 +72,7 @@ public class CommentsPost extends DeclarativeWebScript {
Map<String, Object> model = generateModel(nodeRef, commentNodeRef);
// post an activity item
postActivity(json, nodeRef);
postActivity(json, req, nodeRef, COMMENT_CREATED_ACTIVITY);
return model;
}
@@ -119,49 +81,14 @@ public class CommentsPost extends DeclarativeWebScript {
this.behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
}
}
private void postActivity(JSONObject json, NodeRef nodeRef)
{
// post an activity item, but only if we've got a site
if(json.containsKey(JSON_KEY_SITE) && json.containsKey(JSON_KEY_ITEM_TITLE) && json.containsKey(JSON_KEY_PAGE))
{
String siteId = getOrNull(json, JSON_KEY_SITE);
if (siteId != null && siteId != "")
{
try
{
org.json.JSONObject params = new org.json.JSONObject(getOrNull(json, JSON_KEY_PAGE_PARAMS));
String strParams = "";
Iterator<?> itr = params.keys();
while (itr.hasNext())
{
String strParam = itr.next().toString();
strParams += strParam + "=" + params.getString(strParam) + "&";
}
String page = getOrNull(json, JSON_KEY_PAGE) + "?" + (strParams != "" ? strParams.substring(0, strParams.length()-1) : "");
String title = getOrNull(json, JSON_KEY_ITEM_TITLE);
String strNodeRef = nodeRef.toString();
JSONWriter jsonWriter = new JSONStringer().object();
jsonWriter.key(JSON_KEY_TITLE).value(title);
jsonWriter.key(JSON_KEY_PAGE).value(page);
jsonWriter.key(JSON_KEY_NODEREF).value(strNodeRef);
String jsonActivityData = jsonWriter.endObject().toString();
activityService.postActivity("org.alfresco.comments.comment-created", siteId, "comments", jsonActivityData, Client.asType(ClientType.webclient));
}
catch(Exception e)
{
logger.warn("Error adding comment to activities feed", e);
}
}
}
}
/**
* add the comment from json to given nodeRef
*
* @param nodeRef
* @param json
* @return
*/
private NodeRef addComment(NodeRef nodeRef, JSONObject json)
{
// fetch the parent to add the node to
@@ -192,6 +119,12 @@ public class CommentsPost extends DeclarativeWebScript {
return commentNodeRef;
}
/**
* generates an comment item value
*
* @param commentNodeRef
* @return
*/
private Map<String, Object> generateItemValue(NodeRef commentNodeRef)
{
Map<String, Object> result = new HashMap<String, Object>(4, 1.0f);
@@ -222,52 +155,28 @@ public class CommentsPost extends DeclarativeWebScript {
return result;
}
/**
* generates the response model for adding a comment
*
* @param nodeRef
* @param commentNodeRef
* @return
*/
private Map<String, Object> generateModel(NodeRef nodeRef, NodeRef commentNodeRef)
{
Map<String, Object> model = new HashMap<String, Object>(2, 1.0f);
model.put("node", nodeRef);
model.put("item", generateItemValue(commentNodeRef));
model.put(PARAM_NODE, nodeRef);
model.put(PARAM_ITEM, generateItemValue(commentNodeRef));
return model;
}
private String getOrNull(JSONObject json, String key)
{
if (json.containsKey(key))
{
return (String)json.get(key);
}
return null;
}
private JSONObject parseJSON(WebScriptRequest req)
{
JSONObject json = null;
String contentType = req.getContentType();
if (contentType != null && contentType.indexOf(';') != -1)
{
contentType = contentType.substring(0, contentType.indexOf(';'));
}
if (MimetypeMap.MIMETYPE_JSON.equals(contentType))
{
JSONParser parser = new JSONParser();
try
{
json = (JSONObject)parser.parse(req.getContent().getContent());
}
catch (IOException io)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + io.getMessage());
}
catch(ParseException pe)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + pe.getMessage());
}
}
return json;
}
/**
*
* @param nodeRef
* @return
*/
private NodeRef getOrCreateCommentsFolder(NodeRef nodeRef)
{
NodeRef commentsFolder = getCommentsFolder(nodeRef);
@@ -279,6 +188,12 @@ public class CommentsPost extends DeclarativeWebScript {
return commentsFolder;
}
/**
* returns the nodeRef of the existing one
*
* @param nodeRef
* @return
*/
private NodeRef getCommentsFolder(NodeRef nodeRef)
{
if (nodeService.hasAspect(nodeRef, ForumModel.ASPECT_DISCUSSABLE))
@@ -293,24 +208,18 @@ public class CommentsPost extends DeclarativeWebScript {
return null;
}
}
private NodeRef parseRequestForNodeRef(WebScriptRequest req)
{
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
String storeType = templateVars.get("store_type");
String storeId = templateVars.get("store_id");
String nodeId = templateVars.get("id");
// create the NodeRef and ensure it is valid
StoreRef storeRef = new StoreRef(storeType, storeId);
return new NodeRef(storeRef, nodeId);
}
private String getUniqueChildName(String prefix)
{
return prefix + "-" + System.currentTimeMillis();
}
/**
* creates the comments folder if it does not exists
*
* @param nodeRef
* @return
*/
private NodeRef createCommentsFolder(final NodeRef nodeRef)
{
NodeRef commentsFolder = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<NodeRef>()
@@ -360,19 +269,4 @@ public class CommentsPost extends DeclarativeWebScript {
return commentsFolder;
}
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
this.nodeService = serviceRegistry.getNodeService();
this.contentService = serviceRegistry.getContentService();
this.personService = serviceRegistry.getPersonService();
this.permissionService = serviceRegistry.getPermissionService();
}
public void setBehaviourFilter(BehaviourFilter behaviourFilter) {
this.behaviourFilter = behaviourFilter;
}
public void setActivityService(ActivityService activityService) {
this.activityService = activityService;
}
}