diff --git a/pom.xml b/pom.xml
index feecbc8..c39d32d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,12 @@
4.5.9
test
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ 2.16.0
+ test
+
diff --git a/src/main/java/com/inteligr8/alfresco/acs/api/NodeBodyCreateMultipart.java b/src/main/java/com/inteligr8/alfresco/acs/api/NodeBodyCreateMultipart.java
new file mode 100644
index 0000000..90236f8
--- /dev/null
+++ b/src/main/java/com/inteligr8/alfresco/acs/api/NodeBodyCreateMultipart.java
@@ -0,0 +1,99 @@
+package com.inteligr8.alfresco.acs.api;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.inteligr8.alfresco.acs.model.NodeBodyCreate;
+
+public class NodeBodyCreateMultipart extends MultipartBody {
+
+ private static final ObjectMapper om = new ObjectMapper();
+
+ public static NodeBodyCreateMultipart from(
+ NodeBodyCreate nodeBody, String filename, InputStream istream,
+ Boolean autoRename, Boolean majorVersion, Boolean versioningEnabled) throws IOException {
+ List atts = new LinkedList<>();
+ atts.addAll(toAttachments(nodeBody));
+ if (autoRename != null)
+ atts.add(toAttachment("autoRename", String.valueOf(autoRename)));
+ if (majorVersion != null)
+ atts.add(toAttachment("majorVersion", String.valueOf(majorVersion)));
+ if (versioningEnabled != null)
+ atts.add(toAttachment("versioningEnabled", String.valueOf(versioningEnabled)));
+ atts.add(toAttachment(filename, istream));
+ return new NodeBodyCreateMultipart(atts, true);
+ }
+
+ public NodeBodyCreateMultipart(List atts) throws IOException {
+ super(atts);
+ }
+
+ public NodeBodyCreateMultipart(List atts, boolean outbound) throws IOException {
+ super(atts, outbound);
+ }
+
+ public NodeBodyCreate getBody() throws IOException {
+ if (!MediaType.APPLICATION_JSON_TYPE.equals(this.getRootAttachment().getContentType()))
+ throw new IllegalStateException();
+
+ InputStream istream = this.getRootAttachment().getDataHandler().getInputStream();
+ try {
+ return om.readValue(istream, NodeBodyCreate.class);
+ } finally {
+ istream.close();
+ }
+ }
+
+ public Attachment getFiledataAttachment() {
+ return this.getAttachment("filedata");
+ }
+
+
+
+ @SuppressWarnings("unchecked")
+ private static List toAttachments(NodeBodyCreate nodeBody) throws IOException {
+ List atts = new LinkedList<>();
+ atts.add(toAttachment("name", nodeBody.getName()));
+ atts.add(toAttachment("nodeType", nodeBody.getNodeType()));
+ if (nodeBody.getAspectNames() != null)
+ atts.add(toAttachment("aspectNames", nodeBody.getAspectNames()));
+ if (nodeBody.getProperties() != null)
+ atts.add(toAttachment("properties", (Map)nodeBody.getProperties()));
+ return atts;
+ }
+
+ private static Attachment toAttachment(String name, String value) {
+ return new Attachment(name, new ByteArrayInputStream(value.getBytes()), new ContentDisposition("form-data; name=\"" + name + "\""));
+ }
+
+ private static Attachment toAttachment(String name, Collection c) throws JsonProcessingException {
+ return toJsonAttachment(name, c);
+ }
+
+ private static Attachment toAttachment(String name, Map map) throws JsonProcessingException {
+ return toJsonAttachment(name, map);
+ }
+
+ private static Attachment toJsonAttachment(String name, Object obj) throws JsonProcessingException {
+ String json = om.writeValueAsString(obj);
+ return new Attachment(name, new ByteArrayInputStream(json.getBytes()), new ContentDisposition("form-data; name=\"" + name + "\""));
+ }
+
+ private static Attachment toAttachment(String filename, InputStream istream) {
+ return new Attachment("filedata", istream, new ContentDisposition("form-data; name=\"filedata\"; filename=\"" + filename + "\""));
+ }
+
+}
diff --git a/src/main/java/com/inteligr8/alfresco/acs/api/NodesCxfApi.java b/src/main/java/com/inteligr8/alfresco/acs/api/NodesCxfApi.java
index fcfd028..f4f69ea 100644
--- a/src/main/java/com/inteligr8/alfresco/acs/api/NodesCxfApi.java
+++ b/src/main/java/com/inteligr8/alfresco/acs/api/NodesCxfApi.java
@@ -1,20 +1,15 @@
package com.inteligr8.alfresco.acs.api;
-import java.util.List;
-
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 org.apache.cxf.jaxrs.ext.multipart.Attachment;
-import org.apache.cxf.jaxrs.ext.multipart.Multipart;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
import com.inteligr8.alfresco.acs.model.Error;
-import com.inteligr8.alfresco.acs.model.NodeBodyCreate;
import com.inteligr8.alfresco.acs.model.NodeEntry;
import io.swagger.annotations.Api;
@@ -31,7 +26,7 @@ public interface NodesCxfApi {
@Consumes({ MediaType.MULTIPART_FORM_DATA })
@Produces({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Create a node", tags={ })
- @ApiResponses(value = {
+ @ApiResponses(value = {
@ApiResponse(code = 201, message = "Successful response", response = NodeEntry.class),
@ApiResponse(code = 400, message = "Invalid parameter: **nodeId** is not a valid format or **nodeBodyCreate** is invalid "),
@ApiResponse(code = 401, message = "Authentication failed"),
@@ -45,12 +40,35 @@ public interface NodesCxfApi {
@ApiResponse(code = 200, message = "Unexpected error", response = Error.class) })
public NodeEntry createNode(
@PathParam("nodeId") String nodeId,
- @Multipart NodeBodyCreate nodeBodyCreate,
- @QueryParam("autoRename") Boolean autoRename,
- @QueryParam("majorVersion") Boolean majorVersion,
- @QueryParam("versioningEnabled") Boolean versioningEnabled,
- @QueryParam("include") List include,
- @QueryParam("fields") List fields,
- @Multipart(value = "filedata", required = false) Attachment attachment);
-
+ MultipartBody body);
+/*
+ * This better impl doesn't work
+ *
+ @POST
+ @Path("/nodes/{nodeId}/children")
+ @Consumes({ MediaType.MULTIPART_FORM_DATA })
+ @Produces({ MediaType.APPLICATION_JSON })
+ @ApiOperation(value = "Create a node", tags={ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 201, message = "Successful response", response = NodeEntry.class),
+ @ApiResponse(code = 400, message = "Invalid parameter: **nodeId** is not a valid format or **nodeBodyCreate** is invalid "),
+ @ApiResponse(code = 401, message = "Authentication failed"),
+ @ApiResponse(code = 403, message = "Current user does not have permission to create children of **nodeId**"),
+ @ApiResponse(code = 404, message = "**nodeId** or **renditionId** does not exist "),
+ @ApiResponse(code = 409, message = "New name clashes with an existing node in the current parent folder"),
+ @ApiResponse(code = 413, message = "Content exceeds individual file size limit configured for the network or system"),
+ @ApiResponse(code = 415, message = "Content Type is not supported"),
+ @ApiResponse(code = 422, message = "Model integrity exception including a file name containing invalid characters"),
+ @ApiResponse(code = 507, message = "Content exceeds overall storage quota limit configured for the network or system"),
+ @ApiResponse(code = 200, message = "Unexpected error", response = Error.class) })
+ public NodeEntry createNode(
+ @PathParam("nodeId") String nodeId,
+ @Multipart("name") String name,
+ @Multipart("nodeType") String nodeType,
+ @Multipart("filedata") InputStream fileStream,
+ @Multipart("filedata") ContentDisposition disposition,
+ @Multipart(value = "autoRename", required = false) Boolean autoRename,
+ @Multipart(value = "majorVersion", required = false) Boolean majorVersion,
+ @Multipart(value = "versioningEnabled", required = false) Boolean versioningEnabled);
+*/
}
diff --git a/src/main/java/com/inteligr8/alfresco/acs/api/NodesJerseyApi.java b/src/main/java/com/inteligr8/alfresco/acs/api/NodesJerseyApi.java
index 6bbccfb..d5d59b2 100644
--- a/src/main/java/com/inteligr8/alfresco/acs/api/NodesJerseyApi.java
+++ b/src/main/java/com/inteligr8/alfresco/acs/api/NodesJerseyApi.java
@@ -8,7 +8,6 @@ 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 org.glassfish.jersey.media.multipart.FormDataContentDisposition;
@@ -47,11 +46,11 @@ public interface NodesJerseyApi {
public NodeEntry createNode(
@PathParam("nodeId") String nodeId,
NodeBodyCreate nodeBodyCreate,
- @QueryParam("autoRename") Boolean autoRename,
- @QueryParam("majorVersion") Boolean majorVersion,
- @QueryParam("versioningEnabled") Boolean versioningEnabled,
- @QueryParam("include") List include,
- @QueryParam("fields") List fields,
+ @FormDataParam("autoRename") Boolean autoRename,
+ @FormDataParam("majorVersion") Boolean majorVersion,
+ @FormDataParam("versioningEnabled") Boolean versioningEnabled,
+ @FormDataParam("include") List include,
+ @FormDataParam("fields") List fields,
@FormDataParam("filedata") InputStream filedataStream,
@FormDataParam("filedata") FormDataContentDisposition filedataDisposition);
diff --git a/src/test/java/com/inteligr8/alfresco/acs/ConnectionCxfClientIT.java b/src/test/java/com/inteligr8/alfresco/acs/ConnectionCxfClientIT.java
index 4b979c1..31379e7 100644
--- a/src/test/java/com/inteligr8/alfresco/acs/ConnectionCxfClientIT.java
+++ b/src/test/java/com/inteligr8/alfresco/acs/ConnectionCxfClientIT.java
@@ -1,10 +1,21 @@
package com.inteligr8.alfresco.acs;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import com.inteligr8.alfresco.acs.api.NodeBodyCreateMultipart;
+import com.inteligr8.alfresco.acs.model.NodeBodyCreate;
+import com.inteligr8.alfresco.acs.model.RequestQuery;
+import com.inteligr8.alfresco.acs.model.RequestQuery.LanguageEnum;
+import com.inteligr8.alfresco.acs.model.ResultNode;
+import com.inteligr8.alfresco.acs.model.ResultSetPaging;
+import com.inteligr8.alfresco.acs.model.SearchRequest;
import com.inteligr8.rs.ClientConfiguration;
@TestPropertySource(locations = {"/local.properties"})
@@ -13,7 +24,7 @@ public class ConnectionCxfClientIT extends ConnectionClientIT {
@Autowired
@Qualifier("acs.api.cxf")
- private AcsPublicRestApi client;
+ private AcsPublicRestApiCxfImpl client;
@Override
public AcsPublicRestApi getClient() {
@@ -25,4 +36,25 @@ public class ConnectionCxfClientIT extends ConnectionClientIT {
return this.client.getConfig();
}
+ @Test
+ public void uploadFile() throws IOException {
+ RequestQuery query = new RequestQuery();
+ query.setLanguage(LanguageEnum.AFTS);
+ query.setQuery("=@cm:name:'Shared'");
+
+ SearchRequest searchRequest = new SearchRequest();
+ searchRequest.setQuery(query);
+
+ ResultSetPaging paging = this.client.getSearchApi().search(searchRequest);
+ ResultNode folderNode = paging.getList().getEntries().iterator().next().getEntry();
+ String folderNodeId = folderNode.getId();
+
+ NodeBodyCreate nodeBody = new NodeBodyCreate().nodeType("cm:content").name("TestFolder");
+
+ ByteArrayInputStream istream = new ByteArrayInputStream("This is a test".getBytes());
+ NodeBodyCreateMultipart body = NodeBodyCreateMultipart.from(nodeBody, "test.txt", istream, true, null, null);
+
+ this.client.getNodesExtApi().createNode(folderNodeId, body);
+ }
+
}
diff --git a/src/test/resources/log4j2.properties b/src/test/resources/log4j2.properties
new file mode 100644
index 0000000..df444d4
--- /dev/null
+++ b/src/test/resources/log4j2.properties
@@ -0,0 +1,19 @@
+rootLogger.level=trace
+rootLogger.appenderRef.stdout.ref=STDOUT
+
+logger.spring.name=org.springframework
+logger.spring.level=info
+
+logger.common-rest-api.name=com.inteligr8.rs
+logger.common-rest-api.level=trace
+
+logger.this.name=com.inteligr8.alfresco.acs
+logger.this.level=trace
+
+logger.jaxrslog.name=jaxrs.request
+logger.jaxrslog.level=trace
+
+appender.stdout.type=Console
+appender.stdout.name=STDOUT
+appender.stdout.layout.type=PatternLayout
+appender.stdout.layout.pattern=%C [%t] %m%n
diff --git a/src/test/vscode/createNode.http b/src/test/vscode/createNode.http
new file mode 100644
index 0000000..99f9ed1
--- /dev/null
+++ b/src/test/vscode/createNode.http
@@ -0,0 +1,53 @@
+@baseUrl = http://localhost:8080/alfresco
+@username = admin
+@password = admin
+
+### Find Folder
+# @name find
+POST {{baseUrl}}/api/-default-/public/search/versions/1/search HTTP/1.1
+Authorization: Basic {{username}}:{{password}}
+
+{
+ "query": {
+ "language": "afts",
+ "query": "=@cm:name:'Shared'"
+ }
+}
+
+@folderNodeId = {{find.response.body.list.entries[0].entry.id}}
+
+### Upload File
+# @name upload
+POST {{baseUrl}}/api/-default-/public/alfresco/versions/1/nodes/{{folderNodeId}}/children?autoRename=true
+Authorization: Basic {{username}}:{{password}}
+Content-type: application/json
+
+{
+ "name": "TestFolder",
+ "nodeType": "cm:content",
+ "properties": null
+}
+
+### Upload File
+# @name upload-form
+POST {{baseUrl}}/api/-default-/public/alfresco/versions/1/nodes/{{folderNodeId}}/children
+Authorization: Basic {{username}}:{{password}}
+Content-type: multipart/form-data; boundary=----Test
+
+------Test
+Content-disposition: form-data; name="name"
+
+TestFolder
+------Test
+Content-disposition: form-data; name="nodeType"
+
+cm:content
+------Test
+Content-disposition: form-data; name="autoRename"
+
+true
+------Test
+Content-disposition: form-data; name="filedata"; filename="test.txt"
+
+This is a test
+------Test--
\ No newline at end of file