feature/ATS-16

This commit is contained in:
Lucian Tuca 2018-08-17 09:32:25 +01:00
parent c4ca88d876
commit dda632c7a5
22 changed files with 983 additions and 145 deletions

View File

@ -11,22 +11,24 @@
*/ */
package org.alfresco.transformer; 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.alfresco.util.exec.RuntimeExec;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile; 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. * 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<String, String> 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<Resource> transform(HttpServletRequest request, public ResponseEntity<Resource> transform(HttpServletRequest request,
@RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("file") MultipartFile sourceMultipartFile,
@RequestParam("targetExtension") String targetExtension, @RequestParam("targetExtension") String targetExtension,
@ -124,11 +154,21 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
@RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement, @RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement,
@RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio) @RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio)
{ {
String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
File sourceFile = createSourceFile(request, sourceMultipartFile); File sourceFile = createSourceFile(request, sourceMultipartFile);
File targetFile = createTargetFile(request, targetFilename); File targetFile = createTargetFile(request, targetFilename);
// Both files are deleted by TransformInterceptor.afterCompletion // 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(" "); StringJoiner args = new StringJoiner(" ");
if (width != null && width >= 0) if (width != null && width >= 0)
{ {
@ -150,9 +190,6 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
{ {
args.add("--page=" + page); args.add("--page=" + page);
} }
String options = args.toString(); return args.toString();
executeTransformCommand(options, sourceFile, targetFile, timeout);
return createAttachment(targetFilename, targetFile, testDelay);
} }
} }

View File

@ -25,6 +25,11 @@
*/ */
package org.alfresco.transformer; 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.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 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. * Test the AlfrescoPdfRendererController without a server.
* Super class includes tests for the AbstractTransformerController. * Super class includes tests for the AbstractTransformerController.
@ -49,8 +50,10 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
private AlfrescoPdfRendererController controller; private AlfrescoPdfRendererController controller;
@Before @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); super.mockTransformCommand(controller, "pdf", "png", "application/pdf", true);
} }
@ -93,4 +96,11 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
.andExpect(content().bytes(expectedTargetFileBytes)) .andExpect(content().bytes(expectedTargetFileBytes))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension)); .andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
} }
@Override
protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest)
{
transformRequest.setSourceExtension("pdf");
transformRequest.setTargetExtension("png");
}
} }

View File

@ -11,20 +11,26 @@
*/ */
package org.alfresco.transformer; 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.alfresco.util.exec.RuntimeExec;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile; 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. * 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<Resource> transform(HttpServletRequest request, public ResponseEntity<Resource> transform(HttpServletRequest request,
@RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("file") MultipartFile sourceMultipartFile,
@RequestParam("targetExtension") String targetExtension, @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 // ACS 6.0, this is relatively safe as it requires an AMP to be installed
// which supplies the commandOptions. // which supplies the commandOptions.
@RequestParam(value = "commandOptions", required = false) String 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<String, String> 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<String, String> properties = new HashMap<String, String>(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) 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(" "); StringJoiner args = new StringJoiner(" ");
if (alphaRemove != null && alphaRemove) if (alphaRemove != null && alphaRemove)
{ {
@ -190,7 +253,7 @@ public class ImageMagickController extends AbstractTransformerController
} }
if (cropGravity != null || cropWidth != null || cropHeight != null || cropPercentage != null || if (cropGravity != null || cropWidth != null || cropHeight != null || cropPercentage != null ||
cropXOffset != null || cropYOffset != null) cropXOffset != null || cropYOffset != null)
{ {
if (cropGravity != null) if (cropGravity != null)
{ {
@ -268,32 +331,20 @@ public class ImageMagickController extends AbstractTransformerController
} }
} }
String pageRange = return (commandOptions == null || "".equals(commandOptions.trim()) ? "" : commandOptions + ' ') +
startPage == null args.toString();
? 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);
} }
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); return
startPage == null
Map<String, String> properties = new HashMap<String, String>(5); ? endPage == null
properties.put("options", options); ? ""
properties.put("source", sourceFile.getAbsolutePath()+pageRange); : "["+endPage+']'
properties.put("target", targetFile.getAbsolutePath()); : endPage == null || startPage.equals(endPage)
? "["+startPage+']'
executeTransformCommand(properties, targetFile, timeout); : "["+startPage+'-'+endPage+']';
} }
} }

View File

@ -25,6 +25,13 @@
*/ */
package org.alfresco.transformer; 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.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 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. * Test the ImageMagickController without a server.
* Super class includes tests for the AbstractTransformerController. * Super class includes tests for the AbstractTransformerController.
@ -51,6 +54,9 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
@Before @Before
public void before() throws IOException public void before() throws IOException
{ {
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
super.controller = controller;
super.mockTransformCommand(controller, "jpg", "png", "image/jpg", true); super.mockTransformCommand(controller, "jpg", "png", "image/jpg", true);
} }
@ -164,4 +170,11 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
.andExpect(content().bytes(expectedTargetFileBytes)) .andExpect(content().bytes(expectedTargetFileBytes))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension)); .andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
} }
@Override
protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest)
{
transformRequest.setSourceExtension("png");
transformRequest.setTargetExtension("png");
}
} }

View File

@ -11,7 +11,12 @@
*/ */
package org.alfresco.transformer; 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.commons.logging.LogFactory;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPage;
@ -21,15 +26,14 @@ import org.artofsolving.jodconverter.office.OfficeException;
import org.artofsolving.jodconverter.office.OfficeManager; import org.artofsolving.jodconverter.office.OfficeManager;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import com.sun.star.task.ErrorCodeIOException;
import java.io.File;
import java.io.IOException;
/** /**
* Controller for the Docker based LibreOffice transformer. * 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<Resource> transform(HttpServletRequest request, public ResponseEntity<Resource> transform(HttpServletRequest request,
@RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("file") MultipartFile sourceMultipartFile,
@RequestParam("targetExtension") String targetExtension, @RequestParam("targetExtension") String targetExtension,
@RequestParam(value = "timeout", required = false) Long timeout, @RequestParam(value = "timeout", required = false) Long timeout,
@RequestParam(value = "testDelay", required = false) Long testDelay) @RequestParam(value = "testDelay", required = false) Long testDelay)
{ {
String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
File sourceFile = createSourceFile(request, sourceMultipartFile); File sourceFile = createSourceFile(request, sourceMultipartFile);
File targetFile = createTargetFile(request, targetFilename); File targetFile = createTargetFile(request, targetFilename);
// Both files are deleted by TransformInterceptor.afterCompletion // Both files are deleted by TransformInterceptor.afterCompletion
@ -152,6 +156,13 @@ public class LibreOfficeController extends AbstractTransformerController
return createAttachment(targetFilename, targetFile, testDelay); return createAttachment(targetFilename, targetFile, testDelay);
} }
@Override
protected void processTransform(File sourceFile, File targetFile,
Map<String, String> transformOptions, Long timeout)
{
executeTransformCommand(sourceFile, targetFile, timeout);
}
protected void executeTransformCommand(File sourceFile, File targetFile, Long timeout) protected void executeTransformCommand(File sourceFile, File targetFile, Long timeout)
{ {
timeout = timeout != null && timeout > 0 ? timeout : 0; timeout = timeout != null && timeout > 0 ? timeout : 0;

View File

@ -25,6 +25,22 @@
*/ */
package org.alfresco.transformer; 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.artofsolving.jodconverter.office.OfficeException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; 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.mock.web.MockMultipartFile;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.StringUtils;
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;
/** /**
* Test the LibreOfficeController without a server. * Test the LibreOfficeController without a server.
@ -66,6 +67,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
@Before @Before
public void before() throws IOException public void before() throws IOException
{ {
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
super.controller = controller; super.controller = controller;
sourceExtension = "doc"; sourceExtension = "doc";
@ -83,6 +85,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
{ {
File sourceFile = invocation.getArgumentAt(0, File.class); File sourceFile = invocation.getArgumentAt(0, File.class);
File targetFile = invocation.getArgumentAt(1, File.class); File targetFile = invocation.getArgumentAt(1, File.class);
String actualTargetExtension = StringUtils.getFilenameExtension(targetFile.getAbsolutePath());
assertNotNull(sourceFile); assertNotNull(sourceFile);
assertNotNull(targetFile); assertNotNull(targetFile);
@ -101,12 +104,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
{ {
String testFilename = actualTarget.substring(i+1); String testFilename = actualTarget.substring(i+1);
File testFile = getTestFile(testFilename, false); File testFile = getTestFile(testFilename, false);
if (testFile != null) generateTargetFileFromResourceFile(actualTargetExtension, testFile, targetFile);
{
FileChannel source = new FileInputStream(testFile).getChannel();
FileChannel target = new FileOutputStream(targetFile).getChannel();
target.transferFrom(source, 0, source.size());
}
} }
// Check the supplied source file has not been changed. // Check the supplied source file has not been changed.
@ -129,4 +127,11 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
.andExpect(status().is(400)) .andExpect(status().is(400))
.andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:"))); .andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:")));
} }
@Override
protected void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest)
{
transformRequest.setSourceExtension("doc");
transformRequest.setTargetExtension("pdf");
}
} }

View File

@ -11,10 +11,25 @@
*/ */
package org.alfresco.transformer; 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.commons.logging.LogFactory;
import org.apache.tika.exception.TikaException; import org.apache.tika.exception.TikaException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping; 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.springframework.web.multipart.MultipartFile;
import org.xml.sax.SAXException; 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. * 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<Resource> transform(HttpServletRequest request, public ResponseEntity<Resource> transform(HttpServletRequest request,
@RequestParam("file") MultipartFile sourceMultipartFile, @RequestParam("file") MultipartFile sourceMultipartFile,
@RequestParam("targetExtension") String targetExtension, @RequestParam("targetExtension") String targetExtension,
@ -122,7 +130,7 @@ public class TikaController extends AbstractTransformerController
throw new TransformException(400, "Invalid transform value"); throw new TransformException(400, "Invalid transform value");
} }
String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
File sourceFile = createSourceFile(request, sourceMultipartFile); File sourceFile = createSourceFile(request, sourceMultipartFile);
File targetFile = createTargetFile(request, targetFilename); File targetFile = createTargetFile(request, targetFilename);
// Both files are deleted by TransformInterceptor.afterCompletion // Both files are deleted by TransformInterceptor.afterCompletion
@ -137,4 +145,21 @@ public class TikaController extends AbstractTransformerController
return createAttachment(targetFilename, targetFile, testDelay); return createAttachment(targetFilename, targetFile, testDelay);
} }
@Override
protected void processTransform(File sourceFile, File targetFile,
Map<String, String> 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);
}
} }

View File

@ -25,6 +25,44 @@
*/ */
package org.alfresco.transformer; 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 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.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; 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. * Test the TikaController without a server.
* Super class includes tests for the AbstractTransformerController. * Super class includes tests for the AbstractTransformerController.
@ -63,6 +95,16 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
String targetEncoding = "UTF-8"; String targetEncoding = "UTF-8";
String targetMimetype = MIMETYPE_TEXT_PLAIN; 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, private void transform(String transform, String sourceExtension, String targetExtension,
String sourceMimetype, String targetMimetype, String sourceMimetype, String targetMimetype,
Boolean includeContents, String expectedContentContains) throws Exception Boolean includeContents, String expectedContentContains) throws Exception
@ -350,4 +392,14 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
.andExpect(status().is(200)) .andExpect(status().is(200))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension)); .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");
}
} }

View File

@ -47,5 +47,5 @@ public class TikaHttpRequestTest extends AbstractHttpRequestTest
protected String getSourceExtension() protected String getSourceExtension()
{ {
return "pdf"; return "pdf";
}; }
} }

Binary file not shown.

View File

@ -111,6 +111,65 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
} }
~~~ ~~~
* *TransformerName*Controller#processTransform(File sourceFile, File targetFile, Map<String, String> 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<String,String> 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<String, String> 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 * 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: a project's source files. The following may be used:

View File

@ -26,6 +26,10 @@
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-core</artifactId> <artifactId>alfresco-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-data-model</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -25,34 +25,52 @@
*/ */
package org.alfresco.transformer; 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.TempFileProvider;
import org.alfresco.util.exec.RuntimeExec; import org.alfresco.util.exec.RuntimeExec;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.springframework.beans.TypeMismatchException; import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource; import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping; 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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils; 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.*;
/** /**
* <p>Abstract Controller, provides structure and helper methods to sub-class transformer controllers.</p> * <p>Abstract Controller, provides structure and helper methods to sub-class transformer controllers.</p>
* *
@ -85,6 +103,10 @@ public abstract class AbstractTransformerController
{ {
public static final String SOURCE_FILE = "sourceFile"; public static final String SOURCE_FILE = "sourceFile";
public static final String TARGET_FILE = "targetFile"; public static final String TARGET_FILE = "targetFile";
public static final String FILENAME = "filename=";
@Autowired
private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
protected static Log logger; protected static Log logger;
@ -115,6 +137,125 @@ public abstract class AbstractTransformerController
protected abstract String getTransformerName(); 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<TransformReply> 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<String, String> transformOptions, Long timeout);
@RequestMapping("/version") @RequestMapping("/version")
@ResponseBody @ResponseBody
protected String version() protected String version()
@ -269,10 +410,71 @@ public abstract class AbstractTransformerController
// return errorAttributes; // 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<Resource> 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 targetFilename = null;
String sourceFilename = sourceMultipartFile.getOriginalFilename(); String sourceFilename = fileName;
sourceFilename = StringUtils.getFilename(sourceFilename); sourceFilename = StringUtils.getFilename(sourceFilename);
if (sourceFilename != null && !sourceFilename.isEmpty()) if (sourceFilename != null && !sourceFilename.isEmpty())
{ {
@ -317,13 +519,18 @@ public abstract class AbstractTransformerController
*/ */
protected File createTargetFile(HttpServletRequest request, String filename) protected File createTargetFile(HttpServletRequest request, String filename)
{ {
filename = checkFilename( false, filename); File file = buildFile(filename);
LogEntry.setTarget(filename);
File file = TempFileProvider.createTempFile("target_", "_" + filename);
request.setAttribute(TARGET_FILE, file); request.setAttribute(TARGET_FILE, file);
return 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. * 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) private Resource load(File file)
{ {
try try
@ -491,4 +712,37 @@ public abstract class AbstractTransformerController
throw new TransformException(500, "Filename encoding error", e); 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;
}
} }

View File

@ -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<Resource>
*/
public ResponseEntity<Resource> 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<String, Object> map = new LinkedMultiValueMap<>();
map.add("file", value);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map,
headers);
ResponseEntity<FileRefResponse> responseEntity = restTemplate
.exchange(fileStoreUrl, HttpMethod.POST, requestEntity, FileRefResponse.class);
return responseEntity.getBody();
}
catch (HttpClientErrorException e)
{
throw new TransformException(e.getStatusCode().value(), e.getMessage(), e);
}
}
}

View File

@ -35,4 +35,5 @@ public class Application
{ {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }
} }

View File

@ -27,6 +27,7 @@ package org.alfresco.transformer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@ -42,4 +43,16 @@ public class WebApplicationConfig extends WebMvcConfigurerAdapter {
public TransformInterceptor transformInterceptor() { public TransformInterceptor transformInterceptor() {
return new TransformInterceptor(); return new TransformInterceptor();
} }
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
@Bean
public AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient(){
return new AlfrescoSharedFileStoreClient();
}
} }

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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}

View File

@ -25,30 +25,54 @@
*/ */
package org.alfresco.transformer; 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.alfresco.util.exec.RuntimeExec;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired; 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.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.StringUtils;
import java.io.*; import com.fasterxml.jackson.databind.ObjectMapper;
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.*;
/** /**
* Super class for testing controllers without a server. Includes tests for the AbstractTransformerController itself. * Super class for testing controllers without a server. Includes tests for the AbstractTransformerController itself.
@ -58,12 +82,18 @@ public abstract class AbstractTransformerControllerTest
@Autowired @Autowired
protected MockMvc mockMvc; protected MockMvc mockMvc;
@Autowired
protected ObjectMapper objectMapper;
@Mock @Mock
private RuntimeExec mockTransformCommand; private RuntimeExec mockTransformCommand;
@Mock @Mock
private RuntimeExec mockCheckCommand; private RuntimeExec mockCheckCommand;
@Mock
protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
@Mock @Mock
private RuntimeExec.ExecutionResult mockExecutionResult; private RuntimeExec.ExecutionResult mockExecutionResult;
@ -80,6 +110,11 @@ public abstract class AbstractTransformerControllerTest
protected AbstractTransformerController controller; protected AbstractTransformerController controller;
@Before
public void before() throws Exception
{
}
// Called by sub class // Called by sub class
public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension, public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension,
String targetExtension, String sourceMimetype, String targetExtension, String sourceMimetype,
@ -109,6 +144,7 @@ public abstract class AbstractTransformerControllerTest
String actualOptions = actualProperties.get("options"); String actualOptions = actualProperties.get("options");
String actualSource = actualProperties.get("source"); String actualSource = actualProperties.get("source");
String actualTarget = actualProperties.get("target"); String actualTarget = actualProperties.get("target");
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
assertNotNull(actualSource); assertNotNull(actualSource);
assertNotNull(actualTarget); assertNotNull(actualTarget);
@ -137,13 +173,9 @@ public abstract class AbstractTransformerControllerTest
{ {
String testFilename = actualTarget.substring(i+1); String testFilename = actualTarget.substring(i+1);
File testFile = getTestFile(testFilename, false); File testFile = getTestFile(testFilename, false);
if (testFile != null) File targetFile = new File(actualTarget);
{ generateTargetFileFromResourceFile(actualTargetExtension, testFile,
File targetFile = new File(actualTarget); targetFile);
FileChannel source = new FileInputStream(testFile).getChannel();
FileChannel target = new FileOutputStream(targetFile).getChannel();
target.transferFrom(source, 0, source.size());
}
} }
// Check the supplied source file has not been changed. // Check the supplied source file has not been changed.
@ -159,6 +191,37 @@ public abstract class AbstractTransformerControllerTest
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT"); 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 protected byte[] readTestFile(String extension) throws IOException
{ {
return Files.readAllBytes(getTestFile("quick."+extension, true).toPath()); return Files.readAllBytes(getTestFile("quick."+extension, true).toPath());
@ -329,4 +392,60 @@ public abstract class AbstractTransformerControllerTest
assertEquals("", expectedMaxTime, probeTestTransform.maxTime); 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<Resource> 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);
} }

View File

@ -24,6 +24,7 @@
<dependency.alfresco-jodconverter-core.version>3.0.1.1</dependency.alfresco-jodconverter-core.version> <dependency.alfresco-jodconverter-core.version>3.0.1.1</dependency.alfresco-jodconverter-core.version>
<dependency.ch-qos-logback.version>1.2.3</dependency.ch-qos-logback.version> <dependency.ch-qos-logback.version>1.2.3</dependency.ch-qos-logback.version>
<env.project_version>${project.version}</env.project_version> <env.project_version>${project.version}</env.project_version>
<alfresco-transform-data-model.version>0.0.4</alfresco-transform-data-model.version>
</properties> </properties>
<modules> <modules>
@ -85,6 +86,11 @@
<artifactId>pdfbox</artifactId> <artifactId>pdfbox</artifactId>
<version>${dependency.pdfbox.version}</version> <version>${dependency.pdfbox.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-data-model</artifactId>
<version>${alfresco-transform-data-model.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>