ALF-9153 Start on the lucene-free Discussions Webscripts

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@29729 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2011-08-12 17:35:19 +00:00
parent b946119ac8
commit 8a5e56518c
4 changed files with 508 additions and 89 deletions

View File

@@ -0,0 +1,353 @@
/*
* Copyright (C) 2005-2011 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.discussion;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.query.PagingRequest;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.activities.ActivityService;
import org.alfresco.service.cmr.discussion.DiscussionService;
import org.alfresco.service.cmr.discussion.PostInfo;
import org.alfresco.service.cmr.discussion.TopicInfo;
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.PersonService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
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;
/**
* @author Nick Burch
* @since 4.0
*/
public abstract class AbstractDiscussionWebScript extends DeclarativeWebScript
{
public static final String DISCUSSIONS_SERVICE_ACTIVITY_APP_NAME = "discussions";
/**
* When no maximum or paging info is given, what should we use?
*/
protected static final int MAX_QUERY_ENTRY_COUNT = 1000;
private static Log logger = LogFactory.getLog(AbstractDiscussionWebScript.class);
// Injected services
protected NodeService nodeService;
protected SiteService siteService;
protected PersonService personService;
protected ActivityService activityService;
protected DiscussionService discussionService;
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
public void setDiscussionService(DiscussionService discussionService)
{
this.discussionService = discussionService;
}
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public void setActivityService(ActivityService activityService)
{
this.activityService = activityService;
}
protected String getOrNull(JSONObject json, String key) throws JSONException
{
if(json.has(key))
{
return json.getString(key);
}
return null;
}
/**
* Builds up a listing Paging request, based on the arguments
* specified in the URL
*/
protected PagingRequest buildPagingRequest(WebScriptRequest req)
{
int pageSize = MAX_QUERY_ENTRY_COUNT;
int startIndex = 0;
String pageSizeS = req.getParameter("pageSize");
if(pageSizeS != null)
{
try
{
pageSize = Integer.parseInt(pageSizeS);
}
catch(NumberFormatException e)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Paging size parameters invalid");
}
}
String startIndexS = req.getParameter("startIndex");
if(startIndexS != null)
{
try
{
startIndex = Integer.parseInt(startIndexS);
}
catch(NumberFormatException e)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Paging size parameters invalid");
}
}
PagingRequest paging = new PagingRequest( startIndex, pageSize );
paging.setRequestTotalCountMax( Math.max(10,startIndex+1) * pageSize );
return paging;
}
/**
* Generates an activity entry for the discussion item
*
* @param thing Either post or reply
* @param event One of created, updated, deleted
*/
protected void addActivityEntry(String thing, String event, TopicInfo topic,
PostInfo post, SiteInfo site, WebScriptRequest req, JSONObject json)
{
// What page is this for?
String page = req.getParameter("page");
if(page == null && json != null)
{
if(json.has("page"))
{
try
{
page = json.getString("page");
}
catch(JSONException e) {}
}
}
if(page == null)
{
// Default
page = "discussions-topicview";
}
// Get the title
String title = topic.getTitle();
if(post != null)
{
String postTitle = post.getTitle();
if(postTitle != null && postTitle.length() > 0)
{
title = postTitle;
}
}
try
{
JSONObject params = new JSONObject();
params.put("topicId", topic.getSystemName());
JSONObject activity = new JSONObject();
activity.put("title", title);
activity.put("page", page + "?topicId=" + topic.getSystemName());
activity.put("params", params);
activityService.postActivity(
"org.alfresco.discussions." + thing + "-" + event,
site.getShortName(),
DISCUSSIONS_SERVICE_ACTIVITY_APP_NAME,
activity.toString()
);
}
catch(Exception e)
{
// Warn, but carry on
logger.warn("Error adding discussions " + thing + " " + event + " to activities feed", e);
}
}
// TODO Is this needed?
protected Object buildPerson(String username)
{
if(username == null || username.length() == 0)
{
// Empty string needed
return "";
}
// Will turn into a Script Node needed of the person
NodeRef person = personService.getPerson(username);
return person;
}
// TODO Match JS
protected Map<String, Object> renderTopic(TopicInfo topic)
{
Map<String, Object> res = new HashMap<String, Object>();
res.put("topic", topic);
res.put("node", topic.getNodeRef());
res.put("name", topic.getSystemName());
res.put("title", topic.getTitle());
res.put("tags", topic.getTags());
// Both forms used for dates
res.put("createdOn", topic.getCreatedAt());
res.put("modifiedOn", topic.getModifiedAt());
res.put("created", topic.getCreatedAt());
res.put("modified", topic.getModifiedAt());
// FTL needs a script node of the people
res.put("createdBy", buildPerson(topic.getCreator()));
res.put("modifiedBY", buildPerson(topic.getModifier()));
// We want blank instead of null
for(String key : res.keySet())
{
if(res.get(key) == null)
{
res.put(key, "");
}
}
return res;
}
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req,
Status status, Cache cache)
{
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
if(templateVars == null)
{
String error = "No parameters supplied";
throw new WebScriptException(Status.STATUS_BAD_REQUEST, error);
}
// Parse the JSON, if supplied
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))
{
try
{
json = new JSONObject(new JSONTokener(req.getContent().getContent()));
}
catch(IOException io)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + io.getMessage());
}
catch(JSONException je)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid JSON: " + je.getMessage());
}
}
// Did they request it by node reference or site?
NodeRef nodeRef = null;
SiteInfo site = null;
TopicInfo topic = null;
PostInfo post = null;
if(templateVars.containsKey("site"))
{
// Site, and optionally topic
String siteName = templateVars.get("site");
site = siteService.getSite(siteName);
if(site == null)
{
String error = "Could not find site: " + siteName;
throw new WebScriptException(Status.STATUS_NOT_FOUND, error);
}
// Did they give a topic name too?
if(templateVars.containsKey("path"))
{
String name = templateVars.get("path");
topic = discussionService.getTopic(site.getShortName(), name);
}
}
else if(templateVars.containsKey("store_type") &&
templateVars.containsKey("store_id") &&
templateVars.containsKey("id"))
{
// NodeRef, normally Topic or Discussion
StoreRef store = new StoreRef(
templateVars.get("store_type"),
templateVars.get("store_id")
);
nodeRef = new NodeRef(store, templateVars.get("id"));
if(! nodeService.exists(nodeRef))
{
String error = "Could not find node: " + nodeRef;
throw new WebScriptException(Status.STATUS_NOT_FOUND, error);
}
// Try to build the appropriate object for it
Pair<TopicInfo,PostInfo> objects = discussionService.getForNodeRef(nodeRef);
if(objects != null)
{
topic = objects.getFirst();
post = objects.getSecond();
}
}
else
{
String error = "Unsupported template parameters found";
throw new WebScriptException(Status.STATUS_BAD_REQUEST, error);
}
// Have the real work done
return executeImpl(site, nodeRef, topic, post, req, json, status, cache);
}
protected abstract Map<String, Object> executeImpl(SiteInfo site,
NodeRef nodeRef, TopicInfo topic, PostInfo post,
WebScriptRequest req, JSONObject json, Status status, Cache cache);
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2005-2011 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.discussion;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.service.cmr.discussion.PostInfo;
import org.alfresco.service.cmr.discussion.TopicInfo;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.site.SiteInfo;
import org.json.JSONObject;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* This class is the controller for the forum post deleting forum-post.delete webscript.
*
* @author Nick Burch
* @since 4.0
*/
public class ForumPostDelete extends AbstractDiscussionWebScript
{
private static final String DELETED_POST_TEXT = "[[deleted]]";
@Override
protected Map<String, Object> executeImpl(SiteInfo site, NodeRef nodeRef,
TopicInfo topic, PostInfo post, WebScriptRequest req, JSONObject json,
Status status, Cache cache)
{
// Build the common model parts
Map<String, Object> model = new HashMap<String, Object>();
model.put("topic", topic);
model.put("post", post);
if(site != null)
{
model.put("siteId", site.getShortName());
model.put("site", site);
}
// Are we deleting a topic, or a post in it?
String message = null;
if(post != null)
{
message = doDeletePost(topic, post);
}
else if(topic != null)
{
message = doDeleteTopic(topic, site, req, json);
}
else
{
String error = "Node was of the wrong type, only Topic and Post are supported";
throw new WebScriptException(Status.STATUS_BAD_REQUEST, error);
}
// Finish the model and return
model.put("message", message);
return model;
}
private String doDeleteTopic(TopicInfo topic, SiteInfo site,
WebScriptRequest req, JSONObject json)
{
// Delete the topic, which removes all its posts too
discussionService.deleteTopic(topic);
// Add an activity entry for this if it's site based
if(site != null)
{
addActivityEntry("post", "deleted", topic, null, site, req, json);
}
// All done
return "Node " + topic.getNodeRef() + " deleted";
}
/**
* We can't just delete posts with replies attached to them,
* as that breaks the reply threading.
* For that reason, we mark deleted posts with a special
* text contents.
* TODO If a post has no replies, then delete it fully
*/
private String doDeletePost(TopicInfo topic, PostInfo post)
{
// Set the marker text and save
post.setTitle(DELETED_POST_TEXT);
post.setContents(DELETED_POST_TEXT);
discussionService.updatePost(post);
// Note - we don't add activity feed entries for deleted posts
// Only deleted whole topic qualify for that at the moment
return "Node " + post.getNodeRef() + " marked as removed";
}
}