diff --git a/pom.xml b/pom.xml
index 158b185..febe89e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,9 @@
8
8
lines,vars,source
+
+ 2.35
+ 3.4.7
@@ -54,6 +57,18 @@
jackson-module-jaxb-annotations
2.12.2
+
+ org.apache.cxf
+ cxf-rt-frontend-jaxrs
+ ${cxf.version}
+ true
+
+
+ org.glassfish.jersey.media
+ jersey-media-multipart
+ ${jersey.version}
+ true
+
diff --git a/src/main/java/com/inteligr8/alfresco/activiti/ApsPublicRestCxfApi.java b/src/main/java/com/inteligr8/alfresco/activiti/ApsPublicRestCxfApi.java
new file mode 100644
index 0000000..0dea71d
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/activiti/ApsPublicRestCxfApi.java
@@ -0,0 +1,18 @@
+package com.inteligr8.alfresco.activiti;
+
+import com.inteligr8.alfresco.activiti.api.AppDefinitionsCxfApi;
+
+/**
+ * This interface appends Apache CXF implementation specific methods to the
+ * JAX-RS API of the APS Public ReST API. This is due to a lack of multi-part
+ * in the JAX-RS specification.
+ *
+ * @author brian@inteligr8.com
+ */
+public interface ApsPublicRestCxfApi extends ApsPublicRestApi {
+
+ default AppDefinitionsCxfApi getAppDefinitionsCxfApi() {
+ return this.getApi(AppDefinitionsCxfApi.class);
+ }
+
+}
diff --git a/src/main/java/com/inteligr8/alfresco/activiti/ApsPublicRestJerseyApi.java b/src/main/java/com/inteligr8/alfresco/activiti/ApsPublicRestJerseyApi.java
new file mode 100644
index 0000000..eb922ee
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/activiti/ApsPublicRestJerseyApi.java
@@ -0,0 +1,18 @@
+package com.inteligr8.alfresco.activiti;
+
+import com.inteligr8.alfresco.activiti.api.AppDefinitionsJerseyApi;
+
+/**
+ * This interface appends Jersey implementation specific methods to the JAX-RS
+ * API of the APS Public ReST API. This is due to a lack of multi-part in the
+ * JAX-RS specification.
+ *
+ * @author brian@inteligr8.com
+ */
+public interface ApsPublicRestJerseyApi extends ApsPublicRestApi {
+
+ default AppDefinitionsJerseyApi getAppDefinitionsJerseyApi() {
+ return this.getApi(AppDefinitionsJerseyApi.class);
+ }
+
+}
diff --git a/src/main/java/com/inteligr8/alfresco/activiti/api/AppDefinitionsCxfApi.java b/src/main/java/com/inteligr8/alfresco/activiti/api/AppDefinitionsCxfApi.java
new file mode 100644
index 0000000..0d20916
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/activiti/api/AppDefinitionsCxfApi.java
@@ -0,0 +1,51 @@
+
+package com.inteligr8.alfresco.activiti.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import com.inteligr8.alfresco.activiti.model.AppDefinitionRepresentation;
+import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
+import com.inteligr8.alfresco.activiti.model.FileMultipartCxf;
+
+@Path("/api/enterprise/app-definitions")
+public interface AppDefinitionsCxfApi {
+
+ @POST
+ @Path("import")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionRepresentation import_(
+ FileMultipartCxf body,
+ @QueryParam("renewIdmEntries") Boolean renewIdmEntries);
+
+ @POST
+ @Path("{modelId}/import")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionRepresentation import_(
+ @PathParam("modelId") Long appId,
+ FileMultipartCxf body,
+ @QueryParam("renewIdmEntries") Boolean renewIdmEntries);
+
+ @POST
+ @Path("publish-app")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionUpdateResultRepresentation publishApp(
+ FileMultipartCxf body);
+
+ @POST
+ @Path("{modelId}/publish-app")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionUpdateResultRepresentation publishApp(
+ @PathParam("modelId") Long appId,
+ FileMultipartCxf body);
+
+}
diff --git a/src/main/java/com/inteligr8/alfresco/activiti/api/AppDefinitionsJerseyApi.java b/src/main/java/com/inteligr8/alfresco/activiti/api/AppDefinitionsJerseyApi.java
new file mode 100644
index 0000000..866ce7d
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/activiti/api/AppDefinitionsJerseyApi.java
@@ -0,0 +1,51 @@
+
+package com.inteligr8.alfresco.activiti.api;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import com.inteligr8.alfresco.activiti.model.AppDefinitionRepresentation;
+import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
+import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
+
+@Path("/api/enterprise/app-definitions")
+public interface AppDefinitionsJerseyApi {
+
+ @POST
+ @Path("import")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionRepresentation importApp(
+ FileMultipartJersey file,
+ @QueryParam("renewIdmEntries") Boolean renewIdmEntries);
+
+ @POST
+ @Path("{modelId}/import")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionRepresentation importApp(
+ @PathParam("modelId") Long appId,
+ FileMultipartJersey file,
+ @QueryParam("renewIdmEntries") Boolean renewIdmEntries);
+
+ @POST
+ @Path("publish-app")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionUpdateResultRepresentation publishApp(
+ FileMultipartJersey file);
+
+ @POST
+ @Path("{modelId}/publish-app")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ AppDefinitionUpdateResultRepresentation publishApp(
+ @PathParam("modelId") Long appId,
+ FileMultipartJersey file);
+
+}
diff --git a/src/main/java/com/inteligr8/alfresco/activiti/model/FileMultipartCxf.java b/src/main/java/com/inteligr8/alfresco/activiti/model/FileMultipartCxf.java
new file mode 100644
index 0000000..ce84a6d
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/activiti/model/FileMultipartCxf.java
@@ -0,0 +1,38 @@
+package com.inteligr8.alfresco.activiti.model;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+
+public class FileMultipartCxf extends MultipartBody {
+
+ public static FileMultipartCxf from(String filename, InputStream istream) throws IOException {
+ return new FileMultipartCxf(Arrays.asList(toAttachment(filename, istream)));
+ }
+
+ public FileMultipartCxf(List atts) throws IOException {
+ super(atts);
+ }
+
+ public FileMultipartCxf(List atts, boolean outbound) {
+ super(atts, outbound);
+ }
+
+ public Attachment getFileAttachment() {
+ return this.getAttachment("file");
+ }
+
+ private static Attachment toAttachment(String filename, InputStream istream) {
+ if (filename == null) {
+ return new Attachment("file", istream, new ContentDisposition("form-data; name=\"file\""));
+ } else {
+ return new Attachment("file", istream, new ContentDisposition("form-data; name=\"file\"; filename=\"" + filename + "\""));
+ }
+ }
+
+}
diff --git a/src/main/java/com/inteligr8/alfresco/activiti/model/FileMultipartJersey.java b/src/main/java/com/inteligr8/alfresco/activiti/model/FileMultipartJersey.java
new file mode 100644
index 0000000..99160c5
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/activiti/model/FileMultipartJersey.java
@@ -0,0 +1,38 @@
+package com.inteligr8.alfresco.activiti.model;
+
+import java.io.InputStream;
+import java.text.ParseException;
+
+import org.glassfish.jersey.media.multipart.BodyPart;
+import org.glassfish.jersey.media.multipart.FormDataBodyPart;
+import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
+import org.glassfish.jersey.media.multipart.FormDataMultiPart;
+
+public class FileMultipartJersey extends FormDataMultiPart {
+
+ public static FileMultipartJersey from(String filename, InputStream istream) throws ParseException {
+ FileMultipartJersey multipart = new FileMultipartJersey();
+ multipart.bodyPart(toBodyPart(filename, istream));
+ return multipart;
+ }
+
+ private FileMultipartJersey() {
+ }
+
+ public FormDataBodyPart getFileAttachment() {
+ return this.getField("file");
+ }
+
+ private static BodyPart toBodyPart(String filename, InputStream istream) throws ParseException {
+ if (filename == null) {
+ return new FormDataBodyPart()
+ .contentDisposition(new FormDataContentDisposition("form-data; name=\"file\""))
+ .entity(istream);
+ } else {
+ return new FormDataBodyPart()
+ .contentDisposition(new FormDataContentDisposition("form-data; name=\"file\"; filename=\"" + filename + "\""))
+ .entity(istream);
+ }
+ }
+
+}