From 93434ea6b03a64f152df9842c24adf93d620e1f2 Mon Sep 17 00:00:00 2001 From: Patrick Schmitz Date: Mon, 1 Mar 2010 20:49:51 +0000 Subject: [PATCH] CSPACE-852. Added support in tenant-bindings.xml and in associated scanner to specify authority reference fields in a schema. Added support for "authorityrefs" sub-resource, that returns a list of references, and some convenient info for them. Added some RefName utilities to pull apart refnames and produce useful pieces for clients and server to deal with. Currently only working with Intake (must be ported to CollectionObject and Acquisition). This may benefit from some refactoring once I get the other ports done. To test this work, refactored the base test (AbstractServiceTestImpl.java) so that it is easier to produce tests that do not focus on CRUD (these an inherit from a new BaseServiceTest class (yes, it could use a better name). --- .../client/test/AbstractServiceTestImpl.java | 479 +----------------- .../services/client/test/BaseServiceTest.java | 378 ++++++++++++++ .../src/main/config/tenant-bindings.xml | 16 +- .../context/AbstractServiceContextImpl.java | 36 ++ .../common/repository/RepositoryClient.java | 17 + .../common/vocabulary/RefNameUtils.java | 213 ++++++++ .../client/java/RepositoryJavaClientImpl.java | 43 ++ .../src/main/resources/authorityref.xsd | 43 ++ services/intake/client/pom.xml | 5 + .../services/client/IntakeClient.java | 23 +- .../services/client/IntakeProxy.java | 9 + .../client/test/IntakeAuthRefsTest.java | 324 ++++++++++++ .../services/intake/IntakeResource.java | 43 ++ .../nuxeo/IntakeDocumentModelHandler.java | 55 ++ 14 files changed, 1213 insertions(+), 471 deletions(-) create mode 100644 services/client/src/main/java/org/collectionspace/services/client/test/BaseServiceTest.java create mode 100644 services/common/src/main/java/org/collectionspace/services/common/vocabulary/RefNameUtils.java create mode 100644 services/common/src/main/resources/authorityref.xsd create mode 100644 services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.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 dc2826386..827db26e6 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 @@ -23,36 +23,15 @@ */ package org.collectionspace.services.client.test; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.io.StringWriter; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import org.apache.commons.httpclient.methods.DeleteMethod; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.methods.PutMethod; -import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + -import org.collectionspace.services.client.TestServiceClient; -import org.jboss.resteasy.client.ClientResponse; -import org.jboss.resteasy.plugins.providers.multipart.InputPart; -import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; -import org.testng.annotations.DataProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * AbstractServiceTest, abstract base class for the client tests to be performed @@ -60,45 +39,10 @@ import org.slf4j.LoggerFactory; * * For Javadoc descriptions of this class's methods, see the ServiceTest interface. */ -public abstract class AbstractServiceTestImpl implements ServiceTest { - - private final Logger logger = - LoggerFactory.getLogger(AbstractServiceTestImpl.class); - // A base-level client, used (only) to obtain the base service URL. - protected static final TestServiceClient serviceClient = - new TestServiceClient(); - // A resource identifier believed to be non-existent in actual use, - // used when testing service calls that reference non-existent resources. - protected final String NON_EXISTENT_ID = createNonExistentIdentifier(); - // The HTTP status code expected to be returned in the response, - // from a request made to a service (where relevant). - protected int EXPECTED_STATUS_CODE = 0; - // The generic type of service request being tested - // (e.g. CREATE, UPDATE, DELETE). - // - // This makes it possible to check behavior specific to that type of request, - // such as the set of valid status codes that may be returned. - // - // Note that the default value is set to a guard value. - protected ServiceRequestType REQUEST_TYPE = ServiceRequestType.NON_EXISTENT; - // Static data to be submitted in various tests - protected final static String XML_DECLARATION = - ""; - // Note: this constant is intentionally missing its last angle bracket. - protected final static String MALFORMED_XML_DATA = - XML_DECLARATION - + "wrong schema contentswrong schema contents"; - // A MIME media type character set designation for the entity bodies - // of PUT and POST requests. Set this to null to avoid adding a character - // set attribute to the MIME type in the "Content-Type:" HTTP header. - final String NULL_CHARSET = null; +public abstract class AbstractServiceTestImpl extends BaseServiceTest implements ServiceTest { + + protected final Logger logger = LoggerFactory.getLogger(AbstractServiceTestImpl.class); - // --------------------------------------------------------------- - // CRUD tests : CREATE tests - // --------------------------------------------------------------- // Success outcomes @Override public void create(String testName) throws Exception { @@ -109,15 +53,7 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupCreate(String label) { - clearSetup(); - // Expected status code: 201 Created - EXPECTED_STATUS_CODE = Response.Status.CREATED.getStatusCode(); - // Type of service request being tested - REQUEST_TYPE = ServiceRequestType.CREATE; - // Print a banner identifying the test that will be run. - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.CREATED.getStatusCode(), ServiceRequestType.CREATE, label); } @Override @@ -188,13 +124,7 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupRead(String label) { - clearSetup(); - // Expected status code: 200 OK - EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.READ; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.OK.getStatusCode(), ServiceRequestType.READ, label); } // Failure outcomes @@ -206,13 +136,8 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupReadNonExistent(String label) { - clearSetup(); // Expected status code: 404 Not Found - EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.READ; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.NOT_FOUND.getStatusCode(), ServiceRequestType.READ, label); } // --------------------------------------------------------------- @@ -227,13 +152,7 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupReadList(String label) { - clearSetup(); - // Expected status code: 200 OK - EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.READ_LIST; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.OK.getStatusCode(), ServiceRequestType.READ_LIST, label); } // Failure outcomes @@ -250,13 +169,8 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupUpdate(String label) { - clearSetup(); // Expected status code: 200 OK - EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.UPDATE; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.OK.getStatusCode(), ServiceRequestType.UPDATE, label); } // Failure outcomes @@ -268,13 +182,8 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupUpdateWithEmptyEntityBody(String label) { - clearSetup(); // Expected status code: 400 Bad Request - EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.UPDATE; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.BAD_REQUEST.getStatusCode(), ServiceRequestType.UPDATE, label); } @Override @@ -285,13 +194,8 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupUpdateWithMalformedXml(String label) { - clearSetup(); // Expected status code: 400 Bad Request - EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.UPDATE; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.BAD_REQUEST.getStatusCode(), ServiceRequestType.UPDATE, label); } @Override @@ -302,13 +206,8 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupUpdateWithWrongXmlSchema(String label) { - clearSetup(); // Expected status code: 400 Bad Request - EXPECTED_STATUS_CODE = Response.Status.BAD_REQUEST.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.UPDATE; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.BAD_REQUEST.getStatusCode(), ServiceRequestType.UPDATE, label); } @Override @@ -319,13 +218,8 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupUpdateNonExistent(String label) { - clearSetup(); // Expected status code: 404 Not Found - EXPECTED_STATUS_CODE = Response.Status.NOT_FOUND.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.UPDATE; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.NOT_FOUND.getStatusCode(), ServiceRequestType.UPDATE, label); } // --------------------------------------------------------------- @@ -340,13 +234,7 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { } protected void setupDelete(String label) { - clearSetup(); - // Expected status code: 200 OK - EXPECTED_STATUS_CODE = Response.Status.OK.getStatusCode(); - REQUEST_TYPE = ServiceRequestType.DELETE; - if (logger.isDebugEnabled()) { - banner(label); - } + testSetup(Response.Status.OK.getStatusCode(), ServiceRequestType.DELETE, label); } // Failure outcomes @@ -366,341 +254,6 @@ public abstract class AbstractServiceTestImpl implements ServiceTest { banner(label); } } - - // --------------------------------------------------------------- - // Abstract utility methods - // - // Must be implemented by classes that extend - // this abstract base class. - // --------------------------------------------------------------- - /** - * Returns the URL path component of the service. - * - * This component will follow directly after the - * base path, if any. - * - * @return The URL path component of the service. - */ - protected abstract String getServicePathComponent(); - - /** - * Returns the common part name for the service request. - * - * @return The common part name for the service request. - */ - /* - public String getCommonPartName(); - */ - // --------------------------------------------------------------- - // Utility methods - // --------------------------------------------------------------- - /** - * Reinitializes setup values, to help expose any unintended reuse - * of those values between tests. - */ - protected void clearSetup() { - EXPECTED_STATUS_CODE = 0; - REQUEST_TYPE = ServiceRequestType.NON_EXISTENT; - } - - /** - * Returns the name of the currently running test. - * - * Note: although the return type is listed as Object[][], - * this method instead returns a String. - * - * @param m The currently running test method. - * - * @return The name of the currently running test method. - */ - @DataProvider(name = "testName") - public static Object[][] testName(Method m) { - return new Object[][]{ - new Object[]{m.getName()} - }; - } - - /** - * Returns an error message indicating that the status code returned by a - * specific call to a service does not fall within a set of valid status - * codes for that service. - * - * @param serviceRequestType A type of service request (e.g. CREATE, DELETE). - * - * @param statusCode The invalid status code that was returned in the response, - * from submitting that type of request to the service. - * - * @return An error message. - */ - protected String invalidStatusCodeMessage( - ServiceRequestType requestType, int statusCode) { - return "Status code '" + statusCode - + "' in response is NOT within the expected set: " - + requestType.validStatusCodesAsString(); - } - - /** - * Returns the root URL for a service. - * - * This URL consists of a base URL for all services, followed by - * a path component (or components) for a service. - * - * @return The root URL for a service. - */ - protected String getServiceRootURL() { - return serviceClient.getBaseURL() + getServicePathComponent(); - } - - /** - * Returns the URL of a specific resource managed by a service, and - * designated by an identifier (such as a universally unique ID, or UUID). - * - * @param resourceIdentifier An identifier (such as a UUID) for a resource. - * - * @return The URL of a specific resource managed by a service. - */ - protected String getResourceURL(String resourceIdentifier) { - return getServiceRootURL() + "/" + resourceIdentifier; - } - - /** - * Submits an HTTP request to a specified URL, and returns the - * status code of the response. Currently accepts GET and DELETE - * requests. - * - * @param method An HTTP method. - * - * @param url A String representation of a URL. - * - * @return The status code received in the HTTP response. - */ - protected int submitRequest(String method, String url) { - int statusCode = 0; - try { - TestServiceClient client = new TestServiceClient(); - if (method.equals(javax.ws.rs.HttpMethod.DELETE)) { - DeleteMethod deleteMethod = new DeleteMethod(url); - statusCode = client.getHttpClient().executeMethod(deleteMethod); - } else if (method.equals(javax.ws.rs.HttpMethod.GET)) { - GetMethod getMethod = new GetMethod(url); - statusCode = client.getHttpClient().executeMethod(getMethod); - } else { - // Do nothing - leave status code at default value. - } - } catch (Exception e) { - logger.error( - "Exception during HTTP " + method + " request to " - + url + ":", e); - } - return statusCode; - } - - /** - * Submits an HTTP request to a specified URL, with the submitted - * entity body, and returns the status code of the response. - * Currently accepts POST and PUT requests. - * - * @param method An HTTP method. - * - * @param url A String representation of a URL. - * - * @param mediaType The media type of the entity body to be submitted. - * - * @param entity The contents of the entity body to be submitted. - * - * @return The status code received in the HTTP response. - */ - protected int submitRequest(String method, String url, - String mediaType, String entityStr) { - int statusCode = 0; - try { - TestServiceClient client = new TestServiceClient(); - if (method.equals(javax.ws.rs.HttpMethod.POST)) { - StringRequestEntity entityBody = - new StringRequestEntity(mediaType, entityStr, NULL_CHARSET); - PostMethod postMethod = new PostMethod(url); - postMethod.setRequestEntity(entityBody); - statusCode = client.getHttpClient().executeMethod(postMethod); - } else if (method.equals(javax.ws.rs.HttpMethod.PUT)) { - StringRequestEntity entityBody = - new StringRequestEntity(mediaType, entityStr, NULL_CHARSET); - PutMethod putMethod = new PutMethod(url); - putMethod.setRequestEntity(entityBody); - statusCode = client.getHttpClient().executeMethod(putMethod); - } else { - // Do nothing - leave status code at default value. - } - } catch (Exception e) { - logger.error( - "Exception during HTTP " + method + " request to " - + url + ":", e); - } - return statusCode; - } - - // @TODO Add Javadoc comments to all methods requiring them, below. - protected String extractId(ClientResponse res) { - MultivaluedMap mvm = res.getMetadata(); - String uri = (String) ((ArrayList) mvm.get("Location")).get(0); - if (logger.isDebugEnabled()) { - logger.debug("extractId:uri=" + uri); - } - String[] segments = uri.split("/"); - String id = segments[segments.length - 1]; - if (logger.isDebugEnabled()) { - logger.debug("id=" + id); - } - return id; - } - - protected String createIdentifier() { - long identifier = System.currentTimeMillis(); - return Long.toString(identifier); - } - - protected String createNonExistentIdentifier() { - return Long.toString(Long.MAX_VALUE); - } - - protected Object extractPart(MultipartInput input, String label, - Class clazz) throws Exception { - Object obj = null; - String partLabel = ""; - List parts = input.getParts(); - if (parts.size() == 0) { - logger.warn("No parts found in multipart body."); - } - if (logger.isDebugEnabled()) { - logger.debug("Parts:"); - for (InputPart part : parts) { - partLabel = part.getHeaders().getFirst("label"); - logger.debug("part = " + partLabel); - } - } - boolean partLabelMatched = false; - for (InputPart part : parts) { - partLabel = part.getHeaders().getFirst("label"); - if (label.equalsIgnoreCase(partLabel)) { - partLabelMatched = true; - if (logger.isDebugEnabled()) { - logger.debug("found part" + partLabel); - } - String partStr = part.getBodyAsString(); - if (partStr == null || partStr.trim().isEmpty()) { - logger.warn("Part '" + label + "' in multipart body is empty."); - } else { - if (logger.isDebugEnabled()) { - logger.debug("extracted part as str=\n" + partStr); - } - obj = part.getBody(clazz, null); - if (logger.isDebugEnabled()) { - logger.debug("extracted part as obj=\n", - objectAsXmlString(obj, clazz)); - } - } - break; - } - } - if (!partLabelMatched) { - logger.warn("Could not find part '" + label + "' in multipart body."); - // In the event that getBodyAsString() or getBody(), above, do *not* - // throw an IOException, but getBody() nonetheless retrieves a null object. - // This *may* be unreachable. - } else if (obj == null) { - logger.warn("Could not extract part '" + label - + "' in multipart body as an object."); - } - return obj; - } - - protected Object getPartObject(String partStr, Class clazz) - throws JAXBException { - JAXBContext jc = JAXBContext.newInstance(clazz); - ByteArrayInputStream bais = null; - Object obj = null; - try { - bais = new ByteArrayInputStream(partStr.getBytes()); - Unmarshaller um = jc.createUnmarshaller(); - obj = um.unmarshal(bais); - } finally { - if (bais != null) { - try { - bais.close(); - } catch (Exception e) { - } - } - } - return obj; - } - - // @TODO Some of the methods below may be candidates - // to be moved to a utilities module, suitable for use - // by both client-side and server-side code. - protected String objectAsXmlString(Object o, Class clazz) { - StringWriter sw = new StringWriter(); - try { - JAXBContext jc = JAXBContext.newInstance(clazz); - Marshaller m = jc.createMarshaller(); - m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, - Boolean.TRUE); - m.marshal(o, sw); - } catch (Exception e) { - e.printStackTrace(); - } - return sw.toString(); - } - - /** - * getObjectFromFile get object of given class from given file (in classpath) - * @param jaxbClass - * @param fileName of the file to read to construct the object - * @return - * @throws Exception - */ - protected Object getObjectFromFile(Class jaxbClass, String fileName) - throws Exception { - - JAXBContext context = JAXBContext.newInstance(jaxbClass); - Unmarshaller unmarshaller = context.createUnmarshaller(); - //note: setting schema to null will turn validator off - unmarshaller.setSchema(null); - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - InputStream is = tccl.getResourceAsStream(fileName); - return getObjectFromStream(jaxbClass, is); - } - - /** - * getObjectFromStream get object of given class from given inputstream - * @param jaxbClass - * @param is stream to read to construct the object - * @return - * @throws Exception - */ - protected Object getObjectFromStream(Class jaxbClass, InputStream is) throws Exception { - JAXBContext context = JAXBContext.newInstance(jaxbClass); - Unmarshaller unmarshaller = context.createUnmarshaller(); - //note: setting schema to null will turn validator off - unmarshaller.setSchema(null); - return jaxbClass.cast(unmarshaller.unmarshal(is)); - } - - protected String mapAsString(MultivaluedMap map) { - StringBuffer sb = new StringBuffer(); - for (Object entry : map.entrySet()) { - MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry; - sb.append(" name=" + mentry.getKey()); - sb.append(" value=" + mentry.getValue() + "\n"); - } - return sb.toString(); - } - - protected void banner(String label) { - if (logger.isDebugEnabled()) { - logger.debug("==================================================="); - logger.debug(" Test = " + label); - logger.debug("==================================================="); - } - } } diff --git a/services/client/src/main/java/org/collectionspace/services/client/test/BaseServiceTest.java b/services/client/src/main/java/org/collectionspace/services/client/test/BaseServiceTest.java new file mode 100644 index 000000000..1d4cfbd3c --- /dev/null +++ b/services/client/src/main/java/org/collectionspace/services/client/test/BaseServiceTest.java @@ -0,0 +1,378 @@ +package org.collectionspace.services.client.test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.collectionspace.services.client.TestServiceClient; +import org.jboss.resteasy.client.ClientResponse; +import org.jboss.resteasy.plugins.providers.multipart.InputPart; +import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.DataProvider; + +public abstract class BaseServiceTest { + + protected final Logger logger = LoggerFactory.getLogger(BaseServiceTest.class); + protected static final TestServiceClient serviceClient = new TestServiceClient(); + protected final String NON_EXISTENT_ID = createNonExistentIdentifier(); + protected int EXPECTED_STATUS_CODE = 0; + protected ServiceRequestType REQUEST_TYPE = ServiceRequestType.NON_EXISTENT; + protected static final String XML_DECLARATION = ""; + protected static final String MALFORMED_XML_DATA = XML_DECLARATION + + "wrong schema contentswrong schema contents"; + final String NULL_CHARSET = null; + + /** + * Returns the name of the currently running test. + * + * Note: although the return type is listed as Object[][], + * this method instead returns a String. + * + * @param m The currently running test method. + * + * @return The name of the currently running test method. + */ + @DataProvider(name = "testName") + public static Object[][] testName(Method m) { + return new Object[][]{ + new Object[] { m.getName() } + }; + } + + /** + * Returns the URL path component of the service. + * + * This component will follow directly after the + * base path, if any. + * + * @return The URL path component of the service. + */ + protected abstract String getServicePathComponent(); + + /** + * Reinitializes setup values, to help expose any unintended reuse + * of those values between tests. + */ + protected void clearSetup() { + EXPECTED_STATUS_CODE = 0; + REQUEST_TYPE = ServiceRequestType.NON_EXISTENT; + } + + /** + * Initializes setup valuesfor a given test. + */ + protected void testSetup( + int expectedStatusCode, + ServiceRequestType reqType, + String bannerLabel ) { + clearSetup(); + EXPECTED_STATUS_CODE = expectedStatusCode; + REQUEST_TYPE = reqType; + // Print a banner identifying the test that will be run. + if (logger.isDebugEnabled()) { + banner(bannerLabel); + } + } + + + public BaseServiceTest() { + super(); + } + + /** + * Returns an error message indicating that the status code returned by a + * specific call to a service does not fall within a set of valid status + * codes for that service. + * + * @param serviceRequestType A type of service request (e.g. CREATE, DELETE). + * + * @param statusCode The invalid status code that was returned in the response, + * from submitting that type of request to the service. + * + * @return An error message. + */ + protected String invalidStatusCodeMessage(ServiceRequestType requestType, int statusCode) { + return "Status code '" + statusCode + + "' in response is NOT within the expected set: " + + requestType.validStatusCodesAsString(); + } + + /** + * Returns the root URL for a service. + * + * This URL consists of a base URL for all services, followed by + * a path component (or components) for a service. + * + * @return The root URL for a service. + */ + protected String getServiceRootURL() { + return serviceClient.getBaseURL() + getServicePathComponent(); + } + + /** + * Returns the URL of a specific resource managed by a service, and + * designated by an identifier (such as a universally unique ID, or UUID). + * + * @param resourceIdentifier An identifier (such as a UUID) for a resource. + * + * @return The URL of a specific resource managed by a service. + */ + protected String getResourceURL(String resourceIdentifier) { + return getServiceRootURL() + "/" + resourceIdentifier; + } + + /** + * Submits an HTTP request to a specified URL, and returns the + * status code of the response. Currently accepts GET and DELETE + * requests. + * + * @param method An HTTP method. + * + * @param url A String representation of a URL. + * + * @return The status code received in the HTTP response. + */ + protected int submitRequest(String method, String url) { + int statusCode = 0; + try{ + TestServiceClient client = new TestServiceClient(); + if(method.equals(javax.ws.rs.HttpMethod.DELETE)){ + DeleteMethod deleteMethod = new DeleteMethod(url); + statusCode = client.getHttpClient().executeMethod(deleteMethod); + }else if(method.equals(javax.ws.rs.HttpMethod.GET)){ + GetMethod getMethod = new GetMethod(url); + statusCode = client.getHttpClient().executeMethod(getMethod); + }else{ + // Do nothing - leave status code at default value. + } + }catch(Exception e){ + logger.error( + "Exception during HTTP " + method + " request to " + + url + ":", e); + } + return statusCode; + } + + /** + * Submits an HTTP request to a specified URL, with the submitted + * entity body, and returns the status code of the response. + * Currently accepts POST and PUT requests. + * + * @param method An HTTP method. + * + * @param url A String representation of a URL. + * + * @param mediaType The media type of the entity body to be submitted. + * + * @param entity The contents of the entity body to be submitted. + * + * @return The status code received in the HTTP response. + */ + protected int submitRequest(String method, String url, String mediaType, + String entityStr) { + int statusCode = 0; + try{ + TestServiceClient client = new TestServiceClient(); + if(method.equals(javax.ws.rs.HttpMethod.POST)){ + StringRequestEntity entityBody = + new StringRequestEntity(mediaType, entityStr, NULL_CHARSET); + PostMethod postMethod = new PostMethod(url); + postMethod.setRequestEntity(entityBody); + statusCode = client.getHttpClient().executeMethod(postMethod); + }else if(method.equals(javax.ws.rs.HttpMethod.PUT)){ + StringRequestEntity entityBody = + new StringRequestEntity(mediaType, entityStr, NULL_CHARSET); + PutMethod putMethod = new PutMethod(url); + putMethod.setRequestEntity(entityBody); + statusCode = client.getHttpClient().executeMethod(putMethod); + }else{ + // Do nothing - leave status code at default value. + } + }catch(Exception e){ + logger.error( + "Exception during HTTP " + method + " request to " + + url + ":", e); + } + return statusCode; + } + + protected String extractId(ClientResponse res) { + MultivaluedMap mvm = res.getMetadata(); + String uri = (String) ((ArrayList) mvm.get("Location")).get(0); + if(logger.isDebugEnabled()){ + logger.debug("extractId:uri=" + uri); + } + String[] segments = uri.split("/"); + String id = segments[segments.length - 1]; + if(logger.isDebugEnabled()){ + logger.debug("id=" + id); + } + return id; + } + + protected String createIdentifier() { + long identifier = System.currentTimeMillis(); + return Long.toString(identifier); + } + + protected String createNonExistentIdentifier() { + return Long.toString(Long.MAX_VALUE); + } + + protected Object extractPart(MultipartInput input, String label, Class clazz) + throws Exception { + Object obj = null; + String partLabel = ""; + List parts = input.getParts(); + if (parts.size() == 0) { + logger.warn("No parts found in multipart body."); + } + if(logger.isDebugEnabled()){ + logger.debug("Parts:"); + for(InputPart part : parts){ + partLabel = part.getHeaders().getFirst("label"); + logger.debug("part = " + partLabel); + } + } + boolean partLabelMatched = false; + for(InputPart part : parts){ + partLabel = part.getHeaders().getFirst("label"); + if(label.equalsIgnoreCase(partLabel)){ + partLabelMatched = true; + if(logger.isDebugEnabled()){ + logger.debug("found part" + partLabel); + } + String partStr = part.getBodyAsString(); + if (partStr == null || partStr.trim().isEmpty()) { + logger.warn("Part '" + label + "' in multipart body is empty."); + } else { + if (logger.isDebugEnabled()){ + logger.debug("extracted part as str=\n" + partStr); + } + obj = part.getBody(clazz, null); + if(logger.isDebugEnabled()){ + logger.debug("extracted part as obj=\n", + objectAsXmlString(obj, clazz)); + } + } + break; + } + } + if (! partLabelMatched) { + logger.warn("Could not find part '" + label + "' in multipart body."); + // In the event that getBodyAsString() or getBody(), above, do *not* + // throw an IOException, but getBody() nonetheless retrieves a null object. + // This *may* be unreachable. + } else if (obj == null) { + logger.warn("Could not extract part '" + label + + "' in multipart body as an object."); + } + return obj; + } + + protected Object getPartObject(String partStr, Class clazz) + throws JAXBException { + JAXBContext jc = JAXBContext.newInstance(clazz); + ByteArrayInputStream bais = null; + Object obj = null; + try{ + bais = new ByteArrayInputStream(partStr.getBytes()); + Unmarshaller um = jc.createUnmarshaller(); + obj = um.unmarshal(bais); + }finally{ + if(bais != null){ + try{ + bais.close(); + }catch(Exception e){ + } + } + } + return obj; + } + + protected String objectAsXmlString(Object o, Class clazz) { + StringWriter sw = new StringWriter(); + try{ + JAXBContext jc = JAXBContext.newInstance(clazz); + Marshaller m = jc.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, + Boolean.TRUE); + m.marshal(o, sw); + }catch(Exception e){ + e.printStackTrace(); + } + return sw.toString(); + } + + /** + * getObjectFromFile get object of given class from given file (in classpath) + * @param jaxbClass + * @param fileName of the file to read to construct the object + * @return + * @throws Exception + */ + protected Object getObjectFromFile(Class jaxbClass, String fileName) + throws Exception { + + JAXBContext context = JAXBContext.newInstance(jaxbClass); + Unmarshaller unmarshaller = context.createUnmarshaller(); + //note: setting schema to null will turn validator off + unmarshaller.setSchema(null); + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + InputStream is = tccl.getResourceAsStream(fileName); + return getObjectFromStream(jaxbClass, is); + } + + /** + * getObjectFromStream get object of given class from given inputstream + * @param jaxbClass + * @param is stream to read to construct the object + * @return + * @throws Exception + */ + protected Object getObjectFromStream(Class jaxbClass, InputStream is) throws Exception { + JAXBContext context = JAXBContext.newInstance(jaxbClass); + Unmarshaller unmarshaller = context.createUnmarshaller(); + //note: setting schema to null will turn validator off + unmarshaller.setSchema(null); + return jaxbClass.cast(unmarshaller.unmarshal(is)); + } + protected String mapAsString(MultivaluedMap map) { + StringBuffer sb = new StringBuffer(); + for(Object entry : map.entrySet()){ + MultivaluedMap.Entry mentry = (MultivaluedMap.Entry) entry; + sb.append(" name=" + mentry.getKey()); + sb.append(" value=" + mentry.getValue() + "\n"); + } + return sb.toString(); + } + + protected void banner(String label) { + if(logger.isDebugEnabled()){ + logger.debug("==================================================="); + logger.debug(" Test = " + label); + logger.debug("==================================================="); + } + } + +} \ No newline at end of file diff --git a/services/common/src/main/config/tenant-bindings.xml b/services/common/src/main/config/tenant-bindings.xml index 198e8a7b6..8345e9b06 100644 --- a/services/common/src/main/config/tenant-bindings.xml +++ b/services/common/src/main/config/tenant-bindings.xml @@ -4,9 +4,11 @@ Created on : August 31, 2009, 10:52 AM Description: tenant bindings --> - + + authRefcurrentOwner + authRefdepositor + authRefconditionCheckAssesor + authRefinsurer + authReffieldCollector + authRefvaluer + } return objectPartMap; } + + public List getPropertiesForPart(String partLabel) { + Map partMap = getPartsMetadata(); + ObjectPartType part = partMap.get(partLabel); + if(part==null) { + throw new RuntimeException("No such part found: "+partLabel); + } + return part.getProperties(); + } + + public List getPropertyValuesForPart(String partLabel, String propName) { + List allProps = getPropertiesForPart(partLabel); + List values = new ArrayList(); + if(allProps.size()>0) { + List propItems = allProps.get(0).getItem(); + for(PropertyItemType propItem:propItems) { + if(propName.equals(propItem.getKey())) { + String value = propItem.getValue(); + if(value!=null) { + values.add(value); + } + } + } + } + return values; + } + + public List getCommonPartProperties() { + return getPropertiesForPart(getCommonPartLabel()); + } + + public List getCommonPartPropertyValues(String propName) { + return getPropertyValuesForPart(getCommonPartLabel(), propName); + } @Override public String getQualifiedServiceName() { diff --git a/services/common/src/main/java/org/collectionspace/services/common/repository/RepositoryClient.java b/services/common/src/main/java/org/collectionspace/services/common/repository/RepositoryClient.java index 451c3518c..dd23f69ca 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/repository/RepositoryClient.java +++ b/services/common/src/main/java/org/collectionspace/services/common/repository/RepositoryClient.java @@ -23,7 +23,12 @@ */ package org.collectionspace.services.common.repository; +import org.collectionspace.services.common.context.ServiceContext; +import org.collectionspace.services.common.document.DocumentException; +import org.collectionspace.services.common.document.DocumentNotFoundException; +import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.common.storage.StorageClient; +import org.nuxeo.ecm.core.api.DocumentModel; /** * RepositoryClient is a generic Document Repository client @@ -54,4 +59,16 @@ public interface RepositoryClient extends StorageClient { * @throws java.lang.Exception */ public String getWorkspaceId(String tenantDomain, String workspaceName) throws Exception; + + /** + * get wrapped documentModel from the Nuxeo repository + * @param ctx service context under which this method is invoked + * @param id + * of the document to retrieve + * @throws DocumentException + */ + public DocumentWrapper getDoc( + ServiceContext ctx, String id) + throws DocumentNotFoundException, DocumentException; + } diff --git a/services/common/src/main/java/org/collectionspace/services/common/vocabulary/RefNameUtils.java b/services/common/src/main/java/org/collectionspace/services/common/vocabulary/RefNameUtils.java new file mode 100644 index 000000000..59368cac6 --- /dev/null +++ b/services/common/src/main/java/org/collectionspace/services/common/vocabulary/RefNameUtils.java @@ -0,0 +1,213 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + + * http://www.collectionspace.org + * http://wiki.collectionspace.org + + * Copyright 2009 University of California at Berkeley + + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + + * You may obtain a copy of the ECL 2.0 License at + + * https://source.collectionspace.org/collection-space/LICENSE.txt + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.collectionspace.services.common.vocabulary; + +import org.collectionspace.services.common.document.AbstractMultipartDocumentHandlerImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * RefNameUtils is a collection of utilities related to refName URN strings + * refNames are URNs that reference a document entity, often an authority or + * authority term. They are strings that take the form (for authorities): + * urn:cspace:org.collectionspace.demo:vocabulary:name(Entry Methods)'Entry Methods' + * or the form (for authority terms): + * urn:cspace:org.collectionspace.demo:vocabulary:name(Entry Methods):item:name(Loan)'Loan' + * + * $LastChangedRevision: $ + * $LastChangedDate: $ + */ +public class RefNameUtils { + + private final Logger logger = LoggerFactory.getLogger(RefNameUtils.class); + + private static final String URN_PREFIX = "urn:cspace:"; + // FIXME Should not be hard-coded + private static final String ITEMS_REGEX = "item|person|organization"; + // In a list of tokens, these are indices for each part + private static final int DOMAIN_TOKEN = 0; // e.g., 'org.collectionspace.demo' + private static final int RESOURCE_TOKEN = 1; // vocabulary, personauthority, etc. + private static final int AUTH_INSTANCE_TOKEN = 2; // name(Entry Methods)'Entry Methods' + private static final int ITEMS_TOKEN = 3; // 'item', 'person', etc. + private static final int ITEM_INSTANCE_TOKEN = 4; // name(Entry Methods)'Entry Methods' + // Tokenizing the INSTANCE, these are indices for each item-part + private static final int INSTANCE_SPEC_TYPE_TOKEN = 0; // 'name' or 'id' + private static final int INSTANCE_SPEC_TOKEN = 1; // name or id value + private static final int INSTANCE_DISPLAYNAME_TOKEN = 2;// optional displayName suffix + private static final int INSTANCE_TOKENS_MIN = 2; + private static final int INSTANCE_TOKENS_MAX = 3; + private static final int prefixLen = 11; + private static final String SEPARATOR = ":"; + + public static class AuthorityInfo { + private final Logger logger = LoggerFactory.getLogger(AuthorityInfo.class); + private static int MIN_TOKENS = 3; + public String domain; + public String resource; + public String csid; + public String name; + public String displayName; + + public AuthorityInfo(String refNameTokens[]) throws Exception { + try { + if(refNameTokens.length < MIN_TOKENS) { + throw new IllegalArgumentException("Malformed refName for Authority (too few tokens)"); + } + this.domain = refNameTokens[DOMAIN_TOKEN]; + this.resource = refNameTokens[RESOURCE_TOKEN]; + String idTokens[] = refNameTokens[AUTH_INSTANCE_TOKEN].split("[()]", INSTANCE_TOKENS_MAX); + if(idTokens.length getDoc( + ServiceContext ctx, String id) + throws DocumentNotFoundException, DocumentException { + RepositoryInstance repoSession = null; + DocumentWrapper wrapDoc = null; + + try { + repoSession = getRepositorySession(); + DocumentRef docRef = NuxeoUtils.createPathRef(ctx, id); + DocumentModel doc = null; + try { + doc = repoSession.getDocument(docRef); + } catch (ClientException ce) { + String msg = "could not find document with id=" + id; + logger.error(msg, ce); + throw new DocumentNotFoundException(msg, ce); + } + wrapDoc = new DocumentWrapperImpl(doc); + } catch (IllegalArgumentException iae) { + throw iae; + } catch (DocumentException de) { + throw de; + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Caught exception ", e); + } + throw new DocumentException(e); + } finally { + if (repoSession != null) { + releaseRepositorySession(repoSession); + } + } + return wrapDoc; + } + @Override public void get(ServiceContext ctx, List csidList, DocumentHandler handler) throws DocumentNotFoundException, DocumentException { diff --git a/services/common/src/main/resources/authorityref.xsd b/services/common/src/main/resources/authorityref.xsd new file mode 100644 index 000000000..5e23bfed9 --- /dev/null +++ b/services/common/src/main/resources/authorityref.xsd @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/intake/client/pom.xml b/services/intake/client/pom.xml index 011f1eb3f..866d4a5d9 100644 --- a/services/intake/client/pom.xml +++ b/services/intake/client/pom.xml @@ -37,6 +37,11 @@ org.collectionspace.services.client ${cspace.services.client.version} + + org.collectionspace.services + org.collectionspace.services.person.client + ${cspace.services.client.version} + org.testng diff --git a/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeClient.java b/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeClient.java index 860206680..1ede8b3eb 100644 --- a/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeClient.java +++ b/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeClient.java @@ -26,8 +26,11 @@ */ package org.collectionspace.services.client; +import javax.ws.rs.PathParam; import javax.ws.rs.core.Response; +import org.collectionspace.services.common.authorityref.AuthorityRefList; +import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.intake.IntakesCommonList; import org.jboss.resteasy.client.ProxyFactory; @@ -94,16 +97,26 @@ public class IntakeClient extends AbstractServiceClientImpl { /** * @return - * @see org.collectionspace.hello.client.IntakeProxy#getIntake() + * @see org.collectionspace.services.client.IntakeProxy#getIntake() */ public ClientResponse readList() { return intakeProxy.readList(); } + + /** + * @param csid + * @return + * @see org.collectionspace.services.client.IntakeProxy#getAuthorityRefs(java.lang.String) + */ + public ClientResponse getAuthorityRefs(String csid) { + return intakeProxy.getAuthorityRefs(csid); + } + /** * @param csid * @return - * @see org.collectionspace.hello.client.IntakeProxy#getIntake(java.lang.String) + * @see org.collectionspace.services.client.IntakeProxy#getIntake(java.lang.String) */ public ClientResponse read(String csid) { return intakeProxy.read(csid); @@ -112,7 +125,7 @@ public class IntakeClient extends AbstractServiceClientImpl { /** * @param intake * @return - * @see org.collectionspace.hello.client.IntakeProxy#createIntake(org.collectionspace.hello.Intake) + * @see org.collectionspace.services.client.IntakeProxy#createIntake(org.collectionspace.hello.Intake) */ public ClientResponse create(MultipartOutput multipart) { return intakeProxy.create(multipart); @@ -122,7 +135,7 @@ public class IntakeClient extends AbstractServiceClientImpl { * @param csid * @param intake * @return - * @see org.collectionspace.hello.client.IntakeProxy#updateIntake(java.lang.Long, org.collectionspace.hello.Intake) + * @see org.collectionspace.services.client.IntakeProxy#updateIntake(java.lang.Long, org.collectionspace.hello.Intake) */ public ClientResponse update(String csid, MultipartOutput multipart) { return intakeProxy.update(csid, multipart); @@ -132,7 +145,7 @@ public class IntakeClient extends AbstractServiceClientImpl { /** * @param csid * @return - * @see org.collectionspace.hello.client.IntakeProxy#deleteIntake(java.lang.Long) + * @see org.collectionspace.services.client.IntakeProxy#deleteIntake(java.lang.Long) */ public ClientResponse delete(String csid) { return intakeProxy.delete(csid); diff --git a/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeProxy.java b/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeProxy.java index fdf0e2cf7..4ac7ca434 100644 --- a/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeProxy.java +++ b/services/intake/client/src/main/java/org/collectionspace/services/client/IntakeProxy.java @@ -10,7 +10,9 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; +import org.collectionspace.services.common.authorityref.AuthorityRefList; import org.collectionspace.services.intake.IntakesCommonList; +import org.collectionspace.services.person.PersonsCommonList; import org.jboss.resteasy.client.ClientResponse; import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput; @@ -45,4 +47,11 @@ public interface IntakeProxy { @DELETE @Path("/{csid}") ClientResponse delete(@PathParam("csid") String csid); + + // List Items + @GET + @Produces({"application/xml"}) + @Path("/{csid}/authorityrefs/") + ClientResponse getAuthorityRefs(@PathParam("csid") String csid); + } diff --git a/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java b/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java new file mode 100644 index 000000000..3693342d2 --- /dev/null +++ b/services/intake/client/src/test/java/org/collectionspace/services/client/test/IntakeAuthRefsTest.java @@ -0,0 +1,324 @@ +/** + * This document is a part of the source code and related artifacts + * for CollectionSpace, an open source collections management system + * for museums and related institutions: + * + * http://www.collectionspace.org + * http://wiki.collectionspace.org + * + * Copyright © 2009 Regents of the University of California + * + * Licensed under the Educational Community License (ECL), Version 2.0. + * You may not use this file except in compliance with this License. + * + * You may obtain a copy of the ECL 2.0 License at + * https://source.collectionspace.org/collection-space/LICENSE.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.collectionspace.services.client.test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.collectionspace.services.PersonJAXBSchema; +import org.collectionspace.services.client.IntakeClient; +import org.collectionspace.services.client.PersonAuthorityClient; +import org.collectionspace.services.client.PersonAuthorityClientUtils; +import org.collectionspace.services.common.authorityref.AuthorityRefList; +import org.collectionspace.services.intake.IntakesCommon; +import org.collectionspace.services.intake.IntakesCommonList; + +import org.jboss.resteasy.client.ClientResponse; + +import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; +import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput; +import org.jboss.resteasy.plugins.providers.multipart.OutputPart; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * IntakeAuthRefsTest, carries out tests against a + * deployed and running Intake Service. + * + * $LastChangedRevision: 1327 $ + * $LastChangedDate: 2010-02-12 10:35:11 -0800 (Fri, 12 Feb 2010) $ + */ +public class IntakeAuthRefsTest extends BaseServiceTest { + + private final Logger logger = + LoggerFactory.getLogger(IntakeAuthRefsTest.class); + + // Instance variables specific to this test. + private IntakeClient intakeClient = new IntakeClient(); + private PersonAuthorityClient personAuthClient = new PersonAuthorityClient(); + final String SERVICE_PATH_COMPONENT = "intakes"; + final String PERSON_AUTHORITY_NAME = "TestPersonAuth"; + private String knownResourceId = null; + private List intakeIdsCreated = new ArrayList(); + private List personIdsCreated = new ArrayList(); + private int CREATED_STATUS = Response.Status.CREATED.getStatusCode(); + private int OK_STATUS = Response.Status.OK.getStatusCode(); + private String personAuthCSID = null; + private String currentOwnerRefName = null; + private String depositorRefName = null; + private String conditionCheckAssesorRefName = null; + private String insurerRefName = null; + private String fieldCollectorRefName = null; + private String valuerRefName = null; + private final int NUM_AUTH_REFS_EXPECTED = 6; + + // --------------------------------------------------------------- + // CRUD tests : CREATE tests + // --------------------------------------------------------------- + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class) + public void createWithAuthRefs(String testName) throws Exception { + + testSetup(CREATED_STATUS, ServiceRequestType.CREATE,testName); + + // Submit the request to the service and store the response. + String identifier = createIdentifier(); + + // Create all the person refs and entities + createPersonRefs(); + + MultipartOutput multipart = createIntakeInstance( + "entryNumber-" + identifier, + "entryDate-" + identifier, + currentOwnerRefName, + depositorRefName, + conditionCheckAssesorRefName, + insurerRefName, + fieldCollectorRefName, + valuerRefName ); + + ClientResponse res = intakeClient.create(multipart); + + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + // + // Specifically: + // 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); + + // Store the ID returned from the first resource created + // for additional tests below. + if (knownResourceId == null){ + knownResourceId = extractId(res); + if (logger.isDebugEnabled()) { + logger.debug(testName + ": knownResourceId=" + knownResourceId); + } + } + + // Store the IDs from every resource created by tests, + // so they can be deleted after tests have been run. + intakeIdsCreated.add(extractId(res)); + } + + protected void createPersonRefs(){ + String authRefName = + PersonAuthorityClientUtils.createPersonAuthRefName(PERSON_AUTHORITY_NAME, false); + MultipartOutput multipart = PersonAuthorityClientUtils.createPersonAuthorityInstance( + PERSON_AUTHORITY_NAME, authRefName, personAuthClient.getCommonPartName()); + ClientResponse res = personAuthClient.create(multipart); + int statusCode = res.getStatus(); + + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, CREATED_STATUS); + personAuthCSID = extractId(res); + + currentOwnerRefName = PersonAuthorityClientUtils.createPersonRefName( + authRefName, "Olivier Owner", true); + personIdsCreated.add(createPerson("Olivier", "Owner", currentOwnerRefName)); + + depositorRefName = PersonAuthorityClientUtils.createPersonRefName( + authRefName, "Debbie Depositor", true); + personIdsCreated.add(createPerson("Debbie", "Depositor", depositorRefName)); + + conditionCheckAssesorRefName = PersonAuthorityClientUtils.createPersonRefName( + authRefName, "Andrew Assessor", true); + personIdsCreated.add(createPerson("Andrew", "Assessor", conditionCheckAssesorRefName)); + + insurerRefName = PersonAuthorityClientUtils.createPersonRefName( + authRefName, "Ingrid Insurer", true); + personIdsCreated.add(createPerson("Ingrid", "Insurer", insurerRefName)); + + fieldCollectorRefName = PersonAuthorityClientUtils.createPersonRefName( + authRefName, "Connie Collector", true); + personIdsCreated.add(createPerson("Connie", "Collector", fieldCollectorRefName)); + + valuerRefName = PersonAuthorityClientUtils.createPersonRefName( + authRefName, "Vince Valuer", true); + personIdsCreated.add(createPerson("Vince", "Valuer", valuerRefName)); + + + } + + protected String createPerson(String firstName, String surName, String refName ) { + Map personInfo = new HashMap(); + personInfo.put(PersonJAXBSchema.FORE_NAME, firstName); + personInfo.put(PersonJAXBSchema.SUR_NAME, surName); + MultipartOutput multipart = + PersonAuthorityClientUtils.createPersonInstance(personAuthCSID, + refName, personInfo, personAuthClient.getItemCommonPartName()); + ClientResponse res = personAuthClient.createItem(personAuthCSID, multipart); + int statusCode = res.getStatus(); + + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, CREATED_STATUS); + return extractId(res); + } + + // Success outcomes + @Test(dataProvider="testName", dataProviderClass=AbstractServiceTestImpl.class, + dependsOnMethods = {"createWithAuthRefs"}) + public void readAndCheckAuthRefs(String testName) throws Exception { + + // Perform setup. + testSetup(OK_STATUS, ServiceRequestType.READ,testName); + + // Submit the request to the service and store the response. + ClientResponse res = intakeClient.read(knownResourceId); + int statusCode = res.getStatus(); + + // Check the status code of the response: does it match + // the expected response(s)? + if(logger.isDebugEnabled()){ + logger.debug(testName + ".read: status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + + MultipartInput input = (MultipartInput) res.getEntity(); + IntakesCommon intake = (IntakesCommon) extractPart(input, + intakeClient.getCommonPartName(), IntakesCommon.class); + Assert.assertNotNull(intake); + // Check a couple of fields + Assert.assertEquals(intake.getCurrentOwner(), currentOwnerRefName); + Assert.assertEquals(intake.getInsurer(), insurerRefName); + + // Get the auth refs and check them + ClientResponse res2 = intakeClient.getAuthorityRefs(knownResourceId); + statusCode = res2.getStatus(); + + if(logger.isDebugEnabled()){ + logger.debug(testName + ".getAuthorityRefs: status = " + statusCode); + } + Assert.assertTrue(REQUEST_TYPE.isValidStatusCode(statusCode), + invalidStatusCodeMessage(REQUEST_TYPE, statusCode)); + Assert.assertEquals(statusCode, EXPECTED_STATUS_CODE); + AuthorityRefList list = res2.getEntity(); + + // Optionally output additional data about list members for debugging. + boolean iterateThroughList = true; + if(iterateThroughList && logger.isDebugEnabled()){ + List items = + list.getAuthorityRefItem(); + int i = 0; + for(AuthorityRefList.AuthorityRefItem item : items){ + logger.debug(testName + ": list-item[" + i + "] Field:" + + item.getSourceField() + "= " + + item.getAuthDisplayName() + + item.getItemDisplayName()); + logger.debug(testName + ": list-item[" + i + "] refName=" + + item.getRefName()); + logger.debug(testName + ": list-item[" + i + "] URI=" + + item.getUri()); + i++; + } + Assert.assertEquals(i, NUM_AUTH_REFS_EXPECTED, "Did not find all authrefs!"); + } + } + + + // --------------------------------------------------------------- + // Cleanup of resources created during testing + // --------------------------------------------------------------- + + /** + * Deletes all resources created by tests, after all tests have been run. + * + * This cleanup method will always be run, even if one or more tests fail. + * For this reason, it attempts to remove all resources created + * at any point during testing, even if some of those resources + * may be expected to be deleted by certain tests. + */ + // @AfterClass(alwaysRun=true) + public void cleanUp() { + if (logger.isDebugEnabled()) { + logger.debug("Cleaning up temporary resources created for testing ..."); + } + // Note: Any non-success responses are ignored and not reported. + for (String resourceId : intakeIdsCreated) { + ClientResponse res = intakeClient.delete(resourceId); + } + // Delete persons before PersonAuth + for (String resourceId : personIdsCreated) { + ClientResponse res = personAuthClient.deleteItem(personAuthCSID, resourceId); + } + ClientResponse res = personAuthClient.delete(personAuthCSID); + } + + // --------------------------------------------------------------- + // Utility methods used by tests above + // --------------------------------------------------------------- + @Override + public String getServicePathComponent() { + return SERVICE_PATH_COMPONENT; + } + + private MultipartOutput createIntakeInstance(String entryNumber, + String entryDate, + String currentOwner, + String depositor, + String conditionCheckAssesor, + String insurer, + String fieldCollector, + String Valuer ) { + IntakesCommon intake = new IntakesCommon(); + intake.setEntryNumber(entryNumber); + intake.setEntryDate(entryDate); + intake.setCurrentOwner(currentOwner); + intake.setDepositor(depositor); + intake.setConditionCheckAssesor(conditionCheckAssesor); + intake.setInsurer(insurer); + intake.setFieldCollector(fieldCollector); + intake.setValuer(Valuer); + MultipartOutput multipart = new MultipartOutput(); + OutputPart commonPart = + multipart.addPart(intake, MediaType.APPLICATION_XML_TYPE); + commonPart.getHeaders().add("label", intakeClient.getCommonPartName()); + + if(logger.isDebugEnabled()){ + logger.debug("to be created, intake common"); + logger.debug(objectAsXmlString(intake, IntakesCommon.class)); + } + + return multipart; + } +} diff --git a/services/intake/service/src/main/java/org/collectionspace/services/intake/IntakeResource.java b/services/intake/service/src/main/java/org/collectionspace/services/intake/IntakeResource.java index 1a0326280..e6bd2f786 100644 --- a/services/intake/service/src/main/java/org/collectionspace/services/intake/IntakeResource.java +++ b/services/intake/service/src/main/java/org/collectionspace/services/intake/IntakeResource.java @@ -23,7 +23,10 @@ */ package org.collectionspace.services.intake; +import java.util.Iterator; import java.util.List; +import java.util.Map; + import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -39,22 +42,32 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; +import org.collectionspace.services.IntakeJAXBSchema; import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl; +import org.collectionspace.services.common.authorityref.AuthorityRefList; import org.collectionspace.services.common.ClientType; import org.collectionspace.services.common.ServiceMain; import org.collectionspace.services.common.context.MultipartServiceContext; import org.collectionspace.services.common.context.MultipartServiceContextFactory; +import org.collectionspace.services.common.context.MultipartServiceContextImpl; import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentFilter; import org.collectionspace.services.common.document.DocumentNotFoundException; import org.collectionspace.services.common.document.DocumentHandler; +import org.collectionspace.services.common.document.DocumentWrapper; import org.collectionspace.services.common.query.IQueryManager; import org.collectionspace.services.common.query.QueryManager; import org.collectionspace.services.common.security.UnauthorizedException; +import org.collectionspace.services.common.service.ObjectPartType; +import org.collectionspace.services.common.vocabulary.RefNameUtils; +import org.collectionspace.services.intake.IntakesCommonList.IntakeListItem; +import org.collectionspace.services.intake.nuxeo.IntakeDocumentModelHandler; +import org.collectionspace.services.nuxeo.util.NuxeoUtils; import org.jboss.resteasy.plugins.providers.multipart.MultipartInput; import org.jboss.resteasy.plugins.providers.multipart.MultipartOutput; import org.jboss.resteasy.util.HttpResponseCodes; +import org.nuxeo.ecm.core.api.DocumentModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -194,6 +207,36 @@ public class IntakeResource extends AbstractCollectionSpaceResourceImpl { return intakeObjectList; } + @GET + @Path("{csid}/authorityrefs") + @Produces("application/xml") + public AuthorityRefList getAuthorityRefs( + @PathParam("csid") String csid, + @Context UriInfo ui) { + AuthorityRefList authRefList = null; + try { + ServiceContext ctx = MultipartServiceContextFactory.get().createServiceContext(null, getServiceName()); + DocumentWrapper docWrapper = + getRepositoryClient(ctx).getDoc(ctx, csid); + IntakeDocumentModelHandler handler = (IntakeDocumentModelHandler)createDocumentHandler(ctx); + List authRefFields = ((MultipartServiceContextImpl)ctx).getCommonPartPropertyValues("authRef"); + String prefix = ctx.getCommonPartLabel()+":"; + authRefList = handler.getAuthorityRefs(docWrapper, prefix, authRefFields); + } catch (UnauthorizedException ue) { + Response response = Response.status( + Response.Status.UNAUTHORIZED).entity("Index failed reason " + ue.getErrorReason()).type("text/plain").build(); + throw new WebApplicationException(response); + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Caught exception in getAuthorityRefs", e); + } + Response response = Response.status( + Response.Status.INTERNAL_SERVER_ERROR).entity("Index failed").type("text/plain").build(); + throw new WebApplicationException(response); + } + return authRefList; + } + /** * Gets the intake list. * diff --git a/services/intake/service/src/main/java/org/collectionspace/services/intake/nuxeo/IntakeDocumentModelHandler.java b/services/intake/service/src/main/java/org/collectionspace/services/intake/nuxeo/IntakeDocumentModelHandler.java index e141437a1..15b7c873c 100644 --- a/services/intake/service/src/main/java/org/collectionspace/services/intake/nuxeo/IntakeDocumentModelHandler.java +++ b/services/intake/service/src/main/java/org/collectionspace/services/intake/nuxeo/IntakeDocumentModelHandler.java @@ -26,9 +26,21 @@ package org.collectionspace.services.intake.nuxeo; import java.util.Iterator; import java.util.List; +import javax.ws.rs.PathParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + import org.collectionspace.services.IntakeJAXBSchema; +import org.collectionspace.services.common.authorityref.AuthorityRefList; +import org.collectionspace.services.common.context.MultipartServiceContextFactory; +import org.collectionspace.services.common.context.MultipartServiceContextImpl; +import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.document.DocumentHandler.Action; import org.collectionspace.services.common.document.DocumentWrapper; +import org.collectionspace.services.common.security.UnauthorizedException; +import org.collectionspace.services.common.vocabulary.RefNameUtils; import org.collectionspace.services.intake.IntakesCommon; import org.collectionspace.services.intake.IntakesCommonList; import org.collectionspace.services.intake.IntakesCommonList.IntakeListItem; @@ -140,5 +152,48 @@ public class IntakeDocumentModelHandler public String getQProperty(String prop) { return IntakeConstants.NUXEO_SCHEMA_NAME + ":" + prop; } + + public AuthorityRefList getAuthorityRefs( + DocumentWrapper docWrapper, + String pathPrefix, + List authRefFields) { + AuthorityRefList authRefList = new AuthorityRefList(); + try { + DocumentModel docModel = docWrapper.getWrappedObject(); + List list = + authRefList.getAuthorityRefItem(); + + for(String field:authRefFields){ + String refName = (String)docModel.getPropertyValue(pathPrefix+field); + if(refName==null) + continue; + try{ + RefNameUtils.AuthorityTermInfo termInfo = + RefNameUtils.parseAuthorityTermInfo(refName); + AuthorityRefList.AuthorityRefItem ilistItem = + new AuthorityRefList.AuthorityRefItem(); + ilistItem.setRefName(refName); + ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName); + ilistItem.setItemDisplayName(termInfo.displayName); + ilistItem.setSourceField(field); + ilistItem.setUri(termInfo.getRelativeUri()); + list.add(ilistItem); + } catch( Exception e ) { + if (logger.isDebugEnabled()) { + logger.debug("Caught exception in getAuthorityRefs", e); + } + } + } + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("Caught exception in getIntakeList", e); + } + Response response = Response.status( + Response.Status.INTERNAL_SERVER_ERROR).entity("Index failed").type("text/plain").build(); + throw new WebApplicationException(response); + } + return authRefList; + } + } -- 2.47.3