mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-21 18:09:20 +00:00
Merged HEAD-QA to HEAD (4.2) (including moving test classes into separate folders)
51903 to 54309 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@54310 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.web.scripts.content.ContentStreamer;
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.core.HttpMethodSupport;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.ApiException;
|
||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
|
||||
import org.alfresco.rest.framework.resource.actions.ActionExecutor;
|
||||
import org.alfresco.rest.framework.resource.content.BinaryResource;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfo;
|
||||
import org.alfresco.rest.framework.resource.content.FileBinaryResource;
|
||||
import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.codehaus.jackson.JsonGenerationException;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Webscript that handles the request for and execution of a Resource
|
||||
*
|
||||
* 1) Finds a resource
|
||||
* 2) Extracts params
|
||||
* 3) Executes params on a resource
|
||||
* 4) Post processes the response to add embeds or projected relationship
|
||||
* 5) Renders the response
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
// TODO for requests that pass in input streams e.g. binary content for workflow, this is going to need a way to re-read the input stream a la
|
||||
// code in RepositoryContainer due to retrying transaction logic
|
||||
public abstract class AbstractResourceWebScript extends ApiWebScript implements HttpMethodSupport, ActionExecutor
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(AbstractResourceWebScript.class);
|
||||
|
||||
protected ResourceLocator locator;
|
||||
private HttpMethod httpMethod;
|
||||
private ParamsExtractor paramsExtractor;
|
||||
private ContentStreamer streamer;
|
||||
protected ResourceWebScriptHelper helper;
|
||||
protected TransactionService transactionService;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
final Map<String, Object> respons = new HashMap<String, Object>();
|
||||
final Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
|
||||
final ResourceWithMetadata resource = locator.locateResource(api,templateVars, httpMethod);
|
||||
final Params params = paramsExtractor.extractParams(resource.getMetaData(),req);
|
||||
final ActionExecutor executor = findExecutor(httpMethod, params, resource, req.getContentType());
|
||||
|
||||
//This execution usually takes place in a Retrying Transaction (see subclasses)
|
||||
executor.execute(resource, params, new ExecutionCallback()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Object result, ContentInfo contentInfo)
|
||||
{
|
||||
respons.put("toSerialize", result);
|
||||
respons.put("contentInfo", contentInfo);
|
||||
setSuccessResponseStatus(res);
|
||||
}
|
||||
});
|
||||
|
||||
//Outside the transaction.
|
||||
Object toSerialize = respons.get("toSerialize");
|
||||
ContentInfo contentInfo = (ContentInfo) respons.get("contentInfo");
|
||||
|
||||
setContentInfoOnResponse(res, contentInfo);
|
||||
|
||||
if (toSerialize != null)
|
||||
{
|
||||
if (toSerialize instanceof BinaryResource)
|
||||
{
|
||||
streamResponse(req, res, (BinaryResource) toSerialize);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderJsonResponse(res, toSerialize);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (ApiException apiException)
|
||||
{
|
||||
renderErrorResponse(resolveException(apiException), res);
|
||||
}
|
||||
catch (WebScriptException webException)
|
||||
{
|
||||
renderErrorResponse(resolveException(webException), res);
|
||||
}
|
||||
catch (RuntimeException runtimeException)
|
||||
{
|
||||
renderErrorResponse(resolveException(runtimeException), res);
|
||||
}
|
||||
}
|
||||
|
||||
protected void streamResponse(final WebScriptRequest req, final WebScriptResponse res, BinaryResource resource) throws IOException
|
||||
{
|
||||
if (resource instanceof FileBinaryResource)
|
||||
{
|
||||
FileBinaryResource fileResource = (FileBinaryResource) resource;
|
||||
streamer.streamContent(req, res, fileResource.getFile(), null, false, null, null);
|
||||
}
|
||||
else if (resource instanceof NodeBinaryResource)
|
||||
{
|
||||
NodeBinaryResource nodeResource = (NodeBinaryResource) resource;
|
||||
streamer.streamContent(req, res, nodeResource.getNodeRef(), nodeResource.getPropertyQName(), false, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the result of an execution.
|
||||
*
|
||||
* @param res WebScriptResponse
|
||||
* @param respons result of an execution
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void renderJsonResponse(final WebScriptResponse res, final Object toSerialize)
|
||||
throws IOException
|
||||
{
|
||||
jsonHelper.withWriter(res.getOutputStream(), new JacksonHelper.Writer()
|
||||
{
|
||||
@Override
|
||||
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
|
||||
throws JsonGenerationException, JsonMappingException, IOException
|
||||
{
|
||||
objectMapper.writeValue(generator, toSerialize);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The response status must be set before the response is written by Jackson (which will by default close and commit the response).
|
||||
* In a r/w txn, web script buffered responses ensure that it doesn't really matter but for r/o txns this is important.
|
||||
* @param res
|
||||
*/
|
||||
protected void setSuccessResponseStatus(final WebScriptResponse res)
|
||||
{
|
||||
// default for GET, HEAD, OPTIONS, PUT, TRACE
|
||||
res.setStatus(Status.STATUS_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the action executor to execute actions on.
|
||||
* @param httpMethod - the http method
|
||||
* @param params Params
|
||||
* @param resource
|
||||
* @param contentType Request content type
|
||||
* @return ActionExecutor the action executor
|
||||
*/
|
||||
public ActionExecutor findExecutor(HttpMethod httpMethod, Params params, ResourceWithMetadata resource, String contentType)
|
||||
{
|
||||
//Ignore all params and return this
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
public void setLocator(ResourceLocator locator)
|
||||
{
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
public void setHttpMethod(HttpMethod httpMethod)
|
||||
{
|
||||
this.httpMethod = httpMethod;
|
||||
}
|
||||
|
||||
public void setParamsExtractor(ParamsExtractor paramsExtractor)
|
||||
{
|
||||
this.paramsExtractor = paramsExtractor;
|
||||
}
|
||||
|
||||
public void setHelper(ResourceWebScriptHelper helper)
|
||||
{
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
public HttpMethod getHttpMethod()
|
||||
{
|
||||
return this.httpMethod;
|
||||
}
|
||||
|
||||
public void setStreamer(ContentStreamer streamer)
|
||||
{
|
||||
this.streamer = streamer;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.core.exceptions.DefaultExceptionResolver;
|
||||
import org.alfresco.rest.framework.core.exceptions.ErrorResponse;
|
||||
import org.alfresco.rest.framework.core.exceptions.ExceptionResolver;
|
||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
|
||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper.Writer;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfo;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfoImpl;
|
||||
import org.codehaus.jackson.JsonGenerationException;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.springframework.extensions.webscripts.AbstractWebScript;
|
||||
import org.springframework.extensions.webscripts.Cache;
|
||||
import org.springframework.extensions.webscripts.Description.RequiredCache;
|
||||
import org.springframework.extensions.webscripts.Format;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse;
|
||||
|
||||
/**
|
||||
* Entry point for API webscript. Supports version/scope as well
|
||||
* as discovery.
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public abstract class ApiWebScript extends AbstractWebScript
|
||||
{
|
||||
protected JacksonHelper jsonHelper;
|
||||
ExceptionResolver<Exception> defaultResolver = new DefaultExceptionResolver();
|
||||
ExceptionResolver<Exception> resolver;
|
||||
|
||||
public final static String UTF8 = "UTF-8";
|
||||
public final static Cache CACHE_NEVER = new Cache(new RequiredCache()
|
||||
{
|
||||
@Override
|
||||
public boolean getNeverCache()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getIsPublic()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getMustRevalidate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
});
|
||||
final static ContentInfo DEFAULT_JSON_CONTENT = new ContentInfoImpl(Format.JSON.mimetype(),UTF8, 0, null);
|
||||
|
||||
@Override
|
||||
public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException
|
||||
{
|
||||
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
|
||||
Api api = determineApi(templateVars);
|
||||
execute(api, req, res);
|
||||
}
|
||||
|
||||
private Api determineApi(Map<String, String> templateVars)
|
||||
{
|
||||
String apiScope = templateVars.get("apiScope");
|
||||
String apiVersion = templateVars.get("apiVersion");
|
||||
String apiName = templateVars.get("apiName");
|
||||
return Api.valueOf(apiName,apiScope,apiVersion);
|
||||
}
|
||||
|
||||
protected ErrorResponse resolveException(Exception ex)
|
||||
{
|
||||
ErrorResponse error = resolver.resolveException(ex);
|
||||
if (error == null)
|
||||
{
|
||||
error = defaultResolver.resolveException(ex);
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException;
|
||||
|
||||
/**
|
||||
* Renders a JSON error response
|
||||
* @param errorResponse The error
|
||||
* @param res web script response
|
||||
* @throws IOException
|
||||
*/
|
||||
public void renderErrorResponse(final ErrorResponse errorResponse, final WebScriptResponse res) throws IOException {
|
||||
|
||||
errorResponse.setDescriptionURL("http://developer.alfresco.com/ErrorsExplained.html#"+errorResponse.getErrorKey());
|
||||
|
||||
setContentInfoOnResponse(res, DEFAULT_JSON_CONTENT);
|
||||
|
||||
// Status must be set before the response is written by Jackson (which will by default close and commit the response).
|
||||
// In a r/w txn, web script buffered responses ensure that it doesn't really matter but for r/o txns this is important.
|
||||
res.setStatus(errorResponse.getStatusCode());
|
||||
|
||||
jsonHelper.withWriter(res.getOutputStream(), new Writer()
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
|
||||
throws JsonGenerationException, JsonMappingException, IOException
|
||||
{
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("error", errorResponse);
|
||||
objectMapper.writeValue(generator, obj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the response headers with any information we know about the content
|
||||
* @param res WebScriptResponse
|
||||
* @param contentInfo Content Information
|
||||
*/
|
||||
protected void setContentInfoOnResponse(WebScriptResponse res, ContentInfo contentInfo)
|
||||
{
|
||||
if (contentInfo != null)
|
||||
{
|
||||
//Set content info on the response
|
||||
res.setContentType(contentInfo.getMimeType());
|
||||
res.setContentEncoding(contentInfo.getEncoding());
|
||||
if (res instanceof WebScriptServletResponse)
|
||||
{
|
||||
WebScriptServletResponse servletResponse = (WebScriptServletResponse) res;
|
||||
if (contentInfo.getLength() > 0)
|
||||
{
|
||||
if (contentInfo.getLength()>0 && contentInfo.getLength() < Integer.MAX_VALUE)
|
||||
{
|
||||
servletResponse.getHttpServletResponse().setContentLength((int)contentInfo.getLength());
|
||||
}
|
||||
}
|
||||
if (contentInfo.getLocale() != null)
|
||||
{
|
||||
servletResponse.getHttpServletResponse().setLocale(contentInfo.getLocale());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setResolver(ExceptionResolver<Exception> resolver)
|
||||
{
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
public void setJsonHelper(JacksonHelper jsonHelper)
|
||||
{
|
||||
this.jsonHelper = jsonHelper;
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import org.alfresco.rest.framework.core.HttpMethodSupport;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
|
||||
/*
|
||||
* Extracts parameters from the HTTP request.
|
||||
*
|
||||
*/
|
||||
public interface ParamsExtractor extends HttpMethodSupport
|
||||
{
|
||||
public Params extractParams(ResourceMetadata resourceMeta,WebScriptRequest req);
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Handles the HTTP DELETE for a Resource
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class ResourceWebScriptDelete extends AbstractResourceWebScript implements ParamsExtractor
|
||||
{
|
||||
|
||||
public ResourceWebScriptDelete()
|
||||
{
|
||||
super();
|
||||
setHttpMethod(HttpMethod.DELETE);
|
||||
setParamsExtractor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Params extractParams(ResourceMetadata resourceMeta, WebScriptRequest req)
|
||||
{
|
||||
String entityId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.ENTITY_ID);
|
||||
String relationshipId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_ID);
|
||||
|
||||
switch (resourceMeta.getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (StringUtils.isBlank(entityId))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("DELETE is executed against the instance URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Params.valueOf(entityId, relationshipId);
|
||||
|
||||
}
|
||||
case RELATIONSHIP:
|
||||
|
||||
if (StringUtils.isBlank(relationshipId))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("DELETE is executed against the instance URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
return Params.valueOf(entityId, relationshipId);
|
||||
}
|
||||
case PROPERTY:
|
||||
final String resourceName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE);
|
||||
if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(resourceName))
|
||||
{
|
||||
return Params.valueOf(entityId, null, null, null, resourceName, null, null);
|
||||
}
|
||||
//Fall through to unsupported.
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("DELETE not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the action on the resource
|
||||
* @param resource
|
||||
* @param params parameters to use
|
||||
* @return anObject the result of the execute
|
||||
*/
|
||||
private Object executeInternal(ResourceWithMetadata resource, Params params)
|
||||
{
|
||||
switch (resource.getMetaData().getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (resource.getMetaData().isDeleted(EntityResourceAction.Delete.class))
|
||||
{
|
||||
throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
EntityResourceAction.Delete entityDeleter = (EntityResourceAction.Delete) resource.getResource();
|
||||
entityDeleter.delete(params.getEntityId(), params);
|
||||
//Don't pass anything to the callback - its just successful
|
||||
return null;
|
||||
case RELATIONSHIP:
|
||||
if (resource.getMetaData().isDeleted(RelationshipResourceAction.Delete.class))
|
||||
{
|
||||
throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
RelationshipResourceAction.Delete relationDeleter = (RelationshipResourceAction.Delete) resource.getResource();
|
||||
relationDeleter.delete(params.getEntityId(), params.getRelationshipId(), params);
|
||||
//Don't pass anything to the callback - its just successful
|
||||
return null;
|
||||
case PROPERTY:
|
||||
if (resource.getMetaData().isDeleted(BinaryResourceAction.Delete.class))
|
||||
{
|
||||
throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
BinaryResourceAction.Delete binDeleter = (BinaryResourceAction.Delete) resource.getResource();
|
||||
binDeleter.deleteProperty(params.getEntityId(), params);
|
||||
//Don't pass anything to the callback - its just successful
|
||||
return null;
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("DELETE not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final ResourceWithMetadata resource, final Params params, final ExecutionCallback executionCallback)
|
||||
{
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
@Override
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
executeInternal(resource, params); //ignore return result
|
||||
executionCallback.onSuccess(null, DEFAULT_JSON_CONTENT);
|
||||
return null;
|
||||
}
|
||||
}, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setSuccessResponseStatus(WebScriptResponse res)
|
||||
{
|
||||
res.setStatus(Status.STATUS_NO_CONTENT);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,209 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.rest.framework.core.ResourceInspector;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction.Read;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction.ReadById;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||
import org.alfresco.rest.framework.resource.content.BinaryResource;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params.RecognizedParams;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Handles the HTTP Get for a Resource
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class ResourceWebScriptGet extends AbstractResourceWebScript implements ParamsExtractor
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(ResourceWebScriptGet.class);
|
||||
|
||||
public ResourceWebScriptGet()
|
||||
{
|
||||
super();
|
||||
setHttpMethod(HttpMethod.GET);
|
||||
setParamsExtractor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Params extractParams(ResourceMetadata resourceMeta, WebScriptRequest req)
|
||||
{
|
||||
final String entityId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.ENTITY_ID);
|
||||
final String relationshipId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_ID);
|
||||
final RecognizedParams params = ResourceWebScriptHelper.getRecognizedParams(req);
|
||||
|
||||
switch (resourceMeta.getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (StringUtils.isNotBlank(entityId))
|
||||
{
|
||||
return Params.valueOf(params, entityId, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Params.valueOf(params, null, null);// collection resource
|
||||
}
|
||||
case RELATIONSHIP:
|
||||
if (StringUtils.isNotBlank(relationshipId))
|
||||
{
|
||||
return Params.valueOf(params, entityId, relationshipId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Params.valueOf(params, entityId, null); //relationship collection resource
|
||||
}
|
||||
case PROPERTY:
|
||||
final String resourceName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE);
|
||||
if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(resourceName))
|
||||
{
|
||||
return Params.valueOf(entityId, null, null, null, resourceName, params, null);
|
||||
}
|
||||
//Fall through to unsupported.
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("GET not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the action on the resource
|
||||
* @param resource
|
||||
* @param params parameters to use
|
||||
* @return anObject the result of the execute
|
||||
*/
|
||||
private Object executeInternal(ResourceWithMetadata resource, Params params)
|
||||
{
|
||||
|
||||
switch (resource.getMetaData().getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (StringUtils.isBlank(params.getEntityId()))
|
||||
{
|
||||
//Get the collection
|
||||
if (EntityResourceAction.Read.class.isAssignableFrom(resource.getResource().getClass()))
|
||||
{
|
||||
if (resource.getMetaData().isDeleted(EntityResourceAction.Read.class))
|
||||
{
|
||||
throw new DeletedResourceException("(GET) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
EntityResourceAction.Read<?> getter = (Read<?>) resource.getResource();
|
||||
CollectionWithPagingInfo<?> resources = getter.readAll(params);
|
||||
return resources;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedResourceOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (EntityResourceAction.ReadById.class.isAssignableFrom(resource.getResource().getClass()))
|
||||
{
|
||||
if (resource.getMetaData().isDeleted(EntityResourceAction.ReadById.class))
|
||||
{
|
||||
throw new DeletedResourceException("(GET by id) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
EntityResourceAction.ReadById<?> entityGetter = (ReadById<?>) resource.getResource();
|
||||
Object result = entityGetter.readById(params.getEntityId(), params);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedResourceOperationException();
|
||||
}
|
||||
}
|
||||
case RELATIONSHIP:
|
||||
if(StringUtils.isNotBlank(params.getRelationshipId()))
|
||||
{
|
||||
if (RelationshipResourceAction.ReadById.class.isAssignableFrom(resource.getResource().getClass()))
|
||||
{
|
||||
if (resource.getMetaData().isDeleted(RelationshipResourceAction.ReadById.class))
|
||||
{
|
||||
throw new DeletedResourceException("(GET by id) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
RelationshipResourceAction.ReadById<?> relationGetter = (RelationshipResourceAction.ReadById<?>) resource.getResource();
|
||||
Object result = relationGetter.readById(params.getEntityId(), params.getRelationshipId(), params);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedResourceOperationException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (resource.getMetaData().isDeleted(RelationshipResourceAction.Read.class))
|
||||
{
|
||||
throw new DeletedResourceException("(GET) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
RelationshipResourceAction.Read<?> relationGetter = (RelationshipResourceAction.Read<?>) resource.getResource();
|
||||
CollectionWithPagingInfo<?> relations = relationGetter.readAll(params.getEntityId(),params);
|
||||
return relations;
|
||||
}
|
||||
|
||||
case PROPERTY:
|
||||
if (StringUtils.isNotBlank(params.getEntityId()))
|
||||
{
|
||||
if (BinaryResourceAction.Read.class.isAssignableFrom(resource.getResource().getClass()))
|
||||
{
|
||||
if (resource.getMetaData().isDeleted(BinaryResourceAction.Read.class))
|
||||
{
|
||||
throw new DeletedResourceException("(GET) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
BinaryResourceAction.Read getter = (BinaryResourceAction.Read) resource.getResource();
|
||||
BinaryResource prop = getter.readProperty(params.getEntityId(), params);
|
||||
return prop;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedResourceOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnsupportedResourceOperationException();
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("GET not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final ResourceWithMetadata resource, final Params params, final ExecutionCallback executionCallback)
|
||||
{
|
||||
final String entityCollectionName = ResourceInspector.findEntityCollectionNameName(resource.getMetaData());
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
@Override
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Object result = executeInternal(resource, params);
|
||||
if (result instanceof BinaryResource)
|
||||
{
|
||||
executionCallback.onSuccess(result, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
executionCallback.onSuccess(helper.postProcessResponse(resource.getMetaData().getApi(), entityCollectionName, params, result), DEFAULT_JSON_CONTENT);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}, true, true); //Read only
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,661 @@
|
||||
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.core.ResourceInspector;
|
||||
import org.alfresco.rest.framework.core.ResourceInspectorUtil;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.ApiException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.NotFoundException;
|
||||
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.rest.framework.jacksonextensions.BeanPropertiesFilter;
|
||||
import org.alfresco.rest.framework.jacksonextensions.ExecutionResult;
|
||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
|
||||
import org.alfresco.rest.framework.resource.actions.ActionExecutor;
|
||||
import org.alfresco.rest.framework.resource.actions.ActionExecutor.ExecutionCallback;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.InvalidSelectException;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params.RecognizedParams;
|
||||
import org.alfresco.rest.framework.resource.parameters.SortColumn;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.InvalidQueryException;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.Query;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.QueryImpl;
|
||||
import org.alfresco.rest.framework.resource.parameters.where.WhereCompiler;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.antlr.runtime.RecognitionException;
|
||||
import org.antlr.runtime.tree.CommonErrorNode;
|
||||
import org.antlr.runtime.tree.CommonTree;
|
||||
import org.antlr.runtime.tree.RewriteCardinalityException;
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Helps a Webscript with various tasks
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class ResourceWebScriptHelper
|
||||
{
|
||||
|
||||
private static Log logger = LogFactory.getLog(ResourceWebScriptHelper.class);
|
||||
public static final String PARAM_RELATIONS = "relations";
|
||||
public static final String PARAM_FILTER_PROPS = "properties";
|
||||
public static final String PARAM_PAGING_SKIP = "skipCount";
|
||||
public static final String PARAM_PAGING_MAX = "maxItems";
|
||||
public static final String PARAM_SORT = "sort";
|
||||
public static final String PARAM_WHERE = "where";
|
||||
public static final String PARAM_SELECT = "select";
|
||||
public static final List<String> KNOWN_PARAMS = Arrays.asList(PARAM_RELATIONS,PARAM_FILTER_PROPS,PARAM_PAGING_SKIP,PARAM_PAGING_MAX, PARAM_SORT, PARAM_WHERE, PARAM_SELECT);
|
||||
|
||||
private ResourceLocator locator;
|
||||
|
||||
private ActionExecutor executor;
|
||||
|
||||
/**
|
||||
* Takes the web request and looks for a "filter" parameter Parses the
|
||||
* parameter and produces a list of bean properties to use as a filter A
|
||||
* SimpleBeanPropertyFilter it returned that uses the properties If no
|
||||
* filter param is set then a default BeanFilter is returned that will never
|
||||
* filter properties (ie. Returns all bean properties).
|
||||
*
|
||||
* @param req
|
||||
* @return BeanPropertyFilter - if no parameter then returns a new
|
||||
* ReturnAllBeanProperties class
|
||||
*/
|
||||
public static BeanPropertiesFilter getFilter(String filterParams)
|
||||
{
|
||||
if (filterParams != null)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(filterParams, ",");
|
||||
Set<String> filteredProperties = new HashSet<String>(st.countTokens());
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
filteredProperties.add(st.nextToken());
|
||||
}
|
||||
logger.debug("Filtering using the following properties: " + filteredProperties);
|
||||
BeanPropertiesFilter filter = new BeanPropertiesFilter(filteredProperties);
|
||||
return filter;
|
||||
}
|
||||
return BeanPropertiesFilter.ALLOW_ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the web request and looks for a "relations" parameter Parses the
|
||||
* parameter and produces a list of bean properties to use as a filter A
|
||||
* SimpleBeanPropertiesFilter it returned that uses the properties If no
|
||||
* filter param is set then a default BeanFilter is returned that will never
|
||||
* filter properties (ie. Returns all bean properties).
|
||||
*
|
||||
* @param req
|
||||
* @return BeanPropertiesFilter - if no parameter then returns a new
|
||||
* ReturnAllBeanProperties class
|
||||
*/
|
||||
public static Map<String, BeanPropertiesFilter> getRelationFilter(String filterParams)
|
||||
{
|
||||
if (filterParams != null)
|
||||
{
|
||||
// Split by a comma when not in a bracket
|
||||
String[] relations = filterParams.split(",(?![^()]*+\\))");
|
||||
Map<String, BeanPropertiesFilter> filterMap = new HashMap<String, BeanPropertiesFilter>(relations.length);
|
||||
|
||||
for (String relation : relations)
|
||||
{
|
||||
int bracketLocation = relation.indexOf("(");
|
||||
if (bracketLocation != -1)
|
||||
{
|
||||
// We have properties
|
||||
String relationKey = relation.substring(0, bracketLocation);
|
||||
String props = relation.substring(bracketLocation + 1, relation.length() - 1);
|
||||
filterMap.put(relationKey, getFilter(props));
|
||||
}
|
||||
else
|
||||
{
|
||||
// no properties so just get the String
|
||||
filterMap.put(relation, getFilter(null));
|
||||
}
|
||||
}
|
||||
return filterMap;
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the "select" parameter and turns it into a List<String> property names
|
||||
* @param selectParam
|
||||
* @return List<String> bean property names potentially using JSON Pointer syntax
|
||||
*/
|
||||
public static List<String> getSelectClause(String selectParam) throws InvalidArgumentException
|
||||
{
|
||||
if (selectParam == null) return Collections.emptyList();
|
||||
|
||||
try {
|
||||
CommonTree selectedPropsTree = WhereCompiler.compileSelectClause(selectParam);
|
||||
if (selectedPropsTree instanceof CommonErrorNode)
|
||||
{
|
||||
logger.debug("Error parsing the SELECT clause "+selectedPropsTree);
|
||||
throw new InvalidSelectException(selectedPropsTree);
|
||||
}
|
||||
if (selectedPropsTree.getChildCount() == 0 && !selectedPropsTree.getText().isEmpty())
|
||||
{
|
||||
return Arrays.asList(selectedPropsTree.getText());
|
||||
}
|
||||
List<Tree> children = selectedPropsTree.getChildren();
|
||||
if (children!= null && !children.isEmpty())
|
||||
{
|
||||
List<String> properties = new ArrayList<String>(children.size());
|
||||
for (Tree child : children) {
|
||||
properties.add(child.getText());
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
} catch (RewriteCardinalityException re) { //Catch any error so it doesn't get thrown up the stack
|
||||
logger.debug("Unhandled Error parsing the SELECT clause: "+re);
|
||||
} catch (RecognitionException e) {
|
||||
logger.debug("Error parsing the SELECT clause: "+selectParam);
|
||||
}
|
||||
//Default to throw out an invalid query
|
||||
throw new InvalidSelectException(selectParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the "where" parameter and turns it into a Java Object that can be used for querying
|
||||
* @param whereParam
|
||||
* @return Query a parsed version of the where clause, represented in Java
|
||||
*/
|
||||
public static Query getWhereClause(String whereParam) throws InvalidQueryException
|
||||
{
|
||||
if (whereParam == null) return QueryImpl.EMPTY;
|
||||
|
||||
try {
|
||||
CommonTree whereTree = WhereCompiler.compileWhereClause(whereParam);
|
||||
if (whereTree instanceof CommonErrorNode)
|
||||
{
|
||||
logger.debug("Error parsing the WHERE clause "+whereTree);
|
||||
throw new InvalidQueryException(whereTree);
|
||||
}
|
||||
return new QueryImpl(whereTree);
|
||||
} catch (RewriteCardinalityException re) { //Catch any error so it doesn't get thrown up the stack
|
||||
logger.info("Unhandled Error parsing the WHERE clause: "+re);
|
||||
} catch (RecognitionException e) {
|
||||
whereParam += ", "+WhereCompiler.resolveMessage(e);
|
||||
logger.info("Error parsing the WHERE clause: "+whereParam);
|
||||
}
|
||||
//Default to throw out an invalid query
|
||||
throw new InvalidQueryException(whereParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the Sort parameter as a String and parses it into a List of SortColumn objects.
|
||||
* The format is a comma seperated list of "columnName sortDirection",
|
||||
* e.g. "name DESC, age ASC". It is not case sensitive and the sort direction is optional
|
||||
* It default to sort ASCENDING.
|
||||
* @param sortParams - String passed in on the request
|
||||
* @return List<SortColumn> - the sort columns or an empty list if the params were invalid.
|
||||
*/
|
||||
public static List<SortColumn> getSort(String sortParams)
|
||||
{
|
||||
if (sortParams != null)
|
||||
{
|
||||
StringTokenizer st = new StringTokenizer(sortParams, ",");
|
||||
List<SortColumn> sortedColumns = new ArrayList<SortColumn>(st.countTokens());
|
||||
while (st.hasMoreTokens())
|
||||
{
|
||||
String token = st.nextToken();
|
||||
StringTokenizer columnDesc = new StringTokenizer(token, " ");
|
||||
if (columnDesc.countTokens() <= 2)
|
||||
{
|
||||
String columnName = columnDesc.nextToken();
|
||||
String sortOrder = SortColumn.ASCENDING;
|
||||
if (columnDesc.hasMoreTokens())
|
||||
{
|
||||
String sortDef = columnDesc.nextToken().toUpperCase();
|
||||
if (SortColumn.ASCENDING.equals(sortDef) || SortColumn.DESCENDING.equals(sortDef))
|
||||
{
|
||||
sortOrder = sortDef;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("Invalid sort order definition ("+sortDef+"). Valid values are "+SortColumn.ASCENDING+" or "+SortColumn.DESCENDING+".");
|
||||
}
|
||||
}
|
||||
sortedColumns.add(new SortColumn(columnName, SortColumn.ASCENDING.equals(sortOrder)));
|
||||
}
|
||||
// filteredProperties.add();
|
||||
}
|
||||
// logger.debug("Filtering using the following properties: " + filteredProperties);
|
||||
// BeanPropertiesFilter filter = new BeanPropertiesFilter(filteredProperties);
|
||||
return sortedColumns;
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the body contents from the request
|
||||
*
|
||||
* @param req the request
|
||||
* @param jsonHelper Jackson Helper
|
||||
* @param requiredType the type to return
|
||||
* @return the Object in the required type
|
||||
*/
|
||||
public static <T> T extractJsonContent(WebScriptRequest req, JacksonHelper jsonHelper, Class<T> requiredType)
|
||||
{
|
||||
Reader reader;
|
||||
try
|
||||
{
|
||||
reader = req.getContent().getReader();
|
||||
return jsonHelper.construct(reader, requiredType);
|
||||
}
|
||||
catch (JsonMappingException e)
|
||||
{
|
||||
logger.warn("Could not read content from HTTP request body.", e);
|
||||
throw new InvalidArgumentException("Could not read content from HTTP request body.");
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ApiException("Could not read content from HTTP request body.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the body contents from the request as a List, the JSON can be an array or just a single value without the [] symbols
|
||||
*
|
||||
* @param req the request
|
||||
* @param jsonHelper Jackson Helper
|
||||
* @param requiredType the type to return (without the List param)
|
||||
* @return A List of "Object" as the required type
|
||||
*/
|
||||
public static <T> List<T> extractJsonContentAsList(WebScriptRequest req, JacksonHelper jsonHelper, Class<T> requiredType)
|
||||
{
|
||||
Reader reader;
|
||||
try
|
||||
{
|
||||
reader = req.getContent().getReader();
|
||||
return jsonHelper.constructList(reader, requiredType);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ApiException("Could not read content from HTTP request body.", e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the id of theObj to the uniqueId. Attempts to find a set method and
|
||||
* invoke it. If it fails it just swallows the exceptions and doesn't throw
|
||||
* them further.
|
||||
*
|
||||
* @param theObj
|
||||
* @param uniqueId
|
||||
*/
|
||||
public static void setUniqueId(Object theObj, String uniqueId)
|
||||
{
|
||||
Method annotatedMethod = ResourceInspector.findUniqueIdMethod(theObj.getClass());
|
||||
if (annotatedMethod != null)
|
||||
{
|
||||
PropertyDescriptor pDesc = BeanUtils.findPropertyForMethod(annotatedMethod);
|
||||
if (pDesc != null)
|
||||
{
|
||||
Method writeMethod = pDesc.getWriteMethod();
|
||||
if (writeMethod != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
writeMethod.invoke(theObj, uniqueId);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Unique id set for property: " + pDesc.getName());
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException error)
|
||||
{
|
||||
logger.warn("Invocation error", error);
|
||||
}
|
||||
catch (IllegalAccessException error)
|
||||
{
|
||||
logger.warn("IllegalAccessException", error);
|
||||
}
|
||||
catch (InvocationTargetException error)
|
||||
{
|
||||
logger.warn("InvocationTargetException", error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.warn("No setter method for property: " + pDesc.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Renders the response result
|
||||
// *
|
||||
// * @param response
|
||||
// * @param result
|
||||
// */
|
||||
// public static void renderResponseDep(Map<String, Object> response, Object result)
|
||||
// {
|
||||
//
|
||||
// if (result == null) { return; }
|
||||
//
|
||||
// if (result instanceof Collection)
|
||||
// {
|
||||
// response.put("list", result);
|
||||
// }
|
||||
// else if (result instanceof CollectionWithPagingInfo)
|
||||
// {
|
||||
// CollectionWithPagingInfo<?> col = (CollectionWithPagingInfo<?>) result;
|
||||
// if (col.getCollection() !=null && !col.getCollection().isEmpty())
|
||||
// {
|
||||
// response.put("list", col);
|
||||
// }
|
||||
// }
|
||||
// else if (result instanceof Pair<?,?>)
|
||||
// {
|
||||
// Pair<?,?> aPair = (Pair<?, ?>) result;
|
||||
// response.put("entry", aPair.getFirst());
|
||||
// response.put("relations", aPair.getSecond());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// response.put("entry", result);
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Looks at the object passed in and recursively expands any @EmbeddedEntityResource annotations or related relationship.
|
||||
* @EmbeddedEntityResource is expanded by calling the ReadById method for this entity.
|
||||
*
|
||||
* Either returns a ExecutionResult object or a CollectionWithPagingInfo containing a collection of ExecutionResult objects.
|
||||
*
|
||||
* @param objectToWrap
|
||||
* @param result
|
||||
* @return Object - Either ExecutionResult or CollectionWithPagingInfo<ExecutionResult>
|
||||
*/
|
||||
public Object postProcessResponse(Api api, String entityCollectionName, Params params, Object objectToWrap)
|
||||
{
|
||||
PropertyCheck.mandatory(this, null, params);
|
||||
if (objectToWrap == null ) return null;
|
||||
if (objectToWrap instanceof CollectionWithPagingInfo<?>)
|
||||
{
|
||||
CollectionWithPagingInfo<?> collectionToWrap = (CollectionWithPagingInfo<?>) objectToWrap;
|
||||
if (!collectionToWrap.getCollection().isEmpty())
|
||||
{
|
||||
Collection<Object> resultCollection = new ArrayList(collectionToWrap.getCollection().size());
|
||||
for (Object obj : collectionToWrap.getCollection())
|
||||
{
|
||||
resultCollection.add(postProcessResponse(api,entityCollectionName,params,obj));
|
||||
}
|
||||
return CollectionWithPagingInfo.asPaged(collectionToWrap.getPaging(), resultCollection, collectionToWrap.hasMoreItems(), collectionToWrap.getTotalItems());
|
||||
}
|
||||
else
|
||||
{ //It is empty so just return it for rendering.
|
||||
return objectToWrap;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (BeanUtils.isSimpleProperty(objectToWrap.getClass()) || objectToWrap instanceof Collection)
|
||||
{
|
||||
//Simple property or Collection that can't be embedded so just return it.
|
||||
return objectToWrap;
|
||||
}
|
||||
final ExecutionResult execRes = new ExecutionResult(objectToWrap, params.getFilter());
|
||||
|
||||
Map<String,Pair<String,Method>> embeddded = ResourceInspector.findEmbeddedResources(objectToWrap.getClass());
|
||||
if (embeddded != null && !embeddded.isEmpty())
|
||||
{
|
||||
Map<String, Object> results = executeEmbeddedResources(api, params,objectToWrap, embeddded);
|
||||
execRes.addEmbedded(results);
|
||||
}
|
||||
|
||||
if (params.getRelationsFilter() != null && !params.getRelationsFilter().isEmpty())
|
||||
{
|
||||
Map<String, ResourceWithMetadata> relationshipResources = locator.locateRelationResource(api,entityCollectionName, params.getRelationsFilter().keySet(), HttpMethod.GET);
|
||||
String uniqueEntityId = ResourceInspector.findUniqueId(objectToWrap);
|
||||
Map<String,Object> relatedResources = executeRelatedResources(api,params.getRelationsFilter(), relationshipResources, uniqueEntityId);
|
||||
execRes.addRelated(relatedResources);
|
||||
}
|
||||
|
||||
return execRes;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the embedded Resources and executes them. The results are added to list of embedded results used by
|
||||
* the ExecutionResult object.
|
||||
*
|
||||
* @param relatedResources
|
||||
* @param execRes
|
||||
* @param uniqueEntityId
|
||||
*/
|
||||
private Map<String, Object> executeEmbeddedResources(Api api, Params params, Object objectToWrap, Map<String, Pair<String, Method>> embeddded)
|
||||
{
|
||||
final Map<String,Object> results = new HashMap<String,Object>(embeddded.size());
|
||||
for (Entry<String, Pair<String,Method>> embeddedEntry : embeddded.entrySet())
|
||||
{
|
||||
ResourceWithMetadata res = locator.locateEntityResource(api,embeddedEntry.getValue().getFirst(), HttpMethod.GET);
|
||||
if (res != null)
|
||||
{
|
||||
Object id = ResourceInspectorUtil.invokeMethod(embeddedEntry.getValue().getSecond(), objectToWrap);
|
||||
if (id != null)
|
||||
{
|
||||
Object execEmbeddedResult = executeRelatedResource(api, params.getRelationsFilter(), String.valueOf(id), embeddedEntry.getKey(), res);
|
||||
if (execEmbeddedResult != null)
|
||||
{
|
||||
if (execEmbeddedResult instanceof ExecutionResult)
|
||||
{
|
||||
((ExecutionResult) execEmbeddedResult).setAnEmbeddedEntity(true);
|
||||
}
|
||||
results.put(embeddedEntry.getKey(), execEmbeddedResult);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Call to embedded id for null value,
|
||||
logger.warn("Cannot embed resource with path "+embeddedEntry.getKey()+". No unique id because the method annotated with @EmbeddedEntityResource returned null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the related Resources and executed them. The results are added to list of embedded results used by
|
||||
* the ExecutionResult object.
|
||||
*
|
||||
* @param relatedResources
|
||||
* @param execRes
|
||||
* @param uniqueEntityId
|
||||
*/
|
||||
private Map<String,Object> executeRelatedResources(final Api api, Map<String, BeanPropertiesFilter> filters,
|
||||
Map<String, ResourceWithMetadata> relatedResources,
|
||||
String uniqueEntityId)
|
||||
{
|
||||
final Map<String,Object> results = new HashMap<String,Object>(relatedResources.size());
|
||||
for (final Entry<String, ResourceWithMetadata> relation : relatedResources.entrySet())
|
||||
{
|
||||
Object execResult = executeRelatedResource(api, filters, uniqueEntityId, relation.getKey(), relation.getValue());
|
||||
if (execResult != null)
|
||||
{
|
||||
results.put(relation.getKey(), execResult);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single related Resource. The results are added to list of embedded results used by
|
||||
* the ExecutionResult object.
|
||||
*
|
||||
* @param relatedResources
|
||||
* @param uniqueEntityId
|
||||
*/
|
||||
private Object executeRelatedResource(final Api api, final Map<String, BeanPropertiesFilter> filters,
|
||||
final String uniqueEntityId, final String resourceKey, final ResourceWithMetadata resource)
|
||||
{
|
||||
try
|
||||
{
|
||||
BeanPropertiesFilter paramFilter = null;
|
||||
final Object[] resultOfExecution = new Object[1];
|
||||
|
||||
if (filters!=null)
|
||||
{
|
||||
paramFilter = filters.get(resourceKey);
|
||||
}
|
||||
final Params executionParams = Params.valueOf(paramFilter, uniqueEntityId);
|
||||
executor.execute(resource, executionParams, new ExecutionCallback()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Object result, ContentInfo contentInfo)
|
||||
{
|
||||
resultOfExecution[0] = result;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return resultOfExecution[0];
|
||||
}
|
||||
catch(NotFoundException e)
|
||||
{
|
||||
// ignore, cannot access the object so don't embed it
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignored error, cannot access the object so can't embed it ", e);
|
||||
}
|
||||
}
|
||||
catch(PermissionDeniedException e)
|
||||
{
|
||||
// ignore, cannot access the object so don't embed it
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Ignored error, cannot access the object so can't embed it ", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null; //default
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all request parameters that aren't already know about (eg. not paging or filter params)
|
||||
* and returns them for use.
|
||||
*
|
||||
* @param req - the WebScriptRequest object
|
||||
* @return Map<String, String[]> the request parameters
|
||||
*/
|
||||
public static Map<String, String[]> getRequestParameters(WebScriptRequest req)
|
||||
{
|
||||
if (req!= null)
|
||||
{
|
||||
String[] paramNames = req.getParameterNames();
|
||||
if (paramNames!= null)
|
||||
{
|
||||
Map<String, String[]> requestParameteters = new HashMap<String, String[]>(paramNames.length);
|
||||
|
||||
for (int i = 0; i < paramNames.length; i++)
|
||||
{
|
||||
String paramName = paramNames[i];
|
||||
if (!KNOWN_PARAMS.contains(paramName))
|
||||
{
|
||||
String[] vals = req.getParameterValues(paramName);
|
||||
requestParameteters.put(paramName, vals);
|
||||
}
|
||||
}
|
||||
return requestParameteters;
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the formal set of params that any rest service could potentially have passed in as request params
|
||||
* @param req WebScriptRequest
|
||||
* @return RecognizedParams a POJO containing the params for use with the Params objects
|
||||
*/
|
||||
public static RecognizedParams getRecognizedParams(WebScriptRequest req)
|
||||
{
|
||||
Paging paging = findPaging(req);
|
||||
List<SortColumn> sorting = getSort(req.getParameter(ResourceWebScriptHelper.PARAM_SORT));
|
||||
Map<String, BeanPropertiesFilter> relationFilter = getRelationFilter(req.getParameter(ResourceWebScriptHelper.PARAM_RELATIONS));
|
||||
BeanPropertiesFilter filter = getFilter(req.getParameter(ResourceWebScriptHelper.PARAM_FILTER_PROPS));
|
||||
Query whereQuery = getWhereClause(req.getParameter(ResourceWebScriptHelper.PARAM_WHERE));
|
||||
Map<String, String[]> requestParams = getRequestParameters(req);
|
||||
List<String> theSelect = getSelectClause(req.getParameter(ResourceWebScriptHelper.PARAM_SELECT));
|
||||
return new RecognizedParams(requestParams, paging, filter, relationFilter, theSelect, whereQuery, sorting);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find paging setings based on the request parameters.
|
||||
*
|
||||
* @param req
|
||||
* @return Paging
|
||||
*/
|
||||
public static Paging findPaging(WebScriptRequest req)
|
||||
{
|
||||
int skipped = Paging.DEFAULT_SKIP_COUNT;
|
||||
int max = Paging.DEFAULT_MAX_ITEMS;
|
||||
String skip = req.getParameter(PARAM_PAGING_SKIP);
|
||||
String maxItems = req.getParameter(PARAM_PAGING_MAX);
|
||||
|
||||
try
|
||||
{
|
||||
if (skip != null) { skipped = Integer.parseInt(skip);}
|
||||
if (maxItems != null) { max = Integer.parseInt(maxItems); }
|
||||
if (max < 0 || skipped < 0)
|
||||
{
|
||||
throw new InvalidArgumentException("Negative values not supported.");
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException error)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Invalid paging params skip: " + skip + ",maxItems:" + maxItems);
|
||||
}
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
return Paging.valueOf(skipped, max);
|
||||
}
|
||||
|
||||
public void setLocator(ResourceLocator locator)
|
||||
{
|
||||
this.locator = locator;
|
||||
}
|
||||
|
||||
public void setExecutor(ActionExecutor executor)
|
||||
{
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,203 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.rest.framework.core.ResourceInspector;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata;
|
||||
import org.alfresco.rest.framework.core.ResourceParameter;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params.RecognizedParams;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Handles the HTTP POST for a Resource, equivalent to CRUD Create
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class ResourceWebScriptPost extends AbstractResourceWebScript implements ParamsExtractor
|
||||
{
|
||||
|
||||
public ResourceWebScriptPost()
|
||||
{
|
||||
super();
|
||||
setHttpMethod(HttpMethod.POST);
|
||||
setParamsExtractor(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Params extractParams(ResourceMetadata resourceMeta, WebScriptRequest req)
|
||||
{
|
||||
final RecognizedParams params = ResourceWebScriptHelper.getRecognizedParams(req);
|
||||
|
||||
switch (resourceMeta.getType())
|
||||
{
|
||||
case ENTITY:
|
||||
|
||||
String entityIdCheck = req.getServiceMatch().getTemplateVars().get(ResourceLocator.ENTITY_ID);
|
||||
if (StringUtils.isNotBlank(entityIdCheck))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("POST is executed against the collection URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
Object postedObj = extractObjFromJson(resourceMeta, req);
|
||||
return Params.valueOf(null, params, postedObj);
|
||||
}
|
||||
case RELATIONSHIP:
|
||||
String entityId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.ENTITY_ID);
|
||||
String relationshipId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_ID);
|
||||
if (StringUtils.isNotBlank(relationshipId))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("POST is executed against the collection URL");
|
||||
}
|
||||
else
|
||||
{
|
||||
Object postedRel = extractObjFromJson(resourceMeta, req);
|
||||
return Params.valueOf(entityId,params,postedRel);
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("POST not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the @WebApiParam has been used and set allowMultiple to false then this will get a single entry. It
|
||||
* should error if an array is passed in.
|
||||
* @param resourceMeta
|
||||
* @param req
|
||||
* @return Either an object
|
||||
*/
|
||||
private Object extractObjFromJson(ResourceMetadata resourceMeta, WebScriptRequest req)
|
||||
{
|
||||
List<ResourceParameter> params = resourceMeta.getParameters(HttpMethod.POST);
|
||||
Class<?> objType = resourceMeta.getObjectType(HttpMethod.POST);
|
||||
|
||||
if (!params.isEmpty())
|
||||
{
|
||||
for (ResourceParameter resourceParameter : params)
|
||||
{
|
||||
if (ResourceParameter.KIND.HTTP_BODY_OBJECT.equals(resourceParameter.getParamType()) && !resourceParameter.isAllowMultiple())
|
||||
{
|
||||
// Only allow 1 value.
|
||||
try
|
||||
{
|
||||
Object content = ResourceWebScriptHelper.extractJsonContent(req,jsonHelper, objType);
|
||||
return Arrays.asList(content);
|
||||
}
|
||||
catch (InvalidArgumentException iae)
|
||||
{
|
||||
if (iae.getMessage().contains("START_ARRAY") && iae.getMessage().contains("line: 1, column: 1"))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("Only 1 entity is supported in the HTTP request body");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw iae;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ResourceWebScriptHelper.extractJsonContentAsList(req, jsonHelper, objType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the action on the resource
|
||||
* @param resource
|
||||
* @param params parameters to use
|
||||
* @return anObject the result of the execute
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object executeInternal(ResourceWithMetadata resource, Params params)
|
||||
{
|
||||
switch (resource.getMetaData().getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (resource.getMetaData().isDeleted(EntityResourceAction.Create.class))
|
||||
{
|
||||
throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
EntityResourceAction.Create<Object> creator = (EntityResourceAction.Create<Object>) resource.getResource();
|
||||
List<Object> created = creator.create((List<Object>) params.getPassedIn(), params);
|
||||
if (created !=null && created.size() == 1)
|
||||
{
|
||||
//return just one object instead of an array
|
||||
return created.get(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return wrapWithCollectionWithPaging(created);
|
||||
}
|
||||
case RELATIONSHIP:
|
||||
if (resource.getMetaData().isDeleted(RelationshipResourceAction.Create.class))
|
||||
{
|
||||
throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
RelationshipResourceAction.Create<Object> createRelation = (RelationshipResourceAction.Create) resource.getResource();
|
||||
List<Object> createdRel = createRelation.create(params.getEntityId(), (List<Object>) params.getPassedIn(), params);
|
||||
if (createdRel !=null && createdRel.size() == 1)
|
||||
{
|
||||
//return just one object instead of an array
|
||||
return createdRel.get(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return wrapWithCollectionWithPaging(createdRel);
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("POST not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
private Object wrapWithCollectionWithPaging(List<Object> created)
|
||||
{
|
||||
if (created !=null && created.size() > 1)
|
||||
{
|
||||
return CollectionWithPagingInfo.asPagedCollection(created.toArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(final ResourceWithMetadata resource, final Params params, final ExecutionCallback executionCallback)
|
||||
{
|
||||
final String entityCollectionName = ResourceInspector.findEntityCollectionNameName(resource.getMetaData());
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Object result = executeInternal(resource, params);
|
||||
executionCallback.onSuccess(helper.postProcessResponse(resource.getMetaData().getApi(), entityCollectionName, params, result), DEFAULT_JSON_CONTENT);
|
||||
return null;
|
||||
}
|
||||
}, false, true);
|
||||
}
|
||||
@Override
|
||||
protected void setSuccessResponseStatus(WebScriptResponse res)
|
||||
{
|
||||
res.setStatus(Status.STATUS_CREATED);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,193 @@
|
||||
package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.rest.framework.core.ResourceInspector;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.DeletedResourceException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
|
||||
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
|
||||
import org.alfresco.rest.framework.resource.content.ContentInfoImpl;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params;
|
||||
import org.alfresco.rest.framework.resource.parameters.Params.RecognizedParams;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
/**
|
||||
* Handles the HTTP PUT for a Resource, equivalent to CRUD Update
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class ResourceWebScriptPut extends AbstractResourceWebScript implements ParamsExtractor
|
||||
{
|
||||
|
||||
private static Log logger = LogFactory.getLog(ResourceWebScriptPut.class);
|
||||
|
||||
public ResourceWebScriptPut()
|
||||
{
|
||||
super();
|
||||
setHttpMethod(HttpMethod.PUT);
|
||||
setParamsExtractor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Params extractParams(ResourceMetadata resourceMeta, WebScriptRequest req)
|
||||
{
|
||||
|
||||
final String relationshipId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_ID);
|
||||
final String entityId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.ENTITY_ID);
|
||||
final RecognizedParams params = ResourceWebScriptHelper.getRecognizedParams(req);
|
||||
|
||||
switch (resourceMeta.getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (StringUtils.isBlank(entityId))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("PUT is executed against the instance URL");
|
||||
} else
|
||||
{
|
||||
|
||||
Object putEnt = ResourceWebScriptHelper.extractJsonContent(req, jsonHelper, resourceMeta.getObjectType(HttpMethod.PUT));
|
||||
return Params.valueOf(entityId,params,putEnt);
|
||||
}
|
||||
case RELATIONSHIP:
|
||||
if (StringUtils.isBlank(relationshipId))
|
||||
{
|
||||
throw new UnsupportedResourceOperationException("PUT is executed against the instance URL");
|
||||
} else
|
||||
{
|
||||
Object putRel = ResourceWebScriptHelper.extractJsonContent(req, jsonHelper, resourceMeta.getObjectType(HttpMethod.PUT));
|
||||
ResourceWebScriptHelper.setUniqueId(putRel,relationshipId);
|
||||
return Params.valueOf(entityId, params, putRel);
|
||||
}
|
||||
case PROPERTY:
|
||||
final String resourceName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE);
|
||||
if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(resourceName))
|
||||
{
|
||||
return Params.valueOf(entityId, null, null, getStream(req), resourceName, params, getContentInfo(req));
|
||||
}
|
||||
//Fall through to unsupported.
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("PUT not supported for this request.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the basic content info from the request.
|
||||
* @param req
|
||||
* @return BasicContentInfo
|
||||
* @throws IOException
|
||||
*/
|
||||
private BasicContentInfo getContentInfo(WebScriptRequest req) {
|
||||
|
||||
String encoding = "UTF-8";
|
||||
String contentType = MimetypeMap.MIMETYPE_BINARY;
|
||||
|
||||
if (StringUtils.isNotEmpty(req.getContentType()))
|
||||
{
|
||||
MediaType media = MediaType.parseMediaType(req.getContentType());
|
||||
contentType = media.getType()+'/'+media.getSubtype();
|
||||
if (media.getCharSet() != null)
|
||||
{
|
||||
encoding = media.getCharSet().toString();
|
||||
}
|
||||
}
|
||||
|
||||
return new ContentInfoImpl(contentType, encoding, -1, Locale.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input stream for the request
|
||||
* @param req
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private InputStream getStream(WebScriptRequest req)
|
||||
{
|
||||
if (req instanceof WebScriptServletRequest)
|
||||
{
|
||||
WebScriptServletRequest servletRequest = (WebScriptServletRequest) req;
|
||||
try
|
||||
{
|
||||
return servletRequest.getHttpServletRequest().getInputStream();
|
||||
}
|
||||
catch (IOException error)
|
||||
{
|
||||
logger.warn("Failed to get the input stream.", error);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the action on the resource
|
||||
* @param resource
|
||||
* @param params parameters to use
|
||||
* @return anObject the result of the execute
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object executeInternal(ResourceWithMetadata resource, Params params)
|
||||
{
|
||||
switch (resource.getMetaData().getType())
|
||||
{
|
||||
case ENTITY:
|
||||
if (resource.getMetaData().isDeleted(EntityResourceAction.Update.class))
|
||||
{
|
||||
throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
EntityResourceAction.Update<Object> updateEnt = (EntityResourceAction.Update<Object>) resource.getResource();
|
||||
Object result = updateEnt.update(params.getEntityId(), params.getPassedIn(), params);
|
||||
return result;
|
||||
case RELATIONSHIP:
|
||||
if (resource.getMetaData().isDeleted(RelationshipResourceAction.Update.class))
|
||||
{
|
||||
throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
RelationshipResourceAction.Update<Object> relationUpdater = (RelationshipResourceAction.Update<Object>) resource.getResource();
|
||||
Object relResult = relationUpdater.update(params.getEntityId(), params.getPassedIn(), params);
|
||||
return relResult;
|
||||
case PROPERTY:
|
||||
if (resource.getMetaData().isDeleted(BinaryResourceAction.Update.class))
|
||||
{
|
||||
throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId());
|
||||
}
|
||||
BinaryResourceAction.Update binUpdater = (BinaryResourceAction.Update) resource.getResource();
|
||||
binUpdater.update(params.getEntityId(),params.getContentInfo(), params.getStream(), params);
|
||||
//Don't pass anything to the callback - its just successful
|
||||
return null;
|
||||
default:
|
||||
throw new UnsupportedResourceOperationException("PUT not supported for Actions");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(final ResourceWithMetadata resource, final Params params, final ExecutionCallback executionCallback)
|
||||
{
|
||||
final String entityCollectionName = ResourceInspector.findEntityCollectionNameName(resource.getMetaData());
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
@Override
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Object result = executeInternal(resource, params);
|
||||
executionCallback.onSuccess(helper.postProcessResponse(resource.getMetaData().getApi(), entityCollectionName, params, result), DEFAULT_JSON_CONTENT);
|
||||
return null;
|
||||
}
|
||||
}, false, true);
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package org.alfresco.rest.framework.webscripts.metadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.core.ResourceDictionary;
|
||||
import org.alfresco.rest.framework.core.ResourceLookupDictionary;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.jacksonextensions.ExecutionResult;
|
||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper.Writer;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.webscripts.ApiWebScript;
|
||||
import org.codehaus.jackson.JsonGenerationException;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
|
||||
/**
|
||||
* Provides general information about the Api calls and methods.
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class InfoWebScriptGet extends ApiWebScript
|
||||
{
|
||||
|
||||
private ResourceLookupDictionary lookupDictionary;
|
||||
|
||||
|
||||
public void setLookupDictionary(ResourceLookupDictionary lookupDictionary)
|
||||
{
|
||||
this.lookupDictionary = lookupDictionary;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException
|
||||
{
|
||||
|
||||
ResourceDictionary resourceDic = lookupDictionary.getDictionary();
|
||||
final Map<String, ResourceWithMetadata> apiResources = resourceDic.getAllResources().get(api);
|
||||
if (apiResources == null)
|
||||
{
|
||||
throw new InvalidArgumentException(InvalidArgumentException.DEFAULT_INVALID_API);
|
||||
}
|
||||
|
||||
jsonHelper.withWriter(res.getOutputStream(), new Writer()
|
||||
{
|
||||
@Override
|
||||
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
|
||||
throws JsonGenerationException, JsonMappingException, IOException
|
||||
{
|
||||
|
||||
List<ExecutionResult> entities = new ArrayList<ExecutionResult>();
|
||||
for (ResourceWithMetadata resource : apiResources.values())
|
||||
{
|
||||
entities.add(new ExecutionResult(resource.getMetaData(), null));
|
||||
}
|
||||
Collections.sort(entities, new Comparator<ExecutionResult>()
|
||||
{
|
||||
public int compare(ExecutionResult r1, ExecutionResult r2)
|
||||
{
|
||||
return ((ResourceMetadata) r1.getRoot()).getUniqueId().compareTo(((ResourceMetadata) r2.getRoot()).getUniqueId());
|
||||
}
|
||||
});
|
||||
objectMapper.writeValue(generator, CollectionWithPagingInfo.asPaged(Paging.DEFAULT,entities));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,151 @@
|
||||
package org.alfresco.rest.framework.webscripts.metadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.core.ResourceDictionary;
|
||||
import org.alfresco.rest.framework.core.ResourceLocator;
|
||||
import org.alfresco.rest.framework.core.ResourceLookupDictionary;
|
||||
import org.alfresco.rest.framework.core.ResourceMetadata.RESOURCE_TYPE;
|
||||
import org.alfresco.rest.framework.core.ResourceWithMetadata;
|
||||
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||
import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationException;
|
||||
import org.alfresco.rest.framework.jacksonextensions.ExecutionResult;
|
||||
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper.Writer;
|
||||
import org.alfresco.rest.framework.metadata.ResourceMetaDataWriter;
|
||||
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||
import org.alfresco.rest.framework.resource.parameters.Paging;
|
||||
import org.alfresco.rest.framework.webscripts.ApiWebScript;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.codehaus.jackson.JsonGenerationException;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.JsonMappingException;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
|
||||
/**
|
||||
* Provides general information about an Api call and its methods.
|
||||
*
|
||||
* @author Gethin James
|
||||
*/
|
||||
public class WebScriptOptionsMetaData extends ApiWebScript implements ResourceMetaDataWriter
|
||||
{
|
||||
|
||||
private static Log logger = LogFactory.getLog(WebScriptOptionsMetaData.class);
|
||||
private ResourceLookupDictionary lookupDictionary;
|
||||
private Map<String, ResourceMetaDataWriter> writers;
|
||||
|
||||
public void setLookupDictionary(ResourceLookupDictionary lookupDictionary)
|
||||
{
|
||||
this.lookupDictionary = lookupDictionary;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException
|
||||
{
|
||||
final Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
|
||||
|
||||
ResourceDictionary resourceDic = lookupDictionary.getDictionary();
|
||||
Map<String, ResourceWithMetadata> apiResources = resourceDic.getAllResources().get(api);
|
||||
if (apiResources == null)
|
||||
{
|
||||
throw new InvalidArgumentException(InvalidArgumentException.DEFAULT_INVALID_API);
|
||||
}
|
||||
String collectionName = templateVars.get(ResourceLocator.COLLECTION_RESOURCE);
|
||||
String resourceName = templateVars.get(ResourceLocator.RELATIONSHIP_RESOURCE);
|
||||
String resourceKey = ResourceDictionary.resourceKey(collectionName, resourceName);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Locating resource :" + resourceKey);
|
||||
}
|
||||
ResourceWithMetadata resource = apiResources.get(resourceKey);
|
||||
if (resource == null)
|
||||
{
|
||||
//Get entity resource and check if we are referencing a property on it.
|
||||
resourceKey = ResourceDictionary.propertyResourceKey(collectionName, resourceName);
|
||||
resource = apiResources.get(resourceKey);
|
||||
}
|
||||
ResourceMetaDataWriter writer = chooseWriter(req);
|
||||
writer.writeMetaData(res.getOutputStream(), resource, apiResources);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Chooses the correct writer to use based on the supplied "format" param
|
||||
* @param req - the WebScriptRequest
|
||||
* @return ResourceMetaDataWriter - a matching writer - DEFAULT is this class.
|
||||
*/
|
||||
protected ResourceMetaDataWriter chooseWriter(WebScriptRequest req)
|
||||
{
|
||||
if (writers != null)
|
||||
{
|
||||
ResourceMetaDataWriter theWriter = writers.get(req.getParameter("format"));
|
||||
if (theWriter != null) return theWriter;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes the resulting resource and returns the data to be displayed
|
||||
* @param resource
|
||||
* @param apiResources
|
||||
* @return Either a ExecutionResult or a CollectionWithPagingInfo
|
||||
*/
|
||||
public static Object processResult(ResourceWithMetadata resource, Map<String, ResourceWithMetadata> apiResources)
|
||||
{
|
||||
List<ExecutionResult> results = new ArrayList();
|
||||
if (RESOURCE_TYPE.ENTITY.equals(resource.getMetaData().getType()))
|
||||
{
|
||||
results.add(new ExecutionResult(resource, null));
|
||||
for (ResourceWithMetadata aResource : apiResources.values())
|
||||
{
|
||||
if (resource.getMetaData().getUniqueId().equals(aResource.getMetaData().getParentResource()))
|
||||
{
|
||||
results.add(new ExecutionResult(aResource, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (results.isEmpty())
|
||||
{
|
||||
return new ExecutionResult(resource, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CollectionWithPagingInfo.asPaged(Paging.DEFAULT, results);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeMetaData(OutputStream out, ResourceWithMetadata resource, Map<String, ResourceWithMetadata> allApiResources) throws IOException
|
||||
{
|
||||
|
||||
final Object result = processResult(resource,allApiResources);
|
||||
|
||||
jsonHelper.withWriter(out, new Writer()
|
||||
{
|
||||
@Override
|
||||
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
|
||||
throws JsonGenerationException, JsonMappingException, IOException
|
||||
{
|
||||
objectMapper.writeValue(generator, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void setWriters(Map<String, ResourceMetaDataWriter> writers)
|
||||
{
|
||||
this.writers = writers;
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user