diff --git a/source/java/org/alfresco/rest/framework/core/ResourceInspector.java b/source/java/org/alfresco/rest/framework/core/ResourceInspector.java index f47aa58470..6b5ac50e4a 100644 --- a/source/java/org/alfresco/rest/framework/core/ResourceInspector.java +++ b/source/java/org/alfresco/rest/framework/core/ResourceInspector.java @@ -49,6 +49,7 @@ import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAct import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.MultiPartRelationshipResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.actions.interfaces.ResourceAction; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.Pair; @@ -161,6 +162,10 @@ public class ResourceInspector findOperation(BinaryResourceAction.Delete.class, HttpMethod.DELETE, helperForAddressProps); findOperation(BinaryResourceAction.Update.class, HttpMethod.PUT, helperForAddressProps); + findOperation(RelationshipResourceBinaryAction.Read.class, HttpMethod.GET, helperForAddressProps); + findOperation(RelationshipResourceBinaryAction.Delete.class, HttpMethod.DELETE, helperForAddressProps); + findOperation(RelationshipResourceBinaryAction.Update.class, HttpMethod.PUT, helperForAddressProps); + boolean noAuth = resource.isAnnotationPresent(WebApiNoAuth.class); if (noAuth) { diff --git a/source/java/org/alfresco/rest/framework/resource/actions/interfaces/RelationshipResourceBinaryAction.java b/source/java/org/alfresco/rest/framework/resource/actions/interfaces/RelationshipResourceBinaryAction.java new file mode 100755 index 0000000000..5766c93f1a --- /dev/null +++ b/source/java/org/alfresco/rest/framework/resource/actions/interfaces/RelationshipResourceBinaryAction.java @@ -0,0 +1,74 @@ +package org.alfresco.rest.framework.resource.actions.interfaces; + +import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; +import org.alfresco.rest.framework.resource.content.BasicContentInfo; +import org.alfresco.rest.framework.resource.content.BinaryResource; +import org.alfresco.rest.framework.resource.content.FileBinaryResource; +import org.alfresco.rest.framework.resource.content.NodeBinaryResource; +import org.alfresco.rest.framework.resource.parameters.Parameters; + +import java.io.InputStream; + +/** + * Permissible actions for binary resources of an @RelationshipResource + * + * Supports full CRUD (Read, Update, Delete) + * + * @author Gethin James + */ + +public interface RelationshipResourceBinaryAction +{ + + /** + * HTTP GET - Retrieve a binary resource + */ + public static interface Read extends ResourceAction + { + /** + * Retrieves a binary property by returning a BinaryResource object. The specific property is specified in the {@link Parameters} object. + * See {@link Parameters#hasBinaryProperty(String)} or {@link Parameters#getBinaryProperty()} + * @param entityId unique id + * @param entityResourceId Entity resource context for this relationship + * @param parameters {@link Parameters} + * @return BinaryResource - Either {@link FileBinaryResource} or {@link NodeBinaryResource} + * @throws EntityNotFoundException + */ + public BinaryResource readProperty(String entityId, String entityResourceId, Parameters parameters) throws EntityNotFoundException; + } + + /** + * HTTP DELETE - Deletes a binary resource + */ + public static interface Delete extends ResourceAction + { + + /** + * Deletes a binary property. The specific property is specified in the {@link Parameters} object. + * See {@link Parameters#hasBinaryProperty(String)} or {@link Parameters#getBinaryProperty()} + * @param entityId unique id + * @param entityResourceId Entity resource context for this relationship + * @param parameters {@link Parameters} + */ + public void deleteProperty(String entityId, String entityResourceId, Parameters parameters); + } + + /** + * HTTP PUT - Updates a binary resource if it exists, error if not + */ + public static interface Update extends ResourceAction + { + + /** + * Updates a binary property. The specific property is specified in the {@link Parameters} object. + * See {@link Parameters#hasBinaryProperty(String)} or {@link Parameters#getBinaryProperty()} + * @param entityId unique id + * @param entityResourceId Entity resource context for this relationship + * @param stream An inputstream + * @param contentInfo Basic information about the content stream + * @param params {@link Parameters} + */ + public E updateProperty(String entityId, String entityResourceId, BasicContentInfo contentInfo, InputStream stream, Parameters params); + } + +} diff --git a/source/java/org/alfresco/rest/framework/resource/parameters/Parameters.java b/source/java/org/alfresco/rest/framework/resource/parameters/Parameters.java index 56b0bf66d4..ebcdfe455f 100644 --- a/source/java/org/alfresco/rest/framework/resource/parameters/Parameters.java +++ b/source/java/org/alfresco/rest/framework/resource/parameters/Parameters.java @@ -18,7 +18,7 @@ import org.springframework.extensions.webscripts.Status; public interface Parameters { /** - * Gets a single request parameter passed in by the user. + * Gets a single request query parameter passed in by the user. * Currently doesn't support multiple values. * @param parameterName String * @return String The Parameter value diff --git a/source/java/org/alfresco/rest/framework/resource/parameters/Params.java b/source/java/org/alfresco/rest/framework/resource/parameters/Params.java index 4b8e274f10..c463318435 100644 --- a/source/java/org/alfresco/rest/framework/resource/parameters/Params.java +++ b/source/java/org/alfresco/rest/framework/resource/parameters/Params.java @@ -68,7 +68,12 @@ public class Params implements Parameters { return new Params(entityId, null, passedIn, null, null, recognizedParams, null); } - + + public static Params valueOf(String entityId, String relationshipId, RecognizedParams recognizedParams, Object passedIn) + { + return new Params(entityId, relationshipId, passedIn, null, null, recognizedParams, null); + } + public static Params valueOf(String entityId, String relationshipId, Object passedIn, InputStream stream, String addressedProperty, RecognizedParams recognizedParams, BasicContentInfo contentInfo) { return new Params(entityId, relationshipId, passedIn, stream, addressedProperty, recognizedParams, contentInfo); diff --git a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java index 9272f1c5c9..e725974646 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java +++ b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptDelete.java @@ -9,6 +9,7 @@ import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationE 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.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.parameters.Params; import org.apache.commons.lang.StringUtils; import org.springframework.extensions.webscripts.Status; @@ -61,9 +62,18 @@ public class ResourceWebScriptDelete extends AbstractResourceWebScript implement } case PROPERTY: final String resourceName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE); + final String propertyName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.PROPERTY); + if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(resourceName)) { - return Params.valueOf(entityId, null, null, null, resourceName, null, null); + if (StringUtils.isNotBlank(propertyName)) + { + return Params.valueOf(entityId, relationshipId, null, null, propertyName, null, null); + } + else + { + return Params.valueOf(entityId, null, null, null, resourceName, null, null); + } } //Fall through to unsupported. default: @@ -100,14 +110,28 @@ public class ResourceWebScriptDelete extends AbstractResourceWebScript implement //Don't pass anything to the callback - its just successful return null; case PROPERTY: - if (resource.getMetaData().isDeleted(BinaryResourceAction.Delete.class)) + if (BinaryResourceAction.Delete.class.isAssignableFrom(resource.getResource().getClass())) { - throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId()); + 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; + } + if (RelationshipResourceBinaryAction.Delete.class.isAssignableFrom(resource.getResource().getClass())) + { + if (resource.getMetaData().isDeleted(RelationshipResourceBinaryAction.Delete.class)) + { + throw new DeletedResourceException("(DELETE) "+resource.getMetaData().getUniqueId()); + } + RelationshipResourceBinaryAction.Delete binDeleter = (RelationshipResourceBinaryAction.Delete) resource.getResource(); + binDeleter.deleteProperty(params.getEntityId(), params.getRelationshipId(), params); + //Don't pass anything to the callback - its just successful + return null; } - 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"); } diff --git a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptGet.java b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptGet.java index 7626c401e9..85e2f46c8a 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptGet.java +++ b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptGet.java @@ -30,6 +30,7 @@ import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAct 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.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.content.ContentInfo; import org.alfresco.rest.framework.resource.content.NodeBinaryResource; @@ -87,9 +88,18 @@ public class ResourceWebScriptGet extends AbstractResourceWebScript implements P } case PROPERTY: final String resourceName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE); + final String propertyName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.PROPERTY); + if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(resourceName)) { - return Params.valueOf(entityId, null, null, null, resourceName, params, null); + if (StringUtils.isNotBlank(propertyName)) + { + return Params.valueOf(entityId, relationshipId, null, null, propertyName, params, null); + } + else + { + return Params.valueOf(entityId, null, null, null, resourceName, params, null); + } } //Fall through to unsupported. default: @@ -187,11 +197,16 @@ public class ResourceWebScriptGet extends AbstractResourceWebScript implements P BinaryResource prop = getter.readProperty(params.getEntityId(), params); return prop; } - else + if (RelationshipResourceBinaryAction.Read.class.isAssignableFrom(resource.getResource().getClass())) { - throw new UnsupportedResourceOperationException(); + if (resource.getMetaData().isDeleted(RelationshipResourceBinaryAction.Read.class)) + { + throw new DeletedResourceException("(GET) "+resource.getMetaData().getUniqueId()); + } + RelationshipResourceBinaryAction.Read getter = (RelationshipResourceBinaryAction.Read) resource.getResource(); + BinaryResource prop = getter.readProperty(params.getEntityId(), params.getRelationshipId(), params); + return prop; } - } else { diff --git a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPost.java b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPost.java index 6a8f270ac9..d1d4ef96b1 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPost.java +++ b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPost.java @@ -68,6 +68,7 @@ public class ResourceWebScriptPost extends AbstractResourceWebScript implements { final RecognizedParams params = ResourceWebScriptHelper.getRecognizedParams(req); final String entityId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.ENTITY_ID); + final String relationshipId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_ID); switch (resourceMeta.getType()) { @@ -83,7 +84,6 @@ public class ResourceWebScriptPost extends AbstractResourceWebScript implements return Params.valueOf(null, params, postedObj); } case RELATIONSHIP: - String relationshipId = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_ID); if (StringUtils.isNotBlank(relationshipId)) { throw new UnsupportedResourceOperationException("POST is executed against the collection URL"); @@ -95,6 +95,8 @@ public class ResourceWebScriptPost extends AbstractResourceWebScript implements } case OPERATION: final String operationName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE); + final String propertyName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.PROPERTY); + if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(operationName)) { Class objectType = resourceMeta.getObjectType(HttpMethod.POST); @@ -104,7 +106,15 @@ public class ResourceWebScriptPost extends AbstractResourceWebScript implements //Operations don't support a List as json body postedObj = ResourceWebScriptHelper.extractJsonContent(req, jsonHelper, objectType); } - return Params.valueOf(entityId, params, postedObj); + + if (StringUtils.isNotBlank(propertyName)) + { + return Params.valueOf(entityId, relationshipId, params, postedObj); + } + else + { + return Params.valueOf(entityId, params, postedObj); + } } //Fall through to unsupported. default: diff --git a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java index 4d147eace6..f59d6dc084 100644 --- a/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java +++ b/source/java/org/alfresco/rest/framework/webscripts/ResourceWebScriptPut.java @@ -34,7 +34,9 @@ import org.alfresco.rest.framework.core.exceptions.UnsupportedResourceOperationE 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.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.content.BasicContentInfo; +import org.alfresco.rest.framework.resource.content.BinaryResource; 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; @@ -95,9 +97,19 @@ public class ResourceWebScriptPut extends AbstractResourceWebScript implements P } case PROPERTY: final String resourceName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.RELATIONSHIP_RESOURCE); + final String propertyName = req.getServiceMatch().getTemplateVars().get(ResourceLocator.PROPERTY); + if (StringUtils.isNotBlank(entityId) && StringUtils.isNotBlank(resourceName)) { - return Params.valueOf(entityId, null, null, getStream(req), resourceName, params, getContentInfo(req)); + if (StringUtils.isNotBlank(propertyName)) + { + return Params.valueOf(entityId, relationshipId, null, getStream(req), propertyName, params, getContentInfo(req)); + } + else + { + return Params.valueOf(entityId, null, null, getStream(req), resourceName, params, getContentInfo(req)); + } + } //Fall through to unsupported. default: @@ -185,12 +197,24 @@ public class ResourceWebScriptPut extends AbstractResourceWebScript implements P Object relResult = relationUpdater.update(params.getEntityId(), params.getPassedIn(), params); return relResult; case PROPERTY: - if (resource.getMetaData().isDeleted(BinaryResourceAction.Update.class)) + if (BinaryResourceAction.Update.class.isAssignableFrom(resource.getResource().getClass())) { - throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId()); + if (resource.getMetaData().isDeleted(BinaryResourceAction.Update.class)) + { + throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId()); + } + BinaryResourceAction.Update binUpdater = (BinaryResourceAction.Update) resource.getResource(); + return binUpdater.updateProperty(params.getEntityId(), params.getContentInfo(), params.getStream(), params); + } + if (RelationshipResourceBinaryAction.Update.class.isAssignableFrom(resource.getResource().getClass())) + { + if (resource.getMetaData().isDeleted(RelationshipResourceBinaryAction.Update.class)) + { + throw new DeletedResourceException("(UPDATE) "+resource.getMetaData().getUniqueId()); + } + RelationshipResourceBinaryAction.Update binUpdater = (RelationshipResourceBinaryAction.Update) resource.getResource(); + return binUpdater.updateProperty(params.getEntityId(), params.getRelationshipId(), params.getContentInfo(), params.getStream(), params); } - BinaryResourceAction.Update binUpdater = (BinaryResourceAction.Update) resource.getResource(); - return binUpdater.updateProperty(params.getEntityId(), params.getContentInfo(), params.getStream(), params); default: throw new UnsupportedResourceOperationException("PUT not supported for Actions"); } diff --git a/source/test-java/org/alfresco/rest/framework/tests/api/mocks/SheepBaaaahResource.java b/source/test-java/org/alfresco/rest/framework/tests/api/mocks/SheepBaaaahResource.java index d6d14eac33..e9889a5294 100644 --- a/source/test-java/org/alfresco/rest/framework/tests/api/mocks/SheepBaaaahResource.java +++ b/source/test-java/org/alfresco/rest/framework/tests/api/mocks/SheepBaaaahResource.java @@ -11,6 +11,7 @@ import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.resource.RelationshipResource; import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.content.BasicContentInfo; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; @@ -22,7 +23,7 @@ import org.alfresco.rest.framework.resource.parameters.Parameters; * @author Gethin James */ @RelationshipResource(name = "baaahh", entityResource=SheepEntityResource.class, title = "Sheep baaah") -public class SheepBaaaahResource implements RelationshipResourceAction.Read, RelationshipResourceAction.ReadById, BinaryResourceAction.Read, BinaryResourceAction.Delete,BinaryResourceAction.Update +public class SheepBaaaahResource implements RelationshipResourceAction.Read, RelationshipResourceAction.ReadById, RelationshipResourceBinaryAction.Read, RelationshipResourceBinaryAction.Delete,RelationshipResourceBinaryAction.Update { @Override @@ -39,27 +40,26 @@ public class SheepBaaaahResource implements RelationshipResourceAction.Read, BinaryResourceAction.Read +public class GoatRelationshipResource implements RelationshipResourceAction.Read, RelationshipResourceBinaryAction.Read { @Override public CollectionWithPagingInfo readAll(String entityResourceId, Parameters params) @@ -30,10 +31,9 @@ public class GoatRelationshipResource implements RelationshipResourceAction.Read @WebApiDescription(title = "Download content", description = "Download content") @BinaryProperties({"content"}) - public BinaryResource readProperty(String herdId, Parameters parameters) throws EntityNotFoundException + public BinaryResource readProperty(String herdId, String entityResourceId, Parameters parameters) throws EntityNotFoundException { File file = TempFileProvider.createTempFile("Its a goat", ".txt"); return new FileBinaryResource(file); } - } diff --git a/source/test-java/org/alfresco/rest/framework/tests/core/ParamsExtractorTests.java b/source/test-java/org/alfresco/rest/framework/tests/core/ParamsExtractorTests.java index 02ac3d2ccc..0365e7b060 100644 --- a/source/test-java/org/alfresco/rest/framework/tests/core/ParamsExtractorTests.java +++ b/source/test-java/org/alfresco/rest/framework/tests/core/ParamsExtractorTests.java @@ -86,7 +86,12 @@ public class ParamsExtractorTests assertNotNull(params); assertNotNull(params.getRelationsFilter()); assertFalse(params.includeSource()); - + + templateVars.put(ResourceLocator.RELATIONSHIP_RESOURCE, "codfish"); + params = extractor.extractParams(mockRelationship(), request); + assertNotNull(params); + assertNull("For getting a Collection there should be no relationshipId params.",params.getRelationshipId()); + templateVars.put(ResourceLocator.RELATIONSHIP_ID, "45678"); params = extractor.extractParams(mockRelationship(), request); assertNotNull(params); @@ -107,6 +112,14 @@ public class ParamsExtractorTests assertTrue(params.hasBinaryProperty("codfish")); assertFalse(params.hasBinaryProperty("something")); assertEquals("codfish", params.getBinaryProperty()); + + templateVars.put(ResourceLocator.RELATIONSHIP_ID, "9865"); + templateVars.put(ResourceLocator.PROPERTY, "monkFish"); + params = extractor.extractParams(mockProperty(), request); + assertNotNull(params); + assertEquals("1234", params.getEntityId()); + assertEquals("9865", params.getRelationshipId()); + assertTrue(params.hasBinaryProperty("monkFish")); return params; } @@ -172,7 +185,7 @@ public class ParamsExtractorTests { assertNotNull("POSTING to a relationship collection by id is not correct.",iae); //Must throw this exception } - + templateVars.clear(); when(content.getReader()).thenReturn(new StringReader(JsonJacksonTests.FARMER_JSON)); //reset the reader templateVars.put(ResourceLocator.ENTITY_ID, "1234"); @@ -187,6 +200,25 @@ public class ParamsExtractorTests { assertNotNull(uoe); //Must throw this exception } + testExtractOperationParams(templateVars, request, extractor); + } + + private Params testExtractOperationParams(Map templateVars, WebScriptRequest request, ParamsExtractor extractor) + { + templateVars.clear(); + templateVars.put(ResourceLocator.ENTITY_ID, "1234"); + templateVars.put(ResourceLocator.RELATIONSHIP_RESOURCE, "codfish"); + Params params = extractor.extractParams(mockOperation(), request); + assertNotNull(params); + assertNull("For a Collection there should be no relationshipId params.",params.getRelationshipId()); + + templateVars.put(ResourceLocator.RELATIONSHIP_ID, "9865"); + templateVars.put(ResourceLocator.PROPERTY, "monkFish"); + params = extractor.extractParams(mockOperation(), request); + assertNotNull(params); + assertEquals("1234", params.getEntityId()); + assertEquals("9865", params.getRelationshipId()); + return params; } @Test