diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index a001639eb0..edc828a422 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -140,9 +140,13 @@ import org.springframework.extensions.webscripts.servlet.FormData; import org.springframework.http.InvalidMediaTypeException; import org.springframework.http.MediaType; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.math.BigInteger; +import java.nio.charset.Charset; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; @@ -1625,10 +1629,8 @@ public class NodesImpl implements Nodes if (isContent) { - // add empty file - ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - setWriterContentType(writer, new ContentInfoWrapper(nodeInfo.getContent()), nodeRef, false); - writer.putContent(""); + // add empty file - note: currently will be set to default encoding only (UTF-8) + writeContent(nodeRef, nodeName, new ByteArrayInputStream("".getBytes()), false); } // eg. to create mandatory assoc(s) @@ -2256,7 +2258,7 @@ public class NodesImpl implements Nodes behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); try { - writeContent(nodeRef, contentInfo, stream); + writeContent(nodeRef, fileName, stream, true); if ((isVersioned) || (versionMajor != null) || (versionComment != null) ) { @@ -2281,13 +2283,71 @@ public class NodesImpl implements Nodes return getFolderOrDocumentFullInfo(nodeRef, null, null, parameters); } - private void writeContent(NodeRef nodeRef, BasicContentInfo contentInfo, InputStream stream) + private void writeContent(NodeRef nodeRef, String fileName, InputStream stream, boolean guessEncoding) { ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - setWriterContentType(writer, new ContentInfoWrapper(contentInfo), nodeRef, true); - writer.putContent(stream); + + String mimeType = mimetypeService.guessMimetype(fileName); + writer.setMimetype(mimeType); + + InputStream is = null; + + if (guessEncoding) + { + is = new BufferedInputStream(stream); + is.mark(1024); + writer.setEncoding(guessEncoding(is, mimeType, false)); + try + { + is.reset(); + } + catch (IOException ioe) + { + if (logger.isWarnEnabled()) + { + logger.warn("Failed to reset stream after trying to guess encoding: " + ioe.getMessage()); + } + } + } + else + { + is = stream; + } + + writer.putContent(is); } + private String guessEncoding(InputStream in, String mimeType, boolean close) + { + String encoding = "UTF-8"; + try + { + if (in != null) + { + Charset charset = mimetypeService.getContentCharsetFinder().getCharset(in, mimeType); + encoding = charset.name(); + } + } + finally + { + try + { + if (close && (in != null)) + { + in.close(); + } + } + catch (IOException ioe) + { + if (logger.isWarnEnabled()) + { + logger.warn("Failed to close stream after trying to guess encoding: " + ioe.getMessage()); + } + } + } + return encoding; + } + protected void createVersion(NodeRef nodeRef, boolean isVersioned, VersionType versionType, String reason) { if (! isVersioned) @@ -2308,34 +2368,6 @@ public class NodesImpl implements Nodes } } - private void setWriterContentType(ContentWriter writer, ContentInfoWrapper contentInfo, NodeRef nodeRef, boolean guessEncodingIfNull) - { - String mimeType = contentInfo.mimeType; - // Manage MimeType - if ((mimeType == null) || mimeType.equals(DEFAULT_MIMETYPE)) - { - // the mimeType was not provided (or was the default binary mimeType) via the contentInfo, so try to guess - final String fileName = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - mimeType = mimetypeService.guessMimetype(fileName); - } - writer.setMimetype(mimeType); - - // Manage Encoding - if (contentInfo.encoding == null) - { - if (guessEncodingIfNull) - { - // the encoding was not provided, so try to guess - writer.guessEncoding(); - } - } - else - { - writer.setEncoding(contentInfo.encoding); - } - } - - @Override public Node upload(String parentFolderNodeId, FormData formData, Parameters parameters) { @@ -2543,19 +2575,20 @@ public class NodesImpl implements Nodes { nodeType = ContentModel.TYPE_CONTENT; } - NodeRef newFile = createNodeImpl(parentNodeRef, fileName, nodeType, props, assocTypeQName); + NodeRef nodeRef = createNodeImpl(parentNodeRef, fileName, nodeType, props, assocTypeQName); + // Write content - write(newFile, content); + writeContent(nodeRef, fileName, content.getInputStream(), true); // Ensure the file is versionable (autoVersion = true, autoVersionProps = false) - ensureVersioningEnabled(newFile, true, false); + ensureVersioningEnabled(nodeRef, true, false); // Extract the metadata - extractMetadata(newFile); + extractMetadata(nodeRef); // Create the response - return getFolderOrDocumentFullInfo(newFile, parentNodeRef, nodeType, params); + return getFolderOrDocumentFullInfo(nodeRef, parentNodeRef, nodeType, params); } private String getStringOrNull(String value) @@ -2652,23 +2685,6 @@ public class NodesImpl implements Nodes } } - - - /** - * Writes the content to the repository. - * - * @param nodeRef the reference to the node having a content property - * @param content the content - */ - protected void write(NodeRef nodeRef, Content content) - { - ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - // Per RA-637 & RA-885 requirement the mimeType provided by the client takes precedence, however, - // if the mimeType is null (or default binary mimeType) then it will be guessed. - setWriterContentType(writer, new ContentInfoWrapper(content), nodeRef, true); - writer.putContent(content.getInputStream()); - } - /** * Ensures the given node has the {@code cm:versionable} aspect applied to it, and * that it has the initial version in the version store. @@ -2806,11 +2822,22 @@ public class NodesImpl implements Nodes /** * @author Jamal Kaabi-Mofrad */ - private static class ContentInfoWrapper + /* + private static class ContentInfoWrapper implements BasicContentInfo { private String mimeType; private String encoding; + public String getEncoding() + { + return encoding; + } + + public String getMimeType() + { + return mimeType; + } + ContentInfoWrapper(BasicContentInfo basicContentInfo) { if (basicContentInfo != null) @@ -2851,5 +2878,6 @@ public class NodesImpl implements Nodes } } } + */ } diff --git a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java index de91d08b98..25902cb9bb 100644 --- a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -39,9 +39,6 @@ import org.alfresco.repo.content.ContentLimitProvider.SimpleFixedLimitProvider; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.tenant.TenantAdminService; -import org.alfresco.repo.tenant.TenantService; -import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.model.NodeTarget; @@ -346,10 +343,10 @@ public class NodeApiTest extends AbstractBaseApiTest String folder2 = "folder " + System.currentTimeMillis() + " 2"; String folder2_Id = createFolder(user1, myNodeId, folder2, props).getId(); - String contentF2 = "content" + System.currentTimeMillis() + " in folder 2"; + String contentF2 = "content" + System.currentTimeMillis() + " in folder 2.txt"; String contentF2_Id = createTextFile(user1, folder2_Id, contentF2, "The quick brown fox jumps over the lazy dog 2.").getId(); - String content1 = "content" + System.currentTimeMillis() + " 1"; + String content1 = "content" + System.currentTimeMillis() + " 1.txt"; String content1_Id = createTextFile(user1, myNodeId, content1, "The quick brown fox jumps over the lazy dog.").getId(); props = new HashMap<>(); @@ -666,7 +663,7 @@ public class NodeApiTest extends AbstractBaseApiTest String title = "test title"; Map docProps = new HashMap<>(); docProps.put("cm:title", title); - String contentName = "content " + System.currentTimeMillis(); + String contentName = "content " + System.currentTimeMillis() + ".txt"; String content1Id = createTextFile(user1, folderB_Id, contentName, "The quick brown fox jumps over the lazy dog.", "UTF-8", docProps).getId(); @@ -688,7 +685,7 @@ public class NodeApiTest extends AbstractBaseApiTest ci.setMimeType("text/plain"); ci.setMimeTypeName("Plain Text"); ci.setSizeInBytes(44L); - ci.setEncoding("UTF-8"); + ci.setEncoding("ISO-8859-1"); d1.setContent(ci); d1.setCreatedByUser(expectedUser); @@ -949,7 +946,7 @@ public class NodeApiTest extends AbstractBaseApiTest // User1 uploads a new file reqBody = MultiPartBuilder.create() - .setFileData(new FileData(fileName2, file2, MimetypeMap.MIMETYPE_TEXT_PLAIN, "windows-1252")) + .setFileData(new FileData(fileName2, file2)) .build(); response = post(getNodeChildrenUrl(user1Home.getId()), user1, reqBody.getBody(), null, reqBody.getContentType(), 201); document = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); @@ -958,14 +955,7 @@ public class NodeApiTest extends AbstractBaseApiTest contentInfo = document.getContent(); assertNotNull(contentInfo); assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); - assertEquals("windows-1252", contentInfo.getEncoding()); - - // Test invalid mimeType - reqBody = MultiPartBuilder.create() - .setFileData(new FileData(fileName2, file2, "*/invalidSubType", "ISO-8859-1")) - .setAutoRename(true) - .build(); - post(getNodeChildrenUrl(user1Home.getId()), user1, reqBody.getBody(), null, reqBody.getContentType(), 400); + assertEquals("ISO-8859-1", contentInfo.getEncoding()); // Test content size limit final SimpleFixedLimitProvider limitProvider = applicationContext.getBean("defaultContentLimitProvider", SimpleFixedLimitProvider.class); @@ -2754,27 +2744,29 @@ public class NodeApiTest extends AbstractBaseApiTest assertNotNull(f1_nodeId); Document doc = new Document(); - final String docName = "testdoc"; + final String docName = "testdoc.txt"; doc.setName(docName); doc.setNodeType(TYPE_CM_CONTENT); doc.setProperties(Collections.singletonMap("cm:title", (Object)"test title")); ContentInfo contentInfo = new ContentInfo(); - contentInfo.setMimeType(MimetypeMap.MIMETYPE_TEXT_PLAIN); doc.setContent(contentInfo); // create an empty file within F1 folder response = post(getNodeChildrenUrl(f1_nodeId), user1, toJsonAsStringNonNull(doc), 201); Document docResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + assertEquals(docName, docResp.getName()); assertNotNull(docResp.getContent()); assertEquals(0, docResp.getContent().getSizeInBytes().intValue()); assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, docResp.getContent().getMimeType()); + // Default encoding + assertEquals("UTF-8", docResp.getContent().getEncoding()); // Update the empty node's content String content = "The quick brown fox jumps over the lazy dog."; ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes()); File txtFile = TempFileProvider.createTempFile(inputStream, getClass().getSimpleName(), ".txt"); - BinaryPayload payload = new BinaryPayload(txtFile, MimetypeMap.MIMETYPE_TEXT_PLAIN); + BinaryPayload payload = new BinaryPayload(txtFile); // Try to update a folder! putBinary(getNodeContentUrl(f1_nodeId), user1, payload, null, null, 400); @@ -2804,11 +2796,10 @@ public class NodeApiTest extends AbstractBaseApiTest contentInfo = docResp.getContent(); assertNotNull(contentInfo); assertNotNull(contentInfo.getEncoding()); - // Default encoding - assertEquals("UTF-8", contentInfo.getEncoding()); assertTrue(contentInfo.getSizeInBytes() > 0); assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, contentInfo.getMimeType()); assertNotNull(contentInfo.getMimeTypeName()); + assertEquals("ISO-8859-1", contentInfo.getEncoding()); // path is not part of the default response assertNull(docResp.getPath()); @@ -2816,17 +2807,19 @@ public class NodeApiTest extends AbstractBaseApiTest response = getSingle(url, user1, null, 200); assertEquals(content, response.getResponse()); - // Update the node's content again. Also, change the mimeType and make the response to return path! - File pdfFile = getResourceFile("quick.pdf"); - payload = new BinaryPayload(pdfFile, MimetypeMap.MIMETYPE_PDF, "ISO-8859-1"); + // Update the node's content again. Also make the response return the path! + content = "The quick brown fox jumps over the lazy dog updated !"; + inputStream = new ByteArrayInputStream(content.getBytes()); + txtFile = TempFileProvider.createTempFile(inputStream, getClass().getSimpleName(), ".txt"); + payload = new BinaryPayload(txtFile); response = putBinary(url + "?include=path", user1, payload, null, null, 200); docResp = jacksonUtil.parseEntry(response.getJsonResponse(), Document.class); assertEquals(docName, docResp.getName()); assertNotNull(docResp.getContent()); - assertEquals("ISO-8859-1", docResp.getContent().getEncoding()); assertTrue(docResp.getContent().getSizeInBytes().intValue() > 0); - assertEquals(MimetypeMap.MIMETYPE_PDF, docResp.getContent().getMimeType()); + assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, docResp.getContent().getMimeType()); + assertEquals("ISO-8859-1", docResp.getContent().getEncoding()); PathInfo pathInfo = docResp.getPath(); assertNotNull(pathInfo); assertTrue(pathInfo.getIsComplete()); @@ -2836,16 +2829,6 @@ public class NodeApiTest extends AbstractBaseApiTest // check the last element is F1 assertEquals(f1.getName(), pathElements.get(pathElements.size() - 1).getName()); - // update the original content with different encoding - payload = new BinaryPayload(txtFile, MimetypeMap.MIMETYPE_TEXT_PLAIN, "ISO-8859-15"); - response = putBinary(url, user1, payload, null, null, 200); - docResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); - assertEquals(docName, docResp.getName()); - assertNotNull(docResp.getContent()); - assertEquals("ISO-8859-15", docResp.getContent().getEncoding()); - assertTrue(docResp.getContent().getSizeInBytes().intValue() > 0); - assertEquals(MimetypeMap.MIMETYPE_TEXT_PLAIN, docResp.getContent().getMimeType()); - // Download the file response = getSingle(url, user1, null, 200); assertNotNull(content, response.getResponse()); diff --git a/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java b/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java index a316ca92cf..d7125d96bc 100644 --- a/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java @@ -335,7 +335,7 @@ public class SharedLinkApiTest extends AbstractBaseApiTest assertArrayEquals(content2Text.getBytes(), response.getResponseAsBytes()); responseHeaders = response.getHeaders(); assertNotNull(responseHeaders); - assertEquals(file2_MimeType+";charset=UTF-8", responseHeaders.get("Content-Type")); + assertEquals(file2_MimeType+";charset=ISO-8859-1", responseHeaders.get("Content-Type")); assertNotNull(responseHeaders.get("Expires")); assertNotNull(responseHeaders.get(LAST_MODIFIED_HEADER)); assertEquals("attachment; filename=\"" + fileName2 + "\"; filename*=UTF-8''" + fileName2 + "", responseHeaders.get("Content-Disposition")); diff --git a/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java b/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java index 9aae42d633..98db4aaaae 100644 --- a/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java +++ b/source/test-java/org/alfresco/rest/api/tests/util/MultiPartBuilder.java @@ -190,6 +190,11 @@ public class MultiPartBuilder private final String mimetype; private final String encoding; + public FileData(String fileName, File file) + { + this(fileName, file, null, null); + } + public FileData(String fileName, File file, String mimetype) { this(fileName, file, mimetype, null);