]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
DRYD-879: Implement export service.
authorRay Lee <ray.lee@lyrasis.org>
Fri, 19 Jun 2020 15:22:18 +0000 (11:22 -0400)
committerRay Lee <ray.lee@lyrasis.org>
Tue, 14 Jul 2020 00:37:46 +0000 (20:37 -0400)
31 files changed:
services/JaxRsServiceProvider/pom.xml
services/JaxRsServiceProvider/src/main/java/org/collectionspace/services/jaxrs/CollectionSpaceJaxRsApplication.java
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java
services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml
services/common/src/main/java/org/collectionspace/services/common/invocable/Invocable.java
services/export/build.xml [new file with mode: 0644]
services/export/client/pom.xml [new file with mode: 0644]
services/export/client/src/main/java/org/collectionspace/services/client/ExportClient.java [new file with mode: 0644]
services/export/client/src/main/java/org/collectionspace/services/client/ExportProxy.java [new file with mode: 0644]
services/export/jaxb/.gitignore [new file with mode: 0644]
services/export/jaxb/pom.xml [new file with mode: 0644]
services/export/jaxb/src/main/java/org/collectionspace/services/ExportJAXBSchema.java [new file with mode: 0644]
services/export/jaxb/src/main/resources/exports-common.xsd [new file with mode: 0644]
services/export/pom.xml [new file with mode: 0644]
services/export/service/pom.xml [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/AbstractDocumentsByQueryIterator.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/AbstractExportWriter.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/CsvExportWriter.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/DocumentsByCsidIterator.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/DocumentsByGroupIterator.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/ExportResource.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/ExportWriter.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/RelationObjectsByQueryIterator.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/RelationsByQueryIterator.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/StandardDocumentsByQueryIterator.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/XmlExportWriter.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportConstants.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportDocumentModelHandler.java [new file with mode: 0644]
services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportValidatorHandler.java [new file with mode: 0644]
services/jaxb/src/main/resources/invocationContext.xsd
services/pom.xml

index 5a891e309e106e139cf12c6e0e1749e2dad5d926..0b7fc1da633f623dc047533e46c547719e91874c 100644 (file)
         <dependency>
             <groupId>org.collectionspace.services</groupId>
             <artifactId>org.collectionspace.services.imports.service</artifactId>
-             <version>${project.version}</version>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.collectionspace.services</groupId>
+            <artifactId>org.collectionspace.services.export.service</artifactId>
+            <version>${project.version}</version>
         </dependency>
         <dependency>
             <groupId>org.collectionspace.services</groupId>
index 842f17a3d1b5cd0e243ff7b62fe9ce89289ddfb6..e675a2f75dbafe783fb5be8a307c2430ad782026 100644 (file)
@@ -38,6 +38,7 @@ import org.collectionspace.services.valuationcontrol.ValuationcontrolResource;
 import org.collectionspace.services.objectexit.ObjectExitResource;
 import org.collectionspace.services.batch.BatchResource;
 import org.collectionspace.services.imports.ImportsResource;
+import org.collectionspace.services.export.ExportResource;
 import org.collectionspace.services.location.LocationAuthorityResource;
 import org.collectionspace.services.place.PlaceAuthorityResource;
 import org.collectionspace.services.work.WorkAuthorityResource;
@@ -111,6 +112,7 @@ public class CollectionSpaceJaxRsApplication extends Application
         singletons.add(new PermissionResource());
         singletons.add(new ServiceGroupResource());
         singletons.add(new ImportsResource());
+        singletons.add(new ExportResource());
         singletons.add(new StructuredDateResource());
         singletons.add(new SystemInfoResource());
         singletons.add(new IndexResource());
index d3c47014bad4e8ea3e53178bb2722c0a3b53338f..3709bb24460abbc509a3fde991c95e04ae17b7b6 100644 (file)
@@ -49,6 +49,7 @@ import org.collectionspace.services.client.PoxPayloadIn;
 import org.collectionspace.services.client.PoxPayloadOut;
 import org.collectionspace.services.client.XmlTools;
 import org.collectionspace.services.client.workflow.WorkflowClient;
+import org.collectionspace.services.common.CollectionSpaceResource;
 import org.collectionspace.services.common.CSWebApplicationException;
 import org.collectionspace.services.common.NuxeoBasedResource;
 import org.collectionspace.services.common.ResourceMap;
@@ -777,7 +778,7 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
             ) throws Exception {
        return updateAuthorityItem(null, itemServiceCtx, resourceMap, uriInfo, parentspecifier, itemspecifier, theUpdate, shouldUpdateRevNumber, isProposed, isSASItem);
     }
-    
+
     public PoxPayloadOut updateAuthorityItem(
             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx,
             ServiceContext<PoxPayloadIn, PoxPayloadOut> itemServiceCtx, // Ok to be null.  Will be null on PUT calls, but not on sync calls
@@ -943,7 +944,7 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
         uriInfo = new UriInfoWrapper(uriInfo);
         return updateItemWorkflowWithTransition(null, uriInfo, parentIdentifier, itemIdentifier, transition);
     }
-    
+
     public byte[] updateItemWorkflowWithTransition(
             ServiceContext<PoxPayloadIn, PoxPayloadOut> existingContext,
             UriInfo uriInfo,
@@ -1065,7 +1066,7 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
         }
 
         result = (PoxPayloadOut) ctx.getOutput();
-        if (result != null) {
+        if (result != null && !parentcsid.equals(PARENT_WILDCARD)) {
             String inAuthority = XmlTools.getElementValue(result.getDOMDocument(), "//" + AuthorityItemJAXBSchema.IN_AUTHORITY);
             if (inAuthority.equalsIgnoreCase(parentcsid) == false) {
                 throw new Exception(String.format("Looked up item = '%s' and found with inAuthority = '%s', but expected inAuthority = '%s'.",
@@ -1544,7 +1545,7 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
             String xmlPayload) {
        return updateAuthorityItem(null, resourceMap, uriInfo, parentSpecifier, itemSpecifier, xmlPayload);
     }
+
     public byte[] updateAuthorityItem(
             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx,
             ResourceMap resourceMap,
@@ -1740,4 +1741,20 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
         return csid;
     }
 
+       public static AuthorityResource<?, ?> getResourceForItem(ResourceMap resourceMap, String tenantId, String itemDocType) {
+               for (String serviceName : resourceMap.keySet()) {
+                       CollectionSpaceResource<?, ?> resource = (CollectionSpaceResource<?, ?>) resourceMap.get(serviceName);
+
+                       if (resource instanceof AuthorityResource) {
+                               AuthorityResource<?, ?> authorityResource = (AuthorityResource<?, ?>) resource;
+                               String docType = authorityResource.getItemDocType(tenantId);
+
+                               if (docType.equals(itemDocType)) {
+                                       return authorityResource;
+                               }
+                       }
+               }
+
+               return null;
+       }
 }
index 412d1823e566986efa8fc85315a988ee551409a3..58494adce1cad8b23391de3131c1403f21c5d4a5 100644 (file)
                </tenant:serviceBindings>
                <!-- end imports service meta-data -->
 
+               <!-- begin export service meta-data -->
+               <tenant:serviceBindings id="Exports" merge:matcher="id" name="Exports" type="utility" version="1.0">
+                       <service:repositoryDomain xmlns:service="http://collectionspace.org/services/config/service">default-domain</service:repositoryDomain>
+                       <service:documentHandler xmlns:service="http://collectionspace.org/services/config/service">org.collectionspace.services.export.nuxeo.ExportDocumentModelHandler
+                       </service:documentHandler>
+                       <service:DocHandlerParams xmlns:service="http://collectionspace.org/services/config/service">
+                               <service:params>
+                                       <service:ListResultsFields>
+                                       </service:ListResultsFields>
+                               </service:params>
+                       </service:DocHandlerParams>
+                       <service:object xmlns:service="http://collectionspace.org/services/config/service" name="Export"
+                               version="1.0">
+                               <service:part id="0" control_group="Managed" versionable="true" auditable="false" label="exports-system"
+                                       updated="" order="0">
+                                       <service:content contentType="application/xml">
+                                               <service:xmlContent namespaceURI="http://collectionspace.org/services/config/system"
+                                                       schemaLocation="http://collectionspace.org/services/config/system http://collectionspace.org/services/config/system/system-response.xsd" />
+                                       </service:content>
+                               </service:part>
+                               <service:part id="1" control_group="Managed" versionable="true" auditable="false" label="exports_common"
+                                       updated="" order="1">
+                                       <service:content contentType="application/xml">
+                                               <service:xmlContent namespaceURI="http://collectionspace.org/services/export"
+                                                       schemaLocation="http://collectionspace.org/services/export http://services.collectionspace.org/export/exports_common.xsd" />
+                                       </service:content>
+                               </service:part>
+                               <service:part id="2" control_group="Managed" versionable="true" auditable="false" label="collectionspace_core"
+                                       updated="" order="2">
+                                       <service:content contentType="application/xml">
+                                               <service:xmlContent namespaceURI="http://collectionspace.org/collectionspace_core/"
+                                                       schemaLocation="http://collectionspace.org/collectionspace_core/ http://services.collectionspace.org/collectionspace_core.xsd" />
+                                       </service:content>
+                               </service:part>
+                       </service:object>
+               </tenant:serviceBindings>
+               <!-- end export service meta-data -->
+
                <!-- begin Workflow service meta-data -->
                <tenant:serviceBindings id="Workflow" merge:matcher="id" name="Workflow" type="utility"
                        version="1.0">
index f0863c139cd0c58ede2148d1712055134cb6f09a..03dd98c1064055a11b8c59896b0ea5956cd93cac 100644 (file)
@@ -22,7 +22,6 @@
  */
 package org.collectionspace.services.common.invocable;
 
-import org.collectionspace.services.common.invocable.InvocationContext;
 import java.util.List;
 
 import org.collectionspace.services.client.PoxPayloadIn;
@@ -64,16 +63,16 @@ public interface Invocable {
 
         @Override
         public String toString() {
-            return (Tools.notBlank(message)) ? message : "No error message provided";  
+            return (Tools.notBlank(message)) ? message : "No error message provided";
         }
     }
-    
-    public String INVOCATION_MODE_SINGLE = "single";
-    public String INVOCATION_MODE_LIST = "list";
-    public String INVOCATION_MODE_GROUP = "group";
-    public String INVOCATION_MODE_NO_CONTEXT = "nocontext";
-    //public String INVOCATION_MODE_QUERY = "query"; NYI
-    
+
+    public final String INVOCATION_MODE_SINGLE = "single";
+    public final String INVOCATION_MODE_LIST = "list";
+    public final String INVOCATION_MODE_GROUP = "group";
+    public final String INVOCATION_MODE_NO_CONTEXT = "nocontext";
+    public final String INVOCATION_MODE_QUERY = "query";
+
     public final int STATUS_ERROR = -1;
     public final int STATUS_UNSTARTED = 0;
     public final int STATUS_MIN_PROGRESS = 1;
diff --git a/services/export/build.xml b/services/export/build.xml
new file mode 100644 (file)
index 0000000..1eaaba2
--- /dev/null
@@ -0,0 +1,108 @@
+
+<project name="export" default="package" basedir=".">
+    <description>
+        export service
+    </description>
+  <!-- set global properties for this build -->
+    <property name="services.trunk" value="../.."/>
+    <!-- enviornment 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>
diff --git a/services/export/client/pom.xml b/services/export/client/pom.xml
new file mode 100644 (file)
index 0000000..3b257b2
--- /dev/null
@@ -0,0 +1,83 @@
+<?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.export</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>org.collectionspace.services.export.client</artifactId>
+    <name>services.export.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.common</artifactId>
+            <optional>true</optional>
+            </dependency>
+        <dependency>
+            <groupId>org.collectionspace.services</groupId>
+            <artifactId>org.collectionspace.services.export.jaxb</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.person.client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.collectionspace.services</groupId>
+            <artifactId>org.collectionspace.services.acquisition.client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.collectionspace.services</groupId>
+            <artifactId>org.collectionspace.services.acquisition.jaxb</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.collectionspace.services</groupId>
+            <artifactId>org.collectionspace.services.organization.client</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>
+        </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-export-client</finalName>
+    </build>
+</project>
diff --git a/services/export/client/src/main/java/org/collectionspace/services/client/ExportClient.java b/services/export/client/src/main/java/org/collectionspace/services/client/ExportClient.java
new file mode 100644 (file)
index 0000000..e4bc824
--- /dev/null
@@ -0,0 +1,67 @@
+/**
+ * ExportClient.java
+ *
+ * {Purpose of This Class}
+ *
+ * {Other Notes Relating to This Class (Optional)}
+ *
+ * $LastChangedBy: $
+ * $LastChangedRevision: $
+ * $LastChangedDate: $
+ *
+ * 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 (c) 2009 {Contributing Institution}
+ *
+ * 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.common.invocable.InvocationContext;
+import org.collectionspace.services.export.ExportsCommon;
+
+/**
+ * An Export client.
+
+ * @version $Revision:$
+ */
+public class ExportClient extends AbstractCommonListPoxServiceClientImpl<ExportProxy, ExportsCommon> {
+       public static final String SERVICE_NAME = "exports";
+       public static final String SERVICE_PATH_COMPONENT = SERVICE_NAME;
+       public static final String SERVICE_PATH = "/" + SERVICE_PATH_COMPONENT;
+       public static final String SERVICE_COMMON_PART_NAME = SERVICE_NAME + PART_LABEL_SEPARATOR + PART_COMMON_LABEL;
+
+       public ExportClient() throws Exception {
+               super();
+       }
+
+       public ExportClient(String clientPropertiesFilename) throws Exception {
+               super(clientPropertiesFilename);
+       }
+
+       @Override
+       public String getServiceName() {
+               return SERVICE_NAME;
+       }
+
+       @Override
+       public String getServicePathComponent() {
+               return SERVICE_PATH_COMPONENT;
+       }
+
+       @Override
+       public Class<ExportProxy> getProxyClass() {
+               return ExportProxy.class;
+       }
+}
diff --git a/services/export/client/src/main/java/org/collectionspace/services/client/ExportProxy.java b/services/export/client/src/main/java/org/collectionspace/services/client/ExportProxy.java
new file mode 100644 (file)
index 0000000..705f3c8
--- /dev/null
@@ -0,0 +1,48 @@
+/**
+ * ExportProxy.java
+ *
+ * {Purpose of This Class}
+ *
+ * {Other Notes Relating to This Class (Optional)}
+ *
+ * $LastChangedBy: $
+ * $LastChangedRevision: $
+ * $LastChangedDate: $
+ *
+ * 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 {Contributing Institution}
+ *
+ * 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.GET;
+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.collectionspace.services.common.invocable.InvocationContext;
+
+/**
+ * @version $Revision:$
+ * FIXME: http://issues.collectionspace.org/browse/CSPACE-1684
+ */
+@Path("/exports/")
+@Produces({"application/xml;charset=UTF-8"})
+@Consumes({"application/xml"})
+public interface ExportProxy extends CollectionSpaceCommonListPoxProxy {
+}
diff --git a/services/export/jaxb/.gitignore b/services/export/jaxb/.gitignore
new file mode 100644 (file)
index 0000000..e91d5c4
--- /dev/null
@@ -0,0 +1,3 @@
+/target/
+/target/
+/target/
diff --git a/services/export/jaxb/pom.xml b/services/export/jaxb/pom.xml
new file mode 100644 (file)
index 0000000..b5cea75
--- /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.export</artifactId>
+        <groupId>org.collectionspace.services</groupId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>org.collectionspace.services.export.jaxb</artifactId>
+    <name>services.export.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-export-jaxb</finalName>
+        <defaultGoal>install</defaultGoal>
+        <plugins>
+            <plugin>
+                <groupId>org.jvnet.jaxb2.maven2</groupId>
+                <artifactId>maven-jaxb2-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/services/export/jaxb/src/main/java/org/collectionspace/services/ExportJAXBSchema.java b/services/export/jaxb/src/main/java/org/collectionspace/services/ExportJAXBSchema.java
new file mode 100644 (file)
index 0000000..c30f538
--- /dev/null
@@ -0,0 +1,9 @@
+/**
+ *
+ */
+package org.collectionspace.services;
+
+import org.collectionspace.services.jaxb.InvocableJAXBSchema;
+
+public interface ExportJAXBSchema extends InvocableJAXBSchema {
+}
diff --git a/services/export/jaxb/src/main/resources/exports-common.xsd b/services/export/jaxb/src/main/resources/exports-common.xsd
new file mode 100644 (file)
index 0000000..95e981c
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+
+<!--
+       Export schema (XSD)
+
+       Entity  : Export
+       Part    : Common
+       Used for: JAXB binding between XML and Java objects
+
+       $LastChangedRevision: 2498 $
+       $LastChangedDate: 2010-06-16 14:47:45 -0700 (Wed, 16 Jun 2010) $
+-->
+
+<xs:schema
+  xmlns:xs="http://www.w3.org/2001/XMLSchema"
+  xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
+  jaxb:version="1.0" elementFormDefault="unqualified"
+  xmlns:ns="http://collectionspace.org/services/export"
+  xmlns="http://collectionspace.org/services/export"
+  targetNamespace="http://collectionspace.org/services/export"
+  version="0.1"
+>
+
+       <!--
+               Avoid XmlRootElement nightmare:
+               See http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html
+       -->
+       <xs:element name="exports_common">
+               <xs:complexType>
+                       <xs:sequence>
+                               <xs:element name="csid" type="xs:string" />
+                               <xs:element name="name" type="xs:string" />
+                       </xs:sequence>
+               </xs:complexType>
+       </xs:element>
+</xs:schema>
diff --git a/services/export/pom.xml b/services/export/pom.xml
new file mode 100644 (file)
index 0000000..72b75d3
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- A comment. -->
+<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>
+       <groupId>org.collectionspace.services</groupId>
+       <artifactId>org.collectionspace.services.export</artifactId>
+       <name>services.export</name>
+       <packaging>pom</packaging>
+
+       <dependencies>
+       </dependencies>
+
+       <modules>
+               <module>jaxb</module>
+               <module>service</module>
+               <module>client</module>
+       </modules>
+</project>
diff --git a/services/export/service/pom.xml b/services/export/service/pom.xml
new file mode 100644 (file)
index 0000000..2cd3e71
--- /dev/null
@@ -0,0 +1,122 @@
+<?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.export</artifactId>
+               <version>${revision}</version>
+       </parent>
+
+       <modelVersion>4.0.0</modelVersion>
+       <artifactId>org.collectionspace.services.export.service</artifactId>
+       <name>services.export.service</name>
+       <packaging>jar</packaging>
+
+       <dependencies>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.authentication.service</artifactId>
+                       <version>${project.version}</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.account.service</artifactId>
+                       <version>${project.version}</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.authority.service</artifactId>
+                       <version>${project.version}</version>
+                       <scope>provided</scope>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.config</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.authority.jaxb</artifactId>
+                       <optional>true</optional>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.common</artifactId>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.export.jaxb</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.export.client</artifactId>
+                       <version>${project.version}</version>
+               </dependency>
+               <dependency>
+                       <groupId>org.collectionspace.services</groupId>
+                       <artifactId>org.collectionspace.services.collectionobject.jaxb</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>
+               </dependency>
+               <dependency>
+                       <groupId>org.apache.commons</groupId>
+                       <artifactId>commons-csv</artifactId>
+                       <version>1.8</version>
+               </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>
+               </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>
+
+       </dependencies>
+
+       <build>
+               <finalName>collectionspace-services-export</finalName>
+       </build>
+</project>
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/AbstractDocumentsByQueryIterator.java b/services/export/service/src/main/java/org/collectionspace/services/export/AbstractDocumentsByQueryIterator.java
new file mode 100644 (file)
index 0000000..9a6ad70
--- /dev/null
@@ -0,0 +1,244 @@
+package org.collectionspace.services.export;
+
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.ws.rs.core.PathSegment;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.utils.URIBuilder;
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.NuxeoBasedResource;
+import org.collectionspace.services.common.ServiceMain;
+import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
+import org.collectionspace.services.common.context.ServiceBindingUtils;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.collectionspace.services.common.query.UriInfoImpl;
+import org.collectionspace.services.common.vocabulary.AuthorityResource;
+import org.collectionspace.services.config.service.ServiceBindingType;
+import org.collectionspace.services.jaxb.AbstractCommonList;
+import org.jboss.resteasy.specimpl.PathSegmentImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractDocumentsByQueryIterator<ListItemType> implements Iterator<PoxPayloadOut> {
+       private final Logger logger = LoggerFactory.getLogger(AbstractDocumentsByQueryIterator.class);
+
+       private NuxeoBasedResource resource;
+       private String vocabulary;
+       private boolean isAuthorityItem = false;
+       private AbstractCommonList resultList;
+       private Iterator<ListItemType> resultItemIterator;
+       private InvocationContext.Query query;
+
+       protected ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext;
+
+       AbstractDocumentsByQueryIterator(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               String docType,
+               String vocabulary,
+               InvocationContext.Query query) throws Exception {
+
+               TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
+               ServiceBindingType serviceBinding = tenantBindingConfigReader.getServiceBindingForDocType(serviceContext.getTenantId(), docType);
+               String serviceType = serviceBinding.getType();
+               String serviceName = serviceBinding.getName();
+
+               this.serviceContext = serviceContext;
+               this.isAuthorityItem = ServiceBindingUtils.SERVICE_TYPE_AUTHORITY.equals(serviceType);
+               this.vocabulary = vocabulary;
+
+               this.resource = isAuthorityItem
+                       ? AuthorityResource.getResourceForItem(serviceContext.getResourceMap(), serviceContext.getTenantId(), docType)
+                       : (NuxeoBasedResource) serviceContext.getResource(serviceName.toLowerCase());
+
+               this.query = query;
+
+               getResults(query);
+       }
+
+       private void getResults(InvocationContext.Query query) throws Exception {
+               UriInfo uriInfo = createUriInfo(query);
+
+               resultList = isAuthorityItem
+                       ? ((AuthorityResource<?, ?>) resource).getAuthorityItemList(serviceContext, vocabulary == null ? AuthorityResource.PARENT_WILDCARD : vocabulary, uriInfo)
+                       : resource.getList(serviceContext, uriInfo);
+
+               resultItemIterator = (resultList == null) ? null : getListItems(resultList).iterator();
+       }
+
+       protected abstract List<ListItemType> getListItems(AbstractCommonList list);
+
+       private boolean hasMoreResultPages() {
+               if (resultList == null || query.getPgNum() != null) {
+                       return false;
+               }
+
+               long pageSize = resultList.getPageSize();
+               long pageNum = resultList.getPageNum();
+               long totalItems = resultList.getTotalItems();
+
+               return (totalItems > (pageSize * (pageNum + 1)));
+       }
+
+       private void getNextResultPage() throws Exception {
+               if (hasMoreResultPages()) {
+                       long pageSize = resultList.getPageSize();
+                       long pageNum = resultList.getPageNum();
+
+                       InvocationContext.Query nextPageQuery = new InvocationContext.Query();
+
+                       nextPageQuery.setAs(query.getAs());
+                       nextPageQuery.setKw(query.getKw());
+                       nextPageQuery.setPgNum(BigInteger.valueOf(pageNum + 1));
+                       nextPageQuery.setPgSz(BigInteger.valueOf(pageSize));
+                       nextPageQuery.setWfDeleted(query.isWfDeleted());
+
+                       getResults(nextPageQuery);
+               }
+       }
+
+       @Override
+       public boolean hasNext() {
+               return (
+                       resultList != null
+                       && resultItemIterator != null
+                       && (resultItemIterator.hasNext() || hasMoreResultPages())
+               );
+       }
+
+       @Override
+       public PoxPayloadOut next() {
+               if (resultList == null || resultItemIterator == null) {
+                       throw new NoSuchElementException();
+               }
+
+               if (!resultItemIterator.hasNext()) {
+                       if (!hasMoreResultPages()) {
+                       throw new NoSuchElementException();
+                       }
+
+                       try {
+                       getNextResultPage();
+                       }
+                       catch (Exception e) {
+                       logger.warn("Could not get result page", e);
+
+                       return null;
+                       }
+               }
+
+               return getDocument(resultItemIterator.next());
+       }
+
+       protected PoxPayloadOut getDocument(ListItemType item) {
+               String csid = getListItemCsid(item);
+
+               try {
+                       return (isAuthorityItem
+                       ? ((AuthorityResource<?, ?>) resource).getAuthorityItemWithExistingContext(serviceContext, AuthorityResource.PARENT_WILDCARD, csid)
+                       : resource.getWithParentCtx(serviceContext, csid));
+               }
+               catch (Exception e) {
+                       logger.warn("Could not get document with csid " + csid, e);
+
+                       return null;
+               }
+       }
+
+       protected abstract String getListItemCsid(ListItemType listItem);
+
+       protected UriInfo createUriInfo(InvocationContext.Query query) throws URISyntaxException {
+               URI     absolutePath = new URI("");
+               URI     baseUri = new URI("");
+               String encodedPath = "";
+
+               // Some code in services assumes pathSegments will have at least one element, so add an
+               // empty one.
+               List<PathSegment> pathSegments = Arrays.asList((PathSegment) new PathSegmentImpl("", false));
+
+               URIBuilder uriBuilder = new URIBuilder();
+
+               String as = query.getAs();
+
+               if (StringUtils.isNotEmpty(as)) {
+                       uriBuilder.addParameter("as", as);
+               }
+
+               String kw = query.getKw();
+
+               if (StringUtils.isNotEmpty(kw)) {
+                       uriBuilder.addParameter("kw", kw);
+               }
+
+               BigInteger pgNum = query.getPgNum();
+
+               if (pgNum != null) {
+                       uriBuilder.addParameter("pgNum", pgNum.toString());
+               }
+
+               BigInteger pgSz = query.getPgSz();
+
+               if (pgSz != null) {
+                       uriBuilder.addParameter("pgSz", pgSz.toString());
+               }
+
+               Boolean wfDeleted = query.isWfDeleted();
+
+               if (wfDeleted != null) {
+                       uriBuilder.addParameter("wf_deleted", Boolean.toString(wfDeleted));
+               }
+
+               String sbj = query.getSbj();
+
+               if (StringUtils.isNotEmpty(sbj)) {
+                       uriBuilder.addParameter("sbj", sbj);
+               }
+
+               String sbjType = query.getSbjType();
+
+               if (StringUtils.isNotEmpty(sbjType)) {
+                       uriBuilder.addParameter("sbjType", sbjType);
+               }
+
+               String prd = query.getPrd();
+
+               if (StringUtils.isNotEmpty(prd)) {
+                       uriBuilder.addParameter("prd", prd);
+               }
+
+               String obj = query.getObj();
+
+               if (StringUtils.isNotEmpty(obj)) {
+                       uriBuilder.addParameter("obj", obj);
+               }
+
+               String objType = query.getObjType();
+
+               if (StringUtils.isNotEmpty(objType)) {
+                       uriBuilder.addParameter("objType", objType);
+               }
+
+               Boolean andReciprocal = query.isAndReciprocal();
+
+               if (andReciprocal != null) {
+                       uriBuilder.addParameter("andReciprocal", Boolean.toString(andReciprocal));
+               }
+
+               String queryString = uriBuilder.toString();
+
+               if (StringUtils.isNotEmpty(queryString)) {
+                       queryString = queryString.substring(1); // Remove ? from beginning
+               }
+
+               return new UriInfoImpl(absolutePath, baseUri, encodedPath, queryString, pathSegments);
+       }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/AbstractExportWriter.java b/services/export/service/src/main/java/org/collectionspace/services/export/AbstractExportWriter.java
new file mode 100644 (file)
index 0000000..80d1fd0
--- /dev/null
@@ -0,0 +1,57 @@
+package org.collectionspace.services.export;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+
+public abstract class AbstractExportWriter implements ExportWriter {
+  protected InvocationContext invocationContext;
+  protected ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext;
+  protected TenantBindingConfigReaderImpl tenantBindingConfigReader;
+  protected Writer writer;
+
+  @Override
+  public void setInvocationContext(InvocationContext invocationContext) {
+    this.invocationContext = invocationContext;
+  }
+
+  @Override
+  public void setOutputStream(OutputStream outputStream) {
+    this.writer = new OutputStreamWriter(outputStream);
+  }
+
+  @Override
+  public void setServiceContext(ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext) {
+    this.serviceContext = serviceContext;
+  }
+
+  @Override
+  public void setTenantBindingConfigReader(TenantBindingConfigReaderImpl tenantBindingConfigReader) {
+    this.tenantBindingConfigReader = tenantBindingConfigReader;
+  }
+
+  @Override
+  public void start() throws Exception {
+  };
+
+  @Override
+  public abstract void writeDocument(PoxPayloadOut document) throws Exception;
+
+  @Override
+  public void finish() throws Exception {
+  }
+
+  @Override
+  public void close() throws Exception {
+    writer.close();
+  }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/CsvExportWriter.java b/services/export/service/src/main/java/org/collectionspace/services/export/CsvExportWriter.java
new file mode 100644 (file)
index 0000000..fb404d5
--- /dev/null
@@ -0,0 +1,209 @@
+package org.collectionspace.services.export;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.lang.StringUtils;
+import org.collectionspace.services.client.PayloadOutputPart;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.api.RefNameUtils;
+import org.collectionspace.services.common.context.ServiceBindingUtils;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.collectionspace.services.config.service.ServiceBindingType;
+import org.collectionspace.services.config.service.ServiceObjectType;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+import org.dom4j.Node;
+import org.dom4j.VisitorSupport;
+
+public class CsvExportWriter extends AbstractExportWriter {
+       private static final Pattern VALID_FIELD_XPATH_PATTERN = Pattern.compile("^\\w+:(\\w+\\/)*(\\w+)$");
+       private static final String AUTH_ITEM_TERM_GROUP_SUFFIX = "TermGroup";
+
+       private CSVPrinter csvPrinter;
+       private Map<String, Map<String, Set<String>>> refFieldsByDocType = new HashMap<>();
+
+       @Override
+       public void start() throws Exception {
+               // For CSV output, the invocation context must specify the exact fields to be included
+               // (no wildcard xpaths), so that the columns can be known in advance. Otherwise the entire
+               // result set would need to be scanned first, in order to determine the fields that are
+               // present.
+
+               InvocationContext.IncludeFields includeFields = invocationContext.getIncludeFields();
+               List<String> fields = (includeFields != null ? includeFields.getField() : new ArrayList<String>());
+
+               if (fields.size() == 0) {
+                       throw new Exception("For CSV output, the fields to export must be specified using includeFields.");
+               }
+
+               List<String> headers = new ArrayList<>();
+
+               for (String field : fields) {
+                       Matcher matcher = VALID_FIELD_XPATH_PATTERN.matcher(field);
+
+                       if (!matcher.matches()) {
+                               throw new Exception("The includeField XPath expression \"" + field + "\" is not valid for CSV output. For CSV output, all included fields must be individually specified without using wildcards.");
+                       }
+
+                       String fieldName = matcher.group(2);
+
+                       headers.add(fieldName);
+
+                       if (isFieldWithinAuthItemTermGroup(field)) {
+                               headers.add(fieldName + "NonPreferred");
+                       }
+               }
+
+               String[] headersArray = new String[headers.size()];
+
+               this.csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT.withHeader(headers.toArray(headersArray)));
+       }
+
+       @Override
+       public void writeDocument(PoxPayloadOut document) throws Exception {
+               InvocationContext.IncludeFields includeFields = invocationContext.getIncludeFields();
+
+               if (includeFields == null) {
+                       return;
+               }
+
+               List<String> fields = includeFields.getField();
+               List<String> csvRecord = new ArrayList<>();
+
+               for (String field : fields) {
+                       if (isFieldWithinAuthItemTermGroup(field)) {
+                               // Write a column for values within the preferred (primary) term group.
+                               csvRecord.add(collectValues(document, field.replace(AUTH_ITEM_TERM_GROUP_SUFFIX + "/", AUTH_ITEM_TERM_GROUP_SUFFIX + "[position()=1]/")));
+
+                               // Write a column for values within non-preferred term groups.
+                               csvRecord.add(collectValues(document, field.replace(AUTH_ITEM_TERM_GROUP_SUFFIX + "/", AUTH_ITEM_TERM_GROUP_SUFFIX + "[position()>1]/")));
+                       }
+                       else {
+                               csvRecord.add(collectValues(document, field));
+                       }
+               }
+
+               if (csvRecord.size() > 0) {
+                       csvPrinter.printRecord(csvRecord);
+               }
+       }
+
+       @Override
+       public void close() throws Exception {
+         csvPrinter.close();
+       }
+
+       private boolean isFieldWithinAuthItemTermGroup(String field) {
+               // FIXME: How to know a NonPreferred column is needed without hardcoding "TermGroup"?
+
+               return field.contains(AUTH_ITEM_TERM_GROUP_SUFFIX + "/");
+       }
+
+       private String collectValues(PoxPayloadOut document, String field) {
+               String delimitedValues = "";
+               String[] segments = field.split(":", 2);
+               String partName = segments[0];
+               String xpath = segments[1];
+
+               PayloadOutputPart part = document.getPart(partName);
+
+               if (part != null) {
+                       delimitedValues = collectValues(document.getName(), partName, part.getElementBody(), Arrays.asList(xpath.split("/")), 0);
+               }
+
+               return delimitedValues;
+       }
+
+       private String collectValues(String docType, String partName, Element element, List<String> path, int depth) {
+               String delimitedValues = "";
+               String fieldName = path.get(depth);
+               String delimiter = (depth / 2 > 0) ? "^^" : ";";
+               List<Node> matches = element.createXPath(fieldName).selectNodes(element);
+
+               if (matches.size() > 0) {
+                       List<String> values = new ArrayList<>();
+                       boolean hasValue = false;
+
+                       for (Node node : matches) {
+                               String textValue = "";
+
+                               if (depth < path.size() - 1) {
+                                       textValue = collectValues(docType, partName, (Element) node, path, depth + 1);
+                               }
+                               else {
+                                       textValue = node.getText();
+
+                                       boolean isRefName = isRefField(docType, partName, fieldName);
+
+                                       if (isRefName && StringUtils.isNotEmpty(textValue)) {
+                                               textValue = RefNameUtils.getDisplayName(textValue);
+                                       }
+                               }
+
+                               if (StringUtils.isNotEmpty(textValue)) {
+                                       hasValue = true;
+                               }
+
+                               values.add(textValue);
+                       }
+
+                       if (hasValue) {
+                               delimitedValues = String.join(delimiter, values);
+                       }
+               }
+
+               return delimitedValues;
+       }
+
+       private boolean isRefField(String docType, String partName, String fieldName) {
+               return getRefFields(docType, partName).contains(fieldName);
+       }
+
+       private Set<String> getRefFields(String docType, String partName) {
+               Set<String> refFields = refFieldsByDocType.containsKey(docType)
+                       ? refFieldsByDocType.get(docType).get(partName)
+                       : null;
+
+               if (refFields != null) {
+                       return refFields;
+               }
+
+               refFields = new HashSet<>();
+
+               ServiceBindingType serviceBinding = tenantBindingConfigReader.getServiceBinding(serviceContext.getTenantId(), docType);
+
+               for (String termRefField : ServiceBindingUtils.getPropertyValuesForPart(serviceBinding, partName, ServiceBindingUtils.TERM_REF_PROP, false)) {
+                       String[] segments = termRefField.split("[\\/\\|]");
+                       String fieldName = segments[segments.length - 1];
+
+                       refFields.add(fieldName);
+               }
+
+               for (String authRefField : ServiceBindingUtils.getPropertyValuesForPart(serviceBinding, partName, ServiceBindingUtils.AUTH_REF_PROP, false)) {
+                       String[] segments = authRefField.split("[\\/\\|]");
+                       String fieldName = segments[segments.length - 1];
+
+                       refFields.add(fieldName);
+               }
+
+               if (!refFieldsByDocType.containsKey(docType)) {
+                       refFieldsByDocType.put(docType, new HashMap<String, Set<String>>());
+               }
+
+               refFieldsByDocType.get(docType).put(partName, refFields);
+
+               return refFields;
+       }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/DocumentsByCsidIterator.java b/services/export/service/src/main/java/org/collectionspace/services/export/DocumentsByCsidIterator.java
new file mode 100644 (file)
index 0000000..787065b
--- /dev/null
@@ -0,0 +1,68 @@
+package org.collectionspace.services.export;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.NuxeoBasedResource;
+import org.collectionspace.services.common.ServiceMain;
+import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
+import org.collectionspace.services.common.context.ServiceBindingUtils;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.vocabulary.AuthorityResource;
+import org.collectionspace.services.config.service.ServiceBindingType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DocumentsByCsidIterator implements Iterator<PoxPayloadOut> {
+       private final Logger logger = LoggerFactory.getLogger(DocumentsByCsidIterator.class);
+
+  private NuxeoBasedResource resource;
+  private String vocabulary;
+  private ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext;
+  private Iterator<String> csidIterator;
+  private boolean isAuthorityItem = false;
+
+  DocumentsByCsidIterator(
+    ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+    String docType,
+    String vocabulary,
+    List<String> csids) throws Exception {
+
+    TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
+    ServiceBindingType serviceBinding = tenantBindingConfigReader.getServiceBindingForDocType(serviceContext.getTenantId(), docType);
+    String serviceType = serviceBinding.getType();
+    String serviceName = serviceBinding.getName();
+
+    this.serviceContext = serviceContext;
+    this.csidIterator = csids.iterator();
+    this.isAuthorityItem = ServiceBindingUtils.SERVICE_TYPE_AUTHORITY.equals(serviceType);
+    this.vocabulary = vocabulary;
+
+    this.resource = isAuthorityItem
+      ? AuthorityResource.getResourceForItem(serviceContext.getResourceMap(), serviceContext.getTenantId(), docType)
+      : (NuxeoBasedResource) serviceContext.getResource(serviceName.toLowerCase());
+  }
+
+  @Override
+  public boolean hasNext() {
+    return csidIterator.hasNext();
+  }
+
+  @Override
+  public PoxPayloadOut next() {
+    String csid = csidIterator.next();
+
+    try {
+      return (isAuthorityItem
+        ? ((AuthorityResource<?, ?>) resource).getAuthorityItemWithExistingContext(serviceContext, vocabulary == null ? AuthorityResource.PARENT_WILDCARD : vocabulary, csid)
+        : resource.getWithParentCtx(serviceContext, csid));
+    }
+    catch (Exception e) {
+      logger.warn("Could not get document with csid " + csid, e);
+
+      return null;
+    }
+  }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/DocumentsByGroupIterator.java b/services/export/service/src/main/java/org/collectionspace/services/export/DocumentsByGroupIterator.java
new file mode 100644 (file)
index 0000000..20fd25c
--- /dev/null
@@ -0,0 +1,40 @@
+package org.collectionspace.services.export;
+
+import java.util.Iterator;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.RelationClient;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+
+public class DocumentsByGroupIterator implements Iterator<PoxPayloadOut> {
+  private RelationObjectsByQueryIterator relationsIterator;
+
+  DocumentsByGroupIterator(
+    ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+    String csid) throws Exception {
+
+    InvocationContext.Query query = new InvocationContext.Query();
+
+    query.setSbjType("Group");
+    query.setSbj(csid);
+    query.setPrd("affects");
+    query.setWfDeleted(false);
+
+    relationsIterator = new RelationObjectsByQueryIterator(serviceContext, RelationClient.SERVICE_DOC_TYPE, null, query);
+  }
+
+  @Override
+  public boolean hasNext() {
+    return (
+      relationsIterator != null
+      && relationsIterator.hasNext()
+    );
+  }
+
+  @Override
+  public PoxPayloadOut next() {
+    return relationsIterator.next();
+  }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/ExportResource.java b/services/export/service/src/main/java/org/collectionspace/services/export/ExportResource.java
new file mode 100644 (file)
index 0000000..b1e76cb
--- /dev/null
@@ -0,0 +1,371 @@
+/**
+ *  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.export;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.collectionspace.services.client.ExportClient;
+import org.collectionspace.services.client.PayloadOutputPart;
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.RelationClient;
+import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl;
+import org.collectionspace.services.common.ServiceMessages;
+import org.collectionspace.services.common.context.MultipartServiceContextFactory;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.context.ServiceContextFactory;
+import org.collectionspace.services.common.invocable.Invocable;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.dom4j.Node;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+@Path(ExportClient.SERVICE_PATH)
+@Consumes("application/xml")
+@Produces("application/xml")
+public class ExportResource extends AbstractCollectionSpaceResourceImpl<PoxPayloadIn, PoxPayloadOut> {
+       private final Logger logger = LoggerFactory.getLogger(ExportResource.class);
+
+       // There is no way to tell from config that collectionspace_core should not be exported, so it
+       // has to be hardcoded here. At that point we might as well also hardcode account_permission,
+       // so we don't need to look at config at all.
+
+       private static final List<String> EXCLUDE_PARTS = Arrays.asList("collectionspace_core", "account_permission");
+
+       private static final String MIME_TYPE_CSV = "text/csv";
+       private static final String MIME_TYPE_XML = "application/xml";
+       private static final String INCLUDE_ATTRIBUTE_NAME = "cspace-export-include";
+
+       @Override
+       protected String getVersionString() {
+               final String lastChangeRevision = "$LastChangedRevision: 1982 $";
+               return lastChangeRevision;
+       }
+
+       @Override
+       public String getServiceName() {
+               return ExportClient.SERVICE_NAME;
+       }
+
+       @Override
+       public Class<?> getCommonPartClass() {
+               return ExportsCommon.class;
+       }
+
+       @Override
+       public ServiceContextFactory<PoxPayloadIn, PoxPayloadOut> getServiceContextFactory() {
+               return MultipartServiceContextFactory.get();
+       }
+
+       @GET
+       @Produces("text/html")
+       public String getInputForm() {
+               return "<html><head></head><body>Export</body></html>";
+       }
+
+       @POST
+       public Response invokeExport(@Context UriInfo uriInfo, InvocationContext invocationContext) throws Exception {
+               String outputMimeType = getOutputMimeType(invocationContext);
+
+               if (!(outputMimeType.equals(MIME_TYPE_XML) || outputMimeType.equals(MIME_TYPE_CSV))) {
+                       throw new Exception("Unsupported output MIME type " + outputMimeType);
+               }
+
+               String outputFileName = "export." + (outputMimeType.equals(MIME_TYPE_XML) ? "xml" : "csv");
+
+               try {
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext = createServiceContext();
+                       InputStream exportStream = invokeExport(serviceContext, invocationContext);
+
+                       return Response.ok(exportStream, outputMimeType)
+                                       .header("Content-Disposition", "inline;filename=\"" + outputFileName + "\"").build();
+               } catch (Exception e) {
+                       String message = e.getMessage();
+
+                       throw bigReThrow(e, ServiceMessages.POST_FAILED + (message != null ? message : ""));
+               }
+       }
+
+       private String getOutputMimeType(InvocationContext invocationContext) {
+               String outputMimeType = invocationContext.getOutputMIME();
+
+               if (StringUtils.isEmpty(outputMimeType)) {
+                       outputMimeType = MIME_TYPE_XML;
+               }
+
+               return outputMimeType;
+       }
+
+       private InputStream invokeExport(ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       InvocationContext invocationContext) throws Exception {
+
+               Iterator<PoxPayloadOut> documents = getDocuments(serviceContext, invocationContext);
+
+               return exportDocuments(serviceContext, invocationContext, documents);
+       }
+
+       private InputStream exportDocuments(ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       InvocationContext invocationContext, Iterator<PoxPayloadOut> documents) throws Exception {
+
+               File exportFile = File.createTempFile("export-", null);
+               FileOutputStream outputStream = new FileOutputStream(exportFile);
+               ExportWriter exportWriter = getExportWriter(serviceContext, invocationContext, outputStream);
+
+               exportWriter.start();
+
+               while (documents.hasNext()) {
+                       PoxPayloadOut document = documents.next();
+
+                       if (document != null) {
+                               filterFields(document, invocationContext);
+
+                               exportWriter.writeDocument(document);
+                       }
+               }
+
+               exportWriter.finish();
+               exportWriter.close();
+
+               return new FileInputStream(exportFile);
+       }
+
+       private void filterFields(PoxPayloadOut document, InvocationContext invocationContext) {
+               if (document == null) {
+                       return;
+               }
+
+               for (String partName : EXCLUDE_PARTS) {
+                       PayloadOutputPart part = document.getPart(partName);
+
+                       if (part != null) {
+                               document.removePart(part);
+                       }
+               }
+
+               InvocationContext.ExcludeFields excludeFields = invocationContext.getExcludeFields();
+
+               if (excludeFields != null) {
+                       List<String> fields = excludeFields.getField();
+
+                       for (String field : fields) {
+                               String[] segments = field.split(":", 2);
+
+                               String partName = segments[0];
+                               String xpath = segments[1];
+
+                               PayloadOutputPart part = document.getPart(partName);
+
+                               if (part != null) {
+                                       org.dom4j.Element partElement = part.getElementBody();
+                                       List<Node> matches = (List<Node>) partElement.createXPath(xpath).selectNodes(partElement);
+
+                                       for (Node excludeNode : matches) {
+                                               if (excludeNode.getNodeType() == Node.ELEMENT_NODE) {
+                                                       excludeNode.detach();
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               InvocationContext.IncludeFields includeFields = invocationContext.getIncludeFields();
+
+               if (includeFields != null) {
+                       List<String> fields = includeFields.getField();
+
+                       for (String field : fields) {
+                               String[] segments = field.split(":", 2);
+
+                               String partName = segments[0];
+                               String xpath = segments[1];
+
+                               PayloadOutputPart part = document.getPart(partName);
+
+                               if (part != null) {
+                                       org.dom4j.Element partElement = part.getElementBody();
+                                       List<Node> matches = (List<Node>) partElement.createXPath(xpath).selectNodes(partElement);
+
+                                       for (Node includeNode : matches) {
+                                               if (includeNode.getNodeType() == Node.ELEMENT_NODE) {
+                                                       markIncluded((org.dom4j.Element) includeNode);
+                                               }
+                                       }
+                               }
+                       }
+
+                       ArrayList<PayloadOutputPart> includedParts = new ArrayList<>();
+
+                       for (PayloadOutputPart part : document.getParts()) {
+                               org.dom4j.Element partElement = part.getElementBody();
+
+                               if (partElement.attributeValue(INCLUDE_ATTRIBUTE_NAME) != null) {
+                                       includedParts.add(part);
+                                       removeUnincluded(partElement);
+                               }
+                       }
+
+                       document.setParts(includedParts);
+               }
+       }
+
+       private void markIncluded(org.dom4j.Element element) {
+               org.dom4j.Element parentElement = element.getParent();
+
+               if (parentElement != null) {
+                       markIncluded(parentElement);
+               }
+
+               element.addAttribute(INCLUDE_ATTRIBUTE_NAME, "1");
+       }
+
+       private void removeUnincluded(org.dom4j.Element element) {
+               if (element.attributeValue(INCLUDE_ATTRIBUTE_NAME) == null) {
+                       element.detach();
+               }
+               else {
+                       element.addAttribute(INCLUDE_ATTRIBUTE_NAME, null);
+
+                       Iterator childIterator = element.elementIterator();
+
+                       while (childIterator.hasNext()) {
+                               org.dom4j.Element childElement = (org.dom4j.Element) childIterator.next();
+
+                               removeUnincluded(childElement);
+                       }
+               }
+       }
+
+       private ExportWriter getExportWriter(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               InvocationContext invocationContext,
+               OutputStream outputStream) {
+
+               String outputMimeType = getOutputMimeType(invocationContext);
+               AbstractExportWriter exportWriter = null;
+
+               if (outputMimeType.equals(MIME_TYPE_XML)) {
+                       exportWriter = new XmlExportWriter();
+               }
+               else if (outputMimeType.equals(MIME_TYPE_CSV)) {
+                       exportWriter = new CsvExportWriter();
+               }
+
+               if (exportWriter != null) {
+                       exportWriter.setInvocationContext(invocationContext);
+                       exportWriter.setOutputStream(outputStream);
+                       exportWriter.setServiceContext(serviceContext);
+                       exportWriter.setTenantBindingConfigReader(getTenantBindingsReader());
+               }
+
+               return exportWriter;
+       }
+
+       private Iterator<PoxPayloadOut> getDocuments(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               InvocationContext invocationContext) throws Exception {
+
+               String targetDocType = invocationContext.getDocType();
+               String targetVocabulary = invocationContext.getVocabulary();
+
+               switch (invocationContext.getMode().toLowerCase()) {
+                       case Invocable.INVOCATION_MODE_QUERY:
+                               return getDocumentsByQuery(serviceContext, targetDocType, targetVocabulary, invocationContext.getQuery());
+                       case Invocable.INVOCATION_MODE_SINGLE:
+                               return getDocumentByCsid(serviceContext, targetDocType, targetVocabulary, invocationContext.getSingleCSID());
+                       case Invocable.INVOCATION_MODE_LIST:
+                               return getDocumentsByCsid(serviceContext, targetDocType, targetVocabulary, invocationContext.getListCSIDs().getCsid());
+                       case Invocable.INVOCATION_MODE_GROUP:
+                               return getDocumentsByGroup(serviceContext, invocationContext.getGroupCSID());
+                       case Invocable.INVOCATION_MODE_NO_CONTEXT:
+                               return getDocumentsByType(serviceContext, targetDocType, targetVocabulary);
+                       default:
+                               throw new UnsupportedOperationException("Unsupported invocation mode: " + invocationContext.getMode());
+               }
+       }
+
+       private Iterator<PoxPayloadOut> getDocumentsByType(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               String docType,
+               String vocabulary) throws Exception {
+
+               return getDocumentsByQuery(serviceContext, docType, vocabulary, new InvocationContext.Query());
+       }
+
+       private Iterator<PoxPayloadOut> getDocumentsByQuery(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               String docType,
+               String vocabulary,
+               InvocationContext.Query query) throws Exception {
+
+               if (RelationClient.SERVICE_DOC_TYPE.equals(docType)) {
+                       return new RelationsByQueryIterator(serviceContext, docType, vocabulary, query);
+               }
+
+               return new StandardDocumentsByQueryIterator(serviceContext, docType, vocabulary, query);
+       }
+
+       private Iterator<PoxPayloadOut> getDocumentByCsid(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               String docType,
+               String vocabulary,
+               String csid) throws Exception {
+
+               return getDocumentsByCsid(serviceContext, docType, vocabulary, Arrays.asList(csid));
+       }
+
+       private Iterator<PoxPayloadOut> getDocumentsByCsid(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               String docType,
+               String vocabulary,
+               List<String> csids) throws Exception {
+
+               return new DocumentsByCsidIterator(serviceContext, docType, vocabulary, csids);
+       }
+
+       private Iterator<PoxPayloadOut> getDocumentsByGroup(
+               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+               String csid) throws Exception {
+
+               return new DocumentsByGroupIterator(serviceContext, csid);
+       }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/ExportWriter.java b/services/export/service/src/main/java/org/collectionspace/services/export/ExportWriter.java
new file mode 100644 (file)
index 0000000..fc8f719
--- /dev/null
@@ -0,0 +1,23 @@
+package org.collectionspace.services.export;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Set;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+
+public interface ExportWriter {
+       public void setInvocationContext(InvocationContext invocationContext);
+       public void setOutputStream(OutputStream outputStream);
+       public void setServiceContext(ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext);
+       public void setTenantBindingConfigReader(TenantBindingConfigReaderImpl tenantBindingConfigReader);
+
+       public void start() throws Exception;
+       public void writeDocument(PoxPayloadOut document) throws Exception;
+       public void finish() throws Exception;
+       public void close() throws Exception;
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/RelationObjectsByQueryIterator.java b/services/export/service/src/main/java/org/collectionspace/services/export/RelationObjectsByQueryIterator.java
new file mode 100644 (file)
index 0000000..857bb24
--- /dev/null
@@ -0,0 +1,61 @@
+package org.collectionspace.services.export;
+
+import java.util.Iterator;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.NuxeoBasedResource;
+import org.collectionspace.services.common.ServiceMain;
+import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
+import org.collectionspace.services.common.context.ServiceBindingUtils;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.collectionspace.services.common.vocabulary.AuthorityResource;
+import org.collectionspace.services.config.service.ServiceBindingType;
+import org.collectionspace.services.relation.RelationsCommonList.RelationListItem;
+import org.collectionspace.services.relation.RelationsDocListItem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RelationObjectsByQueryIterator extends RelationsByQueryIterator implements Iterator<PoxPayloadOut> {
+       private final Logger logger = LoggerFactory.getLogger(RelationObjectsByQueryIterator.class);
+
+  RelationObjectsByQueryIterator(
+    ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+    String docType,
+    String vocabulary,
+    InvocationContext.Query query) throws Exception {
+
+    super(serviceContext, docType, vocabulary, query);
+  }
+
+  @Override
+  protected PoxPayloadOut getDocument(RelationListItem item) {
+    RelationsDocListItem relationObject = item.getObject();
+
+    String relationObjectCsid = relationObject.getCsid();
+    String relationObjectDocType = relationObject.getDocumentType();
+
+               TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader();
+    ServiceBindingType relationObjectServiceBinding = tenantBindingConfigReader.getServiceBindingForDocType(serviceContext.getTenantId(), relationObjectDocType);
+    String relationObjectServiceType = relationObjectServiceBinding.getType();
+    String relationObjectServiceName = relationObjectServiceBinding.getName();
+
+    boolean relationObjectIsAuthorityItem = ServiceBindingUtils.SERVICE_TYPE_AUTHORITY.equals(relationObjectServiceType);
+
+    try {
+      NuxeoBasedResource relationObjectResource = relationObjectIsAuthorityItem
+        ? AuthorityResource.getResourceForItem(serviceContext.getResourceMap(), serviceContext.getTenantId(), relationObjectDocType)
+        : (NuxeoBasedResource) serviceContext.getResource(relationObjectServiceName.toLowerCase());
+
+      return (relationObjectIsAuthorityItem
+        ? ((AuthorityResource<?, ?>) relationObjectResource).getAuthorityItemWithExistingContext(serviceContext, AuthorityResource.PARENT_WILDCARD, relationObjectCsid)
+        : relationObjectResource.getWithParentCtx(serviceContext, relationObjectCsid));
+    }
+    catch (Exception e) {
+      logger.warn("Could not get document with csid " + relationObjectCsid, e);
+
+      return null;
+    }
+  }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/RelationsByQueryIterator.java b/services/export/service/src/main/java/org/collectionspace/services/export/RelationsByQueryIterator.java
new file mode 100644 (file)
index 0000000..9e2883f
--- /dev/null
@@ -0,0 +1,34 @@
+
+package org.collectionspace.services.export;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.collectionspace.services.jaxb.AbstractCommonList;
+import org.collectionspace.services.relation.RelationsCommonList;
+import org.collectionspace.services.relation.RelationsCommonList.RelationListItem;
+
+public class RelationsByQueryIterator extends AbstractDocumentsByQueryIterator<RelationListItem> implements Iterator<PoxPayloadOut> {
+  RelationsByQueryIterator(
+    ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+    String docType,
+    String vocabulary,
+    InvocationContext.Query query) throws Exception {
+
+    super(serviceContext, docType, vocabulary, query);
+  }
+
+  @Override
+  protected List<RelationListItem> getListItems(AbstractCommonList list) {
+    return ((RelationsCommonList) list).getRelationListItem();
+  }
+
+  @Override
+  protected String getListItemCsid(RelationListItem listItem) {
+    return listItem.getCsid();
+  }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/StandardDocumentsByQueryIterator.java b/services/export/service/src/main/java/org/collectionspace/services/export/StandardDocumentsByQueryIterator.java
new file mode 100644 (file)
index 0000000..fe85c76
--- /dev/null
@@ -0,0 +1,40 @@
+package org.collectionspace.services.export;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.collectionspace.services.jaxb.AbstractCommonList;
+import org.collectionspace.services.jaxb.AbstractCommonList.ListItem;
+
+import org.w3c.dom.Element;
+
+public class StandardDocumentsByQueryIterator extends AbstractDocumentsByQueryIterator<ListItem> implements Iterator<PoxPayloadOut> {
+  StandardDocumentsByQueryIterator(
+    ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+    String docType,
+    String vocabulary,
+    InvocationContext.Query query) throws Exception {
+
+    super(serviceContext, docType, vocabulary, query);
+  }
+
+  @Override
+  protected List<ListItem> getListItems(AbstractCommonList list) {
+    return list.getListItem();
+  }
+
+  @Override
+  protected String getListItemCsid(ListItem listItem) {
+    for (Element element : listItem.getAny()) {
+      if (element.getTagName().equals("csid")) {
+        return element.getTextContent();
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/XmlExportWriter.java b/services/export/service/src/main/java/org/collectionspace/services/export/XmlExportWriter.java
new file mode 100644 (file)
index 0000000..35ffa54
--- /dev/null
@@ -0,0 +1,63 @@
+package org.collectionspace.services.export;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.collectionspace.services.client.PayloadOutputPart;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.config.service.ServiceBindingType;
+import org.collectionspace.services.config.service.ServiceObjectType;
+import org.dom4j.Element;
+import org.dom4j.Namespace;
+
+public class XmlExportWriter extends AbstractExportWriter {
+       private int seqNum = 0;
+
+       @Override
+       public void start() throws Exception {
+               seqNum = 0;
+
+               writer.write("<imports>\n");
+       }
+
+       @Override
+       public void writeDocument(PoxPayloadOut document) throws Exception {
+               String docType = document.getName();
+               ServiceBindingType serviceBinding = tenantBindingConfigReader.getServiceBinding(serviceContext.getTenantId(), docType);
+
+               String serviceName = serviceBinding.getName();
+               ServiceObjectType objectConfig = serviceBinding.getObject();
+               String objectName = objectConfig.getName();
+
+               seqNum += 1;
+
+               writer.write(String.format("<import seq=\"%d\" service=\"%s\" type=\"%s\">\n", seqNum, serviceName, objectName));
+
+               for (PayloadOutputPart part : document.getParts()) {
+                       String partLabel = part.getLabel();
+                       Element element = part.asElement();
+                       Namespace partNamespace = element.getNamespaceForPrefix("ns2");
+                       String partNamespaceUri = partNamespace.getURI();
+
+                       for (Object namespace : element.additionalNamespaces()) {
+                               element.remove((Namespace) namespace);
+                       }
+
+                       element.remove(partNamespace);
+
+                       element.setName("schema");
+                       element.addAttribute("name", partLabel);
+                       element.addNamespace(partLabel, partNamespaceUri);
+
+                       writer.write(element.asXML());
+               }
+
+               writer.write("</import>\n");
+       }
+
+       @Override
+       public void finish() throws Exception {
+               writer.write("</imports>");
+       }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportConstants.java b/services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportConstants.java
new file mode 100644 (file)
index 0000000..71a6109
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ *  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.export.nuxeo;
+
+public class ExportConstants {
+       public final static String NUXEO_DOCTYPE = "Export";
+       public final static String NUXEO_SCHEMA_NAME = "export";
+       public final static String DB_COMMON_PART_TABLE_NAME = "exports_common";
+       public final static String NUXEO_DC_TITLE = "CollectionSpace-Export";
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportDocumentModelHandler.java b/services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportDocumentModelHandler.java
new file mode 100644 (file)
index 0000000..a971eb5
--- /dev/null
@@ -0,0 +1,249 @@
+/**
+ *  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.export.nuxeo;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.naming.NamingException;
+import javax.ws.rs.core.Response;
+
+import org.apache.commons.lang.StringUtils;
+import org.collectionspace.authentication.AuthN;
+import org.collectionspace.services.ExportJAXBSchema;
+import org.collectionspace.services.account.AccountResource;
+import org.collectionspace.services.authorization.AuthZ;
+import org.collectionspace.services.authorization.CSpaceResource;
+import org.collectionspace.services.authorization.PermissionException;
+import org.collectionspace.services.authorization.URIResourceImpl;
+import org.collectionspace.services.authorization.perms.ActionType;
+import org.collectionspace.services.client.PoxPayloadIn;
+import org.collectionspace.services.client.PoxPayloadOut;
+import org.collectionspace.services.client.ExportClient;
+import org.collectionspace.services.common.CSWebApplicationException;
+import org.collectionspace.services.common.NuxeoBasedResource;
+import org.collectionspace.services.common.ResourceMap;
+import org.collectionspace.services.common.ServiceMain;
+import org.collectionspace.services.common.api.JEEServerDeployment;
+import org.collectionspace.services.common.api.FileTools;
+import org.collectionspace.services.common.api.Tools;
+import org.collectionspace.services.common.authorization_mgt.ActionGroup;
+import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
+import org.collectionspace.services.common.context.ServiceBindingUtils;
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.document.BadRequestException;
+import org.collectionspace.services.common.document.DocumentException;
+import org.collectionspace.services.common.document.DocumentWrapper;
+import org.collectionspace.services.common.invocable.Invocable;
+import org.collectionspace.services.common.invocable.InvocationContext;
+import org.collectionspace.services.common.invocable.InvocationContext.ListCSIDs;
+import org.collectionspace.services.common.storage.JDBCTools;
+import org.collectionspace.services.config.service.ServiceBindingType;
+import org.collectionspace.services.config.types.PropertyItemType;
+import org.collectionspace.services.export.ExportsCommon;
+import org.collectionspace.services.jaxb.InvocableJAXBSchema;
+import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler;
+import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
+import org.collectionspace.services.nuxeo.client.java.NuxeoRepositoryClientImpl;
+import org.collectionspace.services.nuxeo.util.NuxeoUtils;
+
+import org.nuxeo.ecm.core.api.model.PropertyException;
+import org.nuxeo.ecm.core.api.DocumentModel;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ExportDocumentModelHandler
+ *
+ * $LastChangedRevision: $
+ * $LastChangedDate: $
+ */
+public class ExportDocumentModelHandler extends NuxeoDocumentModelHandler<ExportsCommon> {
+       private final Logger logger = LoggerFactory.getLogger(ExportDocumentModelHandler.class);
+
+       public InputStream invokeExport(
+                       TenantBindingConfigReaderImpl tenantBindingReader,
+                       ResourceMap resourceMap,
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       InvocationContext invocationContext,
+                       StringBuffer outputMimeType,
+                       StringBuffer outputFileName) throws Exception {
+
+               NuxeoRepositoryClientImpl repoClient = (NuxeoRepositoryClientImpl) this.getRepositoryClient(serviceContext);
+               boolean releaseRepoSession = false;
+               CoreSessionInterface repoSession = this.getRepositorySession();
+
+               if (repoSession == null) {
+                       repoSession = repoClient.getRepositorySession(serviceContext);
+                       releaseRepoSession = true;
+               }
+
+               try {
+                       Iterator<DocumentModel> documents = findDocuments(tenantBindingReader, resourceMap, serviceContext, repoSession, invocationContext);
+
+               }
+               finally {
+                       if (releaseRepoSession && repoSession != null) {
+                               repoClient.releaseRepositorySession(serviceContext, repoSession);
+                       }
+               }
+
+               return null;
+               // return buildExportResult(csid, params, exportFileNameProperty, outMimeType.toString(), outExportFileName);
+       }
+
+       private Iterator<DocumentModel> findDocuments(
+                       TenantBindingConfigReaderImpl tenantBindingReader,
+                       ResourceMap resourceMap,
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       CoreSessionInterface repoSession,
+                       InvocationContext invocationContext) throws Exception {
+
+               String docType = invocationContext.getDocType();
+               ServiceBindingType binding = tenantBindingReader.getServiceBindingForDocType(serviceContext.getTenantId(), docType);
+               String serviceName = binding.getName();
+
+               switch (invocationContext.getMode().toLowerCase()) {
+                       case Invocable.INVOCATION_MODE_SINGLE:
+                               return findDocumentByCsid(resourceMap, serviceContext, repoSession, serviceName, invocationContext.getSingleCSID());
+                       case Invocable.INVOCATION_MODE_LIST:
+                               return findDocumentsByCsid(resourceMap, serviceContext, repoSession, serviceName, invocationContext.getListCSIDs().getCsid());
+                       case Invocable.INVOCATION_MODE_GROUP:
+                               return findDocumentsByGroup(resourceMap, serviceContext, repoSession, invocationContext.getGroupCSID());
+                       case Invocable.INVOCATION_MODE_NO_CONTEXT:
+                               return findDocumentsByType(resourceMap, serviceContext, repoSession, invocationContext.getDocType());
+                       default:
+                               return null;
+               }
+       }
+
+       private Iterator<DocumentModel> findDocumentByCsid(
+                       ResourceMap resourceMap,
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       CoreSessionInterface repoSession,
+                       String serviceName,
+                       String csid) throws Exception {
+
+               return findDocumentsByCsid(resourceMap, serviceContext, repoSession, serviceName, Arrays.asList(csid));
+       }
+
+       private Iterator<DocumentModel> findDocumentsByCsid(
+                       ResourceMap resourceMap,
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       CoreSessionInterface repoSession,
+                       String serviceName,
+                       List<String> csids) throws Exception {
+
+               return new DocumentsByCsidIterator(resourceMap, serviceContext, repoSession, serviceName, csids);
+       }
+
+       private Iterator<DocumentModel> findDocumentsByType(
+                       ResourceMap resourceMap,
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       CoreSessionInterface repoSession,
+                       String docType) {
+
+               return null;
+       }
+
+       private Iterator<DocumentModel> findDocumentsByGroup(
+                       ResourceMap resourceMap,
+                       ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                       CoreSessionInterface repoSession,
+                       String docType) {
+
+               return null;
+       }
+
+       // private InputStream buildExportResult(String exportCSID,
+       //              HashMap<String, Object> params,
+       //              String exportFileName,
+       //              String outputMimeType,
+       //              StringBuffer outExportFileName) throws Exception {
+       //      Connection conn = null;
+       //      InputStream result = null;
+
+       //      return result;
+       // }
+
+       private class DocumentsByCsidIterator implements Iterator<DocumentModel> {
+               private NuxeoBasedResource resource;
+               private ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext;
+               private CoreSessionInterface repoSession;
+               private Iterator<String> csidIterator;
+
+               DocumentsByCsidIterator(
+                               ResourceMap resourceMap,
+                               ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext,
+                               CoreSessionInterface repoSession,
+                               String serviceName,
+                               List<String> csids) throws Exception {
+
+                       NuxeoBasedResource resource = (NuxeoBasedResource) resourceMap.get(serviceName.toLowerCase());
+
+                       if (resource == null) {
+                               throw new Exception("Resource not found for service name " + serviceName);
+                       }
+
+                       this.resource = resource;
+                       this.serviceContext = serviceContext;
+                       this.repoSession = repoSession;
+                       this.csidIterator = csids.iterator();
+               }
+
+               @Override
+               public boolean hasNext() {
+                       return csidIterator.hasNext();
+               }
+
+               @Override
+               public DocumentModel next() {
+                       String csid = csidIterator.next();
+
+                       try {
+                               // PoxPayloadOut payload = resource.getWithParentCtx(serviceContext, csid);
+                               return null;
+                       }
+                       catch (Exception e) {
+                               logger.warn("Could not get document with csid " + csid, e);
+
+                               return null;
+                       }
+               }
+       }
+}
diff --git a/services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportValidatorHandler.java b/services/export/service/src/main/java/org/collectionspace/services/export/nuxeo/ExportValidatorHandler.java
new file mode 100644 (file)
index 0000000..e184902
--- /dev/null
@@ -0,0 +1,18 @@
+package org.collectionspace.services.export.nuxeo;
+
+import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.document.InvalidDocumentException;
+import org.collectionspace.services.common.document.ValidatorHandler;
+import org.collectionspace.services.common.document.DocumentHandler.Action;
+
+public class ExportValidatorHandler implements ValidatorHandler {
+
+       @Override
+       public void validate(Action action, ServiceContext ctx)
+                       throws InvalidDocumentException {
+               // TODO Auto-generated method stub
+               System.out.println("ExportValidatorHandler executed.");
+
+       }
+
+}
index 2da78da2ed07e1c123aa3f2a2d6c1f5be1434b4e..5c970359d46fcdf99debdffde2c81250505084b9 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <!--
     Invocation Context schema (XSD)
-    
+
     Entity  : Invocation
     Used for: JAXB binding between XML and Java objects
 
@@ -17,6 +17,7 @@
                 <xs:element name="mode" type="xs:string"></xs:element>
                 <xs:element name="updateCoreValues" type="xs:string"></xs:element>
                 <xs:element name="docType" type="xs:string"></xs:element>
+                <xs:element name="vocabulary" type="xs:string"></xs:element>
                 <xs:element name="singleCSID" type="xs:string"></xs:element>
                 <xs:element name="groupCSID" type="xs:string"></xs:element>
                 <xs:element name="listCSIDs">
                         </xs:sequence>
                     </xs:complexType>
                 </xs:element>
+                <xs:element name="query">
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:element name="kw" type="xs:string"></xs:element>
+                            <xs:element name="as" type="xs:string"></xs:element>
+                            <xs:element name="pgSz" type="xs:integer"></xs:element>
+                            <xs:element name="pgNum" type="xs:integer"></xs:element>
+                            <xs:element name="wf_deleted" type="xs:boolean" minOccurs="0"></xs:element>
+
+                            <!-- Relation query parameters -->
+                            <xs:element name="sbj" type="xs:string"></xs:element>
+                            <xs:element name="sbjType" type="xs:string"></xs:element>
+                            <xs:element name="prd" type="xs:string"></xs:element>
+                            <xs:element name="obj" type="xs:string"></xs:element>
+                            <xs:element name="objType" type="xs:string"></xs:element>
+                            <xs:element name="andReciprocal" type="xs:boolean" minOccurs="0"></xs:element>
+                        </xs:sequence>
+                    </xs:complexType>
+                </xs:element>
+                <xs:element name="includeFields">
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:element name="field" type="xs:string" minOccurs="0" maxOccurs="unbounded"></xs:element>
+                        </xs:sequence>
+                    </xs:complexType>
+                </xs:element>
+                <xs:element name="excludeFields">
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:element name="field" type="xs:string" minOccurs="0" maxOccurs="unbounded"></xs:element>
+                        </xs:sequence>
+                    </xs:complexType>
+                </xs:element>
                 <xs:element name="params">
                     <xs:complexType>
                         <xs:sequence>
index 5352562df098d2e35bf9bde494ca7a2e9a2944df..51a78354fcba0eb5647b33d3a8f522205e35fdc0 100644 (file)
@@ -77,6 +77,7 @@
                <module>pottag</module>
                <module>batch</module>
                <module>imports</module>
+               <module>export</module>
                <module>location</module>
                <module>place</module>
                <module>work</module>