From c8bc4614ad352aa0550c00b0f667f4d83ae3c466 Mon Sep 17 00:00:00 2001 From: Richard Millet Date: Fri, 7 Jan 2011 17:29:57 +0000 Subject: [PATCH] CSPACE-1087, CSPACE-1141: Relation validation handler now prevents creating relationships where the subject ID and object ID are the same. Also, relation validation now enforces non-null values for subject id, predicate type, and object id upon creation. --- .../client/test/AbstractServiceTestImpl.java | 10 ++ services/common/.classpath | 2 +- .../context/AbstractServiceContextImpl.java | 23 +++- .../common/context/ServiceContext.java | 2 +- .../document/InvalidDocumentException.java | 2 +- .../common/document/ValidatorHandler.java | 4 +- .../common/document/ValidatorHandlerImpl.java | 118 ++++++++++++++++++ .../common/src/main/resources/service.xsd | 1 + .../client/test/RelationServiceTest.java | 66 ++++++++-- .../relation/NewRelationResource.java | 6 + .../nuxeo/RelationValidatorHandler.java | 81 +++++++++++- 11 files changed, 290 insertions(+), 25 deletions(-) create mode 100644 services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandlerImpl.java diff --git a/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java b/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java index 318841c95..007a19005 100644 --- a/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java +++ b/services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java @@ -123,6 +123,16 @@ public abstract class AbstractServiceTestImpl extends BaseServiceTest implements REQUEST_TYPE = ServiceRequestType.CREATE; testSetup(EXPECTED_STATUS_CODE, REQUEST_TYPE); } + + /** + * Sets up create tests with empty entity body. + */ + protected void setupCreateWithInvalidBody() { + EXPECTED_STATUS_CODE = STATUS_BAD_REQUEST; + REQUEST_TYPE = ServiceRequestType.CREATE; + testSetup(EXPECTED_STATUS_CODE, REQUEST_TYPE); + } + /* (non-Javadoc) * @see org.collectionspace.services.client.test.ServiceTest#createWithMalformedXml(java.lang.String) diff --git a/services/common/.classpath b/services/common/.classpath index 557f8a4e0..c9fc5a5af 100644 --- a/services/common/.classpath +++ b/services/common/.classpath @@ -1,7 +1,7 @@ - + diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java b/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java index 658c14b2e..e12b4daaf 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java @@ -84,7 +84,7 @@ public abstract class AbstractServiceContextImpl /** The override document type. */ private String overrideDocumentType = null; /** The val handlers. */ - private List valHandlers = null; + private List> valHandlers = null; /** The doc handler. */ private DocumentHandler docHandler = null; /** security context */ @@ -507,21 +507,36 @@ public abstract class AbstractServiceContextImpl } return serviceBinding.getDocumentHandler().trim(); } - + + /* + * If this element is set in the service binding then use it otherwise + * assume that asserts are NOT disabled. + */ + private boolean disableValidationAsserts() { + boolean result; + Boolean disableAsserts = getServiceBinding().isDisableAsserts(); + result = (disableAsserts != null) ? disableAsserts : false; + return result; + } + /* (non-Javadoc) * @see org.collectionspace.services.common.context.ServiceContext#getValidatorHandlers() */ @Override - public List getValidatorHandlers() throws Exception { + public List> getValidatorHandlers() throws Exception { if (valHandlers != null) { return valHandlers; } List handlerClazzes = getServiceBinding().getValidatorHandler(); - List handlers = new ArrayList(handlerClazzes.size()); + List> handlers = new ArrayList>(handlerClazzes.size()); ClassLoader tccl = Thread.currentThread().getContextClassLoader(); for (String clazz : handlerClazzes) { clazz = clazz.trim(); Class c = tccl.loadClass(clazz); + if (disableValidationAsserts() == false) { + // enable validation assertions + tccl.setClassAssertionStatus(clazz, true); + } if (ValidatorHandler.class.isAssignableFrom(c)) { handlers.add((ValidatorHandler) c.newInstance()); } diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java index fd53db1f1..369e2a664 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java @@ -237,7 +237,7 @@ public interface ServiceContext { * for the service. it creates the handlers if necessary. * @return validation handlers */ - public List getValidatorHandlers() throws Exception; + public List> getValidatorHandlers() throws Exception; /** * Gets the query params. diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/InvalidDocumentException.java b/services/common/src/main/java/org/collectionspace/services/common/document/InvalidDocumentException.java index c1000e963..4051dbd57 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/InvalidDocumentException.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/InvalidDocumentException.java @@ -43,7 +43,7 @@ public class InvalidDocumentException extends BadRequestException { * Creates a new instance of InvalidDocumentException without detail message. */ public InvalidDocumentException() { - //empty constructor + super(); } /** diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandler.java b/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandler.java index 2fc6559a7..ec33de30e 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandler.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandler.java @@ -30,7 +30,7 @@ import org.collectionspace.services.common.document.DocumentHandler.Action; * ValidatorHandler provides plugin for application level validation * for content received by services serving objects with extensible schema */ -public interface ValidatorHandler { +public interface ValidatorHandler { /** * validate is called by the document handler for CREATE and UPDATE actions. @@ -41,6 +41,6 @@ public interface ValidatorHandler { * @param ctx * @throws InvalidDocumentException */ - public void validate(Action action, ServiceContext ctx) + public void validate(Action action, ServiceContext ctx) throws InvalidDocumentException; } diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandlerImpl.java b/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandlerImpl.java new file mode 100644 index 000000000..562566b09 --- /dev/null +++ b/services/common/src/main/java/org/collectionspace/services/common/document/ValidatorHandlerImpl.java @@ -0,0 +1,118 @@ +package org.collectionspace.services.common.document; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; + +import org.collectionspace.services.common.context.MultipartServiceContext; +import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.DocumentHandler.Action; +import org.collectionspace.services.relation.RelationsCommon; +import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; +import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: Auto-generated Javadoc +/** + * The Class ValidatorHandlerImpl. + */ +public abstract class ValidatorHandlerImpl implements ValidatorHandler { + + /** The logger. */ + private final Logger logger = LoggerFactory.getLogger(ValidatorHandlerImpl.class); + + private ServiceContext ctx; + + protected ServiceContext getServiceContext() { + return ctx; + } + + protected void setServiceContext(ServiceContext ctx) { + this.ctx = ctx; + } + + /* (non-Javadoc) + * @see org.collectionspace.services.common.document.ValidatorHandler#validate(org.collectionspace.services.common.document.DocumentHandler.Action, org.collectionspace.services.common.context.ServiceContext) + */ + @Override + public void validate(Action action, ServiceContext ctx) + throws InvalidDocumentException { + setServiceContext(ctx); + switch (action) { + case CREATE: + handleCreate(); + break; + case GET: + handleGet(); + break; + case GET_ALL: + handleGetAll(); + break; + case UPDATE: + handleUpdate(); + break; + case DELETE: + handleDelete(); + break; + default: + throw new UnsupportedOperationException("ValidatorHandlerImpl: Unknow action = " + + action); + } + } + + protected Object getCommonPart() { + Object result = null; + + try { + MultipartServiceContext multiPartCtx = (MultipartServiceContext) getServiceContext(); + result = multiPartCtx.getInputPart(ctx.getCommonPartLabel(), + getCommonPartClass()); + } catch (Exception e) { + if (logger.isDebugEnabled() == true) { + logger.debug("Could not extract common part from multipart input.", e); + } + } + + return result; + } + + abstract protected Class getCommonPartClass(); + + /** + * Handle create. + * + * @param ctx the ctx + */ + abstract protected void handleCreate() throws InvalidDocumentException; + + /** + * Handle get. + * + * @param ctx the ctx + */ + abstract protected void handleGet() throws InvalidDocumentException; + + /** + * Handle get all. + * + * @param ctx the ctx + */ + abstract protected void handleGetAll() throws InvalidDocumentException; + + /** + * Handle update. + * + * @param ctx the ctx + */ + abstract protected void handleUpdate() throws InvalidDocumentException; + + /** + * Handle delete. + * + * @param ctx the ctx + */ + abstract protected void handleDelete() throws InvalidDocumentException; +} \ No newline at end of file diff --git a/services/common/src/main/resources/service.xsd b/services/common/src/main/resources/service.xsd index af351db82..45b49438b 100644 --- a/services/common/src/main/resources/service.xsd +++ b/services/common/src/main/resources/service.xsd @@ -44,6 +44,7 @@ + diff --git a/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java b/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java index c9ac4b32c..a12c5f291 100644 --- a/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java +++ b/services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java @@ -143,6 +143,39 @@ public class RelationServiceTest extends AbstractServiceTestImpl { create(testName); } } + + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class, + dependsOnMethods = {"create"}) + public void createWithSelfRelation(String testName) throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug(testBanner(testName, CLASS_NAME)); + } + + setupCreateWithInvalidBody(); + + // Submit the request to the service and store the response. + RelationClient client = new RelationClient(); + String identifier = createIdentifier(); + RelationsCommon relationsCommon = createRelationsCommon(identifier); + // Make the subject ID equal to the object ID + relationsCommon.setDocumentId1(relationsCommon.getDocumentId2()); + MultipartOutput multipart = createRelationInstance(relationsCommon); + ClientResponse res = client.create(multipart); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + // + // Does it fall within the set of valid status codes? + // Does it exactly match the expected status code? + if(logger.isDebugEnabled()){ + logger.debug(testName + ": status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + } // Failure outcomes // Placeholders until the three tests below can be uncommented. @@ -777,6 +810,24 @@ public class RelationServiceTest extends AbstractServiceTestImpl { return SERVICE_PATH_COMPONENT; } + private RelationsCommon createRelationsCommon(String identifier) { + RelationsCommon result = new RelationsCommon(); + fillRelation(result, identifier); + return result; + } + + private MultipartOutput createRelationInstance(RelationsCommon relation) { + MultipartOutput result = new MultipartOutput(); + OutputPart commonPart = + result.addPart(relation, MediaType.APPLICATION_XML_TYPE); + commonPart.getHeaders().add("label", new RelationClient().getCommonPartName()); + if(logger.isDebugEnabled()){ + logger.debug("to be created, relation common"); + logger.debug(objectAsXmlString(relation, RelationsCommon.class)); + } + return result; + } + /** * Creates the relation instance. * @@ -784,18 +835,9 @@ public class RelationServiceTest extends AbstractServiceTestImpl { * @return the multipart output */ private MultipartOutput createRelationInstance(String identifier) { - RelationsCommon relation = new RelationsCommon(); - fillRelation(relation, identifier); - - MultipartOutput multipart = new MultipartOutput(); - OutputPart commonPart = - multipart.addPart(relation, MediaType.APPLICATION_XML_TYPE); - commonPart.getHeaders().add("label", new RelationClient().getCommonPartName()); - if(logger.isDebugEnabled()){ - logger.debug("to be created, relation common"); - logger.debug(objectAsXmlString(relation, RelationsCommon.class)); - } - return multipart; + RelationsCommon relation = createRelationsCommon(identifier); + MultipartOutput result = createRelationInstance(relation); + return result; } /** diff --git a/services/relation/service/src/main/java/org/collectionspace/services/relation/NewRelationResource.java b/services/relation/service/src/main/java/org/collectionspace/services/relation/NewRelationResource.java index 988882954..f822ad694 100644 --- a/services/relation/service/src/main/java/org/collectionspace/services/relation/NewRelationResource.java +++ b/services/relation/service/src/main/java/org/collectionspace/services/relation/NewRelationResource.java @@ -47,6 +47,7 @@ import org.collectionspace.services.common.relation.IRelationsManager; import org.collectionspace.services.common.relation.nuxeo.RelationsUtils; import org.collectionspace.services.common.AbstractMultiPartCollectionSpaceResourceImpl; import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.BadRequestException; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.document.DocumentHandler; import org.collectionspace.services.common.security.UnauthorizedException; @@ -122,6 +123,11 @@ public class NewRelationResource extends .entity("Create failed reason " + ue.getErrorReason()) .type("text/plain").build(); throw new WebApplicationException(response); + } catch (BadRequestException bre) { + Response response = Response.status(Response.Status.BAD_REQUEST) + .entity("Create failed reason " + bre.getErrorReason()) + .type("text/plain").build(); + throw new WebApplicationException(response); } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Caught exception in createRelation", e); diff --git a/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationValidatorHandler.java b/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationValidatorHandler.java index 3a2adac86..1299076a2 100644 --- a/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationValidatorHandler.java +++ b/services/relation/service/src/main/java/org/collectionspace/services/relation/nuxeo/RelationValidatorHandler.java @@ -1,17 +1,90 @@ package org.collectionspace.services.relation.nuxeo; +//import junit.framework.Assert; + +import org.collectionspace.services.common.context.MultipartServiceContext; import org.collectionspace.services.common.context.ServiceContext; + import org.collectionspace.services.common.document.InvalidDocumentException; import org.collectionspace.services.common.document.ValidatorHandler; +import org.collectionspace.services.common.document.ValidatorHandlerImpl; import org.collectionspace.services.common.document.DocumentHandler.Action; -public class RelationValidatorHandler implements ValidatorHandler { +import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl; +import org.collectionspace.services.relation.RelationsCommon; + +import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; +import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +//import org.testng.Assert; + +public class RelationValidatorHandler extends ValidatorHandlerImpl { + + /** The logger. */ + private final Logger logger = LoggerFactory.getLogger(RelationValidatorHandler.class); + + /* Error messages + */ + private static final String VALIDATION_ERROR = "The relation record is invalid. See log file for more details."; + private static final String SUBJECT_EQUALS_PREDICATE_ERROR = "The subject ID and object ID cannot be the same."; + + @Override + protected Class getCommonPartClass() { + return RelationsCommon.class; + } + + @Override + protected void handleCreate() + throws InvalidDocumentException{ + ServiceContext ctx = getServiceContext(); + try { + RelationsCommon relationsCommon = (RelationsCommon)getCommonPart(); + assert(relationsCommon != null); + + assert(relationsCommon.getDocumentId1() != null); + assert(relationsCommon.getDocumentId1().length() != 0); + + assert(relationsCommon.getDocumentId2() != null); + assert(relationsCommon.getDocumentId2().length() != 0); + + assert(relationsCommon.getRelationshipType() != null); + // + // Assert that the Subject ID and Predicate ID are not the same + // + assert(relationsCommon.getDocumentId1().equalsIgnoreCase(relationsCommon.getDocumentId2()) == false) : + SUBJECT_EQUALS_PREDICATE_ERROR; + } catch (AssertionError e) { + if (logger.isErrorEnabled() == true) { + logger.error(e.getMessage(), e); + } + throw new InvalidDocumentException(VALIDATION_ERROR, e); + } + } + + @Override + protected void handleGet() { + // TODO Auto-generated method stub + + } + + @Override + protected void handleGetAll() { + // TODO Auto-generated method stub + + } + + @Override + protected void handleUpdate() { + // TODO Auto-generated method stub + + } @Override - public void validate(Action action, ServiceContext ctx) - throws InvalidDocumentException { + protected void handleDelete() { // TODO Auto-generated method stub - System.out.println("RelationValidatorHandler executed."); + } } -- 2.47.3