ATS-175 : T-Engine code cleanup

This commit is contained in:
Cezar.Leahu 2018-10-25 18:21:50 +03:00
parent ba8707c762
commit d85c03d362
42 changed files with 2042 additions and 1475 deletions

View File

@ -11,14 +11,23 @@
*/
package org.alfresco.transformer;
import static org.alfresco.transformer.fs.FileManager.createAttachment;
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
import java.io.File;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Map;
import java.util.StringJoiner;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.util.exec.RuntimeExec;
import org.alfresco.transformer.executors.PdfRendererCommandExecutor;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.transformer.probes.ProbeTestTransform;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
@ -52,69 +61,49 @@ import org.springframework.web.multipart.MultipartFile;
@Controller
public class AlfrescoPdfRendererController extends AbstractTransformerController
{
private static final String EXE = "/usr/bin/alfresco-pdf-renderer";
private static final Log logger = LogFactory.getLog(AlfrescoPdfRendererController.class);
@Autowired
private PdfRendererCommandExecutor commandExecutor;
@Autowired
public AlfrescoPdfRendererController()
{
logger = LogFactory.getLog(AlfrescoPdfRendererController.class);
logger.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
logEnterpriseLicenseMessage();
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
logger.info("alfresco-pdf-renderer uses the PDFium library from Google Inc. See the license at https://pdfium.googlesource.com/pdfium/+/master/LICENSE or in /pdfium.txt");
logger.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
setTransformCommand(createTransformCommand());
setCheckCommand(createCheckCommand());
}
@Override
protected String getTransformerName()
public String getTransformerName()
{
return "Alfresco PDF Renderer";
}
private static RuntimeExec createTransformCommand()
@Override
public String version()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*", new String[]{EXE, "SPLIT:${options}", "${source}", "${target}"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
Map<String, String> defaultProperties = new HashMap<>();
defaultProperties.put("key", null);
runtimeExec.setDefaultProperties(defaultProperties);
runtimeExec.setErrorCodes("1");
return runtimeExec;
}
private static RuntimeExec createCheckCommand()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*", new String[]{EXE, "--version"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
return runtimeExec;
return commandExecutor.version();
}
@Override
protected ProbeTestTransform getProbeTestTransform()
public ProbeTestTransform getProbeTestTransform()
{
// See the Javadoc on this method and Probes.md for the choice of these values.
return new ProbeTestTransform(this,"quick.pdf", "quick.png",
return new ProbeTestTransform(this, logger, "quick.pdf", "quick.png",
7455, 1024, 150, 10240, 60*20+1, 60*15-15)
{
@Override
protected void executeTransformCommand(File sourceFile, File targetFile)
{
AlfrescoPdfRendererController.this.executeTransformCommand("", sourceFile, targetFile, null);
commandExecutor.run("", sourceFile, targetFile, null);
}
};
}
@Override
protected void processTransform(File sourceFile, File targetFile,
public void processTransform(File sourceFile, File targetFile,
Map<String, String> transformOptions, Long timeout)
{
String page = transformOptions.get("page");
@ -137,7 +126,7 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
String options = buildTransformOptions(pageOption, widthOption, heightOption,
allowEnlargementOption, maintainAspectRatioOption);
executeTransformCommand(options, sourceFile, targetFile, timeout);
commandExecutor.run(options, sourceFile, targetFile, timeout);
}
@Deprecated
@ -155,19 +144,26 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
@RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio)
{
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
getProbeTestTransform().incrementTransformerCount();
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);
commandExecutor.run(options, sourceFile, targetFile, timeout);
return createAttachment(targetFilename, targetFile, testDelay);
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
LogEntry.setTargetSize(targetFile.length());
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
time += LogEntry.addDelay(testDelay);
getProbeTestTransform().recordTransformTime(time);
return body;
}
public String buildTransformOptions(Integer page,Integer width,Integer height,Boolean allowEnlargement,Boolean maintainAspectRatio)
private static String buildTransformOptions(Integer page,Integer width,Integer height,Boolean
allowEnlargement,Boolean maintainAspectRatio)
{
StringJoiner args = new StringJoiner(" ");
if (width != null && width >= 0)

View File

@ -12,6 +12,8 @@
package org.alfresco.transformer;
import io.micrometer.core.instrument.MeterRegistry;
import org.alfresco.transformer.executors.PdfRendererCommandExecutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
@ -27,10 +29,16 @@ public class Application
@Value("${container.name}")
private String containerName;
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("containerName", containerName);
}
@Bean
public PdfRendererCommandExecutor commandExecutor() {
return new PdfRendererCommandExecutor();
}
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);

View File

@ -0,0 +1,43 @@
package org.alfresco.transformer.executors;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.util.exec.RuntimeExec;
import org.springframework.stereotype.Component;
/**
*/
@Component
public class PdfRendererCommandExecutor extends AbstractCommandExecutor
{
private static final String EXE = "/usr/bin/alfresco-pdf-renderer";
@Override
protected RuntimeExec createTransformCommand()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*",
new String[]{EXE, "SPLIT:${options}", "${source}", "${target}"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
Map<String, String> defaultProperties = new HashMap<>();
defaultProperties.put("key", null);
runtimeExec.setDefaultProperties(defaultProperties);
runtimeExec.setErrorCodes("1");
return runtimeExec;
}
@Override
protected RuntimeExec createCheckCommand()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*", new String[]{EXE, "--version"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
return runtimeExec;
}
}

View File

@ -25,19 +25,48 @@
*/
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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
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.IOException;
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.executors.PdfRendererCommandExecutor;
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.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.StringUtils;
/**
* Test the AlfrescoPdfRendererController without a server.
@ -47,22 +76,111 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@WebMvcTest(AlfrescoPdfRendererController.class)
public class AlfrescoPdfRendererControllerTest extends AbstractTransformerControllerTest
{
@Mock
private RuntimeExec.ExecutionResult mockExecutionResult;
@Mock
private RuntimeExec mockTransformCommand;
@Mock
private RuntimeExec mockCheckCommand;
@SpyBean
private PdfRendererCommandExecutor commandExecutor;
@SpyBean
private AlfrescoPdfRendererController controller;
@Before
public void before() throws Exception
public void before() throws IOException
{
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
super.controller = controller;
super.mockTransformCommand(controller, "pdf", "png", "application/pdf", true);
commandExecutor.setTransformCommand(mockTransformCommand);
commandExecutor.setCheckCommand(mockCheckCommand);
mockTransformCommand("pdf", "png", "application/pdf", true);
}
@Override
public void mockTransformCommand(String sourceExtension,
String targetExtension, String sourceMimetype,
boolean readTargetFileBytes) throws IOException
{
this.sourceExtension = sourceExtension;
this.targetExtension = targetExtension;
this.sourceMimetype = sourceMimetype;
expectedOptions = null;
expectedSourceSuffix = null;
expectedSourceFileBytes = readTestFile(sourceExtension);
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
when(mockTransformCommand.execute(any(), anyLong())).thenAnswer(
(Answer<RuntimeExec.ExecutionResult>) invocation -> {
Map<String, String> actualProperties = invocation.getArgument(0);
assertEquals("There should be 3 properties", 3, actualProperties.size());
String actualOptions = actualProperties.get("options");
String actualSource = actualProperties.get("source");
String actualTarget = actualProperties.get("target");
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
assertNotNull(actualSource);
assertNotNull(actualTarget);
if (expectedSourceSuffix != null)
{
assertTrue("The source file \""+actualSource+"\" should have ended in \""+expectedSourceSuffix+"\"", actualSource.endsWith(expectedSourceSuffix));
actualSource = actualSource.substring(0, actualSource.length()-expectedSourceSuffix.length());
}
assertNotNull(actualOptions);
if (expectedOptions != null)
{
assertEquals("expectedOptions", expectedOptions, actualOptions);
}
Long actualTimeout = invocation.getArgument(1);
assertNotNull(actualTimeout);
if (expectedTimeout != null)
{
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
}
// Copy a test file into the target file location if it exists
int i = actualTarget.lastIndexOf('_');
if (i >= 0)
{
String testFilename = actualTarget.substring(i+1);
File testFile = getTestFile(testFilename, false);
File targetFile = new File(actualTarget);
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
targetFile);
}
// Check the supplied source file has not been changed.
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
return mockExecutionResult;
});
when(mockExecutionResult.getExitValue()).thenReturn(0);
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
}
@Override
protected AbstractTransformerController getController()
{
return controller;
}
@Test
public void optionsTest() throws Exception
{
expectedOptions = "--width=321 --height=654 --allow-enlargement --maintain-aspect-ratio --page=2";
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
@ -82,7 +200,7 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
public void optionsNegateBooleansTest() throws Exception
{
expectedOptions = "--width=321 --height=654 --page=2";
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
@ -106,4 +224,64 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
transformRequest.setSourceMediaType(MediaType.APPLICATION_PDF_VALUE);
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
}
@Test
public void badExitCodeTest() throws Exception
{
when(mockExecutionResult.getExitValue()).thenReturn(1);
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
.andExpect(status().is(400))
.andExpect(status().reason(containsString("Transformer exit code was not 0: \nSTDERR")));
}
@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.setTransformRequestOptions(new HashMap<>());
transformRequest.setSourceReference(sourceFileRef);
transformRequest.setSourceExtension(sourceExtension);
transformRequest.setSourceSize(sourceFile.length());
transformRequest.setTargetExtension(targetExtension);
// 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());
}
}

View File

@ -25,7 +25,6 @@
*/
package org.alfresco.transformer;
import org.alfresco.transformer.AbstractHttpRequestTest;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@ -48,5 +47,5 @@ public class AlfrescoPdfRendererHttpRequestTest extends AbstractHttpRequestTest
protected String getSourceExtension()
{
return "pdf";
};
}
}

View File

@ -12,6 +12,8 @@
package org.alfresco.transformer;
import io.micrometer.core.instrument.MeterRegistry;
import org.alfresco.transformer.executors.ImageMagickCommandExecutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
@ -27,10 +29,16 @@ public class Application
@Value("${container.name}")
private String containerName;
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("containerName", containerName);
}
@Bean
public ImageMagickCommandExecutor commandExecutor() {
return new ImageMagickCommandExecutor();
}
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);

View File

@ -11,16 +11,27 @@
*/
package org.alfresco.transformer;
import static org.alfresco.transformer.fs.FileManager.createAttachment;
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
import static org.alfresco.transformer.util.Util.stringToBoolean;
import static org.alfresco.transformer.util.Util.stringToInteger;
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.transformer.exceptions.TransformException;
import org.alfresco.transformer.executors.ImageMagickCommandExecutor;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.transformer.probes.ProbeTestTransform;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
@ -56,73 +67,46 @@ import org.springframework.web.multipart.MultipartFile;
@Controller
public class ImageMagickController extends AbstractTransformerController
{
private static final String ROOT = "/usr/lib64/ImageMagick-7.0.7";
private static final String DYN = ROOT+"/lib";
private static final String EXE = "/usr/bin/convert";
private static final Log logger = LogFactory.getLog(ImageMagickController.class);
private static final List<String> GRAVITY_VALUES = Arrays.asList(
"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center");
@Autowired
private ImageMagickCommandExecutor commandExecutor;
@Autowired
public ImageMagickController()
{
logger = LogFactory.getLog(ImageMagickController.class);
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
logEnterpriseLicenseMessage();
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
logger.info("This transformer uses ImageMagick from ImageMagick Studio LLC. See the license at http://www.imagemagick.org/script/license.php or in /ImageMagick-license.txt");
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
setTransformCommand(createTransformCommand());
setCheckCommand(createCheckCommand());
}
@Override
protected String getTransformerName()
public String getTransformerName()
{
return "ImageMagick";
}
private static RuntimeExec createTransformCommand()
@Override
public String version()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*", new String[]{EXE, "${source}", "SPLIT:${options}", "-strip", "-quiet", "${target}"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
Map<String, String> processProperties = new HashMap<>();
processProperties.put("MAGICK_HOME", ROOT);
processProperties.put("DYLD_FALLBACK_LIBRARY_PATH", DYN);
processProperties.put("LD_LIBRARY_PATH", DYN);
runtimeExec.setProcessProperties(processProperties);
Map<String, String> defaultProperties = new HashMap<>();
defaultProperties.put("options", null);
runtimeExec.setDefaultProperties(defaultProperties);
runtimeExec.setErrorCodes("1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799");
return runtimeExec;
}
private static RuntimeExec createCheckCommand()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*", new String[]{EXE, "-version"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
return runtimeExec;
return commandExecutor.version();
}
@Override
protected ProbeTestTransform getProbeTestTransform()
public ProbeTestTransform getProbeTestTransform()
{
// See the Javadoc on this method and Probes.md for the choice of these values.
return new ProbeTestTransform(this, "quick.jpg", "quick.png",
35593, 1024, 150, 1024, 60*15+1,60*15+0)
return new ProbeTestTransform(this, logger, "quick.jpg", "quick.png",
35593, 1024, 150, 1024, 60*15+1,60*15)
{
@Override
protected void executeTransformCommand(File sourceFile, File targetFile)
{
ImageMagickController.this.executeTransformCommand("", sourceFile, "", targetFile, null);
commandExecutor.run("", sourceFile, "", targetFile, null);
}
};
}
@ -168,6 +152,7 @@ public class ImageMagickController extends AbstractTransformerController
{
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(),
targetExtension);
getProbeTestTransform().incrementTransformerCount();
File sourceFile = createSourceFile(request, sourceMultipartFile);
File targetFile = createTargetFile(request, targetFilename);
// Both files are deleted by TransformInterceptor.afterCompletion
@ -176,14 +161,20 @@ public class ImageMagickController extends AbstractTransformerController
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions);
String pageRange = calculatePageRange(startPage, endPage);
executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout);
commandExecutor.run(options, sourceFile, pageRange, targetFile,
timeout);
return createAttachment(targetFilename, targetFile, testDelay);
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
LogEntry.setTargetSize(targetFile.length());
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
time += LogEntry.addDelay(testDelay);
getProbeTestTransform().recordTransformTime(time);
return body;
}
@Override
protected void processTransform(File sourceFile, File targetFile,
Map<String, String> transformOptions, Long timeout)
public void processTransform(final File sourceFile, final File targetFile,
final Map<String, String> transformOptions, final Long timeout)
{
Integer startPage = stringToInteger(transformOptions.get("startPage"));
Integer endPage = stringToInteger(transformOptions.get("endPage"));
@ -201,28 +192,17 @@ public class ImageMagickController extends AbstractTransformerController
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);
final String options = buildTransformOptions(startPage, endPage, alphaRemove, autoOrient,
cropGravity, cropWidth, cropHeight, cropPercentage,
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, null);
final String pageRange = calculatePageRange(startPage, endPage);
executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout);
commandExecutor.run(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,
private static 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,
@ -261,7 +241,7 @@ public class ImageMagickController extends AbstractTransformerController
args.add(cropGravity);
}
StringBuilder crop = new StringBuilder("");
StringBuilder crop = new StringBuilder();
if (cropWidth != null && cropWidth >= 0)
{
crop.append(cropWidth);
@ -303,7 +283,7 @@ public class ImageMagickController extends AbstractTransformerController
if (resizeHeight != null || resizeWidth != null || resizePercentage !=null || maintainAspectRatio != null)
{
args.add(thumbnail != null && thumbnail ? "-thumbnail" : "-resize");
StringBuilder resize = new StringBuilder("");
StringBuilder resize = new StringBuilder();
if (resizeWidth != null && resizeWidth >= 0)
{
resize.append(resizeWidth);
@ -335,10 +315,9 @@ public class ImageMagickController extends AbstractTransformerController
args.toString();
}
private String calculatePageRange(Integer startPage, Integer endPage)
private static String calculatePageRange(Integer startPage, Integer endPage)
{
return
startPage == null
return startPage == null
? endPage == null
? ""
: "["+endPage+']'

View File

@ -0,0 +1,52 @@
package org.alfresco.transformer.executors;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.util.exec.RuntimeExec;
import org.springframework.stereotype.Component;
/**
*/
@Component
public class ImageMagickCommandExecutor extends AbstractCommandExecutor
{
private static final String ROOT = "/usr/lib64/ImageMagick-7.0.7";
private static final String DYN = ROOT + "/lib";
private static final String EXE = "/usr/bin/convert";
@Override
protected RuntimeExec createTransformCommand()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*",
new String[]{EXE, "${source}", "SPLIT:${options}", "-strip", "-quiet", "${target}"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
Map<String, String> processProperties = new HashMap<>();
processProperties.put("MAGICK_HOME", ROOT);
processProperties.put("DYLD_FALLBACK_LIBRARY_PATH", DYN);
processProperties.put("LD_LIBRARY_PATH", DYN);
runtimeExec.setProcessProperties(processProperties);
Map<String, String> defaultProperties = new HashMap<>();
defaultProperties.put("options", null);
runtimeExec.setDefaultProperties(defaultProperties);
runtimeExec.setErrorCodes(
"1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799");
return runtimeExec;
}
@Override
protected RuntimeExec createCheckCommand()
{
RuntimeExec runtimeExec = new RuntimeExec();
Map<String, String[]> commandsAndArguments = new HashMap<>();
commandsAndArguments.put(".*", new String[]{EXE, "-version"});
runtimeExec.setCommandsAndArguments(commandsAndArguments);
return runtimeExec;
}
}

View File

@ -25,21 +25,48 @@
*/
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.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
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.IOException;
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.executors.ImageMagickCommandExecutor;
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.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.StringUtils;
/**
* Test the ImageMagickController without a server.
@ -49,16 +76,104 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@WebMvcTest(ImageMagickController.class)
public class ImageMagickControllerTest extends AbstractTransformerControllerTest
{
@Mock
private RuntimeExec.ExecutionResult mockExecutionResult;
@Mock
private RuntimeExec mockTransformCommand;
@Mock
private RuntimeExec mockCheckCommand;
@SpyBean
private ImageMagickCommandExecutor commandExecutor;
@SpyBean
private ImageMagickController controller;
@Before
public void before() throws IOException
{
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
super.controller = controller;
commandExecutor.setTransformCommand(mockTransformCommand);
commandExecutor.setCheckCommand(mockCheckCommand);
super.mockTransformCommand(controller, "jpg", "png", "image/jpg", true);
mockTransformCommand("jpg", "png", "image/jpg", true);
}
@Override
protected void mockTransformCommand(String sourceExtension,
String targetExtension, String sourceMimetype,
boolean readTargetFileBytes) throws IOException
{
this.sourceExtension = sourceExtension;
this.targetExtension = targetExtension;
this.sourceMimetype = sourceMimetype;
expectedOptions = null;
expectedSourceSuffix = null;
expectedSourceFileBytes = readTestFile(sourceExtension);
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
when(mockTransformCommand.execute(any(), anyLong())).thenAnswer(
(Answer<RuntimeExec.ExecutionResult>) invocation -> {
Map<String, String> actualProperties = invocation.getArgument(0);
assertEquals("There should be 3 properties", 3, actualProperties.size());
String actualOptions = actualProperties.get("options");
String actualSource = actualProperties.get("source");
String actualTarget = actualProperties.get("target");
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
assertNotNull(actualSource);
assertNotNull(actualTarget);
if (expectedSourceSuffix != null)
{
assertTrue("The source file \""+actualSource+"\" should have ended in \""+expectedSourceSuffix+"\"", actualSource.endsWith(expectedSourceSuffix));
actualSource = actualSource.substring(0, actualSource.length()-expectedSourceSuffix.length());
}
assertNotNull(actualOptions);
if (expectedOptions != null)
{
assertEquals("expectedOptions", expectedOptions, actualOptions);
}
Long actualTimeout = invocation.getArgument(1);
assertNotNull(actualTimeout);
if (expectedTimeout != null)
{
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
}
// Copy a test file into the target file location if it exists
int i = actualTarget.lastIndexOf('_');
if (i >= 0)
{
String testFilename = actualTarget.substring(i+1);
File testFile = getTestFile(testFilename, false);
File targetFile = new File(actualTarget);
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
targetFile);
}
// Check the supplied source file has not been changed.
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
return mockExecutionResult;
});
when(mockExecutionResult.getExitValue()).thenReturn(0);
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
}
@Override
protected AbstractTransformerController getController()
{
return controller;
}
@Test
@ -67,7 +182,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
for (String value: new String[] {"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center"})
{
expectedOptions = "-gravity "+value+" +repage";
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
.param("cropGravity", value))
@ -80,7 +195,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
@Test
public void cropGravityBadTest() throws Exception
{
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
.param("cropGravity", "badValue"))
@ -92,7 +207,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
{
expectedOptions = "-alpha remove -gravity SouthEast -crop 123x456%+90+12 +repage -thumbnail 321x654%!";
expectedSourceSuffix = "[2-3]";
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
@ -126,7 +241,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
{
expectedOptions = "-auto-orient -gravity SouthEast -crop 123x456+90+12 +repage -resize 321x654>";
expectedSourceSuffix = "[2-3]";
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
@ -160,7 +275,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
{
// Example of why the commandOptions parameter is a bad idea.
expectedOptions = "( horrible command / ); -resize 321x654>";
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", targetExtension)
.param("thumbnail", "false")
@ -180,4 +295,64 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
transformRequest.setSourceMediaType(MediaType.IMAGE_PNG_VALUE);
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
}
@Test
public void badExitCodeTest() throws Exception
{
when(mockExecutionResult.getExitValue()).thenReturn(1);
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
.andExpect(status().is(400))
.andExpect(status().reason(containsString("Transformer exit code was not 0: \nSTDERR")));
}
@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.setTransformRequestOptions(new HashMap<>());
transformRequest.setSourceReference(sourceFileRef);
transformRequest.setSourceExtension(sourceExtension);
transformRequest.setSourceSize(sourceFile.length());
transformRequest.setTargetExtension(targetExtension);
// 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());
}
}

View File

@ -25,7 +25,6 @@
*/
package org.alfresco.transformer;
import org.alfresco.transformer.AbstractHttpRequestTest;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@ -48,5 +47,5 @@ public class ImageMagickHttpRequestTest extends AbstractHttpRequestTest
protected String getSourceExtension()
{
return "jpg";
};
}
}

View File

@ -12,6 +12,8 @@
package org.alfresco.transformer;
import io.micrometer.core.instrument.MeterRegistry;
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
@ -27,10 +29,17 @@ public class Application
@Value("${container.name}")
private String containerName;
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("containerName", containerName);
}
@Bean
public LibreOfficeJavaExecutor javaExecutor()
{
return new LibreOfficeJavaExecutor();
}
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);

View File

@ -11,19 +11,23 @@
*/
package org.alfresco.transformer;
import static org.alfresco.transformer.fs.FileManager.createAttachment;
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.transformer.probes.ProbeTestTransform;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
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;
@ -33,8 +37,6 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import com.sun.star.task.ErrorCodeIOException;
/**
* Controller for the Docker based LibreOffice transformer.
*
@ -59,86 +61,48 @@ import com.sun.star.task.ErrorCodeIOException;
@Controller
public class LibreOfficeController extends AbstractTransformerController
{
private static final String OFFICE_HOME = "/opt/libreoffice5.4";
private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088;
private JodConverter jodconverter;
private static final Log logger = LogFactory.getLog(LibreOfficeController.class);
@Autowired
public LibreOfficeController() throws Exception
private LibreOfficeJavaExecutor javaExecutor;
@Autowired
public LibreOfficeController()
{
logger = LogFactory.getLog(LibreOfficeController.class);
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
logEnterpriseLicenseMessage();
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
logger.info("This transformer uses LibreOffice from The Document Foundation. See the license at https://www.libreoffice.org/download/license/ or in /libreoffice.txt");
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
}
private static JodConverter createJodConverter(Long taskExecutionTimeout)
{
String timeout = taskExecutionTimeout == null || taskExecutionTimeout <= 0 ? "120000" : taskExecutionTimeout.toString();
JodConverterSharedInstance jodconverter = new JodConverterSharedInstance();
jodconverter.setOfficeHome(OFFICE_HOME); // jodconverter.officeHome
jodconverter.setMaxTasksPerProcess("200"); // jodconverter.maxTasksPerProcess
jodconverter.setTaskExecutionTimeout(timeout); // jodconverter.maxTaskExecutionTimeout
jodconverter.setTaskQueueTimeout("30000"); // jodconverter.taskQueueTimeout
jodconverter.setConnectTimeout("28000"); // jodconverter.connectTimeout
jodconverter.setPortNumbers("8100"); // jodconverter.portNumbers
jodconverter.setTemplateProfileDir(""); // jodconverter.templateProfileDir
jodconverter.setEnabled("true"); // jodconverter.enabled
jodconverter.afterPropertiesSet();
return jodconverter;
}
public void setJodConverter(JodConverter jodconverter)
{
this.jodconverter = jodconverter;
}
/**
* Jodconverter timeouts are per OfficeManager, so we would need multiple OfficeManagers if we
* have different timeouts. Alfresco only has one. So we delay building it until the first request.
* This was not done previously.
*/
private synchronized void setJodConverterOnFirstRequest(Long timeout)
{
if (jodconverter == null)
{
setJodConverter(createJodConverter(timeout));
}
}
@Override
protected String getTransformerName()
public String getTransformerName()
{
return "LibreOffice";
}
@Override
protected String version()
public String version()
{
return "LibreOffice available";
}
@Override
protected ProbeTestTransform getProbeTestTransform()
public ProbeTestTransform getProbeTestTransform()
{
// See the Javadoc on this method and Probes.md for the choice of these values.
return new ProbeTestTransform(this, "quick.doc", "quick.pdf",
return new ProbeTestTransform(this, logger, "quick.doc", "quick.pdf",
11817, 1024, 150, 10240, 60*30+1, 60*15+20)
{
@Override
protected void executeTransformCommand(File sourceFile, File targetFile)
{
LibreOfficeController.this.executeTransformCommand(sourceFile, targetFile, null);
javaExecutor.call(sourceFile, targetFile);
}
};
}
//todo: the "timeout" request parameter is ignored; the timeout is preset at JodConverter creation
@PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Resource> transform(HttpServletRequest request,
@RequestParam("file") MultipartFile sourceMultipartFile,
@ -147,122 +111,25 @@ public class LibreOfficeController extends AbstractTransformerController
@RequestParam(value = "testDelay", required = false) Long testDelay)
{
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
getProbeTestTransform().incrementTransformerCount();
File sourceFile = createSourceFile(request, sourceMultipartFile);
File targetFile = createTargetFile(request, targetFilename);
// Both files are deleted by TransformInterceptor.afterCompletion
executeTransformCommand(sourceFile, targetFile, timeout);
javaExecutor.call(sourceFile, targetFile);
return createAttachment(targetFilename, targetFile, testDelay);
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
LogEntry.setTargetSize(targetFile.length());
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
time += LogEntry.addDelay(testDelay);
getProbeTestTransform().recordTransformTime(time);
return body;
}
@Override
protected void processTransform(File sourceFile, File targetFile,
public 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)
{
timeout = timeout != null && timeout > 0 ? timeout : 0;
try
{
convert(sourceFile, targetFile, timeout);
}
catch (OfficeException e)
{
throw new TransformException(400, "LibreOffice server conversion failed: \n"+
" from file: " + sourceFile + "\n" +
" to file: " + targetFile,
e);
}
catch (Throwable throwable)
{
// Because of the known bug with empty Spreadsheets in JodConverter try to catch exception and produce empty pdf file
if (throwable.getCause() instanceof ErrorCodeIOException &&
((ErrorCodeIOException) throwable.getCause()).ErrCode == JODCONVERTER_TRANSFORMATION_ERROR_CODE)
{
logger.warn("Transformation failed: \n" +
"from file: " + sourceFile + "\n" +
"to file: " + targetFile +
"Source file " + sourceFile + " has no content");
produceEmptyPdfFile(targetFile);
}
else
{
throw throwable;
}
}
if (!targetFile.exists() || targetFile.length() == 0L)
{
throw new TransformException(500, "Transformer failed to create an output file");
}
}
void convert(File sourceFile, File targetFile, long timeout)
{
setJodConverterOnFirstRequest(timeout);
OfficeManager officeManager = jodconverter.getOfficeManager();
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
converter.convert(sourceFile, targetFile);
}
/**
* This method produces an empty PDF file at the specified File location.
* Apache's PDFBox is used to create the PDF file.
*/
private void produceEmptyPdfFile(File targetFile)
{
// If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
// org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
// https://issues.apache.org/jira/browse/PDFBOX-914
PDDocument pdfDoc = null;
PDPageContentStream contentStream = null;
try
{
pdfDoc = new PDDocument();
PDPage pdfPage = new PDPage();
// Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
// that have literally nothing in them. So we'll put a content stream in it.
contentStream = new PDPageContentStream(pdfDoc, pdfPage);
pdfDoc.addPage(pdfPage);
// Now write the in-memory PDF document into the temporary file.
pdfDoc.save(targetFile.getAbsolutePath());
}
catch (IOException iox)
{
throw new TransformException(500, "Error creating empty PDF file", iox);
}
finally
{
if (contentStream != null)
{
try
{
contentStream.close();
}
catch (IOException ignored)
{
// Intentionally empty
}
}
if (pdfDoc != null)
{
try
{
pdfDoc.close();
}
catch (IOException ignored)
{
// Intentionally empty.
}
}
}
javaExecutor.call(sourceFile, targetFile);
}
}

View File

@ -9,11 +9,11 @@
* agreement is prohibited.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.executors;
import org.artofsolving.jodconverter.office.OfficeManager;
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
public interface JodConverter
{
@ -21,11 +21,11 @@ public interface JodConverter
* Gets the JodConverter OfficeManager.
* @return
*/
public abstract OfficeManager getOfficeManager();
OfficeManager getOfficeManager();
/**
* This method returns a boolean indicating whether the JodConverter connection to OOo is available.
* @return <code>true</code> if available, else <code>false</code>
*/
public abstract boolean isAvailable();
boolean isAvailable();
}

View File

@ -23,12 +23,11 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.executors;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
@ -41,7 +40,7 @@ import org.artofsolving.jodconverter.office.OfficeManager;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
/**
* Makes use of the JodConverter library and an installed
@ -51,10 +50,10 @@ import org.springframework.beans.factory.InitializingBean;
*/
public class JodConverterSharedInstance implements InitializingBean, DisposableBean, JodConverter
{
private static Log logger = LogFactory.getLog(JodConverterSharedInstance.class);
private static final Log logger = LogFactory.getLog(JodConverterSharedInstance.class);
private OfficeManager officeManager;
boolean isAvailable = false;
private boolean isAvailable = false;
// JodConverter's built-in configuration settings.
//
@ -82,7 +81,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
private Boolean deprecatedOooEnabled;
private int[] deprecatedOooPortNumbers;
public void setMaxTasksPerProcess(String maxTasksPerProcess)
void setMaxTasksPerProcess(String maxTasksPerProcess)
{
Long l = parseStringForLong(maxTasksPerProcess.trim());
if (l != null)
@ -96,7 +95,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
this.url = url;
}
public void setOfficeHome(String officeHome)
void setOfficeHome(String officeHome)
{
this.officeHome = officeHome == null ? "" : officeHome.trim();
}
@ -106,7 +105,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
this.deprecatedOooExe = deprecatedOooExe == null ? "" : deprecatedOooExe.trim();
}
public void setPortNumbers(String s)
void setPortNumbers(String s)
{
portNumbers = parsePortNumbers(s, "jodconverter");
}
@ -147,12 +146,12 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
return portNumbers;
}
public void setTaskExecutionTimeout(String taskExecutionTimeout)
void setTaskExecutionTimeout(String taskExecutionTimeout)
{
this.taskExecutionTimeout = parseStringForLong(taskExecutionTimeout.trim());
}
public void setTemplateProfileDir(String templateProfileDir)
void setTemplateProfileDir(String templateProfileDir)
{
if (templateProfileDir == null || templateProfileDir.trim().length() == 0)
{
@ -169,26 +168,26 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
}
}
public void setTaskQueueTimeout(String taskQueueTimeout)
void setTaskQueueTimeout(String taskQueueTimeout)
{
this.taskQueueTimeout = parseStringForLong(taskQueueTimeout.trim());
}
public void setConnectTimeout(String connectTimeout)
void setConnectTimeout(String connectTimeout)
{
this.connectTimeout = parseStringForLong(connectTimeout.trim());
}
public void setEnabled(String enabled)
void setEnabled(final String enabledStr)
{
this.enabled = parseEnabled(enabled);
enabled = parseEnabled(enabledStr);
// If this is a request from the Enterprise Admin console to disable the JodConverter.
if (this.enabled == false && (deprecatedOooEnabled == null || deprecatedOooEnabled == false))
if (!enabled && (deprecatedOooEnabled == null || !deprecatedOooEnabled))
{
// We need to change isAvailable to false so we don't make calls to a previously started OfficeManger.
// In the case of Enterprise it is very unlikely that ooo.enabled will have been set to true.
this.isAvailable = false;
isAvailable = false;
}
}
@ -207,7 +206,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
// So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated
// ooo.exe setting rather than the jodconverter.officeHome setting if we don't have the jod setting as
// oooDirect was replaced by jodconverter after this release.
String getOfficeHome()
private String getOfficeHome()
{
String officeHome = this.officeHome;
if ((officeHome == null || officeHome.isEmpty()) && (deprecatedOooExe != null && !deprecatedOooExe.isEmpty()))
@ -243,7 +242,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
// Community set properties via alfresco-global.properties.
// Enterprise may do the same but may also reset jodconverter.enabled them via the Admin console.
// In the case of Enterprise it is very unlikely that ooo.enabled will be set to true.
boolean isEnabled()
private boolean isEnabled()
{
return (deprecatedOooEnabled != null && deprecatedOooEnabled) || (enabled != null && enabled);
}
@ -251,7 +250,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
// So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated
// ooo.port setting rather than the jodconverter.portNumbers if ooo.enabled is true and jodconverter.enabled
// is false.
int[] getPortNumbers()
private int[] getPortNumbers()
{
return (enabled == null || !enabled) && deprecatedOooEnabled != null && deprecatedOooEnabled
? deprecatedOooPortNumbers
@ -260,11 +259,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
private Long parseStringForLong(String string)
{
Long result = null;
try
{
long l = Long.parseLong(string);
result = new Long(l);
return Long.parseLong(string);
}
catch (NumberFormatException nfe)
{
@ -272,9 +269,8 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
{
logger.debug("Cannot parse numerical value from " + string);
}
// else intentionally empty
}
return result;
return null;
}
/*
@ -283,14 +279,14 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
*/
public boolean isAvailable()
{
final boolean result = isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
return result;
return isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet()
{
// isAvailable defaults to false afterPropertiesSet. It only becomes true on successful completion of this method.
@ -318,7 +314,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
}
// Only start the JodConverter instance(s) if the subsystem is enabled.
if (isEnabled() == false)
if (!isEnabled())
{
return;
}
@ -418,7 +414,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
private void logAllSofficeFilesUnderOfficeHome()
{
if (logger.isDebugEnabled() == false)
if (!logger.isDebugEnabled())
{
return;
}
@ -430,7 +426,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
logFileInfo(requestedOfficeHome);
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<File>(), 2))
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<>(), 2))
{
logFileInfo(f);
}
@ -449,26 +445,11 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
return results;
}
File[] matchingFiles = searchRoot.listFiles(new FilenameFilter()
{
@Override
public boolean accept(File dir, String name)
{
return name.startsWith("soffice");
}
});
for (File f : matchingFiles)
{
results.add(f);
}
File[] matchingFiles = searchRoot.listFiles((dir, name) -> name.startsWith("soffice"));
Arrays.stream(matchingFiles)
.forEach(results::add);
for (File dir : searchRoot.listFiles(new FileFilter()
{
@Override
public boolean accept(File f) {
return f.isDirectory();
}
}))
for (File dir : searchRoot.listFiles(File::isDirectory))
{
findSofficePrograms(dir, results, currentRecursionDepth + 1, maxRecursionDepth);
}
@ -482,7 +463,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
*/
private void logFileInfo(File f)
{
if (logger.isDebugEnabled() == false)
if (!logger.isDebugEnabled())
{
return;
}
@ -512,7 +493,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
* (non-Javadoc)
* @see org.springframework.beans.factory.DisposableBean#destroy()
*/
public void destroy() throws Exception {
@Override
public void destroy()
{
this.isAvailable = false;
if (officeManager != null)
{
@ -530,6 +513,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
/* (non-Javadoc)
* @see org.alfresco.repo.content.JodConverterWorker#getOfficeManager()
*/
@Override
public OfficeManager getOfficeManager()
{
return officeManager;

View File

@ -0,0 +1,118 @@
package org.alfresco.transformer.executors;
import java.io.File;
import java.io.IOException;
import org.alfresco.transformer.exceptions.TransformException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.office.OfficeException;
import org.artofsolving.jodconverter.office.OfficeManager;
import org.springframework.stereotype.Component;
import com.sun.star.task.ErrorCodeIOException;
@Component
public class LibreOfficeJavaExecutor implements JavaExecutor
{
private static final Log logger = LogFactory.getLog(LibreOfficeJavaExecutor.class);
private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088;
private static final String OFFICE_HOME = "/opt/libreoffice5.4";
private JodConverter jodconverter = createJodConverter();
private static JodConverter createJodConverter()
{
final String timeout = "120000";
JodConverterSharedInstance jodconverter = new JodConverterSharedInstance();
jodconverter.setOfficeHome(OFFICE_HOME); // jodconverter.officeHome
jodconverter.setMaxTasksPerProcess("200"); // jodconverter.maxTasksPerProcess
jodconverter.setTaskExecutionTimeout(timeout); // jodconverter.maxTaskExecutionTimeout
jodconverter.setTaskQueueTimeout("30000"); // jodconverter.taskQueueTimeout
jodconverter.setConnectTimeout("28000"); // jodconverter.connectTimeout
jodconverter.setPortNumbers("8100"); // jodconverter.portNumbers
jodconverter.setTemplateProfileDir(""); // jodconverter.templateProfileDir
jodconverter.setEnabled("true"); // jodconverter.enabled
jodconverter.afterPropertiesSet();
return jodconverter;
}
@Override
public void call(File sourceFile, File targetFile, String... args)
{
try
{
convert(sourceFile, targetFile);
}
catch (OfficeException e)
{
throw new TransformException(400, "LibreOffice server conversion failed: \n" +
" from file: " + sourceFile + "\n" +
" to file: " + targetFile, e);
}
catch (Throwable throwable)
{
// Because of the known bug with empty Spreadsheets in JodConverter try to catch exception and produce empty pdf file
if (throwable.getCause() instanceof ErrorCodeIOException &&
((ErrorCodeIOException) throwable.getCause()).ErrCode == JODCONVERTER_TRANSFORMATION_ERROR_CODE)
{
logger.warn("Transformation failed: \n" +
"from file: " + sourceFile + "\n" +
"to file: " + targetFile +
"Source file " + sourceFile + " has no content");
produceEmptyPdfFile(targetFile);
}
else
{
throw throwable;
}
}
if (!targetFile.exists() || targetFile.length() == 0L)
{
throw new TransformException(500, "Transformer failed to create an output file");
}
}
public void convert(File sourceFile, File targetFile)
{
OfficeManager officeManager = jodconverter.getOfficeManager();
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
converter.convert(sourceFile, targetFile);
}
/**
* This method produces an empty PDF file at the specified File location.
* Apache's PDFBox is used to create the PDF file.
*/
private static void produceEmptyPdfFile(File targetFile)
{
// If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
// org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
// https://issues.apache.org/jira/browse/PDFBOX-914
PDPage pdfPage = new PDPage();
try (PDDocument pdfDoc = new PDDocument();
PDPageContentStream contentStream = new PDPageContentStream(pdfDoc, pdfPage))
{
// Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
// that have literally nothing in them. So we'll put a content stream in it.
pdfDoc.addPage(pdfPage);
// Now write the in-memory PDF document into the temporary file.
pdfDoc.save(targetFile.getAbsolutePath());
}
catch (IOException iox)
{
throw new TransformException(500, "Error creating empty PDF file", iox);
}
}
}

View File

@ -29,26 +29,38 @@ 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
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 java.util.HashMap;
import java.util.UUID;
import org.alfresco.transform.client.model.TransformReply;
import org.alfresco.transform.client.model.TransformRequest;
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
import org.alfresco.transformer.model.FileRefEntity;
import org.alfresco.transformer.model.FileRefResponse;
import org.alfresco.util.exec.RuntimeExec;
import org.artofsolving.jodconverter.office.OfficeException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.stubbing.Answer;
import org.mockito.Mock;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@ -62,15 +74,18 @@ import org.springframework.util.StringUtils;
@WebMvcTest(LibreOfficeControllerTest.class)
public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
{
@Mock
private RuntimeExec.ExecutionResult mockExecutionResult;
@SpyBean
private LibreOfficeJavaExecutor javaExecutor;
@SpyBean
private LibreOfficeController controller;
@Before
public void before() throws IOException
{
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
super.controller = controller;
sourceExtension = "doc";
targetExtension = "pdf";
sourceMimetype = "application/msword";
@ -82,7 +97,7 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick." + targetExtension, true).toPath());
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
doAnswer((Answer) invocation ->
doAnswer(invocation ->
{
File sourceFile = invocation.getArgument(0);
File targetFile = invocation.getArgument(1);
@ -91,13 +106,6 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
assertNotNull(sourceFile);
assertNotNull(targetFile);
Long actualTimeout = invocation.getArgument(2);
assertNotNull(actualTimeout);
if (expectedTimeout != null)
{
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
}
// Copy a test file into the target file location if it exists
String actualTarget = targetFile.getAbsolutePath();
int i = actualTarget.lastIndexOf('_');
@ -113,16 +121,28 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
return null;
}).when(controller).convert(any(), any(), anyLong());
}).when(javaExecutor).convert(any(), any());
}
@Override
protected void mockTransformCommand(String sourceExtension, String targetExtension,
String sourceMimetype, boolean readTargetFileBytes)
{
throw new IllegalStateException();
}
@Override
protected AbstractTransformerController getController()
{
return controller;
}
@Test
@Override
public void badExitCodeTest() throws Exception
{
doThrow(OfficeException.class).when(controller).convert(any(), any(), anyLong());
doThrow(OfficeException.class).when(javaExecutor).convert(any(), any());
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
.file(sourceFile)
.param("targetExtension", "xxx"))
.andExpect(status().is(400))
@ -137,4 +157,58 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
transformRequest.setSourceMediaType("application/msword");
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
}
@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.setTransformRequestOptions(new HashMap<>());
transformRequest.setSourceReference(sourceFileRef);
transformRequest.setSourceExtension(sourceExtension);
transformRequest.setSourceSize(sourceFile.length());
transformRequest.setTargetExtension(targetExtension);
// 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());
}
}

View File

@ -25,7 +25,6 @@
*/
package org.alfresco.transformer;
import org.alfresco.transformer.AbstractHttpRequestTest;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@ -47,5 +46,5 @@ public class LibreOfficeHttpRequestTest extends AbstractHttpRequestTest
protected String getSourceExtension()
{
return "doc";
};
}
}

View File

@ -11,15 +11,17 @@
*/
package org.alfresco.transformer;
import io.micrometer.core.instrument.MeterRegistry;
import org.alfresco.transformer.executors.TikaJavaExecutor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import io.micrometer.core.instrument.MeterRegistry;
@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class Application
@ -27,10 +29,18 @@ public class Application
@Value("${container.name}")
private String containerName;
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags()
{
return registry -> registry.config().commonTags("containerName", containerName);
}
@Bean
public TikaJavaExecutor javaExecutor() throws Exception
{
return new TikaJavaExecutor();
}
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);

View File

@ -12,21 +12,31 @@
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 static org.alfresco.transformer.executors.Tika.INCLUDE_CONTENTS;
import static org.alfresco.transformer.executors.Tika.NOT_EXTRACT_BOOKMARKS_TEXT;
import static org.alfresco.transformer.executors.Tika.PDF_BOX;
import static org.alfresco.transformer.executors.Tika.TARGET_ENCODING;
import static org.alfresco.transformer.executors.Tika.TARGET_MIMETYPE;
import static org.alfresco.transformer.executors.Tika.TRANSFORM_NAMES;
import static org.alfresco.transformer.fs.FileManager.createAttachment;
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
import static org.alfresco.transformer.util.Util.stringToBoolean;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.executors.TikaJavaExecutor;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.transformer.probes.ProbeTestTransform;
import org.apache.commons.logging.Log;
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;
@ -35,7 +45,6 @@ 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 org.xml.sax.SAXException;
/**
* Controller for the Docker based Tika transformers.
@ -61,50 +70,44 @@ import org.xml.sax.SAXException;
@Controller
public class TikaController extends AbstractTransformerController
{
private Tika tika;
private static final Log logger = LogFactory.getLog(TikaController.class);
@Autowired
public TikaController() throws TikaException, IOException, SAXException
private TikaJavaExecutor javaExecutor;
@Autowired
public TikaController()
{
logger = LogFactory.getLog(TikaController.class);
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
logEnterpriseLicenseMessage();
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
logger.info("Tika is from Apache. See the license at http://www.apache.org/licenses/LICENSE-2.0. or in /Apache\\ 2.0.txt");
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
tika = new Tika();
}
@Override
protected String getTransformerName()
public String getTransformerName()
{
return "Tika";
}
@Override
public void callTransform(String... args)
{
tika.transform(args);
}
@Override
protected String version()
public String version()
{
return "Tika available";
}
@Override
protected ProbeTestTransform getProbeTestTransform()
public ProbeTestTransform getProbeTestTransform()
{
// See the Javadoc on this method and Probes.md for the choice of these values.
// the livenessPercentage is a little large as Tika does tend to suffer from slow transforms that class with a gc.
return new ProbeTestTransform(this, "quick.pdf", "quick.txt",
return new ProbeTestTransform(this, logger, "quick.pdf", "quick.txt",
60, 16, 400, 10240, 60 * 30 + 1, 60 * 15 + 20)
{
@Override
protected void executeTransformCommand(File sourceFile, File targetFile)
{
TikaController.this.callTransform(sourceFile, targetFile, PDF_BOX,
javaExecutor.call(sourceFile, targetFile, PDF_BOX,
TARGET_MIMETYPE + MIMETYPE_TEXT_PLAIN, TARGET_ENCODING + "UTF-8");
}
};
@ -123,7 +126,6 @@ public class TikaController extends AbstractTransformerController
@RequestParam(value = "transform") String transform,
@RequestParam(value = "includeContents", required = false) Boolean includeContents,
@RequestParam(value = "notExtractBookmarksText", required = false) Boolean notExtractBookmarksText)
{
if (!TRANSFORM_NAMES.contains(transform))
{
@ -131,6 +133,7 @@ public class TikaController extends AbstractTransformerController
}
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
getProbeTestTransform().incrementTransformerCount();
File sourceFile = createSourceFile(request, sourceMultipartFile);
File targetFile = createTargetFile(request, targetFilename);
// Both files are deleted by TransformInterceptor.afterCompletion
@ -138,16 +141,21 @@ public class TikaController extends AbstractTransformerController
// TODO Consider streaming the request and response rather than using temporary files
// https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/streaming-response-body.html
callTransform(sourceFile, targetFile, transform,
javaExecutor.call(sourceFile, targetFile, transform,
includeContents != null && includeContents ? INCLUDE_CONTENTS : null,
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT : null,
TARGET_MIMETYPE + targetMimetype, TARGET_ENCODING + targetEncoding);
return createAttachment(targetFilename, targetFile, testDelay);
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
LogEntry.setTargetSize(targetFile.length());
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
time += LogEntry.addDelay(testDelay);
getProbeTestTransform().recordTransformTime(time);
return body;
}
@Override
protected void processTransform(File sourceFile, File targetFile,
public void processTransform(File sourceFile, File targetFile,
Map<String, String> transformOptions, Long timeout)
{
@ -157,7 +165,7 @@ public class TikaController extends AbstractTransformerController
String targetMimetype = transformOptions.get("targetMimetype");
String targetEncoding = transformOptions.get("targetEncoding");
callTransform(sourceFile, targetFile, transform,
javaExecutor.call(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

@ -9,7 +9,36 @@
* agreement is prohibited.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.executors;
import static java.util.Arrays.asList;
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_HTML;
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_IMAGE_JPEG;
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_IMAGE_PNG;
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_IMAGE_TIFF;
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_XHTML;
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XML;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.util.List;
import java.util.regex.Pattern;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.exception.TikaException;
@ -30,19 +59,6 @@ import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import static org.alfresco.repo.content.MimetypeMap.*;
/**
* Stripped down command line Tika transformers. Not actually run as a separate process, but the code fits the patten
* used by transformers that do.
@ -424,7 +440,7 @@ public class Tika
public static final String TIKA_AUTO = "TikaAuto";
public static final String TEXT_MINING = "TextMining";
public static final List<String> TRANSFORM_NAMES = Arrays.asList(
public static final List<String> TRANSFORM_NAMES = asList(
ARCHIVE, OUTLOOK_MSG, PDF_BOX, POI_OFFICE, POI, POI_OO_XML, TIKA_AUTO, TEXT_MINING);
public static final String TARGET_MIMETYPE = "--targetMimetype=";
@ -445,17 +461,17 @@ public class Tika
public static final String XML = "xml";
public static final String ZIP = "zip";
private Parser packageParser = new PackageParser();
private Parser pdfParser = new PDFParser();
private Parser officeParser = new OfficeParser();
private Parser autoDetectParser;
private Parser ooXmlParser = new OOXMLParser();
private Parser tikaOfficeDetectParser = new TikaOfficeDetectParser();
private PDFParserConfig pdfParserConfig = new PDFParserConfig();
private final Parser packageParser = new PackageParser();
private final Parser pdfParser = new PDFParser();
private final Parser officeParser = new OfficeParser();
private final Parser autoDetectParser;
private final Parser ooXmlParser = new OOXMLParser();
private final Parser tikaOfficeDetectParser = new TikaOfficeDetectParser();
private final PDFParserConfig pdfParserConfig = new PDFParserConfig();
private DocumentSelector pdfBoxEmbededDocumentSelector = new DocumentSelector()
{
private List<String> disabledMediaTypes = Arrays.asList(new String[] {MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_TIFF, MIMETYPE_IMAGE_PNG});
private final List<String> disabledMediaTypes = asList(MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_TIFF, MIMETYPE_IMAGE_PNG);
@Override
public boolean select(Metadata metadata)
@ -628,17 +644,14 @@ public class Tika
String sourceFilename,
String targetFilename, String targetMimetype, String targetEncoding)
{
InputStream is = null;
OutputStream os = null;
Writer ow = null;
try
try (InputStream is = new BufferedInputStream(new FileInputStream(sourceFilename));
OutputStream os = new FileOutputStream(targetFilename);
Writer ow = new BufferedWriter(new OutputStreamWriter(os, targetEncoding)))
{
is = new BufferedInputStream(new FileInputStream(sourceFilename));
os = new FileOutputStream(targetFilename);
ow = new BufferedWriter(new OutputStreamWriter(os, targetEncoding));
Metadata metadata = new Metadata();
ParseContext context = buildParseContext(documentSelector, includeContents, notExtractBookmarksText);
ParseContext context = buildParseContext(documentSelector, includeContents,
notExtractBookmarksText);
ContentHandler handler = getContentHandler(targetMimetype, ow);
parser.parse(is, handler, metadata, context);
@ -647,24 +660,9 @@ public class Tika
{
throw new IllegalStateException(e.getMessage(), e);
}
finally
{
if (is != null)
{
try { is.close(); } catch (Throwable e) {}
}
if (os != null)
{
try { os.close(); } catch (Throwable e) {}
}
if (ow != null)
{
try { ow.close(); } catch (Throwable e) {}
}
}
}
protected ContentHandler getContentHandler(String targetMimetype, Writer output)
private ContentHandler getContentHandler(String targetMimetype, Writer output)
{
try
{
@ -676,7 +674,7 @@ public class Tika
else
{
SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
TransformerHandler transformerHandler = null;
TransformerHandler transformerHandler;
transformerHandler = factory.newTransformerHandler();
transformerHandler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
transformerHandler.setResult(new StreamResult(output));
@ -792,7 +790,8 @@ public class Tika
}
}
protected ParseContext buildParseContext(DocumentSelector documentSelector, Boolean includeContents, Boolean notExtractBookmarksText)
private ParseContext buildParseContext(DocumentSelector documentSelector,
Boolean includeContents, Boolean notExtractBookmarksText)
{
ParseContext context = new ParseContext();

View File

@ -0,0 +1,92 @@
package org.alfresco.transformer.executors;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringJoiner;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.logging.LogEntry;
import org.apache.tika.exception.TikaException;
import org.springframework.beans.factory.annotation.Autowired;
import org.xml.sax.SAXException;
@Component
public class TikaJavaExecutor implements JavaExecutor
{
private final Tika tika;
@Autowired
public TikaJavaExecutor() throws TikaException, IOException, SAXException
{
tika = new Tika();
}
@Override
public void call(File sourceFile, File targetFile, String... args)
throws TransformException
{
args = buildArgs(sourceFile, targetFile, args);
try
{
tika.transform(args);
}
catch (IllegalArgumentException e)
{
throw new TransformException(400, getMessage(e));
}
catch (Exception e)
{
throw new TransformException(500, getMessage(e));
}
if (!targetFile.exists() || targetFile.length() == 0)
{
throw new TransformException(500, "Transformer failed to create an output file");
}
}
private static String getMessage(Exception e)
{
return e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage();
}
private static String[] buildArgs(File sourceFile, File targetFile, String[] args)
{
ArrayList<String> methodArgs = new ArrayList<>(args.length + 2);
StringJoiner sj = new StringJoiner(" ");
for (String arg : args)
{
addArg(methodArgs, sj, arg);
}
addFileArg(methodArgs, sj, sourceFile);
addFileArg(methodArgs, sj, targetFile);
LogEntry.setOptions(sj.toString());
return methodArgs.toArray(new String[0]);
}
private static void addArg(ArrayList<String> methodArgs, StringJoiner sj, String arg)
{
if (arg != null)
{
sj.add(arg);
methodArgs.add(arg);
}
}
private static void addFileArg(ArrayList<String> methodArgs, StringJoiner sj, File arg)
{
if (arg != null)
{
String path = arg.getAbsolutePath();
int i = path.lastIndexOf('.');
String ext = i == -1 ? "???" : path.substring(i + 1);
sj.add(ext);
methodArgs.add(path);
}
}
}

View File

@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.executors;
import java.io.IOException;
import java.io.InputStream;
@ -43,7 +43,7 @@ import org.apache.tika.parser.microsoft.ooxml.OOXMLParser;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
/**
* <a href="http://tika.apache.org/Apache Tika">Apache Tika</a> assumes that
@ -58,11 +58,11 @@ import org.xml.sax.SAXException;
* @author Nick Burch
*/
public class TikaOfficeDetectParser implements Parser {
private Parser ole2Parser = new OfficeParser();
private Parser ooxmlParser = new OOXMLParser();
private final Parser ole2Parser = new OfficeParser();
private final Parser ooxmlParser = new OOXMLParser();
public Set<MediaType> getSupportedTypes(ParseContext parseContext) {
Set<MediaType> types = new HashSet<MediaType>();
Set<MediaType> types = new HashSet<>();
types.addAll(ole2Parser.getSupportedTypes(parseContext));
types.addAll(ooxmlParser.getSupportedTypes(parseContext));
return types;

View File

@ -37,41 +37,68 @@ 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.alfresco.transformer.executors.Tika.ARCHIVE;
import static org.alfresco.transformer.executors.Tika.CSV;
import static org.alfresco.transformer.executors.Tika.DOC;
import static org.alfresco.transformer.executors.Tika.DOCX;
import static org.alfresco.transformer.executors.Tika.HTML;
import static org.alfresco.transformer.executors.Tika.MSG;
import static org.alfresco.transformer.executors.Tika.OUTLOOK_MSG;
import static org.alfresco.transformer.executors.Tika.PDF;
import static org.alfresco.transformer.executors.Tika.PDF_BOX;
import static org.alfresco.transformer.executors.Tika.POI;
import static org.alfresco.transformer.executors.Tika.POI_OFFICE;
import static org.alfresco.transformer.executors.Tika.POI_OO_XML;
import static org.alfresco.transformer.executors.Tika.PPTX;
import static org.alfresco.transformer.executors.Tika.TEXT_MINING;
import static org.alfresco.transformer.executors.Tika.TIKA_AUTO;
import static org.alfresco.transformer.executors.Tika.TXT;
import static org.alfresco.transformer.executors.Tika.XHTML;
import static org.alfresco.transformer.executors.Tika.XML;
import static org.alfresco.transformer.executors.Tika.XSLX;
import static org.alfresco.transformer.executors.Tika.ZIP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
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.IOException;
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.executors.TikaJavaExecutor;
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.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.StringUtils;
/**
* Test the TikaController without a server.
@ -81,37 +108,126 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
@WebMvcTest(TikaController.class)
public class TikaControllerTest extends AbstractTransformerControllerTest
{
public static final String EXPECTED_XHTML_CONTENT_CONTAINS = "<p>The quick brown fox jumps over the lazy dog</p>";
public static final String EXPECTED_TEXT_CONTENT_CONTAINS = "The quick brown fox jumps over the lazy dog";
public static final String EXPECTED_MSG_CONTENT_CONTAINS = "Recipients\n" +
private static final String EXPECTED_XHTML_CONTENT_CONTAINS = "<p>The quick brown fox jumps over the lazy dog</p>";
private static final String EXPECTED_TEXT_CONTENT_CONTAINS = "The quick brown fox jumps over the lazy dog";
private static final String EXPECTED_MSG_CONTENT_CONTAINS = "Recipients\n" +
"\tmark.rogers@alfresco.com; speedy@quick.com; mrquick@nowhere.com\n" +
"\n" +
"The quick brown fox jumps over the lazy dogs";
public static final String EXPECTED_CSV_CONTENT_CONTAINS = "\"The\",\"quick\",\"brown\",\"fox\"";
private static final String EXPECTED_CSV_CONTENT_CONTAINS = "\"The\",\"quick\",\"brown\",\"fox\"";
@Mock
private RuntimeExec.ExecutionResult mockExecutionResult;
@Mock
private RuntimeExec mockTransformCommand;
@Mock
private RuntimeExec mockCheckCommand;
@SpyBean
private TikaJavaExecutor javaExecutor;
@SpyBean
private TikaController controller;
String transform = PDF_BOX;
String targetEncoding = "UTF-8";
String targetMimetype = MIMETYPE_TEXT_PLAIN;
private String transform = PDF_BOX;
private String targetEncoding = "UTF-8";
private String targetMimetype = MIMETYPE_TEXT_PLAIN;
@Before
public void before() throws Exception
public void before()
{
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
super.controller = controller;
sourceExtension = "pdf";
targetExtension = "txt";
}
@Override
protected void mockTransformCommand(String sourceExtension,
String targetExtension, String sourceMimetype,
boolean readTargetFileBytes) throws IOException
{
this.sourceExtension = sourceExtension;
this.targetExtension = targetExtension;
this.sourceMimetype = sourceMimetype;
expectedOptions = null;
expectedSourceSuffix = null;
expectedSourceFileBytes = readTestFile(sourceExtension);
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype,
expectedSourceFileBytes);
when(mockTransformCommand.execute(any(), anyLong())).thenAnswer(
(Answer<RuntimeExec.ExecutionResult>) invocation -> {
Map<String, String> actualProperties = invocation.getArgument(0);
assertEquals("There should be 3 properties", 3, actualProperties.size());
String actualOptions = actualProperties.get("options");
String actualSource = actualProperties.get("source");
String actualTarget = actualProperties.get("target");
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
assertNotNull(actualSource);
assertNotNull(actualTarget);
if (expectedSourceSuffix != null)
{
assertTrue(
"The source file \"" + actualSource + "\" should have ended in \"" + expectedSourceSuffix + "\"",
actualSource.endsWith(expectedSourceSuffix));
actualSource = actualSource.substring(0,
actualSource.length() - expectedSourceSuffix.length());
}
assertNotNull(actualOptions);
if (expectedOptions != null)
{
assertEquals("expectedOptions", expectedOptions, actualOptions);
}
Long actualTimeout = invocation.getArgument(1);
assertNotNull(actualTimeout);
if (expectedTimeout != null)
{
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
}
// Copy a test file into the target file location if it exists
int i = actualTarget.lastIndexOf('_');
if (i >= 0)
{
String testFilename = actualTarget.substring(i + 1);
File testFile = getTestFile(testFilename, false);
File targetFile = new File(actualTarget);
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
targetFile);
}
// Check the supplied source file has not been changed.
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
assertTrue("Source file is not the same",
Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
return mockExecutionResult;
});
when(mockExecutionResult.getExitValue()).thenReturn(0);
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
}
@Override
protected AbstractTransformerController getController()
{
return controller;
}
private void transform(String transform, String sourceExtension, String targetExtension,
String sourceMimetype, String targetMimetype,
Boolean includeContents, String expectedContentContains) throws Exception
{
// We don't use targetFileBytes as some of the transforms contain different date text based on the os being used.
super.mockTransformCommand(controller, sourceExtension, targetExtension, sourceMimetype, false);
mockTransformCommand(sourceExtension, targetExtension, sourceMimetype, false);
this.transform = transform;
this.targetMimetype = targetMimetype;
@ -141,7 +257,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void simpleTransformTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.simpleTransformTest();
}
@ -149,21 +265,13 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void testDelayTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.testDelayTest();
}
@Test
@Override
public void badExitCodeTest() throws Exception
{
// Ignore the test in super class as the Tika transforms are real rather than mocked up.
// It is the mock that returns a non zero exit code.
}
@Test
@Override
public void noTargetFileTest() throws Exception
public void noTargetFileTest()
{
// Ignore the test in super class as the Tika transforms are real rather than mocked up.
// It is the mock that returns a zero length file for other transformers, when we supply an invalid targetExtension.
@ -175,7 +283,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void dotDotSourceFilenameTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.dotDotSourceFilenameTest();
}
@ -183,7 +291,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void noExtensionSourceFilenameTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.noExtensionSourceFilenameTest();
}
@ -191,7 +299,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void badSourceFilenameTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.badSourceFilenameTest();
}
@ -199,7 +307,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void blankSourceFilenameTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.blankSourceFilenameTest();
}
@ -207,7 +315,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void noTargetExtensionTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.noTargetExtensionTest();
}
@ -215,7 +323,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Override
public void calculateMaxTime() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
super.calculateMaxTime();
}
@ -224,7 +332,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Test
public void badEncodingTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
targetEncoding = "rubbish";
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
.andExpect(status().is(500));
@ -388,7 +496,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
@Test
public void pdfToTxtExtractBookmarksTest() throws Exception
{
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension).param("notExtractBookmarksText", "true"))
.andExpect(status().is(200))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
@ -405,4 +513,54 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
transformRequest.getTransformRequestOptions().put("targetMimetype", MediaType.TEXT_PLAIN_VALUE);
transformRequest.getTransformRequestOptions().put("targetEncoding", "UTF-8");
}
@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.setTransformRequestOptions(new HashMap<>());
transformRequest.setSourceReference(sourceFileRef);
transformRequest.setSourceExtension(sourceExtension);
transformRequest.setSourceSize(sourceFile.length());
transformRequest.setTargetExtension(targetExtension);
// 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());
}
}

View File

@ -94,7 +94,7 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
String options = args.toString();
LogEntry.setOptions(options);
Map<String, String> properties = new HashMap<String, String>(5);
Map<String, String> properties = new HashMap<>();
properties.put("options", options);
properties.put("source", sourceFile.getAbsolutePath());
properties.put("target", targetFile.getAbsolutePath());

View File

@ -25,55 +25,39 @@
*/
package org.alfresco.transformer;
import static org.alfresco.transformer.fs.FileManager.buildFile;
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
import static org.alfresco.transformer.fs.FileManager.getFilenameFromContentDisposition;
import static org.alfresco.transformer.fs.FileManager.save;
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 java.util.stream.Collectors;
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.transform.client.model.TransformRequestValidator;
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.logging.LogEntry;
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.apache.commons.logging.LogFactory;
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.validation.DirectFieldBindingResult;
import org.springframework.validation.Errors;
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;
/**
* <p>Abstract Controller, provides structure and helper methods to sub-class transformer controllers.</p>
@ -103,11 +87,9 @@ import org.springframework.web.util.UriUtils;
* <p>Provides methods to help super classes perform /transform requests. Also responses to /version, /ready and /live
* requests.</p>
*/
public abstract class AbstractTransformerController
public abstract class AbstractTransformerController implements TransformController
{
public static final String SOURCE_FILE = "sourceFile";
public static final String TARGET_FILE = "targetFile";
public static final String FILENAME = "filename=";
private static final Log logger = LogFactory.getLog(AbstractTransformerController.class);
@Autowired
private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
@ -115,123 +97,90 @@ public abstract class AbstractTransformerController
@Autowired
private TransformRequestValidator transformRequestValidator;
protected static Log logger;
protected RuntimeExec transformCommand;
private RuntimeExec checkCommand;
private ProbeTestTransform probeTestTransform = null;
public void setTransformCommand(RuntimeExec runtimeExec)
{
transformCommand = runtimeExec;
}
public void setCheckCommand(RuntimeExec runtimeExec)
{
checkCommand = runtimeExec;
}
protected void logEnterpriseLicenseMessage()
{
logger.info("This image is only intended to be used with the Alfresco Enterprise Content Repository which is covered by ");
logger.info("https://www.alfresco.com/legal/agreements and https://www.alfresco.com/terms-use");
logger.info("");
logger.info("License rights for this program may be obtained from Alfresco Software, Ltd. pursuant to a written agreement");
logger.info("and any use of this program without such an agreement is prohibited.");
logger.info("");
}
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 request 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,
public ResponseEntity<TransformReply> transform(@RequestBody TransformRequest request,
@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());
final TransformReply reply = new TransformReply();
reply.setRequestId(request.getRequestId());
reply.setSourceReference(request.getSourceReference());
reply.setSchema(request.getSchema());
reply.setClientData(request.getClientData());
Errors errors = validateTransformRequest(transformRequest);
final Errors errors = validateTransformRequest(request);
if (!errors.getAllErrors().isEmpty())
{
transformReply.setStatus(HttpStatus.BAD_REQUEST.value());
transformReply.setErrorDetails(errors.getAllErrors().stream().map(Object::toString)
reply.setStatus(HttpStatus.BAD_REQUEST.value());
reply.setErrorDetails(errors.getAllErrors().stream().map(Object::toString)
.collect(Collectors.joining(", ")));
return new ResponseEntity<>(transformReply,
HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply,
HttpStatus.valueOf(reply.getStatus()));
}
// Load the source file
File sourceFile;
try
{
sourceFile = loadSourceFile(transformRequest.getSourceReference());
sourceFile = loadSourceFile(request.getSourceReference());
}
catch (TransformException te)
{
transformReply.setStatus(te.getStatusCode());
transformReply
.setErrorDetails("Failed at reading the source file. " + te.getMessage());
reply.setStatus(te.getStatusCode());
reply .setErrorDetails("Failed at reading the source file. " + te.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
catch (HttpClientErrorException hcee)
{
transformReply.setStatus(hcee.getStatusCode().value());
transformReply
.setErrorDetails("Failed at reading the source file. " + hcee.getMessage());
reply.setStatus(hcee.getStatusCode().value());
reply .setErrorDetails("Failed at reading the source file. " + hcee.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
catch (Exception e)
{
transformReply.setStatus(500);
transformReply.setErrorDetails("Failed at reading the source file. " + e.getMessage());
reply.setStatus(500);
reply.setErrorDetails("Failed at reading the source file. " + e.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
// Create local temp target file in order to run the transformation
String targetFilename = createTargetFileName(sourceFile.getName(),
transformRequest.getTargetExtension());
request.getTargetExtension());
File targetFile = buildFile(targetFilename);
// Run the transformation
try
{
processTransform(sourceFile, targetFile,
transformRequest.getTransformRequestOptions(), timeout);
request.getTransformRequestOptions(), timeout);
}
catch (TransformException te)
{
transformReply.setStatus(te.getStatusCode());
transformReply
.setErrorDetails("Failed at processing transformation. " + te.getMessage());
reply.setStatus(te.getStatusCode());
reply.setErrorDetails("Failed at processing transformation. " + te.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
catch (Exception e)
{
transformReply.setStatus(500);
transformReply
.setErrorDetails("Failed at processing transformation. " + e.getMessage());
reply.setStatus(500);
reply.setErrorDetails("Failed at processing transformation. " + e.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
// Write the target file
@ -242,211 +191,50 @@ public abstract class AbstractTransformerController
}
catch (TransformException te)
{
transformReply.setStatus(te.getStatusCode());
transformReply
.setErrorDetails("Failed at writing the transformed file. " + te.getMessage());
reply.setStatus(te.getStatusCode());
reply.setErrorDetails("Failed at writing the transformed file. " + te.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
catch (HttpClientErrorException hcee)
{
transformReply.setStatus(hcee.getStatusCode().value());
transformReply
.setErrorDetails("Failed at writing the transformed file. " + hcee.getMessage());
reply.setStatus(hcee.getStatusCode().value());
reply.setErrorDetails("Failed at writing the transformed file. " + hcee.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
catch (Exception e)
{
transformReply.setStatus(500);
transformReply
.setErrorDetails("Failed at writing the transformed file. " + e.getMessage());
reply.setStatus(500);
reply.setErrorDetails("Failed at writing the transformed file. " + e.getMessage());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
transformReply.setTargetReference(targetRef.getEntry().getFileRef());
transformReply.setStatus(HttpStatus.CREATED.value());
reply.setTargetReference(targetRef.getEntry().getFileRef());
reply.setStatus(HttpStatus.CREATED.value());
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
}
private Errors validateTransformRequest(TransformRequest transformRequest)
private Errors validateTransformRequest(final TransformRequest transformRequest)
{
DirectFieldBindingResult errors = new DirectFieldBindingResult(transformRequest, "request");
transformRequestValidator.validate(transformRequest, errors);
return errors;
}
protected abstract void processTransform(File sourceFile, File targetFile,
Map<String, String> transformOptions, Long timeout);
@RequestMapping("/version")
@ResponseBody
protected String version()
{
String version = "Version not checked";
if (checkCommand != null)
{
RuntimeExec.ExecutionResult result = checkCommand.execute();
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
{
throw new TransformException(500, "Transformer version check exit code was not 0: \n" + result);
}
version = result.getStdOut().trim();
if (version.isEmpty())
{
throw new TransformException(500, "Transformer version check failed to create any output");
}
}
return version;
}
@GetMapping("/ready")
@ResponseBody
public String ready(HttpServletRequest request)
{
return probe(request, false);
}
@GetMapping("/live")
@ResponseBody
public String live(HttpServletRequest request)
{
return probe(request, true);
}
private String probe(HttpServletRequest request, boolean isLiveProbe)
{
return getProbeTestTransformInternal().doTransformOrNothing(request, isLiveProbe);
}
private ProbeTestTransform getProbeTestTransformInternal()
{
if (probeTestTransform == null)
{
probeTestTransform = getProbeTestTransform();
}
return probeTestTransform;
}
abstract ProbeTestTransform getProbeTestTransform();
@GetMapping("/")
public String transformForm(Model model)
{
return "transformForm"; // the name of the template
}
@GetMapping("/log")
public String log(Model model)
{
model.addAttribute("title", getTransformerName() + " Log Entries");
Collection<LogEntry> log = LogEntry.getLog();
if (!log.isEmpty())
{
model.addAttribute("log", log);
}
return "log"; // the name of the template
}
@GetMapping("/error")
public String error()
{
return "error"; // the name of the template
}
@ExceptionHandler(TypeMismatchException.class)
public void handleParamsTypeMismatch(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException
{
String transformerName = getTransformerName();
String name = e.getParameterName();
String message = "Request parameter " + name + " is of the wrong type";
int statusCode = 400;
if (logger != null && logger.isErrorEnabled())
{
logger.error(message);
}
LogEntry.setStatusCodeAndMessage(statusCode, message);
response.sendError(statusCode, transformerName+" - "+message);
}
@ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingParams(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException
{
String transformerName = getTransformerName();
String name = e.getParameterName();
String message = "Request parameter " + name + " is missing";
int statusCode = 400;
if (logger != null && logger.isErrorEnabled())
{
logger.error(message);
}
LogEntry.setStatusCodeAndMessage(statusCode, message);
response.sendError(statusCode, transformerName+" - "+message);
}
@ExceptionHandler(TransformException.class)
public void transformExceptionWithMessage(HttpServletResponse response, TransformException e) throws IOException
{
String transformerName = getTransformerName();
String message = e.getMessage();
int statusCode = e.getStatusCode();
if (logger != null && logger.isErrorEnabled())
{
logger.error(message);
}
long time = LogEntry.setStatusCodeAndMessage(statusCode, message);
getProbeTestTransformInternal().recordTransformTime(time);
// Forced to include the transformer name in the message (see commented out version of this method)
response.sendError(statusCode, transformerName+" - "+message);
}
// Results in HTML rather than json but there is an error in the log about "template might not exist or might
// not be accessible by any of the configured Template Resolvers" for the transformer.html (which is correct
// because that failed). Looks like Spring only supports returning json or XML when returning an Object or even
// a ResponseEntity without this logged exception, which is a shame as it would have been nicer to have just
// added the transformerName to the Object.
// @ExceptionHandler(TransformException.class)
// public final Map<String, Object> transformExceptionWithMessage(HttpServletResponse response, TransformException e, WebRequest request)
// {
// String transformerName = getTransformerName();
// String message = e.getMessage();
// int statusCode = e.getStatusCode();
//
// LogEntry.setStatusCodeAndMessage(statusCode, message);
//
// Map<String, Object> errorAttributes = new HashMap<>();
// errorAttributes.put("title", transformerName);
// errorAttributes.put("message", message);
// errorAttributes.put("status", Integer.toString(statusCode));
// errorAttributes.put("error", HttpStatus.valueOf(statusCode).getReasonPhrase());
// return errorAttributes;
// }
/**
* 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)
private File loadSourceFile(final String sourceReference)
{
ResponseEntity<Resource> responseEntity = alfrescoSharedFileStoreClient
.retrieveFile(sourceReference);
getProbeTestTransformInternal().incrementTransformerCount();
getProbeTestTransform().incrementTransformerCount();
HttpHeaders headers = responseEntity.getHeaders();
String filename = getFilenameFromContentDisposition(headers);
@ -468,299 +256,4 @@ public abstract class AbstractTransformerController
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 = fileName;
sourceFilename = StringUtils.getFilename(sourceFilename);
if (sourceFilename != null && !sourceFilename.isEmpty())
{
String ext = StringUtils.getFilenameExtension(sourceFilename);
targetFilename = (ext != null && !ext.isEmpty()
? sourceFilename.substring(0, sourceFilename.length()-ext.length()-1)
: sourceFilename)+
'.'+targetExtension;
}
return targetFilename;
}
/**
* Returns a File that holds the source content for a transformation.
*
* @param request
* @param multipartFile from the request
* @return a temporary File.
* @throws TransformException if there was no source filename.
*/
protected File createSourceFile(HttpServletRequest request, MultipartFile multipartFile)
{
getProbeTestTransformInternal().incrementTransformerCount();
String filename = multipartFile.getOriginalFilename();
long size = multipartFile.getSize();
filename = checkFilename( true, filename);
File file = TempFileProvider.createTempFile("source_", "_" + filename);
request.setAttribute(SOURCE_FILE, file);
save(multipartFile, file);
LogEntry.setSource(filename, size);
return file;
}
/**
* Returns a File to be used to store the result of a transformation.
*
* @param request
* @param filename The targetFilename supplied in the request. Only the filename if a path is used as part of the
* temporary filename.
* @return a temporary File.
* @throws TransformException if there was no target filename.
*/
protected File createTargetFile(HttpServletRequest request, String 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.
*
* @param filename or path to be checked.
* @return the filename part of the supplied filename if it was a path.
* @throws TransformException if there was no target filename.
*/
private String checkFilename(boolean source, String filename)
{
filename = StringUtils.getFilename(filename);
if (filename == null || filename.isEmpty())
{
String sourceOrTarget = source ? "source" : "target";
int statusCode = source ? 400 : 500;
throw new TransformException(statusCode, "The " + sourceOrTarget + " filename was not supplied");
}
return filename;
}
private void save(MultipartFile multipartFile, File file)
{
try
{
Files.copy(multipartFile.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException e)
{
throw new TransformException(507, "Failed to store the source file", e);
}
}
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
{
Resource resource = new UrlResource(file.toURI());
if (resource.exists() || resource.isReadable())
{
return resource;
}
else
{
throw new TransformException(500, "Could not read the target file: " + file.getPath());
}
}
catch (MalformedURLException e)
{
throw new TransformException(500, "The target filename was malformed: " + file.getPath(), e);
}
}
public void callTransform(File sourceFile, File targetFile, String... args) throws TransformException
{
args = buildArgs(sourceFile, targetFile, args);
try
{
callTransform(args);
}
catch (IllegalArgumentException e)
{
throw new TransformException(400, getMessage(e));
}
catch (Exception e)
{
throw new TransformException(500, getMessage(e));
}
if (!targetFile.exists() || targetFile.length() == 0)
{
throw new TransformException(500, "Transformer failed to create an output file");
}
}
private String getMessage(Exception e)
{
return e.getMessage() == null ? e.getClass().getSimpleName(): e.getMessage();
}
protected void callTransform(String[] args)
{
// Overridden when the transform is done in the JVM rather than in an external command.
}
protected String[] buildArgs(File sourceFile, File targetFile, String[] args)
{
ArrayList<String> methodArgs = new ArrayList<>(args.length+2);
StringJoiner sj = new StringJoiner(" ");
for (String arg: args)
{
addArg(methodArgs, sj, arg);
}
addFileArg(methodArgs, sj, sourceFile);
addFileArg(methodArgs, sj, targetFile);
LogEntry.setOptions(sj.toString());
return methodArgs.toArray(new String[methodArgs.size()]);
}
private void addArg(ArrayList<String> methodArgs, StringJoiner sj, String arg)
{
if (arg != null)
{
sj.add(arg);
methodArgs.add(arg);
}
}
private void addFileArg(ArrayList<String> methodArgs, StringJoiner sj, File arg)
{
if (arg != null)
{
String path = arg.getAbsolutePath();
int i = path.lastIndexOf('.');
String ext = i == -1 ? "???" : path.substring(i+1);
sj.add(ext);
methodArgs.add(path);
}
}
protected void executeTransformCommand(String options, File sourceFile, File targetFile, Long timeout)
{
LogEntry.setOptions(options);
Map<String, String> properties = new HashMap<String, String>(5);
properties.put("options", options);
properties.put("source", sourceFile.getAbsolutePath());
properties.put("target", targetFile.getAbsolutePath());
executeTransformCommand(properties, targetFile, timeout);
}
public void executeTransformCommand(Map<String, String> properties, File targetFile, Long timeout)
{
timeout = timeout != null && timeout > 0 ? timeout : 0;
RuntimeExec.ExecutionResult result = transformCommand.execute(properties, timeout);
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
{
throw new TransformException(400, "Transformer exit code was not 0: \n" + result.getStdErr());
}
if (!targetFile.exists() || targetFile.length() == 0)
{
throw new TransformException(500, "Transformer failed to create an output file");
}
}
protected ResponseEntity<Resource> createAttachment(String targetFilename, File targetFile, Long testDelay)
{
Resource targetResource = load(targetFile);
targetFilename = UriUtils.encodePath(StringUtils.getFilename(targetFilename), "UTF-8");
ResponseEntity<Resource> body = ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename*= UTF-8''" + targetFilename).body(targetResource);
LogEntry.setTargetSize(targetFile.length());
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
time += LogEntry.addDelay(testDelay);
getProbeTestTransformInternal().recordTransformTime(time);
return body;
}
/**
* 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

@ -1,39 +0,0 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2018 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application
{
public static void main(String[] args)
{
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,137 @@
package org.alfresco.transformer;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
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.exceptions.TransformException;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.transformer.probes.ProbeTestTransform;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
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.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
public interface TransformController
{
Log logger = LogFactory.getLog(TransformController.class);
ResponseEntity<TransformReply> transform(TransformRequest transformRequest, Long timeout);
void processTransform(File sourceFile, File targetFile, Map<String, String> transformOptions,
Long timeout);
String getTransformerName();
ProbeTestTransform getProbeTestTransform();
default String probe(HttpServletRequest request, boolean isLiveProbe)
{
return getProbeTestTransform().doTransformOrNothing(request, isLiveProbe);
}
@RequestMapping("/version")
@ResponseBody
String version();
@GetMapping("/")
default String transformForm(Model model)
{
return "transformForm"; // the name of the template
}
@GetMapping("/error")
default String error()
{
return "error"; // the name of the template
}
@GetMapping("/log")
default String log(Model model)
{
model.addAttribute("title", getTransformerName() + " Log Entries");
Collection<LogEntry> log = LogEntry.getLog();
if (!log.isEmpty())
{
model.addAttribute("log", log);
}
return "log"; // the name of the template
}
@GetMapping("/ready")
@ResponseBody
default String ready(HttpServletRequest request)
{
return probe(request, false);
}
@GetMapping("/live")
@ResponseBody
default String live(HttpServletRequest request)
{
return probe(request, true);
}
//region [Exception Handlers]
@ExceptionHandler(TypeMismatchException.class)
default void handleParamsTypeMismatch(HttpServletResponse response,
MissingServletRequestParameterException e) throws IOException
{
String transformerName = getTransformerName();
String name = e.getParameterName();
String message = "Request parameter " + name + " is of the wrong type";
int statusCode = 400;
logger.error(message);
LogEntry.setStatusCodeAndMessage(statusCode, message);
response.sendError(statusCode, transformerName + " - " + message);
}
@ExceptionHandler(MissingServletRequestParameterException.class)
default void handleMissingParams(HttpServletResponse response,
MissingServletRequestParameterException e) throws IOException
{
String transformerName = getTransformerName();
String name = e.getParameterName();
String message = "Request parameter " + name + " is missing";
int statusCode = 400;
logger.error(message);
LogEntry.setStatusCodeAndMessage(statusCode, message);
response.sendError(statusCode, transformerName + " - " + message);
}
@ExceptionHandler(TransformException.class)
default void transformExceptionWithMessage(HttpServletResponse response,
TransformException e) throws IOException
{
String transformerName = getTransformerName();
String message = e.getMessage();
int statusCode = e.getStatusCode();
logger.error(message);
long time = LogEntry.setStatusCodeAndMessage(statusCode, message);
getProbeTestTransform().recordTransformTime(time);
// Forced to include the transformer name in the message (see commented out version of this method)
response.sendError(statusCode, transformerName + " - " + message);
}
//endregion
}

View File

@ -25,18 +25,22 @@
*/
package org.alfresco.transformer;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import static org.alfresco.transformer.fs.FileManager.SOURCE_FILE;
import static org.alfresco.transformer.fs.FileManager.TARGET_FILE;
import static org.alfresco.transformer.fs.FileManager.deleteFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import org.alfresco.transformer.logging.LogEntry;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
public class TransformInterceptor extends HandlerInterceptorAdapter
{
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception
HttpServletResponse response, Object handler)
{
LogEntry.start();
return true;
@ -45,21 +49,11 @@ public class TransformInterceptor extends HandlerInterceptorAdapter
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception
{
// TargetFile cannot be deleted until completion, otherwise 0 bytes are sent.
deleteFile(request, AbstractTransformerController.SOURCE_FILE);
deleteFile(request, AbstractTransformerController.TARGET_FILE);
deleteFile(request, SOURCE_FILE);
deleteFile(request, TARGET_FILE);
LogEntry.complete();
}
private void deleteFile(HttpServletRequest request, String attributeName)
{
File file = (File) request.getAttribute(attributeName);
if (file != null)
{
file.delete();
}
}
}

View File

@ -5,10 +5,11 @@
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.transformer;
package org.alfresco.transformer.clients;
import java.io.File;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.model.FileRefResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

View File

@ -23,8 +23,10 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.config;
import org.alfresco.transformer.TransformInterceptor;
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

View File

@ -23,11 +23,11 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.exceptions;
public class TransformException extends RuntimeException
{
private int statusCode;
private final int statusCode;
public TransformException(int statusCode, String message)
{

View File

@ -0,0 +1,67 @@
package org.alfresco.transformer.executors;
import java.io.File;
import java.util.Map;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.util.exec.RuntimeExec;
/**
*/
public abstract class AbstractCommandExecutor implements CommandExecutor
{
private RuntimeExec transformCommand = createTransformCommand();
private RuntimeExec checkCommand = createCheckCommand();
protected abstract RuntimeExec createTransformCommand();
protected abstract RuntimeExec createCheckCommand();
// todo remove these setters and and make the fields final
public void setTransformCommand(RuntimeExec re) {
transformCommand = re;
}
public void setCheckCommand(RuntimeExec re) {
checkCommand = re;
}
@Override
public void run(Map<String, String> properties, File targetFile, Long timeout)
{
timeout = timeout != null && timeout > 0 ? timeout : 0;
RuntimeExec.ExecutionResult result = transformCommand.execute(properties, timeout);
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
{
throw new TransformException(400, "Transformer exit code was not 0: \n" + result.getStdErr());
}
if (!targetFile.exists() || targetFile.length() == 0)
{
throw new TransformException(500, "Transformer failed to create an output file");
}
}
@Override
public String version()
{
String version = "Version not checked";
if (checkCommand != null)
{
RuntimeExec.ExecutionResult result = checkCommand.execute();
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
{
throw new TransformException(500, "Transformer version check exit code was not 0: \n" + result);
}
version = result.getStdOut().trim();
if (version.isEmpty())
{
throw new TransformException(500, "Transformer version check failed to create any output");
}
}
return version;
}
}

View File

@ -0,0 +1,45 @@
package org.alfresco.transformer.executors;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.transformer.logging.LogEntry;
/**
* Basic interface for executing transformations via Shell commands
*
* @author Cezar Leahu
*/
public interface CommandExecutor
{
void run(Map<String, String> properties, File targetFile, Long timeout);
String version();
default void run(String options, File sourceFile, File targetFile,
Long timeout)
{
LogEntry.setOptions(options);
Map<String, String> properties = new HashMap<>();
properties.put("options", options);
properties.put("source", sourceFile.getAbsolutePath());
properties.put("target", targetFile.getAbsolutePath());
run(properties, targetFile, timeout);
}
default void run(String options, File sourceFile, String pageRange, File
targetFile, Long timeout)
{
LogEntry.setOptions(pageRange + (pageRange.isEmpty() ? "" : " ") + options);
Map<String, String> properties = new HashMap<>();
properties.put("options", options);
properties.put("source", sourceFile.getAbsolutePath() + pageRange);
properties.put("target", targetFile.getAbsolutePath());
run(properties, targetFile, timeout);
}
}

View File

@ -0,0 +1,15 @@
package org.alfresco.transformer.executors;
import java.io.File;
import org.alfresco.transformer.exceptions.TransformException;
/**
* Basic interface for executing transformations inside Java/JVM
*
* @author Cezar Leahu
*/
public interface JavaExecutor
{
void call(File sourceFile, File targetFile, String... args) throws TransformException;
}

View File

@ -0,0 +1,195 @@
package org.alfresco.transformer.fs;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.util.TempFileProvider;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;
/**
*/
public class FileManager
{
public static final String SOURCE_FILE = "sourceFile";
public static final String TARGET_FILE = "targetFile";
private static final String FILENAME = "filename=";
/**
* Returns a File to be used to store the result of a transformation.
*
* @param request
* @param filename The targetFilename supplied in the request. Only the filename if a path is used as part of the
* temporary filename.
* @return a temporary File.
* @throws TransformException if there was no target filename.
*/
public static File createTargetFile(HttpServletRequest request, String filename)
{
File file = buildFile(filename);
request.setAttribute(TARGET_FILE, file);
return file;
}
public static 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.
*
* @param filename or path to be checked.
* @return the filename part of the supplied filename if it was a path.
* @throws TransformException if there was no target filename.
*/
private static String checkFilename(boolean source, String filename)
{
filename = StringUtils.getFilename(filename);
if (filename == null || filename.isEmpty())
{
String sourceOrTarget = source ? "source" : "target";
int statusCode = source ? 400 : 500;
throw new TransformException(statusCode, "The " + sourceOrTarget + " filename was not supplied");
}
return filename;
}
private static void save(MultipartFile multipartFile, File file)
{
try
{
Files.copy(multipartFile.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException e)
{
throw new TransformException(507, "Failed to store the source file", e);
}
}
public static void save(Resource body, File file)
{
try
{
Files.copy(body.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException e)
{
throw new TransformException(507, "Failed to store the source file", e);
}
}
private static Resource load(File file)
{
try
{
Resource resource = new UrlResource(file.toURI());
if (resource.exists() || resource.isReadable())
{
return resource;
}
else
{
throw new TransformException(500, "Could not read the target file: " + file.getPath());
}
}
catch (MalformedURLException e)
{
throw new TransformException(500, "The target filename was malformed: " + file.getPath(), e);
}
}
public static String getFilenameFromContentDisposition(HttpHeaders headers)
{
String filename = "";
String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
if (contentDisposition != null)
{
String[] strings = contentDisposition.split("; *");
filename = Arrays.stream(strings)
.filter(s -> s.startsWith(FILENAME))
.findFirst()
.map(s -> s.substring(FILENAME.length()))
.orElse("");
}
return filename;
}
/**
* Returns the file name for the target file
*
* @param fileName Desired file name
* @param targetExtension File extension
* @return Target file name
*/
public static String createTargetFileName(String fileName, String targetExtension)
{
String targetFilename = null;
String sourceFilename = fileName;
sourceFilename = StringUtils.getFilename(sourceFilename);
if (sourceFilename != null && !sourceFilename.isEmpty())
{
String ext = StringUtils.getFilenameExtension(sourceFilename);
targetFilename = (ext != null && !ext.isEmpty()
? sourceFilename.substring(0, sourceFilename.length()-ext.length()-1)
: sourceFilename)+
'.'+targetExtension;
}
return targetFilename;
}
/**
* Returns a File that holds the source content for a transformation.
*
* @param request
* @param multipartFile from the request
* @return a temporary File.
* @throws TransformException if there was no source filename.
*/
public static File createSourceFile(HttpServletRequest request, MultipartFile multipartFile)
{
String filename = multipartFile.getOriginalFilename();
long size = multipartFile.getSize();
filename = checkFilename( true, filename);
File file = TempFileProvider.createTempFile("source_", "_" + filename);
request.setAttribute(SOURCE_FILE, file);
save(multipartFile, file);
LogEntry.setSource(filename, size);
return file;
}
public static void deleteFile(HttpServletRequest request, String attributeName)
{
File file = (File) request.getAttribute(attributeName);
if (file != null)
{
file.delete();
}
}
public static ResponseEntity<Resource> createAttachment(String targetFilename, File
targetFile)
{
Resource targetResource = load(targetFile);
targetFilename = UriUtils.encodePath(StringUtils.getFilename(targetFilename), "UTF-8");
return ResponseEntity.ok().header(HttpHeaders
.CONTENT_DISPOSITION,
"attachment; filename*= UTF-8''" + targetFilename).body(targetResource);
}
}

View File

@ -23,7 +23,9 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.logging;
import static java.lang.Math.max;
import java.text.SimpleDateFormat;
import java.util.Collection;
@ -32,7 +34,8 @@ import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Math.max;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Provides setter and getter methods to allow the current Thread to set various log properties and for these
@ -40,8 +43,9 @@ import static java.lang.Math.max;
* current entry to an internal log Collection of the latest entries. The {@link #getLog()} method is used to obtain
* access to this collection.
*/
public class LogEntry
public final class LogEntry
{
private static final Log logger = LogFactory.getLog(LogEntry.class);
// TODO allow ProbeTestTransform to find out if there are any transforms running longer than the max time.
private static final AtomicInteger count = new AtomicInteger(0);
@ -49,11 +53,7 @@ public class LogEntry
private static final int MAX_LOG_SIZE = 10;
private static final SimpleDateFormat HH_MM_SS = new SimpleDateFormat("HH:mm:ss");
private static ThreadLocal<LogEntry> currentLogEntry = new ThreadLocal<LogEntry>()
{
@Override
protected LogEntry initialValue()
{
private static final ThreadLocal<LogEntry> currentLogEntry = ThreadLocal.withInitial(() -> {
LogEntry logEntry = new LogEntry();
if (log.size() >= MAX_LOG_SIZE)
{
@ -61,8 +61,7 @@ public class LogEntry
}
log.addFirst(logEntry);
return logEntry;
}
};
});
private final int id = count.incrementAndGet();
private final long start = System.currentTimeMillis();
@ -204,9 +203,9 @@ public class LogEntry
}
currentLogEntry.remove();
if (AbstractTransformerController.logger != null && AbstractTransformerController.logger.isDebugEnabled())
if (logger.isDebugEnabled())
{
AbstractTransformerController.logger.debug(logEntry.toString());
logger.debug(logEntry.toString());
}
}
@ -279,6 +278,7 @@ public class LogEntry
private String size(long size)
{
// TODO fix numeric overflow in TB expression
return size == -1 ? "" : size(size, "1 byte",
new String[] { "bytes", " KB", " MB", " GB", " TB" },
new long[] { 1024, 1024*1024, 1024*1024*1024, 1024*1024*1024*1024, Long.MAX_VALUE });

View File

@ -0,0 +1,12 @@
package org.alfresco.transformer.logging;
public interface StandardMessages
{
String ENTERPRISE_LICENCE =
"This image is only intended to be used with the Alfresco Enterprise Content Repository which is covered by\n"+
"https://www.alfresco.com/legal/agreements and https://www.alfresco.com/terms-use\n" +
"\n" +
"License rights for this program may be obtained from Alfresco Software, Ltd. pursuant to a written agreement\n" +
"and any use of this program without such an agreement is prohibited.\n" +
"\n" ;
}

View File

@ -23,7 +23,10 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.probes;
import static org.alfresco.transformer.fs.FileManager.SOURCE_FILE;
import static org.alfresco.transformer.fs.FileManager.TARGET_FILE;
import java.io.File;
import java.io.IOException;
@ -35,6 +38,9 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.transformer.AbstractTransformerController;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
@ -60,9 +66,9 @@ import org.apache.commons.logging.Log;
* <li>maxTransformSeconds - the maximum time for a transformation, including failed ones.</li>
* </ul>
*/
abstract class ProbeTestTransform
public abstract class ProbeTestTransform
{
public static final int AVERAGE_OVER_TRANSFORMS = 5;
private static final int AVERAGE_OVER_TRANSFORMS = 5;
private final String sourceFilename;
private final String targetFilename;
private final long minExpectedLength;
@ -70,22 +76,32 @@ abstract class ProbeTestTransform
private final Log logger;
int livenessPercent;
long probeCount;
int transCount;
long normalTime;
long maxTime = Long.MAX_VALUE;
long nextTransformTime;
private int livenessPercent;
private long probeCount;
private int transCount;
private long normalTime;
private long maxTime = Long.MAX_VALUE;
private long nextTransformTime;
private final boolean livenessTransformEnabled;
private final long livenessTransformPeriod;
private long maxTransformCount = Long.MAX_VALUE;
private final long maxTransformCount;
private long maxTransformTime;
private AtomicBoolean initialised = new AtomicBoolean(false);
private AtomicBoolean readySent = new AtomicBoolean(false);
private AtomicLong transformCount = new AtomicLong(0);
private AtomicBoolean die = new AtomicBoolean(false);
private final AtomicBoolean initialised = new AtomicBoolean(false);
private final AtomicBoolean readySent = new AtomicBoolean(false);
private final AtomicLong transformCount = new AtomicLong(0);
private final AtomicBoolean die = new AtomicBoolean(false);
public int getLivenessPercent()
{
return livenessPercent;
}
public long getMaxTime()
{
return maxTime;
}
/**
* See Probes.md for more info.
@ -97,12 +113,12 @@ abstract class ProbeTestTransform
* @param maxTransformSeconds default values normally supplied by helm. Not identical so we can be sure which value is used.
* @param livenessTransformPeriodSeconds default values normally supplied by helm. Not identical so we can be sure which value is used.
*/
public ProbeTestTransform(AbstractTransformerController controller,
public ProbeTestTransform(AbstractTransformerController controller, Log logger,
String sourceFilename, String targetFilename, long expectedLength, long plusOrMinus,
int livenessPercent, long maxTransforms, long maxTransformSeconds,
long livenessTransformPeriodSeconds)
{
logger = controller.logger;
this.logger = logger;
this.sourceFilename = sourceFilename;
this.targetFilename = targetFilename;
@ -128,7 +144,7 @@ abstract class ProbeTestTransform
return defaultValue;
}
protected long getPositiveLongEnv(String name, long defaultValue)
private long getPositiveLongEnv(String name, long defaultValue)
{
long l = -1;
String env = System.getenv(name);
@ -179,7 +195,7 @@ abstract class ProbeTestTransform
return message;
}
String doTransform(HttpServletRequest request, boolean isLiveProbe)
private String doTransform(HttpServletRequest request, boolean isLiveProbe)
{
checkMaxTransformTimeAndCount(isLiveProbe);
@ -237,11 +253,11 @@ abstract class ProbeTestTransform
}
}
File getSourceFile(HttpServletRequest request, boolean isLiveProbe)
private File getSourceFile(HttpServletRequest request, boolean isLiveProbe)
{
incrementTransformerCount();
File sourceFile = TempFileProvider.createTempFile("source_", "_"+ sourceFilename);
request.setAttribute(AbstractTransformerController.SOURCE_FILE, sourceFile);
request.setAttribute(SOURCE_FILE, sourceFile);
try (InputStream inputStream = this.getClass().getResourceAsStream('/'+sourceFilename))
{
Files.copy(inputStream, sourceFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
@ -256,15 +272,15 @@ abstract class ProbeTestTransform
return sourceFile;
}
File getTargetFile(HttpServletRequest request)
private File getTargetFile(HttpServletRequest request)
{
File targetFile = TempFileProvider.createTempFile("target_", "_"+targetFilename);
request.setAttribute(AbstractTransformerController.TARGET_FILE, targetFile);
request.setAttribute(TARGET_FILE, targetFile);
LogEntry.setTarget(targetFilename);
return targetFile;
}
void recordTransformTime(long time)
public void recordTransformTime(long time)
{
if (maxTransformTime > 0 && time > maxTransformTime)
{
@ -272,7 +288,7 @@ abstract class ProbeTestTransform
}
}
void calculateMaxTime(long time, boolean isLiveProbe)
public void calculateMaxTime(long time, boolean isLiveProbe)
{
if (transCount <= AVERAGE_OVER_TRANSFORMS)
{
@ -331,4 +347,15 @@ abstract class ProbeTestTransform
{
transformCount.incrementAndGet();
}
public void setLivenessPercent(int livenessPercent)
{
this.livenessPercent = livenessPercent;
}
public long getNormalTime()
{
return normalTime;
}
}

View File

@ -0,0 +1,28 @@
package org.alfresco.transformer.util;
/**
*/
public class Util
{
/**
* 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}
*/
public static 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}
*/
public static Boolean stringToBoolean(String param)
{
return param == null? null : Boolean.parseBoolean(param);
}
}

View File

@ -32,8 +32,6 @@ import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertTrue;
@ -54,7 +52,7 @@ public abstract class AbstractHttpRequestTest
protected abstract String getSourceExtension();
@Test
public void testPageExists() throws Exception
public void testPageExists()
{
String result = restTemplate.getForObject("http://localhost:" + port + "/", String.class);
@ -63,7 +61,7 @@ public abstract class AbstractHttpRequestTest
}
@Test
public void logPageExists() throws Exception
public void logPageExists()
{
String result = restTemplate.getForObject("http://localhost:" + port + "/log", String.class);
@ -72,7 +70,7 @@ public abstract class AbstractHttpRequestTest
}
@Test
public void errorPageExists() throws Exception
public void errorPageExists()
{
String result = restTemplate.getForObject("http://localhost:" + port + "/error", String.class);
@ -81,7 +79,7 @@ public abstract class AbstractHttpRequestTest
}
@Test
public void noFileError() throws Exception
public void noFileError()
{
// Transformer name is not part of the title as this is checked by another handler
assertTransformError(false,
@ -94,22 +92,22 @@ public abstract class AbstractHttpRequestTest
assertMissingParameter("targetExtension");
}
protected void assertMissingParameter(String name) throws IOException
private void assertMissingParameter(String name)
{
assertTransformError(true,
getTransformerName() + " - Request parameter " + name + " is missing");
}
protected void assertTransformError(boolean addFile, String errorMessage) throws IOException
private void assertTransformError(boolean addFile, String errorMessage)
{
LinkedMultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>();
LinkedMultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
if (addFile)
{
parameters.add("file", new org.springframework.core.io.ClassPathResource("quick."+getSourceExtension()));
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> entity = new HttpEntity<LinkedMultiValueMap<String, Object>>(parameters, headers);
HttpEntity<LinkedMultiValueMap<String, Object>> entity = new HttpEntity<>(parameters, headers);
ResponseEntity<String> response = restTemplate.exchange("/transform", HttpMethod.POST, entity, String.class, "");
assertEquals(errorMessage, getErrorMessage(response.getBody()));
}
@ -117,7 +115,7 @@ public abstract class AbstractHttpRequestTest
// Strip out just the error message from the returned json content body
// Had been expecting the Error page to be returned, but we end up with the json in this test harness.
// Is correct if run manually, so not worrying too much about this.
private String getErrorMessage(String content) throws IOException
private String getErrorMessage(String content)
{
String message = "";
int i = content.indexOf("\"message\":\"");

View File

@ -27,12 +27,7 @@ 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;
@ -44,33 +39,21 @@ 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.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
import org.alfresco.transformer.probes.ProbeTestTransform;
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.boot.test.mock.mockito.MockBean;
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 com.fasterxml.jackson.databind.ObjectMapper;
@ -85,18 +68,9 @@ public abstract class AbstractTransformerControllerTest
@Autowired
protected ObjectMapper objectMapper;
@Mock
private RuntimeExec mockTransformCommand;
@Mock
private RuntimeExec mockCheckCommand;
@Mock
@MockBean
protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
@Mock
private RuntimeExec.ExecutionResult mockExecutionResult;
protected String sourceExtension;
protected String targetExtension;
protected String sourceMimetype;
@ -108,88 +82,14 @@ public abstract class AbstractTransformerControllerTest
protected byte[] expectedSourceFileBytes;
protected byte[] expectedTargetFileBytes;
protected AbstractTransformerController controller;
@Before
public void before() throws Exception
{
}
// Called by sub class
public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension,
protected abstract void mockTransformCommand(String sourceExtension,
String targetExtension, String sourceMimetype,
boolean readTargetFileBytes) throws IOException
{
this.controller = controller;
this.sourceExtension = sourceExtension;
this.targetExtension = targetExtension;
this.sourceMimetype = sourceMimetype;
boolean readTargetFileBytes) throws IOException;
expectedOptions = null;
expectedSourceSuffix = null;
expectedSourceFileBytes = readTestFile(sourceExtension);
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
sourceFile = new MockMultipartFile("file", "quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
protected abstract AbstractTransformerController getController();
controller.setTransformCommand(mockTransformCommand);
controller.setCheckCommand(mockCheckCommand);
when(mockTransformCommand.execute(anyObject(), anyLong())).thenAnswer(new Answer<RuntimeExec.ExecutionResult>()
{
public RuntimeExec.ExecutionResult answer(InvocationOnMock invocation) throws Throwable
{
Map<String, String> actualProperties = invocation.getArgument(0);
assertEquals("There should be 3 properties", 3, actualProperties.size());
String actualOptions = actualProperties.get("options");
String actualSource = actualProperties.get("source");
String actualTarget = actualProperties.get("target");
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
assertNotNull(actualSource);
assertNotNull(actualTarget);
if (expectedSourceSuffix != null)
{
assertTrue("The source file \""+actualSource+"\" should have ended in \""+expectedSourceSuffix+"\"", actualSource.endsWith(expectedSourceSuffix));
actualSource = actualSource.substring(0, actualSource.length()-expectedSourceSuffix.length());
}
assertNotNull(actualOptions);
if (expectedOptions != null)
{
assertEquals("expectedOptions", expectedOptions, actualOptions);
}
Long actualTimeout = invocation.getArgument(1);
assertNotNull(actualTimeout);
if (expectedTimeout != null)
{
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
}
// Copy a test file into the target file location if it exists
int i = actualTarget.lastIndexOf('_');
if (i >= 0)
{
String testFilename = actualTarget.substring(i+1);
File testFile = getTestFile(testFilename, false);
File targetFile = new File(actualTarget);
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
targetFile);
}
// Check the supplied source file has not been changed.
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
return mockExecutionResult;
}
});
when(mockExecutionResult.getExitValue()).thenReturn(0);
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
}
protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest);
/**
* This method ends up being the core of the mock.
@ -240,7 +140,7 @@ public abstract class AbstractTransformerControllerTest
protected MockHttpServletRequestBuilder mockMvcRequest(String url, MockMultipartFile sourceFile, String... params)
{
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.fileUpload("/transform").file(sourceFile);
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart("/transform").file(sourceFile);
if (params.length % 2 != 0)
{
@ -284,16 +184,6 @@ public abstract class AbstractTransformerControllerTest
.andExpect(status().is(500));
}
@Test
public void badExitCodeTest() throws Exception
{
when(mockExecutionResult.getExitValue()).thenReturn(1);
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
.andExpect(status().is(400))
.andExpect(status().reason(containsString("Transformer exit code was not 0: \nSTDERR")));
}
@Test
// Looks dangerous but is okay as we only use the final filename
public void dotDotSourceFilenameTest() throws Exception
@ -347,28 +237,11 @@ public abstract class AbstractTransformerControllerTest
.andExpect(status().reason(containsString("Request parameter targetExtension is missing")));
}
// @Test
// // Not a real test, but helpful for trying out the duration times in log code.
// public void testTimes() throws InterruptedException
// {
// LogEntry.start();
// Thread.sleep(50);
// LogEntry.setSource("test File", 1234);
// Thread.sleep(200);
// LogEntry.setStatusCodeAndMessage(200, "Success");
// LogEntry.addDelay(2000L);
// for (LogEntry logEntry: LogEntry.getLog())
// {
// String str = logEntry.getDuration();
// System.out.println(str);
// }
// }
@Test
public void calculateMaxTime() throws Exception
{
ProbeTestTransform probeTestTransform = controller.getProbeTestTransform();
probeTestTransform.livenessPercent = 110;
ProbeTestTransform probeTestTransform = getController().getProbeTestTransform();
probeTestTransform.setLivenessPercent(110);
long[][] values = new long[][]{
{5000, 0, Long.MAX_VALUE}, // 1st transform is ignored
@ -388,60 +261,11 @@ public abstract class AbstractTransformerControllerTest
long expectedMaxTime = v[2];
probeTestTransform.calculateMaxTime(time, true);
assertEquals("", expectedNormalTime, probeTestTransform.normalTime);
assertEquals("", expectedMaxTime, probeTestTransform.maxTime);
assertEquals("", expectedNormalTime, probeTestTransform.getNormalTime());
assertEquals("", expectedMaxTime, probeTestTransform.getMaxTime());
}
}
@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.setTransformRequestOptions(new HashMap<>());
transformRequest.setSourceReference(sourceFileRef);
transformRequest.setSourceExtension(sourceExtension);
transformRequest.setSourceSize(sourceFile.length());
transformRequest.setTargetExtension(targetExtension);
// 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());
}
@Test
public void testEmptyPojoTransform() throws Exception
{
@ -450,9 +274,12 @@ public abstract class AbstractTransformerControllerTest
// Serialize and call the transformer
String tr = objectMapper.writeValueAsString(transformRequest);
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
String transformationReplyAsString = mockMvc
.perform(MockMvcRequestBuilders
.post("/transform")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.content(tr))
.andExpect(status().is(HttpStatus.BAD_REQUEST.value()))
.andReturn().getResponse().getContentAsString();
@ -461,6 +288,4 @@ public abstract class AbstractTransformerControllerTest
// Assert the reply
assertEquals(HttpStatus.BAD_REQUEST.value(), transformReply.getStatus());
}
protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest);
}

32
pom.xml
View File

@ -3,9 +3,10 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-super-pom</artifactId>
<version>9</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath />
</parent>
<groupId>org.alfresco</groupId>
@ -44,13 +45,6 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${dependency.spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
@ -121,4 +115,22 @@
</snapshotRepository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>