]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
DRYD-1399: RestrictedMedia (#428)
authorMichael Ritter <mikejritter@users.noreply.github.com>
Thu, 5 Dec 2024 22:05:45 +0000 (15:05 -0700)
committerGitHub <noreply@github.com>
Thu, 5 Dec 2024 22:05:45 +0000 (15:05 -0700)
17 files changed:
services/JaxRsServiceProvider/pom.xml
services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java
services/common/src/main/resources/db/postgresql/load_id_generators.sql
services/pom.xml
services/restrictedmedia/build.xml [new file with mode: 0644]
services/restrictedmedia/client/pom.xml [new file with mode: 0644]
services/restrictedmedia/client/src/main/java/org/collectionspace/services/client/RestrictedMediaClient.java [new file with mode: 0644]
services/restrictedmedia/client/src/main/java/org/collectionspace/services/client/RestrictedMediaProxy.java [new file with mode: 0644]
services/restrictedmedia/client/src/test/java/org/collectionspace/services/client/test/RestrictedMediaAuthRefsTest.java [new file with mode: 0644]
services/restrictedmedia/client/src/test/java/org/collectionspace/services/client/test/RestrictedMediaServiceTest.java [new file with mode: 0644]
services/restrictedmedia/jaxb/pom.xml [new file with mode: 0644]
services/restrictedmedia/jaxb/src/main/resources/restrictedmedia_common.xsd [new file with mode: 0644]
services/restrictedmedia/pom.xml [new file with mode: 0644]
services/restrictedmedia/service/pom.xml [new file with mode: 0644]
services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/RestrictedMediaResource.java [new file with mode: 0644]
services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/nuxeo/RestrictedMediaDocumentModelHandler.java [new file with mode: 0644]
services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/nuxeo/RestrictedMediaValidatorHandler.java [new file with mode: 0644]

index 6c9de17cc26945edb4c8af7263dea56a6bc71da9..89eb6473562a0a345c2a877ad63f2e8aeb9a6f1a 100644 (file)
           <artifactId>org.collectionspace.services.exit.service</artifactId>
           <version>${project.version}</version>
         </dependency>
-
+        <dependency>
+            <groupId>org.collectionspace.services</groupId>
+            <artifactId>org.collectionspace.services.restrictedmedia.service</artifactId>
+            <version>${project.version}</version>
+        </dependency>
 
       <!--dependency>
           <groupId>org.collectionspace.services</groupId>
index dae8b9053efdd508168af9c7bb6e4c633d20e661..bee06845f113469656f5c108e371798f2f1734fa 100644 (file)
@@ -43,6 +43,7 @@ import org.collectionspace.services.loanin.LoaninResource;
 import org.collectionspace.services.loanout.LoanoutResource;
 import org.collectionspace.services.repatriationrequest.RepatriationRequestResource;
 import org.collectionspace.services.nagprainventory.NagpraInventoryResource;
+import org.collectionspace.services.restrictedmedia.RestrictedMediaResource;
 import org.collectionspace.services.summarydocumentation.SummaryDocumentationResource;
 import org.collectionspace.services.transport.TransportResource;
 import org.collectionspace.services.uoc.UocResource;
@@ -178,6 +179,7 @@ public class CollectionSpaceJaxRsApplication extends Application implements Reso
         addResourceToMapAndSingletons(new ConsultationResource());
         addResourceToMapAndSingletons(new DeaccessionResource());
         addResourceToMapAndSingletons(new ExitResource());
+        addResourceToMapAndSingletons(new RestrictedMediaResource());
 
         singletons.add(new IDResource());
 
index 349d5d0175a91112bd3fab8682da1370d52d0b72..4e1b78124eae4c6a3451d8c29dcf098e2254a8cd 100644 (file)
@@ -1342,3 +1342,39 @@ INSERT INTO id_generators
         SELECT  csid
         FROM    id_generators
         );
+
+-- RESTRICTED_MEDIA_NUMBER
+
+INSERT INTO id_generators
+    (csid, displayname, description, priority, last_generated_id, id_generator_state)
+  SELECT
+     '17772cbd-8de1-4969-ac2e-c51798bc4e75',
+     'Restricted Media Number',
+     'Identifies a restricted media document.',
+     '9',
+     '',
+'<org.collectionspace.services.id.SettableIDGenerator>
+  <parts>
+    <org.collectionspace.services.id.StringIDGeneratorPart>
+      <initialValue>RMR</initialValue>
+      <currentValue>RMR</currentValue>
+    </org.collectionspace.services.id.StringIDGeneratorPart>
+    <org.collectionspace.services.id.YearIDGeneratorPart>
+      <currentValue></currentValue>
+    </org.collectionspace.services.id.YearIDGeneratorPart>
+    <org.collectionspace.services.id.StringIDGeneratorPart>
+      <initialValue>.</initialValue>
+      <currentValue>.</currentValue>
+    </org.collectionspace.services.id.StringIDGeneratorPart>
+    <org.collectionspace.services.id.NumericIDGeneratorPart>
+      <maxLength>6</maxLength>
+      <initialValue>1</initialValue>
+      <currentValue>-1</currentValue>
+    </org.collectionspace.services.id.NumericIDGeneratorPart>
+  </parts>
+</org.collectionspace.services.id.SettableIDGenerator>'
+  WHERE '17772cbd-8de1-4969-ac2e-c51798bc4e75' NOT IN
+        (
+        SELECT  csid
+        FROM    id_generators
+        );
index 2caa2eb67821507189be15925671f29a1240c9be..2fb3eaecccd4f87d1857d4fe505c6e01dd411549 100644 (file)
                <module>nagprainventory</module>
                <module>summarydocumentation</module>
                <module>heldintrust</module>
+               <module>restrictedmedia</module>
                <module>consultation</module>
                <module>deaccession</module>
                <module>IntegrationTests</module>
diff --git a/services/restrictedmedia/build.xml b/services/restrictedmedia/build.xml
new file mode 100644 (file)
index 0000000..8f43e91
--- /dev/null
@@ -0,0 +1,106 @@
+<project name="restrictedmedia" default="package" basedir=".">
+  <description>
+    restrictedmedia service
+  </description>
+  <!-- set global properties for this build -->
+  <property name="services.trunk" value="../.." />
+  <!-- environment should be declared before reading build.properties -->
+  <property environment="env" />
+  <property file="${services.trunk}/build.properties" />
+  <property name="mvn.opts" value="-V" />
+  <property name="src" location="src" />
+
+  <condition property="osfamily-unix">
+    <os family="unix" />
+  </condition>
+  <condition property="osfamily-windows">
+    <os family="windows" />
+  </condition>
+
+  <target name="package" depends="package-unix,package-windows"
+    description="Package CollectionSpace Services" />
+
+  <target name="package-unix" if="osfamily-unix">
+    <exec executable="mvn" failonerror="true">
+      <arg value="package" />
+      <arg value="-Dmaven.test.skip=true" />
+      <arg value="-f" />
+      <arg value="${basedir}/pom.xml" />
+      <arg value="-N" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+
+  <target name="package-windows" if="osfamily-windows">
+    <exec executable="cmd" failonerror="true">
+      <arg value="/c" />
+      <arg value="mvn" />
+      <arg value="package" />
+      <arg value="-Dmaven.test.skip=true" />
+      <arg value="-f" />
+      <arg value="${basedir}/pom.xml" />
+      <arg value="-N" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+
+  <target name="install" depends="install-unix,install-windows"
+    description="Install" />
+  <target name="install-unix" if="osfamily-unix">
+    <exec executable="mvn" failonerror="true">
+      <arg value="install" />
+      <arg value="-Dmaven.test.skip=true" />
+      <arg value="-f" />
+      <arg value="${basedir}/pom.xml" />
+      <arg value="-N" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+  <target name="install-windows" if="osfamily-windows">
+    <exec executable="cmd" failonerror="true">
+      <arg value="/c" />
+      <arg value="mvn" />
+      <arg value="install" />
+      <arg value="-Dmaven.test.skip=true" />
+      <arg value="-f" />
+      <arg value="${basedir}/pom.xml" />
+      <arg value="-N" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+
+  <target name="clean" depends="clean-unix,clean-windows"
+    description="Delete target directories">
+    <delete dir="${build}" />
+  </target>
+  <target name="clean-unix" if="osfamily-unix">
+    <exec executable="mvn" failonerror="true">
+      <arg value="clean" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+  <target name="clean-windows" if="osfamily-windows">
+    <exec executable="cmd" failonerror="true">
+      <arg value="/c" />
+      <arg value="mvn" />
+      <arg value="clean" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+
+  <target name="test" depends="test-unix,test-windows" description="Run tests" />
+  <target name="test-unix" if="osfamily-unix">
+    <exec executable="mvn" failonerror="true">
+      <arg value="test" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+  <target name="test-windows" if="osfamily-windows">
+    <exec executable="cmd" failonerror="true">
+      <arg value="/c" />
+      <arg value="mvn" />
+      <arg value="test" />
+      <arg value="${mvn.opts}" />
+    </exec>
+  </target>
+</project>
\ No newline at end of file
diff --git a/services/restrictedmedia/client/pom.xml b/services/restrictedmedia/client/pom.xml
new file mode 100644 (file)
index 0000000..1a3623c
--- /dev/null
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.collectionspace.services</groupId>
+    <artifactId>org.collectionspace.services.restrictedmedia</artifactId>
+    <version>${revision}</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>org.collectionspace.services.restrictedmedia.client</artifactId>
+  <name>services.restrictedmedia.client</name>
+
+  <dependencies>
+    <!-- CollectionSpace dependencies -->
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.jaxb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.blob.client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.restrictedmedia.jaxb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <!-- External dependencies -->
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.resteasy</groupId>
+      <artifactId>resteasy-jaxrs</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>tjws</groupId>
+          <artifactId>webserver</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.resteasy</groupId>
+      <artifactId>resteasy-jaxb-provider</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.resteasy</groupId>
+      <artifactId>resteasy-multipart-provider</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-httpclient</groupId>
+      <artifactId>commons-httpclient</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <finalName>collectionspace-services-restrictedmedia-client</finalName>
+  </build>
+</project>
\ No newline at end of file
diff --git a/services/restrictedmedia/client/src/main/java/org/collectionspace/services/client/RestrictedMediaClient.java b/services/restrictedmedia/client/src/main/java/org/collectionspace/services/client/RestrictedMediaClient.java
new file mode 100644 (file)
index 0000000..d89ba7e
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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
+ *
+ * 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
+ */
+package org.collectionspace.services.client;
+
+import javax.ws.rs.core.Response;
+import org.collectionspace.services.restrictedmedia.RestrictedMediaCommon;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
+
+/**
+ * RestrictedMediaClient.java
+ */
+public class RestrictedMediaClient
+        extends AbstractCommonListPoxServiceClientImpl<RestrictedMediaProxy, RestrictedMediaCommon> {
+
+    public static final String SERVICE_NAME = "restrictedmedia";
+    public static final String SERVICE_PATH_COMPONENT = SERVICE_NAME;
+    public static final String SERVICE_PATH = "/" + SERVICE_PATH_COMPONENT;
+    public static final String SERVICE_PATH_PROXY = SERVICE_PATH + "/";
+    public static final String SERVICE_PAYLOAD_NAME = SERVICE_NAME;
+
+    public RestrictedMediaClient() throws Exception {
+        super();
+    }
+
+    public RestrictedMediaClient(String clientPropertiesFilename) throws Exception {
+        super(clientPropertiesFilename);
+    }
+
+    @Override
+    public String getServicePathComponent() {
+        return SERVICE_PATH_COMPONENT;
+    }
+
+    @Override
+    public String getServiceName() {
+        return SERVICE_NAME;
+    }
+
+    @Override
+    public Class<RestrictedMediaProxy> getProxyClass() {
+        return RestrictedMediaProxy.class;
+    }
+
+    /**
+     * Creates a new blob resource from the form data and associates it with an existing media resource
+     *
+     * @param csid the media resource csid
+     * @param formDataOutput
+     * @return
+     */
+    public Response createBlobFromFormData(String csid, MultipartFormDataOutput formDataOutput) {
+        return getProxy().createBlobFromFormData(csid, formDataOutput);
+    }
+
+    /**
+     * Creates a new blob
+     *
+     * @param csid
+     * @return
+     */
+    public Response createBlobFromUri(String csid, String blobUri) {
+        // send the URI as both a query param and as content
+        return getProxy().createBlobFromUri(csid, blobUri, blobUri);
+    }
+
+    /*
+     * Create both a new media record
+     */
+    public Response createMediaAndBlobWithUri(PoxPayloadOut xmlPayload, String blobUri, boolean purgeOriginal) {
+        return getProxy().createMediaAndBlobWithUri(xmlPayload.getBytes(), blobUri, purgeOriginal);
+    }
+
+    /**
+     * @param csid
+     * @param xmlPayload
+     * @param URI
+     * @return
+     */
+    public Response update(String csid, PoxPayloadOut xmlPayload, String URI) {
+        return getProxy().update(csid, xmlPayload.getBytes());
+    }
+}
diff --git a/services/restrictedmedia/client/src/main/java/org/collectionspace/services/client/RestrictedMediaProxy.java b/services/restrictedmedia/client/src/main/java/org/collectionspace/services/client/RestrictedMediaProxy.java
new file mode 100644 (file)
index 0000000..a4ce51c
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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
+ *
+ * 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
+ */
+package org.collectionspace.services.client;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
+
+/**
+ * RestrictedMediaProxy.java
+ */
+@Path(RestrictedMediaClient.SERVICE_PATH_PROXY)
+@Produces({"application/xml"})
+@Consumes({"application/xml"})
+public interface RestrictedMediaProxy extends CollectionSpaceCommonListPoxProxy {
+
+    @POST
+    @Path("{csid}")
+    @Consumes("multipart/form-data")
+    Response createBlobFromFormData(@PathParam("csid") String csid, MultipartFormDataOutput formDataOutput);
+
+    /**
+     *
+     * @param csid
+     * @param blobUri
+     * @param emptyXML param to force RESTEasy to produce a Content-Type header
+     * @return
+     */
+    @POST
+    @Path("{csid}")
+    @Produces("application/xml")
+    @Consumes("application/xml")
+    Response createBlobFromUri(
+            @PathParam("csid") String csid, @QueryParam(BlobClient.BLOB_URI_PARAM) String blobUri, String emptyXML);
+
+    @POST
+    @Produces("application/xml")
+    @Consumes("application/xml")
+    Response createMediaAndBlobWithUri(
+            byte[] xmlPayload,
+            @QueryParam(BlobClient.BLOB_URI_PARAM) String blobUri,
+            @QueryParam(BlobClient.BLOB_PURGE_ORIGINAL) boolean purgeOriginal);
+}
diff --git a/services/restrictedmedia/client/src/test/java/org/collectionspace/services/client/test/RestrictedMediaAuthRefsTest.java b/services/restrictedmedia/client/src/test/java/org/collectionspace/services/client/test/RestrictedMediaAuthRefsTest.java
new file mode 100644 (file)
index 0000000..dd03a2b
--- /dev/null
@@ -0,0 +1,192 @@
+/**
+ * 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:
+ * <p>
+ * http://www.collectionspace.org
+ * http://wiki.collectionspace.org
+ * <p>
+ * Copyright Â© 2009 Regents of the University of California
+ * <p>
+ * Licensed under the Educational Community License (ECL), Version 2.0.
+ * You may not use this file except in compliance with this License.
+ * <p>
+ * You may obtain a copy of the ECL 2.0 License at
+ * https://source.collectionspace.org/collection-space/LICENSE.txt
+ * <p>
+ * 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 java.util.UUID;
+import javax.ws.rs.core.Response;
+import org.collectionspace.services.PersonJAXBSchema;
+import org.collectionspace.services.client.CollectionSpaceClient;
+import org.collectionspace.services.client.PersonAuthorityClientUtils;
+import org.collectionspace.services.client.PersonClient;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.RestrictedMediaClient;
+import org.collectionspace.services.jaxb.AbstractCommonList;
+import org.collectionspace.services.person.PersonTermGroup;
+import org.collectionspace.services.restrictedmedia.RestrictedMediaCommon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.Test;
+
+/**
+ * RestrictedMediaAuthRefsTest, carries out Authority References tests against a deployed and running
+ * Restricted Media Service.
+ */
+public class RestrictedMediaAuthRefsTest extends BaseServiceTest<AbstractCommonList> {
+    private final Logger logger = LoggerFactory.getLogger(RestrictedMediaAuthRefsTest.class);
+    final String PERSON_AUTHORITY_NAME = "MediaPersonAuth";
+    private String knownResourceId = null;
+    private List<String> mediaIdsCreated = new ArrayList<String>();
+    private List<String> personIdsCreated = new ArrayList<String>();
+    private String personAuthCSID = null;
+    private String publisherRefName = null;
+
+    @Override
+    public String getServicePathComponent() {
+        return RestrictedMediaClient.SERVICE_PATH_COMPONENT;
+    }
+
+    @Override
+    protected String getServiceName() {
+        return RestrictedMediaClient.SERVICE_NAME;
+    }
+
+    @Override
+    protected CollectionSpaceClient getClientInstance() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected AbstractCommonList getCommonList(Response response) {
+        throw new UnsupportedOperationException();
+    }
+
+    private PoxPayloadOut createMediaInstance(String depositorRefName, String title) throws Exception {
+        RestrictedMediaCommon media = new RestrictedMediaCommon();
+        media.setIdentificationNumber(UUID.randomUUID().toString());
+        media.setTitle(title);
+        media.setPublisher(depositorRefName);
+
+        PoxPayloadOut multipart = new PoxPayloadOut(RestrictedMediaClient.SERVICE_PAYLOAD_NAME);
+        multipart.addPart(new RestrictedMediaClient().getCommonPartName(), media);
+        logger.debug("to be created, media common: {}", objectAsXmlString(media, RestrictedMediaCommon.class));
+        return multipart;
+    }
+
+    @Test(dataProvider = "testName")
+    public void createWithAuthRefs(String testName) throws Exception {
+        testSetup(STATUS_CREATED, ServiceRequestType.CREATE);
+        String identifier = createIdentifier();
+
+        createPersonRefs();
+
+        // Create a new RestrictedMedia resource.
+        // One or more fields in this resource will be PersonAuthority references
+        // and will refer to Person resources by their refNames.
+        RestrictedMediaClient mediaClient = new RestrictedMediaClient();
+        PoxPayloadOut multipart = createMediaInstance(publisherRefName, "media.title-" + identifier);
+        Response res = mediaClient.create(multipart);
+        try {
+            assertStatusCode(res, testName);
+            // Store the ID returned from the first resource created for additional tests below.
+            if (knownResourceId == null) {
+                knownResourceId = extractId(res);
+            }
+            // Store the IDs from every resource created; delete on cleanup
+            mediaIdsCreated.add(extractId(res));
+        } finally {
+            if (res != null) {
+                res.close();
+            }
+        }
+    }
+
+    protected void createPersonRefs() throws Exception {
+        PersonClient personAuthClient = new PersonClient();
+        // Create a temporary PersonAuthority resource, and its corresponding refName by which it can be identified.
+        PoxPayloadOut multipart = PersonAuthorityClientUtils.createPersonAuthorityInstance(
+                PERSON_AUTHORITY_NAME, PERSON_AUTHORITY_NAME, personAuthClient.getCommonPartName());
+        Response res = personAuthClient.create(multipart);
+        assertStatusCode(res, "createPersonRefs (not a surefire test)");
+        personAuthCSID = extractId(res);
+
+        String authRefName = PersonAuthorityClientUtils.getAuthorityRefName(personAuthCSID, null);
+
+        String csid = createPerson("Owen the Cur", "Owner", "owenCurOwner", authRefName);
+        personIdsCreated.add(csid);
+        publisherRefName = PersonAuthorityClientUtils.getPersonRefName(personAuthCSID, csid, null);
+    }
+
+    protected String createPerson(String firstName, String surName, String shortId, String authRefName)
+            throws Exception {
+        PersonClient personAuthClient = new PersonClient();
+        Map<String, String> personInfo = new HashMap<String, String>();
+        personInfo.put(PersonJAXBSchema.FORE_NAME, firstName);
+        personInfo.put(PersonJAXBSchema.SUR_NAME, surName);
+        personInfo.put(PersonJAXBSchema.SHORT_IDENTIFIER, shortId + random.nextInt(1000));
+        List<PersonTermGroup> personTerms = new ArrayList<PersonTermGroup>();
+        PersonTermGroup term = new PersonTermGroup();
+        String termName = firstName + " " + surName;
+        term.setTermDisplayName(termName);
+        term.setTermName(termName);
+        personTerms.add(term);
+        PoxPayloadOut multipart = PersonAuthorityClientUtils.createPersonInstance(
+                personAuthCSID, authRefName, personInfo, personTerms, personAuthClient.getItemCommonPartName());
+        Response res = personAuthClient.createItem(personAuthCSID, multipart);
+        assertStatusCode(res, "createPerson (not a surefire test)");
+        return extractId(res);
+    }
+
+    /**
+     * Deletes all resources created by tests, after all tests have been run.
+     * <p/>
+     * 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. Non-successful deletes are ignored and not reported
+     * @throws Exception
+     */
+    @AfterClass(alwaysRun = true)
+    public void cleanUp() throws Exception {
+        String noTestCleanup = System.getProperty("noTestCleanup");
+        if (Boolean.parseBoolean(noTestCleanup)) {
+            logger.debug("Skipping Cleanup phase...");
+            return;
+        }
+        logger.debug("Cleaning up temporary resources created for testing...");
+        PersonClient personAuthClient = new PersonClient();
+        // Delete Person resource(s) (before PersonAuthority resources).
+        for (String resourceId : personIdsCreated) {
+            // Note: Any non-success responses are ignored and not reported.
+            personAuthClient.deleteItem(personAuthCSID, resourceId);
+        }
+
+        if (personAuthCSID != null) {
+            personAuthClient.delete(personAuthCSID);
+        }
+
+        RestrictedMediaClient mediaClient = new RestrictedMediaClient();
+        for (String resourceId : mediaIdsCreated) {
+            mediaClient.delete(resourceId);
+        }
+    }
+}
diff --git a/services/restrictedmedia/client/src/test/java/org/collectionspace/services/client/test/RestrictedMediaServiceTest.java b/services/restrictedmedia/client/src/test/java/org/collectionspace/services/client/test/RestrictedMediaServiceTest.java
new file mode 100644 (file)
index 0000000..46892ce
--- /dev/null
@@ -0,0 +1,283 @@
+/**
+ * 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.io.File;
+import java.net.URL;
+import java.util.List;
+import java.util.UUID;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.collectionspace.services.client.CollectionSpaceClient;
+import org.collectionspace.services.client.PayloadOutputPart;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.RestrictedMediaClient;
+import org.collectionspace.services.jaxb.AbstractCommonList;
+import org.collectionspace.services.restrictedmedia.LanguageList;
+import org.collectionspace.services.restrictedmedia.RestrictedMediaCommon;
+import org.collectionspace.services.restrictedmedia.SubjectList;
+import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataOutput;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+/**
+ * RestrictedMediaServiceTest, carries out tests against a deployed and running Restricted Media Service.
+ */
+public class RestrictedMediaServiceTest extends AbstractPoxServiceTestImpl<AbstractCommonList, RestrictedMediaCommon> {
+
+    private final Logger logger = LoggerFactory.getLogger(RestrictedMediaServiceTest.class);
+    private static final String PUBLIC_URL_DECK = "https://farm8.staticflickr.com/7231/6962564226_4bdfc17599_k_d.jpg";
+
+    private boolean mediaCleanup = true;
+
+    /**
+     * Sets up create tests.
+     */
+    @Override
+    protected void setupCreate() {
+        super.setupCreate();
+        String noMediaCleanup = System.getProperty(NO_MEDIA_CLEANUP);
+        if (Boolean.parseBoolean(noMediaCleanup)) {
+            // Don't delete the blobs that we created during the test cycle
+            this.mediaCleanup = false;
+        }
+    }
+
+    private boolean isMediaCleanup() {
+        return mediaCleanup;
+    }
+
+    @Override
+    public String getServicePathComponent() {
+        return RestrictedMediaClient.SERVICE_PATH_COMPONENT;
+    }
+
+    @Override
+    protected String getServiceName() {
+        return RestrictedMediaClient.SERVICE_NAME;
+    }
+
+    @Override
+    protected CollectionSpaceClient getClientInstance() throws Exception {
+        return new RestrictedMediaClient();
+    }
+
+    @Override
+    protected CollectionSpaceClient getClientInstance(String clientPropertiesFilename) throws Exception {
+        return new RestrictedMediaClient(clientPropertiesFilename);
+    }
+
+    @Override
+    protected AbstractCommonList getCommonList(Response response) {
+        return response.readEntity(AbstractCommonList.class);
+    }
+
+    /**
+     * Looks in the .../src/test/resources/blobs directory for files from which to create Blob
+     * instances.
+     *
+     * @param testName the test name
+     * @param fromUri - if 'true' then send the service a URI from which to create the blob.
+     * @param fromUri - if 'false' then send the service a multipart/form-data POST from which to create the blob.
+     * @throws Exception the exception
+     */
+    public void createBlob(String testName, boolean fromUri) throws Exception {
+        setupCreate();
+
+        // First create a restricted media record
+        RestrictedMediaClient client = new RestrictedMediaClient();
+        PoxPayloadOut multipart = createMediaInstance(createIdentifier());
+        Response mediaRes = client.create(multipart);
+        String mediaCsid = null;
+        try {
+            assertStatusCode(mediaRes, testName);
+            mediaCsid = extractId(mediaRes);
+        } finally {
+            if (mediaRes != null) {
+                mediaRes.close();
+            }
+        }
+
+        String currentDir = getResourceDir();
+        File blobsDir = new File(currentDir, BLOBS_DIR);
+        File blob = findBlobForMedia(blobsDir);
+        if (blob != null) {
+            createBlob(blob, fromUri, testName, mediaCsid);
+        } else {
+            logger.warn("Could not find blobbable file in {}", blobsDir);
+        }
+    }
+
+    /**
+     * Iterate a directory for a file which matches isBlobbable
+     *
+     * @param blobsDir the directory to iterate
+     * @return a blob or null
+     */
+    public File findBlobForMedia(File blobsDir) {
+        if (blobsDir.exists() && blobsDir.canRead()) {
+            File[] children = blobsDir.listFiles();
+            if (children != null && children.length > 0) {
+                // Since Media records can have only a single associated
+                // blob, we'll stop after we find a valid candidate.
+                for (File child : children) {
+                    if (isBlobbable(child)) {
+                        return child;
+                    }
+                }
+            } else {
+                logger.warn("Directory: {} is empty or cannot be read.", blobsDir);
+            }
+        } else {
+            logger.warn("Directory: {} is missing or cannot be read.", blobsDir);
+        }
+
+        return null;
+    }
+
+    public void createBlob(File blobFile, boolean fromUri, String testName, String mediaCsid) throws Exception {
+        logger.debug("Processing file URI: {}", blobFile.getAbsolutePath());
+
+        String mimeType = getMimeType(blobFile);
+        logger.debug("MIME type is: {}", mimeType);
+
+        Response res;
+        RestrictedMediaClient client = new RestrictedMediaClient();
+        if (fromUri) {
+            URL childUrl = blobFile.toURI().toURL();
+            res = client.createBlobFromUri(mediaCsid, childUrl.toString());
+        } else {
+            MultipartFormDataOutput formData = new MultipartFormDataOutput();
+            formData.addFormData("file", blobFile, MediaType.valueOf(mimeType));
+            res = client.createBlobFromFormData(mediaCsid, formData);
+        }
+
+        try {
+            assertStatusCode(res, testName);
+            String blobCsid = extractId(res);
+            if (isMediaCleanup()) {
+                allResourceIdsCreated.add(blobCsid);
+                allResourceIdsCreated.add(mediaCsid);
+            }
+        } finally {
+            if (res != null) {
+                res.close();
+            }
+        }
+    }
+
+    @Test(dataProvider = "testName", dependsOnMethods = {"CRUDTests"})
+    public void createWithBlobUri(String testName) throws Exception {
+        createBlob(testName, true /*with URI*/);
+    }
+
+    @Test(dataProvider = "testName", dependsOnMethods = {"createWithBlobUri"})
+    public void createMediaAndBlobWithUri(String testName) throws Exception {
+        RestrictedMediaClient client = new RestrictedMediaClient();
+        PoxPayloadOut multipart = createMediaInstance(createIdentifier());
+
+        // purge the original
+        Response mediaRes = client.createMediaAndBlobWithUri(multipart, PUBLIC_URL_DECK, true);
+        String mediaCsid = null;
+        try {
+            assertStatusCode(mediaRes, testName);
+            mediaCsid = extractId(mediaRes);
+            if (isMediaCleanup()) {
+                allResourceIdsCreated.add(mediaCsid);
+            }
+        } finally {
+            if (mediaRes != null) {
+                mediaRes.close();
+            }
+        }
+    }
+
+    @Test(dataProvider = "testName", dependsOnMethods = {"createWithBlobUri"})
+    public void createWithBlobPost(String testName) throws Exception {
+        createBlob(testName, false /*with POST*/);
+    }
+
+    // ---------------------------------------------------------------
+    // Utility tests : tests of code used in tests above
+    // ---------------------------------------------------------------
+
+    @Override
+    protected PoxPayloadOut createInstance(String identifier) throws Exception {
+        return createMediaInstance(identifier);
+    }
+
+    // ---------------------------------------------------------------
+    // Utility methods used by tests above
+    // ---------------------------------------------------------------
+    private PoxPayloadOut createMediaInstance(String title) throws Exception {
+        String identifier = "media.title-" + title;
+        RestrictedMediaCommon media = new RestrictedMediaCommon();
+        media.setTitle(identifier);
+        media.setIdentificationNumber(UUID.randomUUID().toString());
+        media.setContributor("Joe-bob briggs");
+        media.setCoverage("Lots of stuff");
+        media.setPublisher("Ludicrum Enterprises");
+        SubjectList subjects = new SubjectList();
+        List<String> subjList = subjects.getSubject();
+        subjList.add("Pints of blood");
+        subjList.add("Much skin");
+        media.setSubjectList(subjects);
+        LanguageList languages = new LanguageList();
+        List<String> langList = languages.getLanguage();
+        langList.add("English");
+        langList.add("German");
+        media.setLanguageList(languages);
+        PoxPayloadOut multipart = new PoxPayloadOut(RestrictedMediaClient.SERVICE_PAYLOAD_NAME);
+        PayloadOutputPart commonPart = multipart.addPart(media, MediaType.APPLICATION_XML_TYPE);
+        commonPart.setLabel(new RestrictedMediaClient().getCommonPartName());
+
+        logger.debug("to be created, media common");
+        logger.debug(objectAsXmlString(media, RestrictedMediaCommon.class));
+
+        return multipart;
+    }
+
+    @Override
+    protected PoxPayloadOut createInstance(String commonPartName, String identifier) throws Exception {
+        return createMediaInstance(identifier);
+    }
+
+    @Override
+    protected RestrictedMediaCommon updateInstance(final RestrictedMediaCommon original) {
+        RestrictedMediaCommon result = new RestrictedMediaCommon();
+        result.setTitle("updated-" + original.getTitle());
+        return result;
+    }
+
+    @Override
+    protected void compareUpdatedInstances(RestrictedMediaCommon original, RestrictedMediaCommon updated) {
+        Assert.assertEquals(updated.getTitle(), original.getTitle());
+    }
+
+    @Override
+    @Test(dataProvider = "testName",
+          dependsOnMethods = {"org.collectionspace.services.client.test.AbstractServiceTestImpl.baseCRUDTests"})
+    public void CRUDTests(String testName) {}
+}
diff --git a/services/restrictedmedia/jaxb/pom.xml b/services/restrictedmedia/jaxb/pom.xml
new file mode 100644 (file)
index 0000000..b0acaec
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <artifactId>org.collectionspace.services.restrictedmedia</artifactId>
+    <groupId>org.collectionspace.services</groupId>
+    <version>${revision}</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>org.collectionspace.services.restrictedmedia.jaxb</artifactId>
+  <name>services.restrictedmedia.jaxb</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.jaxb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <finalName>collectionspace-services-restrictedmedia-jaxb</finalName>
+    <defaultGoal>install</defaultGoal>
+    <plugins>
+      <plugin>
+        <groupId>org.jvnet.jaxb2.maven2</groupId>
+        <artifactId>maven-jaxb2-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+</project>
\ No newline at end of file
diff --git a/services/restrictedmedia/jaxb/src/main/resources/restrictedmedia_common.xsd b/services/restrictedmedia/jaxb/src/main/resources/restrictedmedia_common.xsd
new file mode 100644 (file)
index 0000000..aed21ba
--- /dev/null
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+
+<!--
+    RestrictedMedia schema (XSD)
+
+    Entity  : RestrictedMedia
+    Part    : Common
+    Used for: JAXB binding between XML and Java objects
+-->
+<xs:schema
+  xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+  jaxb:version="2.0"
+  xmlns="http://collectionspace.org/services/restrictedmedia"
+  targetNamespace="http://collectionspace.org/services/restrictedmedia"
+  version="0.1"
+>
+  <!--  RestrictedMedia Information Group -->
+  <xs:element name="restrictedmedia_common">
+    <xs:annotation>
+      <xs:appinfo>
+        <jaxb:class name="RestrictedMediaCommon" />
+      </xs:appinfo>
+    </xs:annotation>
+
+    <xs:complexType>
+      <xs:sequence>
+        <!--  Media Information Group -->
+        <xs:element name="contributor" type="xs:string"/>
+        <xs:element name="copyrightStatement" type="xs:string"/>
+        <xs:element name="coverage" type="xs:string"/>
+        <xs:element name="creator" type="xs:string"/>
+        <xs:element name="dateGroupList" type="dateGroupList"/>
+        <xs:element name="dateCreated" type="xs:string"/>
+        <xs:element name="dateModified" type="xs:string"/>
+        <xs:element name="description" type="xs:string"/>
+        <xs:element name="measuredPartGroupList" type="measuredPartGroupList"/>
+        <xs:element name="filename" type="xs:string"/>
+        <xs:element name="format" type="xs:string"/>
+        <xs:element name="identificationNumber" type="xs:string"/>
+        <xs:element name="languageList" type="languageList"/>
+        <xs:element name="location" type="xs:string"/>
+        <xs:element name="publisher" type="xs:string"/>
+        <xs:element name="relationList" type="relationList"/>
+        <xs:element name="rightsHolder" type="xs:string"/>
+        <xs:element name="source" type="xs:string"/>
+        <xs:element name="sourceUrl" type="xs:string"/>
+        <xs:element name="subjectList" type="subjectList"/>
+        <xs:element name="title" type="xs:string"/>
+        <xs:element name="typeList" type="typeList"/>
+        <xs:element name="uri" type="xs:string" />
+        <xs:element name="blobCsid" type="xs:string" />
+        <xs:element name="externalUrl" type="xs:string" />
+      </xs:sequence>
+    </xs:complexType>
+  </xs:element>
+
+  <xs:complexType name="dateGroupList">
+    <xs:sequence>
+      <xs:element name="dateGroup" type="dateGroup" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="dateGroup">
+    <xs:sequence>
+      <xs:element name="dateDisplayDate" type="xs:string"/>
+      <xs:element name="dateAssociation" type="xs:string"/>
+      <xs:element name="dateEarliestSingleYear" type="xs:integer"/>
+      <xs:element name="dateEarliestSingleMonth" type="xs:integer"/>
+      <xs:element name="dateEarliestSingleDay" type="xs:integer"/>
+      <xs:element name="dateEarliestSingleEra" type="xs:string"/>
+      <xs:element name="dateEarliestSingleCertainty" type="xs:string"/>
+      <xs:element name="dateEarliestSingleQualifier" type="xs:string"/>
+      <xs:element name="dateEarliestSingleQualifierValue" type="xs:integer"/>
+      <xs:element name="dateEarliestSingleQualifierUnit" type="xs:string"/>
+      <xs:element name="dateLatestYear" type="xs:integer"/>
+      <xs:element name="dateLatestMonth" type="xs:integer"/>
+      <xs:element name="dateLatestDay" type="xs:integer"/>
+      <xs:element name="dateLatestEra" type="xs:string"/>
+      <xs:element name="dateLatestCertainty" type="xs:string"/>
+      <xs:element name="dateLatestQualifier" type="xs:string"/>
+      <xs:element name="dateLatestQualifierValue" type="xs:integer"/>
+      <xs:element name="dateLatestQualifierUnit" type="xs:string"/>
+      <xs:element name="datePeriod" type="xs:string"/>
+      <xs:element name="dateNote" type="xs:string"/>
+      <xs:element name="dateEarliestScalarValue" type="xs:date"/>
+      <xs:element name="dateLatestScalarValue" type="xs:date"/>
+      <xs:element name="scalarValuesComputed" type="xs:boolean"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="dateList">
+    <xs:sequence>
+      <xs:element name="date" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="measuredPartGroupList">
+    <xs:sequence>
+      <xs:element name="measuredPartGroup" type="measuredPartGroup" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+  <xs:complexType name="measuredPartGroup">
+    <xs:sequence>
+      <xs:element name="measuredPart" type="xs:string"/>
+      <xs:element name="dimensionSummary" type="xs:string"/>
+      <xs:element name="dimensionSubGroupList" type="dimensionSubGroupList"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="dimensionSubGroupList">
+    <xs:sequence>
+      <xs:element name="dimensionSubGroup" type="dimensionSubGroup" minOccurs="0" maxOccurs="unbounded" />
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="dimensionSubGroup">
+    <xs:sequence>
+      <xs:element name="dimension" type="xs:string"/>
+      <xs:element name="measuredBy" type="xs:string"/>
+      <xs:element name="measurementUnit" type="xs:string"/>
+      <xs:element name="measurementMethod" type="xs:string"/>
+      <xs:element name="value" type="xs:decimal"/>
+      <xs:element name="valueDate" type="xs:string"/>
+      <xs:element name="valueQualifier" type="xs:string"/>
+      <xs:element name="dimensionNote" type="xs:string"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="languageList">
+    <xs:sequence>
+      <xs:element name="language" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="relationList">
+    <xs:sequence>
+      <xs:element name="relation" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="subjectList">
+    <xs:sequence>
+      <xs:element name="subject" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+
+  <xs:complexType name="typeList">
+    <xs:sequence>
+      <xs:element name="type" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
+  </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/services/restrictedmedia/pom.xml b/services/restrictedmedia/pom.xml
new file mode 100644 (file)
index 0000000..5abd858
--- /dev/null
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.collectionspace.services</groupId>
+    <artifactId>org.collectionspace.services.main</artifactId>
+    <version>${revision}</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>org.collectionspace.services.restrictedmedia</artifactId>
+  <name>services.restrictedmedia</name>
+  <packaging>pom</packaging>
+
+  <properties>
+    <!-- spotless 2.30.0 is the last version that supports java 8 -->
+    <spotless.version>2.30.0</spotless.version>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>com.diffplug.spotless</groupId>
+        <artifactId>spotless-maven-plugin</artifactId>
+        <version>${spotless.version}</version>
+        <configuration>
+          <formats>
+            <!-- you can define as many formats as you want, each is independent -->
+            <format>
+              <includes>
+                <include>src/main/java/**/*.java</include>
+                <include>src/test/java/**/*.java</include>
+              </includes>
+              <trimTrailingWhitespace />
+              <endWithNewline />
+              <indent>
+                <spaces>true</spaces>
+                <spacesPerTab>4</spacesPerTab>
+              </indent>
+            </format>
+          </formats>
+          <!-- define a language-specific format -->
+          <java>
+            <palantirJavaFormat />
+          </java>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <modules>
+    <module>jaxb</module>
+    <module>service</module>
+    <module>client</module>
+  </modules>
+
+</project>
\ No newline at end of file
diff --git a/services/restrictedmedia/service/pom.xml b/services/restrictedmedia/service/pom.xml
new file mode 100644 (file)
index 0000000..6a6530f
--- /dev/null
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <parent>
+    <groupId>org.collectionspace.services</groupId>
+    <artifactId>org.collectionspace.services.restrictedmedia</artifactId>
+    <version>${revision}</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>org.collectionspace.services.restrictedmedia.service</artifactId>
+  <name>services.restrictedmedia.service</name>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.blob.client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.blob.service</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.media.jaxb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.restrictedmedia.jaxb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.collectionspace.services</groupId>
+      <artifactId>org.collectionspace.services.restrictedmedia.client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+    <!-- External dependencies -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.testng</groupId>
+      <artifactId>testng</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- javax -->
+    <dependency>
+      <groupId>javax.security</groupId>
+      <artifactId>jaas</artifactId>
+      <version>1.0.01</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!-- jboss -->
+    <dependency>
+      <groupId>org.jboss.resteasy</groupId>
+      <artifactId>resteasy-jaxrs</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>tjws</groupId>
+          <artifactId>webserver</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.resteasy</groupId>
+      <artifactId>resteasy-jaxb-provider</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.resteasy</groupId>
+      <artifactId>resteasy-multipart-provider</artifactId>
+    </dependency>
+
+    <!-- nuxeo -->
+    <dependency>
+      <groupId>org.nuxeo.ecm.core</groupId>
+      <artifactId>nuxeo-core-api</artifactId>
+      <exclusions>
+        <exclusion>
+          <artifactId>jboss-remoting</artifactId>
+          <groupId>jboss</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-servlet-api</artifactId>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <finalName>collectionspace-services-restrictedmedia</finalName>
+  </build>
+</project>
diff --git a/services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/RestrictedMediaResource.java b/services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/RestrictedMediaResource.java
new file mode 100644 (file)
index 0000000..2c5b5dc
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ *  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
+ *
+ *  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.restrictedmedia;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import org.collectionspace.services.blob.BlobResource;
+import org.collectionspace.services.client.BlobClient;
+import org.collectionspace.services.client.CollectionSpaceClientUtils;
+import org.collectionspace.services.client.PayloadOutputPart;
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.RestrictedMediaClient;
+import org.collectionspace.services.common.NuxeoBasedResource;
+import org.collectionspace.services.common.ResourceMap;
+import org.collectionspace.services.common.ServiceMessages;
+import org.collectionspace.services.common.UriInfoWrapper;
+import org.collectionspace.services.common.blob.BlobInput;
+import org.collectionspace.services.common.blob.BlobUtil;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.nuxeo.client.java.CommonList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path(RestrictedMediaClient.SERVICE_PATH)
+@Consumes("application/xml")
+@Produces("application/xml")
+public class RestrictedMediaResource extends NuxeoBasedResource {
+
+    private static final Logger logger = LoggerFactory.getLogger(RestrictedMediaResource.class);
+
+    static final RestrictedMediaClient mediaClient = createMediaClient();
+
+    @Override
+    protected String getVersionString() {
+        return "$LastChangedRevision$";
+    }
+
+    @Override
+    public String getServiceName() {
+        return RestrictedMediaClient.SERVICE_NAME;
+    }
+
+    @Override
+    public Class<RestrictedMediaCommon> getCommonPartClass() {
+        return RestrictedMediaCommon.class;
+    }
+
+    public String getCommonPartName() {
+        return mediaClient.getCommonPartName();
+    }
+
+    private static RestrictedMediaClient createMediaClient() {
+        RestrictedMediaClient result;
+
+        try {
+            result = new RestrictedMediaClient();
+        } catch (Exception e) {
+            String errMsg = "Could not create a new restricted media client for the RestrictedMedia resource.";
+            logger.error(errMsg, e);
+            throw new RuntimeException();
+        }
+
+        return result;
+    }
+
+    private String getBlobCsid(String mediaCsid) throws Exception {
+        ServiceContext<PoxPayloadIn, PoxPayloadOut> mediaContext = createServiceContext();
+
+        BlobInput blobInput = BlobUtil.getBlobInput(mediaContext);
+        blobInput.setSchemaRequested(true);
+
+        // set the blobInput blobCsid
+        get(mediaCsid, mediaContext);
+
+        String result = blobInput.getBlobCsid();
+
+        ensureCSID(result, READ);
+
+        return result;
+    }
+
+    /*
+     * Creates a new media record/resource AND creates a new blob (using a URL pointing to a media file/resource) and
+     * associates it with the new media record/resource.
+     */
+    protected Response createBlobWithUri(UriInfo uriInfo, String blobUri) {
+        Response response;
+
+        try {
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(BlobClient.SERVICE_NAME, uriInfo);
+            BlobInput blobInput = BlobUtil.getBlobInput(ctx);
+            blobInput.createBlobFile(blobUri);
+
+            // By now the binary bits have been created, and we just need to create the metadata blob
+            // record -this info is in the blobInput var
+            response = create(null, ctx);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
+        }
+
+        return response;
+    }
+
+    /*
+     * Looks for a blobUri query param from a POST.  If it finds one then it creates a blob AND a media resource and
+     * associates them.
+     *
+     * @see org.collectionspace.services.common.ResourceBase#create(org.collectionspace.services.common.context.ServiceContext, org.collectionspace.services.common.ResourceMap, javax.ws.rs.core.UriInfo, java.lang.String)
+     */
+    @Override
+    public Response create(ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx,
+                           @Context ResourceMap resourceMap,
+                           @Context UriInfo uriInfo,
+                           String xmlPayload) {
+        uriInfo = new UriInfoWrapper(uriInfo);
+
+        // create a blob resource/record first and then the media resource/record
+        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
+        String blobUri = queryParams.getFirst(BlobClient.BLOB_URI_PARAM);
+        if (blobUri != null && !blobUri.isEmpty()) {
+            // uses the blob resource and doc handler to create the blob
+            Response result = createBlobWithUri(uriInfo, blobUri);
+            String blobCsid = CollectionSpaceClientUtils.extractId(result);
+
+            // Add the new blob's csid as an artificial query param -the media doc handler will look for this
+            queryParams.add(BlobClient.BLOB_CSID_PARAM, blobCsid);
+        }
+
+        return super.create(parentCtx, resourceMap, uriInfo, xmlPayload);
+    }
+
+    @Override
+    public byte[] update(ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx,
+                         @Context ResourceMap resourceMap,
+                         @Context UriInfo uriInfo,
+                         @PathParam("csid") String csid,
+                         String xmlPayload) {
+        uriInfo = new UriInfoWrapper(uriInfo);
+        // If we find a "blobUri" query param, then we need to create a blob resource/record first and then the media
+        // resource/record
+        MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
+        String blobUri = queryParams.getFirst(BlobClient.BLOB_URI_PARAM);
+        if (blobUri != null && !blobUri.isEmpty()) {
+            // uses the blob resource and doc handler to create the blob
+            Response blobResponse = createBlobWithUri(uriInfo, blobUri);
+            String blobCsid = CollectionSpaceClientUtils.extractId(blobResponse);
+            // Add the new blob's csid as an artificial query param -the media doc handler will look
+            queryParams.add(BlobClient.BLOB_CSID_PARAM, blobCsid);
+        }
+
+        // finish the media resource PUT request
+        return super.update(parentCtx, resourceMap, uriInfo, csid, xmlPayload);
+    }
+
+    /*
+     * Creates a new blob (using a URL pointing to a media file/resource) and associate it with
+     * an existing media record/resource.
+     */
+    @POST
+    @Path("{csid}")
+    @Consumes("application/xml")
+    @Produces("application/xml")
+    public Response createBlobWithUriAndUpdateMedia(@PathParam("csid") String csid,
+                                                    @QueryParam(BlobClient.BLOB_URI_PARAM) String blobUri) {
+        Response response;
+
+        try {
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(BlobClient.SERVICE_NAME);
+            BlobInput blobInput = BlobUtil.getBlobInput(ctx);
+            blobInput.createBlobFile(blobUri);
+            response = create(null, ctx);
+
+            // Next, update the RestrictedMedia record to be linked to the blob
+            // and put the blobInput into the RestrictedMedia context
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> mediaContext = createServiceContext();
+            BlobUtil.setBlobInput(mediaContext, blobInput);
+            update(csid, null, mediaContext);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
+        }
+
+        return response;
+    }
+
+    @PUT
+    @Path("{csid}/blob")
+    @Consumes("multipart/form-data")
+    @Produces("application/xml")
+    public Response updateMediaByCreatingBlob(
+            @Context HttpServletRequest req,
+            @PathParam("csid") String csid,
+            @QueryParam(BlobClient.BLOB_URI_PARAM) String blobUri) {
+        return createBlob(req, csid, blobUri);
+    }
+
+    /*
+     * Creates a new blob (using the incoming multipart form data) and associates it with an existing media
+     * record/resource. If a URL query param is passed in as well, we use the URL to create the new blob instead of
+     * the multipart form data.
+     *
+     * todo: Why is this deprecated?
+     */
+    @POST
+    @Path("{csid}")
+    @Consumes("multipart/form-data")
+    @Produces("application/xml")
+    @Deprecated
+    public Response createBlob(
+            @Context HttpServletRequest req,
+            @PathParam("csid") String csid,
+            @QueryParam(BlobClient.BLOB_URI_PARAM) String blobUri) {
+        Response response;
+        try {
+            if (blobUri == null) {
+                ServiceContext<PoxPayloadIn, PoxPayloadOut> blobContext =
+                        createServiceContext(BlobClient.SERVICE_NAME, (PoxPayloadIn) null);
+                BlobInput blobInput = BlobUtil.getBlobInput(blobContext);
+                blobInput.createBlobFile(req, null);
+                response = create(null, blobContext);
+
+                // Next, update the Media record to be linked to the blob and put the blobInput into the Media context
+                ServiceContext<PoxPayloadIn, PoxPayloadOut> mediaContext = createServiceContext();
+                BlobUtil.setBlobInput(mediaContext, blobInput);
+                update(csid, null, mediaContext);
+            } else {
+                // A URI query param overrides the incoming multipart/form-data payload in the request
+                response = createBlobWithUriAndUpdateMedia(csid, blobUri);
+            }
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
+        }
+
+        return response;
+    }
+
+    @GET
+    @Path("{csid}/blob")
+    public byte[] getBlobInfo(@PathParam("csid") String csid) {
+        PoxPayloadOut result;
+
+        try {
+            String blobCsid = getBlobCsid(csid);
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> blobContext = createServiceContext(BlobClient.SERVICE_NAME);
+            result = get(blobCsid, blobContext);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.READ_FAILED, csid);
+        }
+
+        return result.getBytes();
+    }
+
+    @GET
+    @Path("{csid}/blob/content")
+    public Response getBlobContent(
+            @PathParam("csid") String csid, @Context Request requestInfo, @Context UriInfo uriInfo) {
+        Response result;
+        BlobResource blobResource = new BlobResource();
+
+        try {
+            ensureCSID(csid, READ);
+            String blobCsid = getBlobCsid(csid);
+            result = blobResource.getBlobContent(blobCsid, requestInfo, uriInfo);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.READ_FAILED, csid);
+        }
+
+        return result;
+    }
+
+    @GET
+    @Path("{csid}/blob/derivatives/{derivativeTerm}/content")
+    public Response getDerivativeContent(
+            @PathParam("csid") String csid,
+            @PathParam("derivativeTerm") String derivativeTerm,
+            @Context Request requestInfo,
+            @Context UriInfo uriInfo) {
+        Response result;
+        BlobResource blobResource = new BlobResource();
+
+        try {
+            ensureCSID(csid, READ);
+            String blobCsid = getBlobCsid(csid);
+            result = blobResource.getDerivativeContent(blobCsid, derivativeTerm, requestInfo, uriInfo);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.READ_FAILED, csid);
+        }
+
+        return result;
+    }
+
+    @GET
+    @Path("{csid}/blob/derivatives/{derivativeTerm}")
+    public byte[] getDerivative(@PathParam("csid") String csid, @PathParam("derivativeTerm") String derivativeTerm) {
+        PoxPayloadOut result;
+        BlobResource blobResource = new BlobResource();
+
+        try {
+            ensureCSID(csid, READ);
+            String blobCsid = getBlobCsid(csid);
+            String xmlPayload = blobResource.getDerivative(blobCsid, derivativeTerm);
+            result = new PoxPayloadOut(xmlPayload.getBytes());
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.READ_FAILED, csid);
+        }
+
+        return result.getBytes();
+    }
+
+    @GET
+    @Path("{csid}/blob/derivatives")
+    @Produces("application/xml")
+    public CommonList getDerivatives(@PathParam("csid") String csid) {
+        CommonList result = null;
+        BlobResource blobResource = new BlobResource();
+
+        try {
+            ensureCSID(csid, READ);
+            String blobCsid = getBlobCsid(csid);
+            result = blobResource.getDerivatives(blobCsid);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.READ_FAILED, csid);
+        }
+
+        return result;
+    }
+
+    @DELETE
+    @Path("{csid}")
+    @Override
+    public Response delete(@PathParam("csid") String csid) {
+        BlobResource blob = new BlobResource();
+
+        try {
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext();
+            PoxPayloadOut mediaPayload = get(csid, ctx);
+            PayloadOutputPart mediaPayloadPart = mediaPayload.getPart(getCommonPartName());
+            RestrictedMediaCommon mediaCommon = (RestrictedMediaCommon) mediaPayloadPart.getBody();
+            String blobCsid = mediaCommon.getBlobCsid();
+
+            // Delete the blob if it exists.
+            if (blobCsid != null && !blobCsid.isEmpty()) {
+                Response response = blob.delete(blobCsid);
+                if (response.getStatus() != Response.Status.OK.getStatusCode()) {
+                    logger.debug("Problem deleting related blob record of RestrictedMedia record: " +
+                                 "RestrictedMedia CSID={} Blob CSID={}", csid, blobCsid);
+                }
+            }
+
+            return super.delete(ctx, csid);
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.DELETE_FAILED, csid);
+        }
+    }
+}
diff --git a/services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/nuxeo/RestrictedMediaDocumentModelHandler.java b/services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/nuxeo/RestrictedMediaDocumentModelHandler.java
new file mode 100644 (file)
index 0000000..f29ddf5
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ *  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
+ *
+ *  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.restrictedmedia.nuxeo;
+
+import javax.ws.rs.core.MultivaluedMap;
+import org.collectionspace.services.MediaJAXBSchema;
+import org.collectionspace.services.client.BlobClient;
+import org.collectionspace.services.common.blob.BlobInput;
+import org.collectionspace.services.common.blob.BlobUtil;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.document.DocumentWrapper;
+import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
+import org.collectionspace.services.restrictedmedia.RestrictedMediaCommon;
+import org.nuxeo.ecm.core.api.DocumentModel;
+
+/**
+ * RestrictedMediaDocumentModelHandler
+ */
+public class RestrictedMediaDocumentModelHandler extends NuxeoDocumentModelHandler<RestrictedMediaCommon> {
+
+    private RestrictedMediaCommon getCommonPartProperties(DocumentModel docModel) {
+        String label = getServiceContext().getCommonPartLabel();
+        RestrictedMediaCommon result = new RestrictedMediaCommon();
+
+        result.setBlobCsid((String) docModel.getProperty(label, MediaJAXBSchema.blobCsid));
+        return result;
+    }
+
+    @Override
+    public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
+        ServiceContext ctx = getServiceContext();
+
+        final BlobInput blobInput = BlobUtil.getBlobInput(ctx);
+        if (blobInput.isSchemaRequested()) {
+            DocumentModel docModel = wrapDoc.getWrappedObject();
+            RestrictedMediaCommon mediaCommon = getCommonPartProperties(docModel);
+            String blobCsid = mediaCommon.getBlobCsid();
+            blobInput.setBlobCsid(blobCsid);
+        } else {
+            super.extractAllParts(wrapDoc);
+        }
+    }
+
+    @Override
+    public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
+        String blobCsid;
+        ServiceContext ctx = getServiceContext();
+
+        BlobInput blobInput = BlobUtil.getBlobInput(ctx);
+        if (blobInput.getBlobCsid() != null) {
+            blobCsid = blobInput.getBlobCsid();
+        } else {
+            MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
+            blobCsid = queryParams.getFirst(BlobClient.BLOB_CSID_PARAM);
+            // we're creating a blob from a URI and creating a new media resource as well
+            // extract all the other fields from the input payload
+            super.fillAllParts(wrapDoc, action);
+        }
+
+        if (blobCsid != null) {
+            DocumentModel documentModel = wrapDoc.getWrappedObject();
+            documentModel.setProperty(ctx.getCommonPartLabel(), MediaJAXBSchema.blobCsid, blobCsid);
+        }
+    }
+}
diff --git a/services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/nuxeo/RestrictedMediaValidatorHandler.java b/services/restrictedmedia/service/src/main/java/org/collectionspace/services/restrictedmedia/nuxeo/RestrictedMediaValidatorHandler.java
new file mode 100644 (file)
index 0000000..11fe5f2
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * 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
+ *
+ * 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
+ */
+package org.collectionspace.services.restrictedmedia.nuxeo;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.document.InvalidDocumentException;
+import org.collectionspace.services.common.document.ValidatorHandlerImpl;
+import org.collectionspace.services.restrictedmedia.RestrictedMediaCommon;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ValidatorHandler for RestrictedMedia
+ *
+ * Checks for presence of restrictedmedia_common and restrictedmedia_common/identificationNumber
+ */
+public class RestrictedMediaValidatorHandler extends ValidatorHandlerImpl<PoxPayloadIn, PoxPayloadOut> {
+    private final Logger logger = LoggerFactory.getLogger(RestrictedMediaValidatorHandler.class);
+
+    private static final String COMMON_PART_MISSING =
+        "Validation exception: restrictedmedia_common part is missing";
+    private static final String IDENTIFICATION_NUMBER_MISSING =
+        "Validation exception: The restricted media field \"identificationNumber\" cannot be empty or missing";
+
+    @Override
+    protected Class<?> getCommonPartClass() {
+        return RestrictedMediaCommon.class;
+    }
+
+    @Override
+    protected void handleCreate() throws InvalidDocumentException {
+        final RestrictedMediaCommon restrictedMedia = (RestrictedMediaCommon) getCommonPart();
+        if (restrictedMedia == null) {
+            logger.error(COMMON_PART_MISSING);
+            throw new InvalidDocumentException(COMMON_PART_MISSING);
+        }
+
+        final String identificationNumber = restrictedMedia.getIdentificationNumber();
+        if (identificationNumber == null || identificationNumber.isEmpty()) {
+            logger.error(IDENTIFICATION_NUMBER_MISSING);
+            throw new InvalidDocumentException(IDENTIFICATION_NUMBER_MISSING);
+        }
+    }
+
+    @Override
+    protected void handleGet() {}
+
+    @Override
+    protected void handleGetAll() {}
+
+    @Override
+    protected void handleUpdate() {}
+
+    @Override
+    protected void handleDelete() {}
+}