diff --git a/alfresco-docker-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java b/alfresco-docker-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java index b2fc1ef1..ab7a230b 100644 --- a/alfresco-docker-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java +++ b/alfresco-docker-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java @@ -11,22 +11,24 @@ */ package org.alfresco.transformer; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +import javax.servlet.http.HttpServletRequest; + import org.alfresco.util.exec.RuntimeExec; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.StringJoiner; - /** * Controller for the Docker based alfresco-pdf-renderer transformer. * @@ -111,7 +113,35 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController }; } - @PostMapping("/transform") + @Override + protected void processTransform(File sourceFile, File targetFile, + Map transformOptions, Long timeout) + { + String page = transformOptions.get("page"); + Integer pageOption = page == null ? null : Integer.parseInt(page); + + String width = transformOptions.get("width"); + Integer widthOption = width == null ? null : Integer.parseInt(width); + + String height = transformOptions.get("height"); + Integer heightOption = height == null ? null : Integer.parseInt(height); + + String allowEnlargement = transformOptions.get("allowEnlargement"); + Boolean allowEnlargementOption = + allowEnlargement == null ? null : Boolean.parseBoolean(allowEnlargement); + + String maintainAspectRatio = transformOptions.get("maintainAspectRatio"); + Boolean maintainAspectRatioOption = + maintainAspectRatio == null ? null : Boolean.parseBoolean(maintainAspectRatio); + + String options = buildTransformOptions(pageOption, widthOption, heightOption, + allowEnlargementOption, maintainAspectRatioOption); + + executeTransformCommand(options, sourceFile, targetFile, timeout); + } + + @Deprecated + @PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity transform(HttpServletRequest request, @RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("targetExtension") String targetExtension, @@ -124,11 +154,21 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController @RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement, @RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio) { - String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); + String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension); File sourceFile = createSourceFile(request, sourceMultipartFile); File targetFile = createTargetFile(request, targetFilename); // Both files are deleted by TransformInterceptor.afterCompletion + String options = buildTransformOptions(page, width, height, allowEnlargement, + maintainAspectRatio); + executeTransformCommand(options, sourceFile, targetFile, timeout); + + return createAttachment(targetFilename, targetFile, testDelay); + } + + + public String buildTransformOptions(Integer page,Integer width,Integer height,Boolean allowEnlargement,Boolean maintainAspectRatio) + { StringJoiner args = new StringJoiner(" "); if (width != null && width >= 0) { @@ -150,9 +190,6 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController { args.add("--page=" + page); } - String options = args.toString(); - executeTransformCommand(options, sourceFile, targetFile, timeout); - - return createAttachment(targetFilename, targetFile, testDelay); + return args.toString(); } } diff --git a/alfresco-docker-alfresco-pdf-renderer/src/test/java/org/alfresco/transformer/AlfrescoPdfRendererControllerTest.java b/alfresco-docker-alfresco-pdf-renderer/src/test/java/org/alfresco/transformer/AlfrescoPdfRendererControllerTest.java index 6b2dc12c..e65aad03 100644 --- a/alfresco-docker-alfresco-pdf-renderer/src/test/java/org/alfresco/transformer/AlfrescoPdfRendererControllerTest.java +++ b/alfresco-docker-alfresco-pdf-renderer/src/test/java/org/alfresco/transformer/AlfrescoPdfRendererControllerTest.java @@ -25,6 +25,11 @@ */ package org.alfresco.transformer; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.alfresco.transform.client.model.TransformRequest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,10 +38,6 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.io.IOException; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - /** * Test the AlfrescoPdfRendererController without a server. * Super class includes tests for the AbstractTransformerController. @@ -49,8 +50,10 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro private AlfrescoPdfRendererController controller; @Before - public void before() throws IOException + public void before() throws Exception { + controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient); + super.controller = controller; super.mockTransformCommand(controller, "pdf", "png", "application/pdf", true); } @@ -93,4 +96,11 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro .andExpect(content().bytes(expectedTargetFileBytes)) .andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension)); } + + @Override + protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest) + { + transformRequest.setSourceExtension("pdf"); + transformRequest.setTargetExtension("png"); + } } diff --git a/alfresco-docker-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java b/alfresco-docker-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java index ef493414..87aee38f 100644 --- a/alfresco-docker-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java +++ b/alfresco-docker-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java @@ -11,20 +11,26 @@ */ package org.alfresco.transformer; +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; + +import javax.servlet.http.HttpServletRequest; + import org.alfresco.util.exec.RuntimeExec; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.util.*; - /** * Controller for the Docker based ImageMagick transformer. * @@ -121,7 +127,7 @@ public class ImageMagickController extends AbstractTransformerController }; } - @PostMapping("/transform") + @PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity transform(HttpServletRequest request, @RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("targetExtension") String targetExtension, @@ -159,6 +165,68 @@ public class ImageMagickController extends AbstractTransformerController // ACS 6.0, this is relatively safe as it requires an AMP to be installed // which supplies the commandOptions. @RequestParam(value = "commandOptions", required = false) String commandOptions) + { + String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), + targetExtension); + File sourceFile = createSourceFile(request, sourceMultipartFile); + File targetFile = createTargetFile(request, targetFilename); + // Both files are deleted by TransformInterceptor.afterCompletion + + String options = buildTransformOptions(startPage, endPage , alphaRemove, autoOrient, cropGravity, cropWidth, cropHeight, cropPercentage, + cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions); + String pageRange = calculatePageRange(startPage, endPage); + + executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout); + + return createAttachment(targetFilename, targetFile, testDelay); + } + + @Override + protected void processTransform(File sourceFile, File targetFile, + Map transformOptions, Long timeout) + { + Integer startPage = stringToInteger(transformOptions.get("startPage")); + Integer endPage = stringToInteger(transformOptions.get("endPage")); + Boolean alphaRemove = stringToBoolean(transformOptions.get("alphaRemove")); + Boolean autoOrient = stringToBoolean(transformOptions.get("autoOrient")); + String cropGravity = transformOptions.get("cropGravity"); + Integer cropWidth = stringToInteger(transformOptions.get("cropWidth")); + Integer cropHeight = stringToInteger(transformOptions.get("cropHeight")); + Boolean cropPercentage = stringToBoolean(transformOptions.get("cropPercentage")); + Integer cropXOffset = stringToInteger(transformOptions.get("cropXOffset")); + Integer cropYOffset = stringToInteger(transformOptions.get("cropYOffset")); + Boolean thumbnail = stringToBoolean(transformOptions.get("thumbnail")); + Integer resizeWidth = stringToInteger(transformOptions.get("resizeWidth")); + Integer resizeHeight = stringToInteger(transformOptions.get("resizeHeight")); + Boolean resizePercentage = stringToBoolean(transformOptions.get("resizePercentage")); + Boolean allowEnlargement = stringToBoolean(transformOptions.get("allowEnlargement")); + Boolean maintainAspectRatio = stringToBoolean(transformOptions.get("maintainAspectRatio")); + String commandOptions = transformOptions.get("commandOptions"); + + String options = buildTransformOptions(startPage, endPage , alphaRemove, autoOrient, cropGravity, cropWidth, cropHeight, cropPercentage, + cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions); + String pageRange = calculatePageRange(startPage, endPage); + + executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout); + } + + private void executeTransformCommand(String options, File sourceFile, String pageRange, File targetFile, Long timeout) + { + LogEntry.setOptions(pageRange+(pageRange.isEmpty() ? "" : " ")+options); + + Map properties = new HashMap(5); + properties.put("options", options); + properties.put("source", sourceFile.getAbsolutePath()+pageRange); + properties.put("target", targetFile.getAbsolutePath()); + + executeTransformCommand(properties, targetFile, timeout); + } + + private String buildTransformOptions(Integer startPage, Integer endPage, Boolean alphaRemove, + Boolean autoOrient, String cropGravity, Integer cropWidth, Integer cropHeight, + Boolean cropPercentage, Integer cropXOffset, Integer cropYOffset, Boolean thumbnail, + Integer resizeWidth, Integer resizeHeight, Boolean resizePercentage, + Boolean allowEnlargement, Boolean maintainAspectRatio, String commandOptions) { if (cropGravity != null) { @@ -173,11 +241,6 @@ public class ImageMagickController extends AbstractTransformerController } } - String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); - File sourceFile = createSourceFile(request, sourceMultipartFile); - File targetFile = createTargetFile(request, targetFilename); - // Both files are deleted by TransformInterceptor.afterCompletion - StringJoiner args = new StringJoiner(" "); if (alphaRemove != null && alphaRemove) { @@ -190,7 +253,7 @@ public class ImageMagickController extends AbstractTransformerController } if (cropGravity != null || cropWidth != null || cropHeight != null || cropPercentage != null || - cropXOffset != null || cropYOffset != null) + cropXOffset != null || cropYOffset != null) { if (cropGravity != null) { @@ -268,32 +331,20 @@ public class ImageMagickController extends AbstractTransformerController } } - String pageRange = - startPage == null - ? endPage == null - ? "" - : "["+endPage+']' - : endPage == null || startPage.equals(endPage) - ? "["+startPage+']' - : "["+startPage+'-'+endPage+']'; - - String options = - (commandOptions == null || "".equals(commandOptions.trim()) ? "" : commandOptions + ' ') + - args.toString(); - executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout); - - return createAttachment(targetFilename, targetFile, testDelay); + return (commandOptions == null || "".equals(commandOptions.trim()) ? "" : commandOptions + ' ') + + args.toString(); } - private void executeTransformCommand(String options, File sourceFile, String pageRange, File targetFile, Long timeout) + private String calculatePageRange(Integer startPage, Integer endPage) { - LogEntry.setOptions(pageRange+(pageRange.isEmpty() ? "" : " ")+options); - - Map properties = new HashMap(5); - properties.put("options", options); - properties.put("source", sourceFile.getAbsolutePath()+pageRange); - properties.put("target", targetFile.getAbsolutePath()); - - executeTransformCommand(properties, targetFile, timeout); + return + startPage == null + ? endPage == null + ? "" + : "["+endPage+']' + : endPage == null || startPage.equals(endPage) + ? "["+startPage+']' + : "["+startPage+'-'+endPage+']'; } + } diff --git a/alfresco-docker-imagemagick/src/test/java/org/alfresco/transformer/ImageMagickControllerTest.java b/alfresco-docker-imagemagick/src/test/java/org/alfresco/transformer/ImageMagickControllerTest.java index 08feba17..2b69ff71 100644 --- a/alfresco-docker-imagemagick/src/test/java/org/alfresco/transformer/ImageMagickControllerTest.java +++ b/alfresco-docker-imagemagick/src/test/java/org/alfresco/transformer/ImageMagickControllerTest.java @@ -25,6 +25,13 @@ */ package org.alfresco.transformer; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; + +import org.alfresco.transform.client.model.TransformRequest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,10 +40,6 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.io.IOException; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - /** * Test the ImageMagickController without a server. * Super class includes tests for the AbstractTransformerController. @@ -51,6 +54,9 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest @Before public void before() throws IOException { + controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient); + super.controller = controller; + super.mockTransformCommand(controller, "jpg", "png", "image/jpg", true); } @@ -164,4 +170,11 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest .andExpect(content().bytes(expectedTargetFileBytes)) .andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension)); } + + @Override + protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest) + { + transformRequest.setSourceExtension("png"); + transformRequest.setTargetExtension("png"); + } } diff --git a/alfresco-docker-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java b/alfresco-docker-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java index caa16274..d6f7feeb 100644 --- a/alfresco-docker-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java +++ b/alfresco-docker-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java @@ -11,7 +11,12 @@ */ package org.alfresco.transformer; -import com.sun.star.task.ErrorCodeIOException; +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -21,15 +26,14 @@ import org.artofsolving.jodconverter.office.OfficeException; import org.artofsolving.jodconverter.office.OfficeManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.io.IOException; +import com.sun.star.task.ErrorCodeIOException; /** * Controller for the Docker based LibreOffice transformer. @@ -135,14 +139,14 @@ public class LibreOfficeController extends AbstractTransformerController }; } - @PostMapping("/transform") + @PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity transform(HttpServletRequest request, @RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("targetExtension") String targetExtension, @RequestParam(value = "timeout", required = false) Long timeout, @RequestParam(value = "testDelay", required = false) Long testDelay) { - String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); + String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension); File sourceFile = createSourceFile(request, sourceMultipartFile); File targetFile = createTargetFile(request, targetFilename); // Both files are deleted by TransformInterceptor.afterCompletion @@ -152,6 +156,13 @@ public class LibreOfficeController extends AbstractTransformerController return createAttachment(targetFilename, targetFile, testDelay); } + @Override + protected void processTransform(File sourceFile, File targetFile, + Map transformOptions, Long timeout) + { + executeTransformCommand(sourceFile, targetFile, timeout); + } + protected void executeTransformCommand(File sourceFile, File targetFile, Long timeout) { timeout = timeout != null && timeout > 0 ? timeout : 0; diff --git a/alfresco-docker-libreoffice/src/test/java/org/alfresco/transformer/LibreOfficeControllerTest.java b/alfresco-docker-libreoffice/src/test/java/org/alfresco/transformer/LibreOfficeControllerTest.java index ecfe8235..08c67b72 100644 --- a/alfresco-docker-libreoffice/src/test/java/org/alfresco/transformer/LibreOfficeControllerTest.java +++ b/alfresco-docker-libreoffice/src/test/java/org/alfresco/transformer/LibreOfficeControllerTest.java @@ -25,6 +25,22 @@ */ package org.alfresco.transformer; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; + +import org.alfresco.transform.client.model.TransformRequest; import org.artofsolving.jodconverter.office.OfficeException; import org.junit.Before; import org.junit.Test; @@ -35,22 +51,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.util.Arrays; - -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.springframework.util.StringUtils; /** * Test the LibreOfficeController without a server. @@ -66,6 +67,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest @Before public void before() throws IOException { + controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient); super.controller = controller; sourceExtension = "doc"; @@ -83,6 +85,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest { File sourceFile = invocation.getArgumentAt(0, File.class); File targetFile = invocation.getArgumentAt(1, File.class); + String actualTargetExtension = StringUtils.getFilenameExtension(targetFile.getAbsolutePath()); assertNotNull(sourceFile); assertNotNull(targetFile); @@ -101,12 +104,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest { String testFilename = actualTarget.substring(i+1); File testFile = getTestFile(testFilename, false); - if (testFile != null) - { - FileChannel source = new FileInputStream(testFile).getChannel(); - FileChannel target = new FileOutputStream(targetFile).getChannel(); - target.transferFrom(source, 0, source.size()); - } + generateTargetFileFromResourceFile(actualTargetExtension, testFile, targetFile); } // Check the supplied source file has not been changed. @@ -129,4 +127,11 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest .andExpect(status().is(400)) .andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:"))); } + + @Override + protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest) + { + transformRequest.setSourceExtension("doc"); + transformRequest.setTargetExtension("pdf"); + } } diff --git a/alfresco-docker-tika/src/main/java/org/alfresco/transformer/TikaController.java b/alfresco-docker-tika/src/main/java/org/alfresco/transformer/TikaController.java index 6c23e9d6..65e6b6e1 100644 --- a/alfresco-docker-tika/src/main/java/org/alfresco/transformer/TikaController.java +++ b/alfresco-docker-tika/src/main/java/org/alfresco/transformer/TikaController.java @@ -11,10 +11,25 @@ */ package org.alfresco.transformer; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN; +import static org.alfresco.transformer.Tika.INCLUDE_CONTENTS; +import static org.alfresco.transformer.Tika.NOT_EXTRACT_BOOKMARKS_TEXT; +import static org.alfresco.transformer.Tika.PDF_BOX; +import static org.alfresco.transformer.Tika.TARGET_ENCODING; +import static org.alfresco.transformer.Tika.TARGET_MIMETYPE; +import static org.alfresco.transformer.Tika.TRANSFORM_NAMES; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.logging.LogFactory; import org.apache.tika.exception.TikaException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; @@ -22,13 +37,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import org.xml.sax.SAXException; -import javax.servlet.http.HttpServletRequest; -import java.io.File; -import java.io.IOException; - -import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN; -import static org.alfresco.transformer.Tika.*; - /** * Controller for the Docker based Tika transformers. * @@ -102,7 +110,7 @@ public class TikaController extends AbstractTransformerController }; } - @PostMapping("/transform") + @PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity transform(HttpServletRequest request, @RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("targetExtension") String targetExtension, @@ -122,7 +130,7 @@ public class TikaController extends AbstractTransformerController throw new TransformException(400, "Invalid transform value"); } - String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); + String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension); File sourceFile = createSourceFile(request, sourceMultipartFile); File targetFile = createTargetFile(request, targetFilename); // Both files are deleted by TransformInterceptor.afterCompletion @@ -137,4 +145,21 @@ public class TikaController extends AbstractTransformerController return createAttachment(targetFilename, targetFile, testDelay); } + + @Override + protected void processTransform(File sourceFile, File targetFile, + Map transformOptions, Long timeout) + { + + String transform = transformOptions.get("transform"); + Boolean includeContents = stringToBoolean("includeContents"); + Boolean notExtractBookmarksText = stringToBoolean("notExtractBookmarksText"); + String targetMimetype = transformOptions.get("targetMimetype"); + String targetEncoding = transformOptions.get("targetEncoding"); + + callTransform(sourceFile, targetFile, transform, + includeContents != null && includeContents ? INCLUDE_CONTENTS : null, + notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT: null, + TARGET_MIMETYPE + targetMimetype, TARGET_ENCODING + targetEncoding); + } } diff --git a/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaControllerTest.java b/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaControllerTest.java index dd711d73..27d0e572 100644 --- a/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaControllerTest.java +++ b/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaControllerTest.java @@ -25,6 +25,44 @@ */ package org.alfresco.transformer; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_HTML; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_OPENXML_PRESENTATION; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_OUTLOOK_MSG; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_PDF; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_CSV; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_WORD; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XHTML; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XML; +import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_ZIP; +import static org.alfresco.transformer.Tika.ARCHIVE; +import static org.alfresco.transformer.Tika.CSV; +import static org.alfresco.transformer.Tika.DOC; +import static org.alfresco.transformer.Tika.DOCX; +import static org.alfresco.transformer.Tika.HTML; +import static org.alfresco.transformer.Tika.MSG; +import static org.alfresco.transformer.Tika.OUTLOOK_MSG; +import static org.alfresco.transformer.Tika.PDF; +import static org.alfresco.transformer.Tika.PDF_BOX; +import static org.alfresco.transformer.Tika.POI; +import static org.alfresco.transformer.Tika.POI_OFFICE; +import static org.alfresco.transformer.Tika.POI_OO_XML; +import static org.alfresco.transformer.Tika.PPTX; +import static org.alfresco.transformer.Tika.TEXT_MINING; +import static org.alfresco.transformer.Tika.TIKA_AUTO; +import static org.alfresco.transformer.Tika.TXT; +import static org.alfresco.transformer.Tika.XHTML; +import static org.alfresco.transformer.Tika.XML; +import static org.alfresco.transformer.Tika.XSLX; +import static org.alfresco.transformer.Tika.ZIP; +import static org.springframework.test.util.AssertionErrors.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.alfresco.transform.client.model.TransformRequest; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -34,12 +72,6 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import static org.alfresco.repo.content.MimetypeMap.*; -import static org.alfresco.transformer.Tika.*; -import static org.springframework.test.util.AssertionErrors.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - /** * Test the TikaController without a server. * Super class includes tests for the AbstractTransformerController. @@ -63,6 +95,16 @@ public class TikaControllerTest extends AbstractTransformerControllerTest String targetEncoding = "UTF-8"; String targetMimetype = MIMETYPE_TEXT_PLAIN; + @Before + public void before() throws Exception + { + controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient); + super.controller = controller; + + sourceExtension = "pdf"; + targetExtension = "txt"; + } + private void transform(String transform, String sourceExtension, String targetExtension, String sourceMimetype, String targetMimetype, Boolean includeContents, String expectedContentContains) throws Exception @@ -350,4 +392,14 @@ public class TikaControllerTest extends AbstractTransformerControllerTest .andExpect(status().is(200)) .andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension)); } + + @Override + protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest) + { + transformRequest.setSourceExtension(sourceExtension); + transformRequest.setTargetExtension(targetExtension); + transformRequest.getTransformationRequestOptions().put("transform", "PdfBox"); + transformRequest.getTransformationRequestOptions().put("targetMimetype", "text/plain"); + transformRequest.getTransformationRequestOptions().put("targetEncoding", "UTF-8"); + } } diff --git a/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaHttpRequestTest.java b/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaHttpRequestTest.java index 25547e11..d368ef2a 100644 --- a/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaHttpRequestTest.java +++ b/alfresco-docker-tika/src/test/java/org/alfresco/transformer/TikaHttpRequestTest.java @@ -47,5 +47,5 @@ public class TikaHttpRequestTest extends AbstractHttpRequestTest protected String getSourceExtension() { return "pdf"; - }; + } } diff --git a/alfresco-docker-tika/src/test/resources/quick.pdf b/alfresco-docker-tika/src/test/resources/quick.pdf new file mode 100644 index 00000000..a1779afd Binary files /dev/null and b/alfresco-docker-tika/src/test/resources/quick.pdf differ diff --git a/alfresco-transformer-base/README.md b/alfresco-transformer-base/README.md index 5439183e..b0e052ce 100644 --- a/alfresco-transformer-base/README.md +++ b/alfresco-transformer-base/README.md @@ -111,6 +111,65 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController } ~~~ +* *TransformerName*Controller#processTransform(File sourceFile, File targetFile, Map transformOptions, Long timeout) + +### /transform (Consumes: `application/json`, Produces: `application/json`) +The new *consumes* and *produces* arguments have been specified in order to differentiate this endpoint from the previous one (which **consumes** `multipart/form-data`) + +The endpoint should **always** receive a `TransformationRequest` and should **always** respond with a `TransformationReply`. + +As specific transformers require specific arguments (e.g. `transform` for the **Tika transformer**) the request body should include this in the `transformRequestOptions` via the `Map transformRequestOptions`. + +**Example request body** +```javascript +var transformRequest = { + "requestId": "1", + "sourceReference": "2f9ed237-c734-4366-8c8b-6001819169a4", + "sourceMediaType": "pdf", + "sourceSize": 123456, + "sourceExtension": "pdf", + "targetMediaType": "txt", + "targetExtension": "txt", + "clientType": "ACS", + "clientData": "Yo No Soy Marinero, Soy Capitan, Soy Capitan!", + "schema": 1, + "transformRequestOptions": { + "targetMimetype": "text/plain", + "targetEncoding": "UTF-8", + "transform": "PdfBox" + } +} +``` + +**Example response body** + +```javascript +var transformReply = { + "requestId": "1", + "status": 201, + "errorDetails": null, + "sourceReference": "2f9ed237-c734-4366-8c8b-6001819169a4", + "targetReference": "34d69ff0-7eaa-4741-8a9f-e1915e6995bf", + "clientType": "ACS", + "clientData": "Yo No Soy Marinero, Soy Capitan, Soy Capitan!", + "schema": 1 +} +``` + +### processTransform method +```java +public abstract class AbstractTransformerController +{ + void processTransform(File sourceFile, File targetFile, Map transformOptions, Long timeout) { /* Perform the transformation*/ } +} +``` + +The **abstract** method is declared in the *AbstractTransformerController* and must be implemented by the specific controllers. + +This method is called by the *AbstractTransformerController* directly in the **new** `/transform` endpoint which **consumes** `application/json` and **produces** `application/json`. + +The method is responsible for performing the transformation. Upon a **successful** transformation it updates the `targetFile` parameter. + * Application.java - [Spring Boot](https://projects.spring.io/spring-boot/) expects to find an Application in a project's source files. The following may be used: diff --git a/alfresco-transformer-base/pom.xml b/alfresco-transformer-base/pom.xml index b9d651da..8270af86 100644 --- a/alfresco-transformer-base/pom.xml +++ b/alfresco-transformer-base/pom.xml @@ -26,6 +26,10 @@ org.alfresco alfresco-core + + org.alfresco + alfresco-transform-data-model + diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AbstractTransformerController.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AbstractTransformerController.java index 75da9fd0..f041e82e 100644 --- a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AbstractTransformerController.java +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AbstractTransformerController.java @@ -25,34 +25,52 @@ */ package org.alfresco.transformer; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.transform.client.model.TransformReply; +import org.alfresco.transform.client.model.TransformRequest; +import org.alfresco.transformer.model.FileRefResponse; import org.alfresco.util.TempFileProvider; import org.alfresco.util.exec.RuntimeExec; import org.apache.commons.logging.Log; import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.ui.Model; import org.springframework.util.StringUtils; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.util.UriUtils; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.*; - /** *

Abstract Controller, provides structure and helper methods to sub-class transformer controllers.

* @@ -85,6 +103,10 @@ public abstract class AbstractTransformerController { public static final String SOURCE_FILE = "sourceFile"; public static final String TARGET_FILE = "targetFile"; + public static final String FILENAME = "filename="; + + @Autowired + private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient; protected static Log logger; @@ -115,6 +137,125 @@ public abstract class AbstractTransformerController protected abstract String getTransformerName(); + /** + * '/transform' endpoint which consumes and produces 'application/json' + * + * This is the way to tell Spring to redirect the request to this endpoint + * instead of the older one, which produces 'html' + * + * @param transformRequest The transformation request + * @param timeout Transformation timeout + * @return A transformation reply + */ + @PostMapping(value = "/transform", produces = APPLICATION_JSON_VALUE) + @ResponseBody + public ResponseEntity transform(@RequestBody TransformRequest transformRequest, + @RequestParam(value = "timeout", required = false) Long timeout) + { + TransformReply transformReply = new TransformReply(); + transformReply.setRequestId(transformRequest.getRequestId()); + transformReply.setSourceReference(transformRequest.getSourceReference()); + transformReply.setSchema(transformRequest.getSchema()); + transformReply.setClientData(transformRequest.getClientData()); + + // Load the source file + File sourceFile; + try + { + sourceFile = loadSourceFile(transformRequest.getSourceReference()); + } + catch (TransformException te) + { + transformReply.setStatus(te.getStatusCode()); + transformReply + .setErrorDetails("Failed at reading the source file. " + te.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + catch (HttpClientErrorException hcee) + { + transformReply.setStatus(hcee.getStatusCode().value()); + transformReply + .setErrorDetails("Failed at reading the source file. " + hcee.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + catch (Exception e) + { + transformReply.setStatus(500); + transformReply.setErrorDetails("Failed at reading the source file. " + e.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + + // Create local temp target file in order to run the transformation + String targetFilename = createTargetFileName(sourceFile.getName(), + transformRequest.getTargetExtension()); + File targetFile = buildFile(targetFilename); + + // Run the transformation + try + { + processTransform(sourceFile, targetFile, + transformRequest.getTransformationRequestOptions(), timeout); + } + catch (TransformException te) + { + transformReply.setStatus(te.getStatusCode()); + transformReply + .setErrorDetails("Failed at processing transformation. " + te.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + catch (Exception e) + { + transformReply.setStatus(500); + transformReply + .setErrorDetails("Failed at processing transformation. " + e.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + + // Write the target file + FileRefResponse targetRef; + try + { + targetRef = alfrescoSharedFileStoreClient.saveFile(targetFile); + } + catch (TransformException te) + { + transformReply.setStatus(te.getStatusCode()); + transformReply + .setErrorDetails("Failed at writing the transformed file. " + te.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + catch (HttpClientErrorException hcee) + { + transformReply.setStatus(hcee.getStatusCode().value()); + transformReply + .setErrorDetails("Failed at writing the transformed file. " + hcee.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + catch (Exception e) + { + transformReply.setStatus(500); + transformReply + .setErrorDetails("Failed at writing the transformed file. " + e.getMessage()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + + transformReply.setTargetReference(targetRef.getEntry().getFileRef()); + transformReply.setStatus(HttpStatus.CREATED.value()); + + return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus())); + } + + protected abstract void processTransform(File sourceFile, File targetFile, + Map transformOptions, Long timeout); + @RequestMapping("/version") @ResponseBody protected String version() @@ -269,10 +410,71 @@ public abstract class AbstractTransformerController // return errorAttributes; // } - protected String createTargetFileName(MultipartFile sourceMultipartFile, String targetExtension) + /** + * Loads the file with the specified sourceReference from Alfresco Shared File Store + * + * @param sourceReference reference to the file in Alfresco Shared File Store + * @return the file containing the source content for the transformation + */ + protected File loadSourceFile(String sourceReference) + { + + ResponseEntity responseEntity = alfrescoSharedFileStoreClient + .retrieveFile(sourceReference); + getProbeTestTransformInternal().incrementTransformerCount(); + + HttpHeaders headers = responseEntity.getHeaders(); + String filename = getFilenameFromContentDisposition(headers); + + String extension = StringUtils.getFilenameExtension(filename); + MediaType contentType = headers.getContentType(); + long size = headers.getContentLength(); + + Resource body = responseEntity.getBody(); + File file = TempFileProvider.createTempFile("source_", "." + extension); + + if (logger.isDebugEnabled()) + { + logger.debug( + "Read source content " + sourceReference + " length=" + + size + " contentType=" + contentType); + } + save(body, file); + LogEntry.setSource(filename, size); + return file; + } + + + private String getFilenameFromContentDisposition(HttpHeaders headers) + { + String filename = ""; + String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION); + if (contentDisposition != null) + { + String[] strings = contentDisposition.split("; *"); + for (String string: strings) + { + if (string.startsWith(FILENAME)) + { + filename = string.substring(FILENAME.length()); + break; + } + } + } + return filename; + } + + /** + * Returns the file name for the target file + * + * @param fileName Desired file name + * @param targetExtension File extension + * @return Target file name + */ + protected String createTargetFileName(String fileName, String targetExtension) { String targetFilename = null; - String sourceFilename = sourceMultipartFile.getOriginalFilename(); + String sourceFilename = fileName; sourceFilename = StringUtils.getFilename(sourceFilename); if (sourceFilename != null && !sourceFilename.isEmpty()) { @@ -317,13 +519,18 @@ public abstract class AbstractTransformerController */ protected File createTargetFile(HttpServletRequest request, String filename) { - filename = checkFilename( false, filename); - LogEntry.setTarget(filename); - File file = TempFileProvider.createTempFile("target_", "_" + filename); + File file = buildFile(filename); request.setAttribute(TARGET_FILE, file); return file; } + private File buildFile(String filename) + { + filename = checkFilename( false, filename); + LogEntry.setTarget(filename); + return TempFileProvider.createTempFile("target_", "_" + filename); + } + /** * Checks the filename is okay to uses in a temporary file name. * @@ -355,6 +562,20 @@ public abstract class AbstractTransformerController } } + private void save(Resource body, File file) + { + try + { + InputStream inputStream = body == null ? null : body.getInputStream(); + Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException e) + { + throw new TransformException(507, "Failed to store the source file", e); + } + } + + private Resource load(File file) { try @@ -491,4 +712,37 @@ public abstract class AbstractTransformerController throw new TransformException(500, "Filename encoding error", e); } } + + /** + * Safely converts a {@link String} to an {@link Integer} + * + * @param param String to be converted + * @return Null if param is null or converted value as {@link Integer} + */ + protected Integer stringToInteger(String param) + { + return param == null ? null : Integer.parseInt(param); + } + + /** + * Safely converts a {@link String} to an {@link Integer} + * + * @param param String to be converted + * @return Null if param is null or converted value as {@link Boolean} + */ + protected Boolean stringToBoolean(String param) + { + return param == null? null : Boolean.parseBoolean(param); + } + + public AlfrescoSharedFileStoreClient getAlfrescoSharedFileStoreClient() + { + return alfrescoSharedFileStoreClient; + } + + public void setAlfrescoSharedFileStoreClient( + AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient) + { + this.alfrescoSharedFileStoreClient = alfrescoSharedFileStoreClient; + } } diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AlfrescoSharedFileStoreClient.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AlfrescoSharedFileStoreClient.java new file mode 100644 index 00000000..bbf8a033 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/AlfrescoSharedFileStoreClient.java @@ -0,0 +1,82 @@ +/* + * Copyright 2005-2018 Alfresco Software, Ltd. All rights reserved. + * + * License rights for this program may be obtained from Alfresco Software, Ltd. + * pursuant to a written agreement and any use of this program without such an + * agreement is prohibited. + */ +package org.alfresco.transformer; + +import java.io.File; + +import org.alfresco.transformer.model.FileRefResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +/** + * Simple Rest client that call Alfresco Shared File Store + */ +public class AlfrescoSharedFileStoreClient +{ + @Value("${fileStoreUrl}") + private String fileStoreUrl; + + @Autowired + private RestTemplate restTemplate; + + /** + * Retrieves a file from Shared File Store using given file reference + * + * @param fileRef File reference + * @return ResponseEntity + */ + public ResponseEntity retrieveFile(String fileRef) + { + try + { + return restTemplate.getForEntity(fileStoreUrl + "/" + fileRef, + org.springframework.core.io.Resource.class); + } + catch (HttpClientErrorException e) + { + throw new TransformException(e.getStatusCode().value(), e.getMessage(), e); + } + } + + /** + * Stores given file in Shared File Store + * + * @param file File to be stored + * @return A FileRefResponse containing detail about file's reference + */ + public FileRefResponse saveFile(File file) + { + try + { + FileSystemResource value = new FileSystemResource(file.getAbsolutePath()); + LinkedMultiValueMap map = new LinkedMultiValueMap<>(); + map.add("file", value); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpEntity> requestEntity = new HttpEntity<>(map, + headers); + ResponseEntity responseEntity = restTemplate + .exchange(fileStoreUrl, HttpMethod.POST, requestEntity, FileRefResponse.class); + return responseEntity.getBody(); + } + catch (HttpClientErrorException e) + { + throw new TransformException(e.getStatusCode().value(), e.getMessage(), e); + } + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java index 2d2ad7f8..bb2b9b7d 100644 --- a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java @@ -35,4 +35,5 @@ public class Application { SpringApplication.run(Application.class, args); } + } diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/WebApplicationConfig.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/WebApplicationConfig.java index 983e3163..720dd662 100644 --- a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/WebApplicationConfig.java +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/WebApplicationConfig.java @@ -27,6 +27,7 @@ package org.alfresco.transformer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @@ -42,4 +43,16 @@ public class WebApplicationConfig extends WebMvcConfigurerAdapter { public TransformInterceptor transformInterceptor() { return new TransformInterceptor(); } + + + @Bean + public RestTemplate restTemplate() + { + return new RestTemplate(); + } + + @Bean + public AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient(){ + return new AlfrescoSharedFileStoreClient(); + } } diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/model/FileRefEntity.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/model/FileRefEntity.java new file mode 100644 index 00000000..83a292eb --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/model/FileRefEntity.java @@ -0,0 +1,57 @@ +package org.alfresco.transformer.model; + +import java.util.Objects; + +/** + * TODO: Copied from org.alfresco.store.entity (alfresco-shared-file-store). To be discussed + * + * POJO that represents content reference ({@link java.util.UUID}) + */ +public class FileRefEntity +{ + private String fileRef; + + public FileRefEntity() + { + } + + public FileRefEntity(String fileRef) + { + this.fileRef = fileRef; + } + + public void setFileRef(String fileRef){ + this.fileRef = fileRef; + } + public String getFileRef() + { + return fileRef; + } + + @Override + public String toString() + { + return fileRef; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + FileRefEntity fileRef = (FileRefEntity) o; + return Objects.equals(this.fileRef, fileRef.fileRef); + } + + @Override + public int hashCode() + { + return Objects.hash(fileRef); + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/model/FileRefResponse.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/model/FileRefResponse.java new file mode 100644 index 00000000..3c4b13a2 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/model/FileRefResponse.java @@ -0,0 +1,30 @@ +package org.alfresco.transformer.model; + +/** + * TODO: Copied from org.alfresco.store.entity (alfresco-shared-file-store). To be discussed + * + * POJO that describes the ContentRefEntry response, contains {@link FileRefEntity} according to API spec + */ +public class FileRefResponse +{ + private FileRefEntity entry; + + public FileRefResponse() + { + } + + public FileRefResponse(FileRefEntity entry) + { + this.entry = entry; + } + + public FileRefEntity getEntry() + { + return entry; + } + + public void setEntry(FileRefEntity entry) + { + this.entry = entry; + } +} diff --git a/alfresco-transformer-base/src/main/resources/application.properties b/alfresco-transformer-base/src/main/resources/application.properties deleted file mode 100644 index ca637b84..00000000 --- a/alfresco-transformer-base/src/main/resources/application.properties +++ /dev/null @@ -1,10 +0,0 @@ -spring.http.multipart.max-file-size=8192MB -spring.http.multipart.max-request-size=8192MB -server.port = 8090 - -#logging.level.org.alfresco.util.exec.RuntimeExec=debug -logging.level.org.alfresco.transformer.LibreOfficeController=debug -logging.level.org.alfresco.transformer.JodConverterSharedInstance=debug -logging.level.org.alfresco.transformer.AlfrescoPdfRendererController=debug -logging.level.org.alfresco.transformer.ImageMagickController=debug -logging.level.org.alfresco.transformer.TikaController=debug \ No newline at end of file diff --git a/alfresco-transformer-base/src/main/resources/application.yaml b/alfresco-transformer-base/src/main/resources/application.yaml new file mode 100644 index 00000000..95ca9000 --- /dev/null +++ b/alfresco-transformer-base/src/main/resources/application.yaml @@ -0,0 +1,19 @@ +spring: + http: + multipart: + max-file-size: 8192MB + max-request-size: 8192MB + +server: + port: 8090 + +logging: + level: + #org.alfresco.util.exec.RuntimeExec: debug + org.alfresco.transformer.LibreOfficeController: debug + org.alfresco.transformer.JodConverterSharedInstance: debug + org.alfresco.transformer.AlfrescoPdfRendererController: debug + org.alfresco.transformer.ImageMagickController: debug + org.alfresco.transformer.TikaController: debug + +fileStoreUrl: ${FILE_STORE_URL:http://localhost:8099/alfresco/api/-default-/private/sfs/versions/1/file} \ No newline at end of file diff --git a/alfresco-transformer-base/src/test/java/org/alfresco/transformer/AbstractTransformerControllerTest.java b/alfresco-transformer-base/src/test/java/org/alfresco/transformer/AbstractTransformerControllerTest.java index 5c619442..ee67c2ae 100644 --- a/alfresco-transformer-base/src/test/java/org/alfresco/transformer/AbstractTransformerControllerTest.java +++ b/alfresco-transformer-base/src/test/java/org/alfresco/transformer/AbstractTransformerControllerTest.java @@ -25,30 +25,54 @@ */ package org.alfresco.transformer; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.alfresco.transform.client.model.TransformReply; +import org.alfresco.transform.client.model.TransformRequest; +import org.alfresco.transformer.model.FileRefEntity; +import org.alfresco.transformer.model.FileRefResponse; import org.alfresco.util.exec.RuntimeExec; +import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.util.StringUtils; -import java.io.*; -import java.net.URL; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.util.Arrays; -import java.util.Map; - -import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.*; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import com.fasterxml.jackson.databind.ObjectMapper; /** * Super class for testing controllers without a server. Includes tests for the AbstractTransformerController itself. @@ -58,12 +82,18 @@ public abstract class AbstractTransformerControllerTest @Autowired protected MockMvc mockMvc; + @Autowired + protected ObjectMapper objectMapper; + @Mock private RuntimeExec mockTransformCommand; @Mock private RuntimeExec mockCheckCommand; + @Mock + protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient; + @Mock private RuntimeExec.ExecutionResult mockExecutionResult; @@ -80,6 +110,11 @@ public abstract class AbstractTransformerControllerTest protected AbstractTransformerController controller; + @Before + public void before() throws Exception + { + } + // Called by sub class public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension, String targetExtension, String sourceMimetype, @@ -109,6 +144,7 @@ public abstract class AbstractTransformerControllerTest String actualOptions = actualProperties.get("options"); String actualSource = actualProperties.get("source"); String actualTarget = actualProperties.get("target"); + String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget); assertNotNull(actualSource); assertNotNull(actualTarget); @@ -137,13 +173,9 @@ public abstract class AbstractTransformerControllerTest { String testFilename = actualTarget.substring(i+1); File testFile = getTestFile(testFilename, false); - if (testFile != null) - { - File targetFile = new File(actualTarget); - FileChannel source = new FileInputStream(testFile).getChannel(); - FileChannel target = new FileOutputStream(targetFile).getChannel(); - target.transferFrom(source, 0, source.size()); - } + File targetFile = new File(actualTarget); + generateTargetFileFromResourceFile(actualTargetExtension, testFile, + targetFile); } // Check the supplied source file has not been changed. @@ -159,6 +191,37 @@ public abstract class AbstractTransformerControllerTest when(mockExecutionResult.getStdOut()).thenReturn("STDOUT"); } + /** + * This method ends up being the core of the mock. + * It copies content from an existing file in the resources folder to the desired location + * in order to simulate a successful transformation. + * + * @param actualTargetExtension Requested extension. + * @param testFile The test file (transformed) - basically the result. + * @param targetFile The location where the content from the testFile should be copied + * @throws IOException in case of any errors. + */ + void generateTargetFileFromResourceFile(String actualTargetExtension, File testFile, + File targetFile) throws IOException + { + if (testFile != null) + { + FileChannel source = new FileInputStream(testFile).getChannel(); + FileChannel target = new FileOutputStream(targetFile).getChannel(); + target.transferFrom(source, 0, source.size()); + } + else + { + testFile = getTestFile("quick." + actualTargetExtension, false); + if (testFile != null) + { + FileChannel source = new FileInputStream(testFile).getChannel(); + FileChannel target = new FileOutputStream(targetFile).getChannel(); + target.transferFrom(source, 0, source.size()); + } + } + } + protected byte[] readTestFile(String extension) throws IOException { return Files.readAllBytes(getTestFile("quick."+extension, true).toPath()); @@ -329,4 +392,60 @@ public abstract class AbstractTransformerControllerTest assertEquals("", expectedMaxTime, probeTestTransform.maxTime); } } + + @Test + public void testPojoTransform() throws Exception + { + // Files + String sourceFileRef = UUID.randomUUID().toString(); + File sourceFile = getTestFile("quick." + sourceExtension, true); + String targetFileRef = UUID.randomUUID().toString(); + + + // Transformation Request POJO + TransformRequest transformRequest = new TransformRequest(); + transformRequest.setRequestId("1"); + transformRequest.setSchema(1); + transformRequest.setClientData("Alfresco Digital Business Platform"); + transformRequest.setTransformationRequestOptions(new HashMap<>()); + + transformRequest.setSourceReference(sourceFileRef); + transformRequest.setSourceExtension(sourceExtension); + // TODO: ATS-53 + transformRequest.setSourceMediaType("TODO"); + transformRequest.setSourceSize(sourceFile.length()); + + transformRequest.setTargetExtension(targetExtension); + transformRequest.setTargetMediaType("TODO"); + + // HTTP Request + HttpHeaders headers = new HttpHeaders(); + headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=quick." + sourceExtension); + ResponseEntity response = new ResponseEntity<>(new FileSystemResource( + sourceFile), headers, HttpStatus.OK); + + when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response); + when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(new FileRefResponse(new FileRefEntity(targetFileRef))); + when(mockExecutionResult.getExitValue()).thenReturn(0); + + // Update the Transformation Request with any specific params before sending it + updateTransformRequestWithSpecificOptions(transformRequest); + + // Serialize and call the transformer + String tr = objectMapper.writeValueAsString(transformRequest); + String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform") + .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr)) + .andExpect(status().is(HttpStatus.CREATED.value())) + .andReturn().getResponse().getContentAsString(); + + TransformReply transformReply = objectMapper.readValue(transformationReplyAsString, TransformReply.class); + + // Assert the reply + assertEquals(transformRequest.getRequestId(), transformReply.getRequestId()); + assertEquals(transformRequest.getClientData(), transformReply.getClientData()); + assertEquals(transformRequest.getSchema(), transformReply.getSchema()); + } + + protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest); } diff --git a/pom.xml b/pom.xml index a4a70ccb..9e25a262 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ 3.0.1.1 1.2.3 ${project.version} + 0.0.4 @@ -85,6 +86,11 @@ pdfbox ${dependency.pdfbox.version} + + org.alfresco + alfresco-transform-data-model + ${alfresco-transform-data-model.version} +