mirror of
https://github.com/Alfresco/alfresco-transform-core.git
synced 2025-05-12 17:04:48 +00:00
ATS-175 : T-Engine code cleanup
This commit is contained in:
parent
ba8707c762
commit
d85c03d362
@ -11,14 +11,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
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.File;
|
||||||
import java.util.HashMap;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@ -52,69 +61,49 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
@Controller
|
@Controller
|
||||||
public class AlfrescoPdfRendererController extends AbstractTransformerController
|
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
|
@Autowired
|
||||||
public AlfrescoPdfRendererController()
|
public AlfrescoPdfRendererController()
|
||||||
{
|
{
|
||||||
logger = LogFactory.getLog(AlfrescoPdfRendererController.class);
|
|
||||||
logger.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
|
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("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("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
|
logger.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||||
setTransformCommand(createTransformCommand());
|
|
||||||
setCheckCommand(createCheckCommand());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTransformerName()
|
public String getTransformerName()
|
||||||
{
|
{
|
||||||
return "Alfresco PDF Renderer";
|
return "Alfresco PDF Renderer";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RuntimeExec createTransformCommand()
|
@Override
|
||||||
|
public String version()
|
||||||
{
|
{
|
||||||
RuntimeExec runtimeExec = new RuntimeExec();
|
return commandExecutor.version();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ProbeTestTransform getProbeTestTransform()
|
public ProbeTestTransform getProbeTestTransform()
|
||||||
{
|
{
|
||||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
// 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)
|
7455, 1024, 150, 10240, 60*20+1, 60*15-15)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
protected void executeTransformCommand(File sourceFile, File targetFile)
|
||||||
{
|
{
|
||||||
AlfrescoPdfRendererController.this.executeTransformCommand("", sourceFile, targetFile, null);
|
commandExecutor.run("", sourceFile, targetFile, null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void processTransform(File sourceFile, File targetFile,
|
public void processTransform(File sourceFile, File targetFile,
|
||||||
Map<String, String> transformOptions, Long timeout)
|
Map<String, String> transformOptions, Long timeout)
|
||||||
{
|
{
|
||||||
String page = transformOptions.get("page");
|
String page = transformOptions.get("page");
|
||||||
@ -137,7 +126,7 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
|
|||||||
String options = buildTransformOptions(pageOption, widthOption, heightOption,
|
String options = buildTransformOptions(pageOption, widthOption, heightOption,
|
||||||
allowEnlargementOption, maintainAspectRatioOption);
|
allowEnlargementOption, maintainAspectRatioOption);
|
||||||
|
|
||||||
executeTransformCommand(options, sourceFile, targetFile, timeout);
|
commandExecutor.run(options, sourceFile, targetFile, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -155,19 +144,26 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
|
|||||||
@RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio)
|
@RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio)
|
||||||
{
|
{
|
||||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||||
|
getProbeTestTransform().incrementTransformerCount();
|
||||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||||
File targetFile = createTargetFile(request, targetFilename);
|
File targetFile = createTargetFile(request, targetFilename);
|
||||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||||
|
|
||||||
String options = buildTransformOptions(page, width, height, allowEnlargement,
|
String options = buildTransformOptions(page, width, height, allowEnlargement,
|
||||||
maintainAspectRatio);
|
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(" ");
|
StringJoiner args = new StringJoiner(" ");
|
||||||
if (width != null && width >= 0)
|
if (width != null && width >= 0)
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
|
||||||
|
import org.alfresco.transformer.executors.PdfRendererCommandExecutor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||||
@ -27,9 +29,15 @@ public class Application
|
|||||||
@Value("${container.name}")
|
@Value("${container.name}")
|
||||||
private String containerName;
|
private String containerName;
|
||||||
|
|
||||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
@Bean
|
||||||
|
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||||
return registry -> registry.config().commonTags("containerName", containerName);
|
return registry -> registry.config().commonTags("containerName", containerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PdfRendererCommandExecutor commandExecutor() {
|
||||||
|
return new PdfRendererCommandExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -25,19 +25,48 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.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.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
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.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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
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.autoconfigure.web.servlet.WebMvcTest;
|
||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
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.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the AlfrescoPdfRendererController without a server.
|
* Test the AlfrescoPdfRendererController without a server.
|
||||||
@ -47,22 +76,111 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
|||||||
@WebMvcTest(AlfrescoPdfRendererController.class)
|
@WebMvcTest(AlfrescoPdfRendererController.class)
|
||||||
public class AlfrescoPdfRendererControllerTest extends AbstractTransformerControllerTest
|
public class AlfrescoPdfRendererControllerTest extends AbstractTransformerControllerTest
|
||||||
{
|
{
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec mockTransformCommand;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec mockCheckCommand;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private PdfRendererCommandExecutor commandExecutor;
|
||||||
|
|
||||||
@SpyBean
|
@SpyBean
|
||||||
private AlfrescoPdfRendererController controller;
|
private AlfrescoPdfRendererController controller;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception
|
public void before() throws IOException
|
||||||
{
|
{
|
||||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
commandExecutor.setTransformCommand(mockTransformCommand);
|
||||||
super.controller = controller;
|
commandExecutor.setCheckCommand(mockCheckCommand);
|
||||||
super.mockTransformCommand(controller, "pdf", "png", "application/pdf", true);
|
|
||||||
|
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
|
@Test
|
||||||
public void optionsTest() throws Exception
|
public void optionsTest() throws Exception
|
||||||
{
|
{
|
||||||
expectedOptions = "--width=321 --height=654 --allow-enlargement --maintain-aspect-ratio --page=2";
|
expectedOptions = "--width=321 --height=654 --allow-enlargement --maintain-aspect-ratio --page=2";
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
|
|
||||||
@ -82,7 +200,7 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
|
|||||||
public void optionsNegateBooleansTest() throws Exception
|
public void optionsNegateBooleansTest() throws Exception
|
||||||
{
|
{
|
||||||
expectedOptions = "--width=321 --height=654 --page=2";
|
expectedOptions = "--width=321 --height=654 --page=2";
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
|
|
||||||
@ -106,4 +224,64 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
|
|||||||
transformRequest.setSourceMediaType(MediaType.APPLICATION_PDF_VALUE);
|
transformRequest.setSourceMediaType(MediaType.APPLICATION_PDF_VALUE);
|
||||||
transformRequest.setTargetMediaType(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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
@ -48,5 +47,5 @@ public class AlfrescoPdfRendererHttpRequestTest extends AbstractHttpRequestTest
|
|||||||
protected String getSourceExtension()
|
protected String getSourceExtension()
|
||||||
{
|
{
|
||||||
return "pdf";
|
return "pdf";
|
||||||
};
|
}
|
||||||
}
|
}
|
@ -12,6 +12,8 @@
|
|||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
|
||||||
|
import org.alfresco.transformer.executors.ImageMagickCommandExecutor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||||
@ -27,9 +29,15 @@ public class Application
|
|||||||
@Value("${container.name}")
|
@Value("${container.name}")
|
||||||
private String containerName;
|
private String containerName;
|
||||||
|
|
||||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
@Bean
|
||||||
|
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||||
return registry -> registry.config().commonTags("containerName", containerName);
|
return registry -> registry.config().commonTags("containerName", containerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ImageMagickCommandExecutor commandExecutor() {
|
||||||
|
return new ImageMagickCommandExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
|
@ -11,16 +11,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
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.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@ -56,73 +67,46 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
@Controller
|
@Controller
|
||||||
public class ImageMagickController extends AbstractTransformerController
|
public class ImageMagickController extends AbstractTransformerController
|
||||||
{
|
{
|
||||||
private static final String ROOT = "/usr/lib64/ImageMagick-7.0.7";
|
private static final Log logger = LogFactory.getLog(ImageMagickController.class);
|
||||||
private static final String DYN = ROOT+"/lib";
|
|
||||||
private static final String EXE = "/usr/bin/convert";
|
|
||||||
private static final List<String> GRAVITY_VALUES = Arrays.asList(
|
private static final List<String> GRAVITY_VALUES = Arrays.asList(
|
||||||
"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center");
|
"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center");
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ImageMagickCommandExecutor commandExecutor;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ImageMagickController()
|
public ImageMagickController()
|
||||||
{
|
{
|
||||||
logger = LogFactory.getLog(ImageMagickController.class);
|
|
||||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
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("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("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||||
setTransformCommand(createTransformCommand());
|
|
||||||
setCheckCommand(createCheckCommand());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTransformerName()
|
public String getTransformerName()
|
||||||
{
|
{
|
||||||
return "ImageMagick";
|
return "ImageMagick";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RuntimeExec createTransformCommand()
|
@Override
|
||||||
|
public String version()
|
||||||
{
|
{
|
||||||
RuntimeExec runtimeExec = new RuntimeExec();
|
return commandExecutor.version();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ProbeTestTransform getProbeTestTransform()
|
public ProbeTestTransform getProbeTestTransform()
|
||||||
{
|
{
|
||||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
// See the Javadoc on this method and Probes.md for the choice of these values.
|
||||||
return new ProbeTestTransform(this, "quick.jpg", "quick.png",
|
return new ProbeTestTransform(this, logger, "quick.jpg", "quick.png",
|
||||||
35593, 1024, 150, 1024, 60*15+1,60*15+0)
|
35593, 1024, 150, 1024, 60*15+1,60*15)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
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(),
|
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(),
|
||||||
targetExtension);
|
targetExtension);
|
||||||
|
getProbeTestTransform().incrementTransformerCount();
|
||||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||||
File targetFile = createTargetFile(request, targetFilename);
|
File targetFile = createTargetFile(request, targetFilename);
|
||||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||||
@ -176,14 +161,20 @@ public class ImageMagickController extends AbstractTransformerController
|
|||||||
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions);
|
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions);
|
||||||
String pageRange = calculatePageRange(startPage, endPage);
|
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
|
@Override
|
||||||
protected void processTransform(File sourceFile, File targetFile,
|
public void processTransform(final File sourceFile, final File targetFile,
|
||||||
Map<String, String> transformOptions, Long timeout)
|
final Map<String, String> transformOptions, final Long timeout)
|
||||||
{
|
{
|
||||||
Integer startPage = stringToInteger(transformOptions.get("startPage"));
|
Integer startPage = stringToInteger(transformOptions.get("startPage"));
|
||||||
Integer endPage = stringToInteger(transformOptions.get("endPage"));
|
Integer endPage = stringToInteger(transformOptions.get("endPage"));
|
||||||
@ -201,28 +192,17 @@ public class ImageMagickController extends AbstractTransformerController
|
|||||||
Boolean resizePercentage = stringToBoolean(transformOptions.get("resizePercentage"));
|
Boolean resizePercentage = stringToBoolean(transformOptions.get("resizePercentage"));
|
||||||
Boolean allowEnlargement = stringToBoolean(transformOptions.get("allowEnlargement"));
|
Boolean allowEnlargement = stringToBoolean(transformOptions.get("allowEnlargement"));
|
||||||
Boolean maintainAspectRatio = stringToBoolean(transformOptions.get("maintainAspectRatio"));
|
Boolean maintainAspectRatio = stringToBoolean(transformOptions.get("maintainAspectRatio"));
|
||||||
String commandOptions = transformOptions.get("commandOptions");
|
|
||||||
|
|
||||||
String options = buildTransformOptions(startPage, endPage , alphaRemove, autoOrient, cropGravity, cropWidth, cropHeight, cropPercentage,
|
final String options = buildTransformOptions(startPage, endPage, alphaRemove, autoOrient,
|
||||||
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions);
|
cropGravity, cropWidth, cropHeight, cropPercentage,
|
||||||
String pageRange = calculatePageRange(startPage, endPage);
|
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)
|
private static String buildTransformOptions(Integer startPage, Integer endPage, Boolean alphaRemove,
|
||||||
{
|
|
||||||
LogEntry.setOptions(pageRange+(pageRange.isEmpty() ? "" : " ")+options);
|
|
||||||
|
|
||||||
Map<String, String> properties = new HashMap<String, String>(5);
|
|
||||||
properties.put("options", options);
|
|
||||||
properties.put("source", sourceFile.getAbsolutePath()+pageRange);
|
|
||||||
properties.put("target", targetFile.getAbsolutePath());
|
|
||||||
|
|
||||||
executeTransformCommand(properties, targetFile, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildTransformOptions(Integer startPage, Integer endPage, Boolean alphaRemove,
|
|
||||||
Boolean autoOrient, String cropGravity, Integer cropWidth, Integer cropHeight,
|
Boolean autoOrient, String cropGravity, Integer cropWidth, Integer cropHeight,
|
||||||
Boolean cropPercentage, Integer cropXOffset, Integer cropYOffset, Boolean thumbnail,
|
Boolean cropPercentage, Integer cropXOffset, Integer cropYOffset, Boolean thumbnail,
|
||||||
Integer resizeWidth, Integer resizeHeight, Boolean resizePercentage,
|
Integer resizeWidth, Integer resizeHeight, Boolean resizePercentage,
|
||||||
@ -261,7 +241,7 @@ public class ImageMagickController extends AbstractTransformerController
|
|||||||
args.add(cropGravity);
|
args.add(cropGravity);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder crop = new StringBuilder("");
|
StringBuilder crop = new StringBuilder();
|
||||||
if (cropWidth != null && cropWidth >= 0)
|
if (cropWidth != null && cropWidth >= 0)
|
||||||
{
|
{
|
||||||
crop.append(cropWidth);
|
crop.append(cropWidth);
|
||||||
@ -303,7 +283,7 @@ public class ImageMagickController extends AbstractTransformerController
|
|||||||
if (resizeHeight != null || resizeWidth != null || resizePercentage !=null || maintainAspectRatio != null)
|
if (resizeHeight != null || resizeWidth != null || resizePercentage !=null || maintainAspectRatio != null)
|
||||||
{
|
{
|
||||||
args.add(thumbnail != null && thumbnail ? "-thumbnail" : "-resize");
|
args.add(thumbnail != null && thumbnail ? "-thumbnail" : "-resize");
|
||||||
StringBuilder resize = new StringBuilder("");
|
StringBuilder resize = new StringBuilder();
|
||||||
if (resizeWidth != null && resizeWidth >= 0)
|
if (resizeWidth != null && resizeWidth >= 0)
|
||||||
{
|
{
|
||||||
resize.append(resizeWidth);
|
resize.append(resizeWidth);
|
||||||
@ -335,10 +315,9 @@ public class ImageMagickController extends AbstractTransformerController
|
|||||||
args.toString();
|
args.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String calculatePageRange(Integer startPage, Integer endPage)
|
private static String calculatePageRange(Integer startPage, Integer endPage)
|
||||||
{
|
{
|
||||||
return
|
return startPage == null
|
||||||
startPage == null
|
|
||||||
? endPage == null
|
? endPage == null
|
||||||
? ""
|
? ""
|
||||||
: "["+endPage+']'
|
: "["+endPage+']'
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -25,21 +25,48 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.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.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
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.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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
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.autoconfigure.web.servlet.WebMvcTest;
|
||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
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.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the ImageMagickController without a server.
|
* Test the ImageMagickController without a server.
|
||||||
@ -49,16 +76,104 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
|||||||
@WebMvcTest(ImageMagickController.class)
|
@WebMvcTest(ImageMagickController.class)
|
||||||
public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||||
{
|
{
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec mockTransformCommand;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec mockCheckCommand;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private ImageMagickCommandExecutor commandExecutor;
|
||||||
|
|
||||||
@SpyBean
|
@SpyBean
|
||||||
private ImageMagickController controller;
|
private ImageMagickController controller;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws IOException
|
public void before() throws IOException
|
||||||
{
|
{
|
||||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
commandExecutor.setTransformCommand(mockTransformCommand);
|
||||||
super.controller = controller;
|
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
|
@Test
|
||||||
@ -67,7 +182,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
|||||||
for (String value: new String[] {"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center"})
|
for (String value: new String[] {"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center"})
|
||||||
{
|
{
|
||||||
expectedOptions = "-gravity "+value+" +repage";
|
expectedOptions = "-gravity "+value+" +repage";
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
.param("cropGravity", value))
|
.param("cropGravity", value))
|
||||||
@ -80,7 +195,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Test
|
@Test
|
||||||
public void cropGravityBadTest() throws Exception
|
public void cropGravityBadTest() throws Exception
|
||||||
{
|
{
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
.param("cropGravity", "badValue"))
|
.param("cropGravity", "badValue"))
|
||||||
@ -92,7 +207,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
|||||||
{
|
{
|
||||||
expectedOptions = "-alpha remove -gravity SouthEast -crop 123x456%+90+12 +repage -thumbnail 321x654%!";
|
expectedOptions = "-alpha remove -gravity SouthEast -crop 123x456%+90+12 +repage -thumbnail 321x654%!";
|
||||||
expectedSourceSuffix = "[2-3]";
|
expectedSourceSuffix = "[2-3]";
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
|
|
||||||
@ -126,7 +241,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
|||||||
{
|
{
|
||||||
expectedOptions = "-auto-orient -gravity SouthEast -crop 123x456+90+12 +repage -resize 321x654>";
|
expectedOptions = "-auto-orient -gravity SouthEast -crop 123x456+90+12 +repage -resize 321x654>";
|
||||||
expectedSourceSuffix = "[2-3]";
|
expectedSourceSuffix = "[2-3]";
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
|
|
||||||
@ -160,7 +275,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
|||||||
{
|
{
|
||||||
// Example of why the commandOptions parameter is a bad idea.
|
// Example of why the commandOptions parameter is a bad idea.
|
||||||
expectedOptions = "( horrible command / ); -resize 321x654>";
|
expectedOptions = "( horrible command / ); -resize 321x654>";
|
||||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||||
.file(sourceFile)
|
.file(sourceFile)
|
||||||
.param("targetExtension", targetExtension)
|
.param("targetExtension", targetExtension)
|
||||||
.param("thumbnail", "false")
|
.param("thumbnail", "false")
|
||||||
@ -180,4 +295,64 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
|||||||
transformRequest.setSourceMediaType(MediaType.IMAGE_PNG_VALUE);
|
transformRequest.setSourceMediaType(MediaType.IMAGE_PNG_VALUE);
|
||||||
transformRequest.setTargetMediaType(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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
@ -48,5 +47,5 @@ public class ImageMagickHttpRequestTest extends AbstractHttpRequestTest
|
|||||||
protected String getSourceExtension()
|
protected String getSourceExtension()
|
||||||
{
|
{
|
||||||
return "jpg";
|
return "jpg";
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
|
||||||
|
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||||
@ -27,9 +29,16 @@ public class Application
|
|||||||
@Value("${container.name}")
|
@Value("${container.name}")
|
||||||
private String containerName;
|
private String containerName;
|
||||||
|
|
||||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
@Bean
|
||||||
|
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||||
return registry -> registry.config().commonTags("containerName", containerName);
|
return registry -> registry.config().commonTags("containerName", containerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LibreOfficeJavaExecutor javaExecutor()
|
||||||
|
{
|
||||||
|
return new LibreOfficeJavaExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
|
@ -11,19 +11,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
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.File;
|
||||||
import java.io.IOException;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@ -33,8 +37,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
|||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import com.sun.star.task.ErrorCodeIOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the Docker based LibreOffice transformer.
|
* Controller for the Docker based LibreOffice transformer.
|
||||||
*
|
*
|
||||||
@ -59,86 +61,48 @@ import com.sun.star.task.ErrorCodeIOException;
|
|||||||
@Controller
|
@Controller
|
||||||
public class LibreOfficeController extends AbstractTransformerController
|
public class LibreOfficeController extends AbstractTransformerController
|
||||||
{
|
{
|
||||||
private static final String OFFICE_HOME = "/opt/libreoffice5.4";
|
private static final Log logger = LogFactory.getLog(LibreOfficeController.class);
|
||||||
|
|
||||||
private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088;
|
|
||||||
|
|
||||||
private JodConverter jodconverter;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public LibreOfficeController() throws Exception
|
private LibreOfficeJavaExecutor javaExecutor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public LibreOfficeController()
|
||||||
{
|
{
|
||||||
logger = LogFactory.getLog(LibreOfficeController.class);
|
|
||||||
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
|
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("This transformer uses LibreOffice from The Document Foundation. See the license at https://www.libreoffice.org/download/license/ or in /libreoffice.txt");
|
||||||
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
|
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
|
@Override
|
||||||
protected String getTransformerName()
|
public String getTransformerName()
|
||||||
{
|
{
|
||||||
return "LibreOffice";
|
return "LibreOffice";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String version()
|
public String version()
|
||||||
{
|
{
|
||||||
return "LibreOffice available";
|
return "LibreOffice available";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ProbeTestTransform getProbeTestTransform()
|
public ProbeTestTransform getProbeTestTransform()
|
||||||
{
|
{
|
||||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
// 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)
|
11817, 1024, 150, 10240, 60*30+1, 60*15+20)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
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)
|
@PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
||||||
@RequestParam("file") MultipartFile sourceMultipartFile,
|
@RequestParam("file") MultipartFile sourceMultipartFile,
|
||||||
@ -147,122 +111,25 @@ public class LibreOfficeController extends AbstractTransformerController
|
|||||||
@RequestParam(value = "testDelay", required = false) Long testDelay)
|
@RequestParam(value = "testDelay", required = false) Long testDelay)
|
||||||
{
|
{
|
||||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||||
|
getProbeTestTransform().incrementTransformerCount();
|
||||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||||
File targetFile = createTargetFile(request, targetFilename);
|
File targetFile = createTargetFile(request, targetFilename);
|
||||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||||
|
|
||||||
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
|
@Override
|
||||||
protected void processTransform(File sourceFile, File targetFile,
|
public void processTransform(File sourceFile, File targetFile,
|
||||||
Map<String, String> transformOptions, Long timeout)
|
Map<String, String> transformOptions, Long timeout)
|
||||||
{
|
{
|
||||||
executeTransformCommand(sourceFile, targetFile, timeout);
|
javaExecutor.call(sourceFile, targetFile);
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
* agreement is prohibited.
|
* agreement is prohibited.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer.executors;
|
||||||
|
|
||||||
import org.artofsolving.jodconverter.office.OfficeManager;
|
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
|
public interface JodConverter
|
||||||
{
|
{
|
||||||
@ -21,11 +21,11 @@ public interface JodConverter
|
|||||||
* Gets the JodConverter OfficeManager.
|
* Gets the JodConverter OfficeManager.
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public abstract OfficeManager getOfficeManager();
|
OfficeManager getOfficeManager();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns a boolean indicating whether the JodConverter connection to OOo is available.
|
* This method returns a boolean indicating whether the JodConverter connection to OOo is available.
|
||||||
* @return <code>true</code> if available, else <code>false</code>
|
* @return <code>true</code> if available, else <code>false</code>
|
||||||
*/
|
*/
|
||||||
public abstract boolean isAvailable();
|
boolean isAvailable();
|
||||||
}
|
}
|
@ -23,12 +23,11 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer.executors;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.StringTokenizer;
|
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.DisposableBean;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
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
|
* 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
|
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;
|
private OfficeManager officeManager;
|
||||||
boolean isAvailable = false;
|
private boolean isAvailable = false;
|
||||||
|
|
||||||
// JodConverter's built-in configuration settings.
|
// JodConverter's built-in configuration settings.
|
||||||
//
|
//
|
||||||
@ -82,7 +81,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
private Boolean deprecatedOooEnabled;
|
private Boolean deprecatedOooEnabled;
|
||||||
private int[] deprecatedOooPortNumbers;
|
private int[] deprecatedOooPortNumbers;
|
||||||
|
|
||||||
public void setMaxTasksPerProcess(String maxTasksPerProcess)
|
void setMaxTasksPerProcess(String maxTasksPerProcess)
|
||||||
{
|
{
|
||||||
Long l = parseStringForLong(maxTasksPerProcess.trim());
|
Long l = parseStringForLong(maxTasksPerProcess.trim());
|
||||||
if (l != null)
|
if (l != null)
|
||||||
@ -96,7 +95,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOfficeHome(String officeHome)
|
void setOfficeHome(String officeHome)
|
||||||
{
|
{
|
||||||
this.officeHome = officeHome == null ? "" : officeHome.trim();
|
this.officeHome = officeHome == null ? "" : officeHome.trim();
|
||||||
}
|
}
|
||||||
@ -106,7 +105,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
this.deprecatedOooExe = deprecatedOooExe == null ? "" : deprecatedOooExe.trim();
|
this.deprecatedOooExe = deprecatedOooExe == null ? "" : deprecatedOooExe.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPortNumbers(String s)
|
void setPortNumbers(String s)
|
||||||
{
|
{
|
||||||
portNumbers = parsePortNumbers(s, "jodconverter");
|
portNumbers = parsePortNumbers(s, "jodconverter");
|
||||||
}
|
}
|
||||||
@ -147,12 +146,12 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
return portNumbers;
|
return portNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTaskExecutionTimeout(String taskExecutionTimeout)
|
void setTaskExecutionTimeout(String taskExecutionTimeout)
|
||||||
{
|
{
|
||||||
this.taskExecutionTimeout = parseStringForLong(taskExecutionTimeout.trim());
|
this.taskExecutionTimeout = parseStringForLong(taskExecutionTimeout.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTemplateProfileDir(String templateProfileDir)
|
void setTemplateProfileDir(String templateProfileDir)
|
||||||
{
|
{
|
||||||
if (templateProfileDir == null || templateProfileDir.trim().length() == 0)
|
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());
|
this.taskQueueTimeout = parseStringForLong(taskQueueTimeout.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConnectTimeout(String connectTimeout)
|
void setConnectTimeout(String connectTimeout)
|
||||||
{
|
{
|
||||||
this.connectTimeout = parseStringForLong(connectTimeout.trim());
|
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 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.
|
// 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.
|
// 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
|
// 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
|
// 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.
|
// oooDirect was replaced by jodconverter after this release.
|
||||||
String getOfficeHome()
|
private String getOfficeHome()
|
||||||
{
|
{
|
||||||
String officeHome = this.officeHome;
|
String officeHome = this.officeHome;
|
||||||
if ((officeHome == null || officeHome.isEmpty()) && (deprecatedOooExe != null && !deprecatedOooExe.isEmpty()))
|
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.
|
// Community set properties via alfresco-global.properties.
|
||||||
// Enterprise may do the same but may also reset jodconverter.enabled them via the Admin console.
|
// 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.
|
// 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);
|
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
|
// 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
|
// ooo.port setting rather than the jodconverter.portNumbers if ooo.enabled is true and jodconverter.enabled
|
||||||
// is false.
|
// is false.
|
||||||
int[] getPortNumbers()
|
private int[] getPortNumbers()
|
||||||
{
|
{
|
||||||
return (enabled == null || !enabled) && deprecatedOooEnabled != null && deprecatedOooEnabled
|
return (enabled == null || !enabled) && deprecatedOooEnabled != null && deprecatedOooEnabled
|
||||||
? deprecatedOooPortNumbers
|
? deprecatedOooPortNumbers
|
||||||
@ -260,11 +259,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
|
|
||||||
private Long parseStringForLong(String string)
|
private Long parseStringForLong(String string)
|
||||||
{
|
{
|
||||||
Long result = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long l = Long.parseLong(string);
|
return Long.parseLong(string);
|
||||||
result = new Long(l);
|
|
||||||
}
|
}
|
||||||
catch (NumberFormatException nfe)
|
catch (NumberFormatException nfe)
|
||||||
{
|
{
|
||||||
@ -272,9 +269,8 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
{
|
{
|
||||||
logger.debug("Cannot parse numerical value from " + string);
|
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()
|
public boolean isAvailable()
|
||||||
{
|
{
|
||||||
final boolean result = isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
|
return isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void afterPropertiesSet()
|
public void afterPropertiesSet()
|
||||||
{
|
{
|
||||||
// isAvailable defaults to false afterPropertiesSet. It only becomes true on successful completion of this method.
|
// 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.
|
// Only start the JodConverter instance(s) if the subsystem is enabled.
|
||||||
if (isEnabled() == false)
|
if (!isEnabled())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -418,7 +414,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
|
|
||||||
private void logAllSofficeFilesUnderOfficeHome()
|
private void logAllSofficeFilesUnderOfficeHome()
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled() == false)
|
if (!logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -430,7 +426,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
|
|
||||||
logFileInfo(requestedOfficeHome);
|
logFileInfo(requestedOfficeHome);
|
||||||
|
|
||||||
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<File>(), 2))
|
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<>(), 2))
|
||||||
{
|
{
|
||||||
logFileInfo(f);
|
logFileInfo(f);
|
||||||
}
|
}
|
||||||
@ -449,26 +445,11 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
File[] matchingFiles = searchRoot.listFiles(new FilenameFilter()
|
File[] matchingFiles = searchRoot.listFiles((dir, name) -> name.startsWith("soffice"));
|
||||||
{
|
Arrays.stream(matchingFiles)
|
||||||
@Override
|
.forEach(results::add);
|
||||||
public boolean accept(File dir, String name)
|
|
||||||
{
|
|
||||||
return name.startsWith("soffice");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (File f : matchingFiles)
|
|
||||||
{
|
|
||||||
results.add(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (File dir : searchRoot.listFiles(new FileFilter()
|
for (File dir : searchRoot.listFiles(File::isDirectory))
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public boolean accept(File f) {
|
|
||||||
return f.isDirectory();
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
{
|
{
|
||||||
findSofficePrograms(dir, results, currentRecursionDepth + 1, maxRecursionDepth);
|
findSofficePrograms(dir, results, currentRecursionDepth + 1, maxRecursionDepth);
|
||||||
}
|
}
|
||||||
@ -482,7 +463,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
*/
|
*/
|
||||||
private void logFileInfo(File f)
|
private void logFileInfo(File f)
|
||||||
{
|
{
|
||||||
if (logger.isDebugEnabled() == false)
|
if (!logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -512,7 +493,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||||
*/
|
*/
|
||||||
public void destroy() throws Exception {
|
@Override
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
this.isAvailable = false;
|
this.isAvailable = false;
|
||||||
if (officeManager != null)
|
if (officeManager != null)
|
||||||
{
|
{
|
||||||
@ -530,6 +513,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
|||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.alfresco.repo.content.JodConverterWorker#getOfficeManager()
|
* @see org.alfresco.repo.content.JodConverterWorker#getOfficeManager()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public OfficeManager getOfficeManager()
|
public OfficeManager getOfficeManager()
|
||||||
{
|
{
|
||||||
return officeManager;
|
return officeManager;
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,26 +29,38 @@ import static org.hamcrest.Matchers.containsString;
|
|||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Matchers.anyLong;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doThrow;
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
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.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.artofsolving.jodconverter.office.OfficeException;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.Mock;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
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.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
@ -62,15 +74,18 @@ import org.springframework.util.StringUtils;
|
|||||||
@WebMvcTest(LibreOfficeControllerTest.class)
|
@WebMvcTest(LibreOfficeControllerTest.class)
|
||||||
public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||||
{
|
{
|
||||||
|
@Mock
|
||||||
|
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||||
|
|
||||||
|
@SpyBean
|
||||||
|
private LibreOfficeJavaExecutor javaExecutor;
|
||||||
|
|
||||||
@SpyBean
|
@SpyBean
|
||||||
private LibreOfficeController controller;
|
private LibreOfficeController controller;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws IOException
|
public void before() throws IOException
|
||||||
{
|
{
|
||||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
|
||||||
super.controller = controller;
|
|
||||||
|
|
||||||
sourceExtension = "doc";
|
sourceExtension = "doc";
|
||||||
targetExtension = "pdf";
|
targetExtension = "pdf";
|
||||||
sourceMimetype = "application/msword";
|
sourceMimetype = "application/msword";
|
||||||
@ -78,11 +93,11 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
|||||||
// The following is based on super.mockTransformCommand(...)
|
// The following is based on super.mockTransformCommand(...)
|
||||||
// This is because LibreOffice used JodConverter rather than a RuntimeExec
|
// This is because LibreOffice used JodConverter rather than a RuntimeExec
|
||||||
|
|
||||||
expectedSourceFileBytes = Files.readAllBytes(getTestFile("quick."+sourceExtension, true).toPath());
|
expectedSourceFileBytes = Files.readAllBytes(getTestFile("quick." + sourceExtension, true).toPath());
|
||||||
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick."+targetExtension, true).toPath());
|
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick." + targetExtension, true).toPath());
|
||||||
sourceFile = new MockMultipartFile("file", "quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||||
|
|
||||||
doAnswer((Answer) invocation ->
|
doAnswer(invocation ->
|
||||||
{
|
{
|
||||||
File sourceFile = invocation.getArgument(0);
|
File sourceFile = invocation.getArgument(0);
|
||||||
File targetFile = invocation.getArgument(1);
|
File targetFile = invocation.getArgument(1);
|
||||||
@ -91,19 +106,12 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
|||||||
assertNotNull(sourceFile);
|
assertNotNull(sourceFile);
|
||||||
assertNotNull(targetFile);
|
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
|
// Copy a test file into the target file location if it exists
|
||||||
String actualTarget = targetFile.getAbsolutePath();
|
String actualTarget = targetFile.getAbsolutePath();
|
||||||
int i = actualTarget.lastIndexOf('_');
|
int i = actualTarget.lastIndexOf('_');
|
||||||
if (i >= 0)
|
if (i >= 0)
|
||||||
{
|
{
|
||||||
String testFilename = actualTarget.substring(i+1);
|
String testFilename = actualTarget.substring(i + 1);
|
||||||
File testFile = getTestFile(testFilename, false);
|
File testFile = getTestFile(testFilename, false);
|
||||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile, targetFile);
|
generateTargetFileFromResourceFile(actualTargetExtension, testFile, targetFile);
|
||||||
}
|
}
|
||||||
@ -113,20 +121,32 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
|||||||
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||||
|
|
||||||
return null;
|
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
|
@Test
|
||||||
@Override
|
|
||||||
public void badExitCodeTest() throws Exception
|
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)
|
.file(sourceFile)
|
||||||
.param("targetExtension", "xxx"))
|
.param("targetExtension", "xxx"))
|
||||||
.andExpect(status().is(400))
|
.andExpect(status().is(400))
|
||||||
.andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:")));
|
.andExpect(status().reason(containsString("LibreOffice - LibreOffice server conversion failed:")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -137,4 +157,58 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
|||||||
transformRequest.setSourceMediaType("application/msword");
|
transformRequest.setSourceMediaType("application/msword");
|
||||||
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
@ -47,5 +46,5 @@ public class LibreOfficeHttpRequestTest extends AbstractHttpRequestTest
|
|||||||
protected String getSourceExtension()
|
protected String getSourceExtension()
|
||||||
{
|
{
|
||||||
return "doc";
|
return "doc";
|
||||||
};
|
}
|
||||||
}
|
}
|
@ -11,15 +11,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
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.EnableAutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
|
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
|
||||||
public class Application
|
public class Application
|
||||||
@ -27,10 +29,18 @@ public class Application
|
|||||||
@Value("${container.name}")
|
@Value("${container.name}")
|
||||||
private String containerName;
|
private String containerName;
|
||||||
|
|
||||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
@Bean
|
||||||
|
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags()
|
||||||
|
{
|
||||||
return registry -> registry.config().commonTags("containerName", containerName);
|
return registry -> registry.config().commonTags("containerName", containerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TikaJavaExecutor javaExecutor() throws Exception
|
||||||
|
{
|
||||||
|
return new TikaJavaExecutor();
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(Application.class, args);
|
||||||
|
@ -12,21 +12,31 @@
|
|||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer;
|
||||||
|
|
||||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN;
|
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN;
|
||||||
import static org.alfresco.transformer.Tika.INCLUDE_CONTENTS;
|
import static org.alfresco.transformer.executors.Tika.INCLUDE_CONTENTS;
|
||||||
import static org.alfresco.transformer.Tika.NOT_EXTRACT_BOOKMARKS_TEXT;
|
import static org.alfresco.transformer.executors.Tika.NOT_EXTRACT_BOOKMARKS_TEXT;
|
||||||
import static org.alfresco.transformer.Tika.PDF_BOX;
|
import static org.alfresco.transformer.executors.Tika.PDF_BOX;
|
||||||
import static org.alfresco.transformer.Tika.TARGET_ENCODING;
|
import static org.alfresco.transformer.executors.Tika.TARGET_ENCODING;
|
||||||
import static org.alfresco.transformer.Tika.TARGET_MIMETYPE;
|
import static org.alfresco.transformer.executors.Tika.TARGET_MIMETYPE;
|
||||||
import static org.alfresco.transformer.Tika.TRANSFORM_NAMES;
|
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.File;
|
||||||
import java.io.IOException;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.commons.logging.LogFactory;
|
||||||
import org.apache.tika.exception.TikaException;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@ -35,95 +45,87 @@ import org.springframework.stereotype.Controller;
|
|||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.xml.sax.SAXException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller for the Docker based Tika transformers.
|
* Controller for the Docker based Tika transformers.
|
||||||
*
|
*
|
||||||
* Status Codes:
|
* Status Codes:
|
||||||
*
|
*
|
||||||
* 200 Success
|
* 200 Success
|
||||||
* 400 Bad Request: Invalid target mimetype <mimetype>
|
* 400 Bad Request: Invalid target mimetype <mimetype>
|
||||||
* 400 Bad Request: Request parameter <name> is missing (missing mandatory parameter)
|
* 400 Bad Request: Request parameter <name> is missing (missing mandatory parameter)
|
||||||
* 400 Bad Request: Request parameter <name> is of the wrong type
|
* 400 Bad Request: Request parameter <name> is of the wrong type
|
||||||
* 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file)
|
* 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file)
|
||||||
* 400 Bad Request: The source filename was not supplied
|
* 400 Bad Request: The source filename was not supplied
|
||||||
* 500 Internal Server Error: (no message with low level IO problems)
|
* 500 Internal Server Error: (no message with low level IO problems)
|
||||||
* 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked)
|
* 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked)
|
||||||
* 500 Internal Server Error: Transformer version check exit code was not 0
|
* 500 Internal Server Error: Transformer version check exit code was not 0
|
||||||
* 500 Internal Server Error: Transformer version check failed to create any output
|
* 500 Internal Server Error: Transformer version check failed to create any output
|
||||||
* 500 Internal Server Error: Could not read the target file
|
* 500 Internal Server Error: Could not read the target file
|
||||||
* 500 Internal Server Error: The target filename was malformed (should not happen because of other checks)
|
* 500 Internal Server Error: The target filename was malformed (should not happen because of other checks)
|
||||||
* 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content)
|
* 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content)
|
||||||
* 500 Internal Server Error: Filename encoding error
|
* 500 Internal Server Error: Filename encoding error
|
||||||
* 507 Insufficient Storage: Failed to store the source file
|
* 507 Insufficient Storage: Failed to store the source file
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
public class TikaController extends AbstractTransformerController
|
public class TikaController extends AbstractTransformerController
|
||||||
{
|
{
|
||||||
private Tika tika;
|
private static final Log logger = LogFactory.getLog(TikaController.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public TikaController() throws TikaException, IOException, SAXException
|
private TikaJavaExecutor javaExecutor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public TikaController()
|
||||||
{
|
{
|
||||||
logger = LogFactory.getLog(TikaController.class);
|
|
||||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
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 is from Apache. See the license at http://www.apache.org/licenses/LICENSE-2.0. or in /Apache\\ 2.0.txt");
|
||||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||||
|
|
||||||
tika = new Tika();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTransformerName()
|
public String getTransformerName()
|
||||||
{
|
{
|
||||||
return "Tika";
|
return "Tika";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void callTransform(String... args)
|
public String version()
|
||||||
{
|
|
||||||
tika.transform(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String version()
|
|
||||||
{
|
{
|
||||||
return "Tika available";
|
return "Tika available";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ProbeTestTransform getProbeTestTransform()
|
public ProbeTestTransform getProbeTestTransform()
|
||||||
{
|
{
|
||||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
// 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.
|
// 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)
|
60, 16, 400, 10240, 60 * 30 + 1, 60 * 15 + 20)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
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");
|
TARGET_MIMETYPE + MIMETYPE_TEXT_PLAIN, TARGET_ENCODING + "UTF-8");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
||||||
@RequestParam("file") MultipartFile sourceMultipartFile,
|
@RequestParam("file") MultipartFile sourceMultipartFile,
|
||||||
@RequestParam("targetExtension") String targetExtension,
|
@RequestParam("targetExtension") String targetExtension,
|
||||||
@RequestParam("targetMimetype") String targetMimetype,
|
@RequestParam("targetMimetype") String targetMimetype,
|
||||||
@RequestParam("targetEncoding") String targetEncoding,
|
@RequestParam("targetEncoding") String targetEncoding,
|
||||||
|
|
||||||
@RequestParam(value = "timeout", required = false) Long timeout,
|
@RequestParam(value = "timeout", required = false) Long timeout,
|
||||||
@RequestParam(value = "testDelay", required = false) Long testDelay,
|
@RequestParam(value = "testDelay", required = false) Long testDelay,
|
||||||
|
|
||||||
@RequestParam(value = "transform") String transform,
|
@RequestParam(value = "transform") String transform,
|
||||||
@RequestParam(value="includeContents", required = false) Boolean includeContents,
|
@RequestParam(value = "includeContents", required = false) Boolean includeContents,
|
||||||
@RequestParam(value="notExtractBookmarksText", required = false) Boolean notExtractBookmarksText)
|
@RequestParam(value = "notExtractBookmarksText", required = false) Boolean notExtractBookmarksText)
|
||||||
|
|
||||||
{
|
{
|
||||||
if (!TRANSFORM_NAMES.contains(transform))
|
if (!TRANSFORM_NAMES.contains(transform))
|
||||||
{
|
{
|
||||||
@ -131,6 +133,7 @@ public class TikaController extends AbstractTransformerController
|
|||||||
}
|
}
|
||||||
|
|
||||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||||
|
getProbeTestTransform().incrementTransformerCount();
|
||||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||||
File targetFile = createTargetFile(request, targetFilename);
|
File targetFile = createTargetFile(request, targetFilename);
|
||||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||||
@ -138,16 +141,21 @@ public class TikaController extends AbstractTransformerController
|
|||||||
// TODO Consider streaming the request and response rather than using temporary files
|
// 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
|
// 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,
|
includeContents != null && includeContents ? INCLUDE_CONTENTS : null,
|
||||||
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT: null,
|
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT : null,
|
||||||
TARGET_MIMETYPE+targetMimetype, TARGET_ENCODING+targetEncoding);
|
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
|
@Override
|
||||||
protected void processTransform(File sourceFile, File targetFile,
|
public void processTransform(File sourceFile, File targetFile,
|
||||||
Map<String, String> transformOptions, Long timeout)
|
Map<String, String> transformOptions, Long timeout)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -157,9 +165,9 @@ public class TikaController extends AbstractTransformerController
|
|||||||
String targetMimetype = transformOptions.get("targetMimetype");
|
String targetMimetype = transformOptions.get("targetMimetype");
|
||||||
String targetEncoding = transformOptions.get("targetEncoding");
|
String targetEncoding = transformOptions.get("targetEncoding");
|
||||||
|
|
||||||
callTransform(sourceFile, targetFile, transform,
|
javaExecutor.call(sourceFile, targetFile, transform,
|
||||||
includeContents != null && includeContents ? INCLUDE_CONTENTS : null,
|
includeContents != null && includeContents ? INCLUDE_CONTENTS : null,
|
||||||
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT: null,
|
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT : null,
|
||||||
TARGET_MIMETYPE + targetMimetype, TARGET_ENCODING + targetEncoding);
|
TARGET_MIMETYPE + targetMimetype, TARGET_ENCODING + targetEncoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,36 @@
|
|||||||
* agreement is prohibited.
|
* agreement is prohibited.
|
||||||
* #L%
|
* #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.config.TikaConfig;
|
||||||
import org.apache.tika.exception.TikaException;
|
import org.apache.tika.exception.TikaException;
|
||||||
@ -30,19 +59,6 @@ import org.xml.sax.Attributes;
|
|||||||
import org.xml.sax.ContentHandler;
|
import org.xml.sax.ContentHandler;
|
||||||
import org.xml.sax.SAXException;
|
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
|
* Stripped down command line Tika transformers. Not actually run as a separate process, but the code fits the patten
|
||||||
* used by transformers that do.
|
* used by transformers that do.
|
||||||
@ -424,7 +440,7 @@ public class Tika
|
|||||||
public static final String TIKA_AUTO = "TikaAuto";
|
public static final String TIKA_AUTO = "TikaAuto";
|
||||||
public static final String TEXT_MINING = "TextMining";
|
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);
|
ARCHIVE, OUTLOOK_MSG, PDF_BOX, POI_OFFICE, POI, POI_OO_XML, TIKA_AUTO, TEXT_MINING);
|
||||||
|
|
||||||
public static final String TARGET_MIMETYPE = "--targetMimetype=";
|
public static final String TARGET_MIMETYPE = "--targetMimetype=";
|
||||||
@ -445,17 +461,17 @@ public class Tika
|
|||||||
public static final String XML = "xml";
|
public static final String XML = "xml";
|
||||||
public static final String ZIP = "zip";
|
public static final String ZIP = "zip";
|
||||||
|
|
||||||
private Parser packageParser = new PackageParser();
|
private final Parser packageParser = new PackageParser();
|
||||||
private Parser pdfParser = new PDFParser();
|
private final Parser pdfParser = new PDFParser();
|
||||||
private Parser officeParser = new OfficeParser();
|
private final Parser officeParser = new OfficeParser();
|
||||||
private Parser autoDetectParser;
|
private final Parser autoDetectParser;
|
||||||
private Parser ooXmlParser = new OOXMLParser();
|
private final Parser ooXmlParser = new OOXMLParser();
|
||||||
private Parser tikaOfficeDetectParser = new TikaOfficeDetectParser();
|
private final Parser tikaOfficeDetectParser = new TikaOfficeDetectParser();
|
||||||
private PDFParserConfig pdfParserConfig = new PDFParserConfig();
|
private final PDFParserConfig pdfParserConfig = new PDFParserConfig();
|
||||||
|
|
||||||
private DocumentSelector pdfBoxEmbededDocumentSelector = new DocumentSelector()
|
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
|
@Override
|
||||||
public boolean select(Metadata metadata)
|
public boolean select(Metadata metadata)
|
||||||
@ -628,17 +644,14 @@ public class Tika
|
|||||||
String sourceFilename,
|
String sourceFilename,
|
||||||
String targetFilename, String targetMimetype, String targetEncoding)
|
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();
|
Metadata metadata = new Metadata();
|
||||||
ParseContext context = buildParseContext(documentSelector, includeContents, notExtractBookmarksText);
|
ParseContext context = buildParseContext(documentSelector, includeContents,
|
||||||
|
notExtractBookmarksText);
|
||||||
ContentHandler handler = getContentHandler(targetMimetype, ow);
|
ContentHandler handler = getContentHandler(targetMimetype, ow);
|
||||||
|
|
||||||
parser.parse(is, handler, metadata, context);
|
parser.parse(is, handler, metadata, context);
|
||||||
@ -647,24 +660,9 @@ public class Tika
|
|||||||
{
|
{
|
||||||
throw new IllegalStateException(e.getMessage(), e);
|
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
|
try
|
||||||
{
|
{
|
||||||
@ -676,7 +674,7 @@ public class Tika
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
|
SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
|
||||||
TransformerHandler transformerHandler = null;
|
TransformerHandler transformerHandler;
|
||||||
transformerHandler = factory.newTransformerHandler();
|
transformerHandler = factory.newTransformerHandler();
|
||||||
transformerHandler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
|
transformerHandler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
transformerHandler.setResult(new StreamResult(output));
|
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();
|
ParseContext context = new ParseContext();
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer.executors;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
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.ContentHandler;
|
||||||
import org.xml.sax.SAXException;
|
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
|
* <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
|
* @author Nick Burch
|
||||||
*/
|
*/
|
||||||
public class TikaOfficeDetectParser implements Parser {
|
public class TikaOfficeDetectParser implements Parser {
|
||||||
private Parser ole2Parser = new OfficeParser();
|
private final Parser ole2Parser = new OfficeParser();
|
||||||
private Parser ooxmlParser = new OOXMLParser();
|
private final Parser ooxmlParser = new OOXMLParser();
|
||||||
|
|
||||||
public Set<MediaType> getSupportedTypes(ParseContext parseContext) {
|
public Set<MediaType> getSupportedTypes(ParseContext parseContext) {
|
||||||
Set<MediaType> types = new HashSet<MediaType>();
|
Set<MediaType> types = new HashSet<>();
|
||||||
types.addAll(ole2Parser.getSupportedTypes(parseContext));
|
types.addAll(ole2Parser.getSupportedTypes(parseContext));
|
||||||
types.addAll(ooxmlParser.getSupportedTypes(parseContext));
|
types.addAll(ooxmlParser.getSupportedTypes(parseContext));
|
||||||
return types;
|
return types;
|
@ -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_XHTML;
|
||||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XML;
|
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XML;
|
||||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_ZIP;
|
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_ZIP;
|
||||||
import static org.alfresco.transformer.Tika.ARCHIVE;
|
import static org.alfresco.transformer.executors.Tika.ARCHIVE;
|
||||||
import static org.alfresco.transformer.Tika.CSV;
|
import static org.alfresco.transformer.executors.Tika.CSV;
|
||||||
import static org.alfresco.transformer.Tika.DOC;
|
import static org.alfresco.transformer.executors.Tika.DOC;
|
||||||
import static org.alfresco.transformer.Tika.DOCX;
|
import static org.alfresco.transformer.executors.Tika.DOCX;
|
||||||
import static org.alfresco.transformer.Tika.HTML;
|
import static org.alfresco.transformer.executors.Tika.HTML;
|
||||||
import static org.alfresco.transformer.Tika.MSG;
|
import static org.alfresco.transformer.executors.Tika.MSG;
|
||||||
import static org.alfresco.transformer.Tika.OUTLOOK_MSG;
|
import static org.alfresco.transformer.executors.Tika.OUTLOOK_MSG;
|
||||||
import static org.alfresco.transformer.Tika.PDF;
|
import static org.alfresco.transformer.executors.Tika.PDF;
|
||||||
import static org.alfresco.transformer.Tika.PDF_BOX;
|
import static org.alfresco.transformer.executors.Tika.PDF_BOX;
|
||||||
import static org.alfresco.transformer.Tika.POI;
|
import static org.alfresco.transformer.executors.Tika.POI;
|
||||||
import static org.alfresco.transformer.Tika.POI_OFFICE;
|
import static org.alfresco.transformer.executors.Tika.POI_OFFICE;
|
||||||
import static org.alfresco.transformer.Tika.POI_OO_XML;
|
import static org.alfresco.transformer.executors.Tika.POI_OO_XML;
|
||||||
import static org.alfresco.transformer.Tika.PPTX;
|
import static org.alfresco.transformer.executors.Tika.PPTX;
|
||||||
import static org.alfresco.transformer.Tika.TEXT_MINING;
|
import static org.alfresco.transformer.executors.Tika.TEXT_MINING;
|
||||||
import static org.alfresco.transformer.Tika.TIKA_AUTO;
|
import static org.alfresco.transformer.executors.Tika.TIKA_AUTO;
|
||||||
import static org.alfresco.transformer.Tika.TXT;
|
import static org.alfresco.transformer.executors.Tika.TXT;
|
||||||
import static org.alfresco.transformer.Tika.XHTML;
|
import static org.alfresco.transformer.executors.Tika.XHTML;
|
||||||
import static org.alfresco.transformer.Tika.XML;
|
import static org.alfresco.transformer.executors.Tika.XML;
|
||||||
import static org.alfresco.transformer.Tika.XSLX;
|
import static org.alfresco.transformer.executors.Tika.XSLX;
|
||||||
import static org.alfresco.transformer.Tika.ZIP;
|
import static org.alfresco.transformer.executors.Tika.ZIP;
|
||||||
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
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.header;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
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.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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
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.autoconfigure.web.servlet.WebMvcTest;
|
||||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
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.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
import org.springframework.test.context.junit4.SpringRunner;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test the TikaController without a server.
|
* Test the TikaController without a server.
|
||||||
@ -81,37 +108,126 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
|
|||||||
@WebMvcTest(TikaController.class)
|
@WebMvcTest(TikaController.class)
|
||||||
public class TikaControllerTest extends AbstractTransformerControllerTest
|
public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||||
{
|
{
|
||||||
public static final String EXPECTED_XHTML_CONTENT_CONTAINS = "<p>The quick brown fox jumps over the lazy dog</p>";
|
private 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";
|
private 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_MSG_CONTENT_CONTAINS = "Recipients\n" +
|
||||||
"\tmark.rogers@alfresco.com; speedy@quick.com; mrquick@nowhere.com\n" +
|
"\tmark.rogers@alfresco.com; speedy@quick.com; mrquick@nowhere.com\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"The quick brown fox jumps over the lazy dogs";
|
"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
|
@SpyBean
|
||||||
private TikaController controller;
|
private TikaController controller;
|
||||||
|
|
||||||
String transform = PDF_BOX;
|
private String transform = PDF_BOX;
|
||||||
String targetEncoding = "UTF-8";
|
private String targetEncoding = "UTF-8";
|
||||||
String targetMimetype = MIMETYPE_TEXT_PLAIN;
|
private String targetMimetype = MIMETYPE_TEXT_PLAIN;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() throws Exception
|
public void before()
|
||||||
{
|
{
|
||||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
|
||||||
super.controller = controller;
|
|
||||||
|
|
||||||
sourceExtension = "pdf";
|
sourceExtension = "pdf";
|
||||||
targetExtension = "txt";
|
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,
|
private void transform(String transform, String sourceExtension, String targetExtension,
|
||||||
String sourceMimetype, String targetMimetype,
|
String sourceMimetype, String targetMimetype,
|
||||||
Boolean includeContents, String expectedContentContains) throws Exception
|
Boolean includeContents, String expectedContentContains) throws Exception
|
||||||
{
|
{
|
||||||
// We don't use targetFileBytes as some of the transforms contain different date text based on the os being used.
|
// 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.transform = transform;
|
||||||
this.targetMimetype = targetMimetype;
|
this.targetMimetype = targetMimetype;
|
||||||
|
|
||||||
@ -141,7 +257,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void simpleTransformTest() throws Exception
|
public void simpleTransformTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.simpleTransformTest();
|
super.simpleTransformTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,21 +265,13 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void testDelayTest() throws Exception
|
public void testDelayTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.testDelayTest();
|
super.testDelayTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Override
|
@Override
|
||||||
public void badExitCodeTest() 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 non zero exit code.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Override
|
|
||||||
public void noTargetFileTest() throws Exception
|
|
||||||
{
|
{
|
||||||
// Ignore the test in super class as the Tika transforms are real rather than mocked up.
|
// 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.
|
// 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
|
@Override
|
||||||
public void dotDotSourceFilenameTest() throws Exception
|
public void dotDotSourceFilenameTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.dotDotSourceFilenameTest();
|
super.dotDotSourceFilenameTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +291,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void noExtensionSourceFilenameTest() throws Exception
|
public void noExtensionSourceFilenameTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.noExtensionSourceFilenameTest();
|
super.noExtensionSourceFilenameTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +299,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void badSourceFilenameTest() throws Exception
|
public void badSourceFilenameTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.badSourceFilenameTest();
|
super.badSourceFilenameTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +307,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void blankSourceFilenameTest() throws Exception
|
public void blankSourceFilenameTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.blankSourceFilenameTest();
|
super.blankSourceFilenameTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +315,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void noTargetExtensionTest() throws Exception
|
public void noTargetExtensionTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.noTargetExtensionTest();
|
super.noTargetExtensionTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +323,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Override
|
@Override
|
||||||
public void calculateMaxTime() throws Exception
|
public void calculateMaxTime() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
super.calculateMaxTime();
|
super.calculateMaxTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +332,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Test
|
@Test
|
||||||
public void badEncodingTest() throws Exception
|
public void badEncodingTest() throws Exception
|
||||||
{
|
{
|
||||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||||
targetEncoding = "rubbish";
|
targetEncoding = "rubbish";
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||||
.andExpect(status().is(500));
|
.andExpect(status().is(500));
|
||||||
@ -388,7 +496,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
@Test
|
@Test
|
||||||
public void pdfToTxtExtractBookmarksTest() throws Exception
|
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"))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension).param("notExtractBookmarksText", "true"))
|
||||||
.andExpect(status().is(200))
|
.andExpect(status().is(200))
|
||||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||||
@ -405,4 +513,54 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
|||||||
transformRequest.getTransformRequestOptions().put("targetMimetype", MediaType.TEXT_PLAIN_VALUE);
|
transformRequest.getTransformRequestOptions().put("targetMimetype", MediaType.TEXT_PLAIN_VALUE);
|
||||||
transformRequest.getTransformRequestOptions().put("targetEncoding", "UTF-8");
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
|
|||||||
String options = args.toString();
|
String options = args.toString();
|
||||||
LogEntry.setOptions(options);
|
LogEntry.setOptions(options);
|
||||||
|
|
||||||
Map<String, String> properties = new HashMap<String, String>(5);
|
Map<String, String> properties = new HashMap<>();
|
||||||
properties.put("options", options);
|
properties.put("options", options);
|
||||||
properties.put("source", sourceFile.getAbsolutePath());
|
properties.put("source", sourceFile.getAbsolutePath());
|
||||||
properties.put("target", targetFile.getAbsolutePath());
|
properties.put("target", targetFile.getAbsolutePath());
|
||||||
|
@ -25,55 +25,39 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
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 static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||||
|
|
||||||
import java.io.File;
|
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 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.TransformReply;
|
||||||
import org.alfresco.transform.client.model.TransformRequest;
|
import org.alfresco.transform.client.model.TransformRequest;
|
||||||
import org.alfresco.transform.client.model.TransformRequestValidator;
|
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.transformer.model.FileRefResponse;
|
||||||
import org.alfresco.util.TempFileProvider;
|
import org.alfresco.util.TempFileProvider;
|
||||||
import org.alfresco.util.exec.RuntimeExec;
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.springframework.beans.TypeMismatchException;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.UrlResource;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.ui.Model;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.DirectFieldBindingResult;
|
import org.springframework.validation.DirectFieldBindingResult;
|
||||||
import org.springframework.validation.Errors;
|
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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
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.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
import org.springframework.web.client.HttpClientErrorException;
|
import org.springframework.web.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>
|
* <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
|
* <p>Provides methods to help super classes perform /transform requests. Also responses to /version, /ready and /live
|
||||||
* requests.</p>
|
* requests.</p>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractTransformerController
|
public abstract class AbstractTransformerController implements TransformController
|
||||||
{
|
{
|
||||||
public static final String SOURCE_FILE = "sourceFile";
|
private static final Log logger = LogFactory.getLog(AbstractTransformerController.class);
|
||||||
public static final String TARGET_FILE = "targetFile";
|
|
||||||
public static final String FILENAME = "filename=";
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
|
private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
|
||||||
@ -115,123 +97,90 @@ public abstract class AbstractTransformerController
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TransformRequestValidator transformRequestValidator;
|
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'
|
* '/transform' endpoint which consumes and produces 'application/json'
|
||||||
*
|
*
|
||||||
* This is the way to tell Spring to redirect the request to this endpoint
|
* This is the way to tell Spring to redirect the request to this endpoint
|
||||||
* instead of the older one, which produces 'html'
|
* instead of the older one, which produces 'html'
|
||||||
*
|
*
|
||||||
* @param transformRequest The transformation request
|
* @param request The transformation request
|
||||||
* @param timeout Transformation timeout
|
* @param timeout Transformation timeout
|
||||||
* @return A transformation reply
|
* @return A transformation reply
|
||||||
*/
|
*/
|
||||||
@PostMapping(value = "/transform", produces = APPLICATION_JSON_VALUE)
|
@PostMapping(value = "/transform", produces = APPLICATION_JSON_VALUE)
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
public ResponseEntity<TransformReply> transform(@RequestBody TransformRequest transformRequest,
|
public ResponseEntity<TransformReply> transform(@RequestBody TransformRequest request,
|
||||||
@RequestParam(value = "timeout", required = false) Long timeout)
|
@RequestParam(value = "timeout", required = false) Long timeout)
|
||||||
{
|
{
|
||||||
TransformReply transformReply = new TransformReply();
|
final TransformReply reply = new TransformReply();
|
||||||
transformReply.setRequestId(transformRequest.getRequestId());
|
reply.setRequestId(request.getRequestId());
|
||||||
transformReply.setSourceReference(transformRequest.getSourceReference());
|
reply.setSourceReference(request.getSourceReference());
|
||||||
transformReply.setSchema(transformRequest.getSchema());
|
reply.setSchema(request.getSchema());
|
||||||
transformReply.setClientData(transformRequest.getClientData());
|
reply.setClientData(request.getClientData());
|
||||||
|
|
||||||
Errors errors = validateTransformRequest(transformRequest);
|
final Errors errors = validateTransformRequest(request);
|
||||||
if (!errors.getAllErrors().isEmpty())
|
if (!errors.getAllErrors().isEmpty())
|
||||||
{
|
{
|
||||||
transformReply.setStatus(HttpStatus.BAD_REQUEST.value());
|
reply.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||||
transformReply.setErrorDetails(errors.getAllErrors().stream().map(Object::toString)
|
reply.setErrorDetails(errors.getAllErrors().stream().map(Object::toString)
|
||||||
.collect(Collectors.joining(", ")));
|
.collect(Collectors.joining(", ")));
|
||||||
|
|
||||||
return new ResponseEntity<>(transformReply,
|
return new ResponseEntity<>(reply,
|
||||||
HttpStatus.valueOf(transformReply.getStatus()));
|
HttpStatus.valueOf(reply.getStatus()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the source file
|
// Load the source file
|
||||||
File sourceFile;
|
File sourceFile;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sourceFile = loadSourceFile(transformRequest.getSourceReference());
|
sourceFile = loadSourceFile(request.getSourceReference());
|
||||||
}
|
}
|
||||||
catch (TransformException te)
|
catch (TransformException te)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(te.getStatusCode());
|
reply.setStatus(te.getStatusCode());
|
||||||
transformReply
|
reply .setErrorDetails("Failed at reading the source file. " + te.getMessage());
|
||||||
.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)
|
catch (HttpClientErrorException hcee)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(hcee.getStatusCode().value());
|
reply.setStatus(hcee.getStatusCode().value());
|
||||||
transformReply
|
reply .setErrorDetails("Failed at reading the source file. " + hcee.getMessage());
|
||||||
.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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(500);
|
reply.setStatus(500);
|
||||||
transformReply.setErrorDetails("Failed at reading the source file. " + e.getMessage());
|
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
|
// Create local temp target file in order to run the transformation
|
||||||
String targetFilename = createTargetFileName(sourceFile.getName(),
|
String targetFilename = createTargetFileName(sourceFile.getName(),
|
||||||
transformRequest.getTargetExtension());
|
request.getTargetExtension());
|
||||||
File targetFile = buildFile(targetFilename);
|
File targetFile = buildFile(targetFilename);
|
||||||
|
|
||||||
// Run the transformation
|
// Run the transformation
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
processTransform(sourceFile, targetFile,
|
processTransform(sourceFile, targetFile,
|
||||||
transformRequest.getTransformRequestOptions(), timeout);
|
request.getTransformRequestOptions(), timeout);
|
||||||
}
|
}
|
||||||
catch (TransformException te)
|
catch (TransformException te)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(te.getStatusCode());
|
reply.setStatus(te.getStatusCode());
|
||||||
transformReply
|
reply.setErrorDetails("Failed at processing transformation. " + te.getMessage());
|
||||||
.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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(500);
|
reply.setStatus(500);
|
||||||
transformReply
|
reply.setErrorDetails("Failed at processing transformation. " + e.getMessage());
|
||||||
.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
|
// Write the target file
|
||||||
@ -242,211 +191,50 @@ public abstract class AbstractTransformerController
|
|||||||
}
|
}
|
||||||
catch (TransformException te)
|
catch (TransformException te)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(te.getStatusCode());
|
reply.setStatus(te.getStatusCode());
|
||||||
transformReply
|
reply.setErrorDetails("Failed at writing the transformed file. " + te.getMessage());
|
||||||
.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)
|
catch (HttpClientErrorException hcee)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(hcee.getStatusCode().value());
|
reply.setStatus(hcee.getStatusCode().value());
|
||||||
transformReply
|
reply.setErrorDetails("Failed at writing the transformed file. " + hcee.getMessage());
|
||||||
.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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
transformReply.setStatus(500);
|
reply.setStatus(500);
|
||||||
transformReply
|
reply.setErrorDetails("Failed at writing the transformed file. " + e.getMessage());
|
||||||
.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());
|
reply.setTargetReference(targetRef.getEntry().getFileRef());
|
||||||
transformReply.setStatus(HttpStatus.CREATED.value());
|
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");
|
DirectFieldBindingResult errors = new DirectFieldBindingResult(transformRequest, "request");
|
||||||
transformRequestValidator.validate(transformRequest, errors);
|
transformRequestValidator.validate(transformRequest, errors);
|
||||||
return 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
|
* Loads the file with the specified sourceReference from Alfresco Shared File Store
|
||||||
*
|
*
|
||||||
* @param sourceReference reference to the file in 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
|
* @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
|
ResponseEntity<Resource> responseEntity = alfrescoSharedFileStoreClient
|
||||||
.retrieveFile(sourceReference);
|
.retrieveFile(sourceReference);
|
||||||
getProbeTestTransformInternal().incrementTransformerCount();
|
getProbeTestTransform().incrementTransformerCount();
|
||||||
|
|
||||||
HttpHeaders headers = responseEntity.getHeaders();
|
HttpHeaders headers = responseEntity.getHeaders();
|
||||||
String filename = getFilenameFromContentDisposition(headers);
|
String filename = getFilenameFromContentDisposition(headers);
|
||||||
@ -468,299 +256,4 @@ public abstract class AbstractTransformerController
|
|||||||
LogEntry.setSource(filename, size);
|
LogEntry.setSource(filename, size);
|
||||||
return file;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -25,18 +25,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
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.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
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
|
public class TransformInterceptor extends HandlerInterceptorAdapter
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request,
|
public boolean preHandle(HttpServletRequest request,
|
||||||
HttpServletResponse response, Object handler) throws Exception
|
HttpServletResponse response, Object handler)
|
||||||
{
|
{
|
||||||
LogEntry.start();
|
LogEntry.start();
|
||||||
return true;
|
return true;
|
||||||
@ -45,21 +49,11 @@ public class TransformInterceptor extends HandlerInterceptorAdapter
|
|||||||
@Override
|
@Override
|
||||||
public void afterCompletion(HttpServletRequest request,
|
public void afterCompletion(HttpServletRequest request,
|
||||||
HttpServletResponse response, Object handler, Exception ex)
|
HttpServletResponse response, Object handler, Exception ex)
|
||||||
throws Exception
|
|
||||||
{
|
{
|
||||||
// TargetFile cannot be deleted until completion, otherwise 0 bytes are sent.
|
// TargetFile cannot be deleted until completion, otherwise 0 bytes are sent.
|
||||||
deleteFile(request, AbstractTransformerController.SOURCE_FILE);
|
deleteFile(request, SOURCE_FILE);
|
||||||
deleteFile(request, AbstractTransformerController.TARGET_FILE);
|
deleteFile(request, TARGET_FILE);
|
||||||
|
|
||||||
LogEntry.complete();
|
LogEntry.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteFile(HttpServletRequest request, String attributeName)
|
|
||||||
{
|
|
||||||
File file = (File) request.getAttribute(attributeName);
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,11 @@
|
|||||||
* pursuant to a written agreement and any use of this program without such an
|
* pursuant to a written agreement and any use of this program without such an
|
||||||
* agreement is prohibited.
|
* agreement is prohibited.
|
||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer.clients;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import org.alfresco.transformer.exceptions.TransformException;
|
||||||
import org.alfresco.transformer.model.FileRefResponse;
|
import org.alfresco.transformer.model.FileRefResponse;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
@ -23,8 +23,10 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
@ -23,11 +23,11 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer.exceptions;
|
||||||
|
|
||||||
public class TransformException extends RuntimeException
|
public class TransformException extends RuntimeException
|
||||||
{
|
{
|
||||||
private int statusCode;
|
private final int statusCode;
|
||||||
|
|
||||||
public TransformException(int statusCode, String message)
|
public TransformException(int statusCode, String message)
|
||||||
{
|
{
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,9 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.transformer;
|
package org.alfresco.transformer.logging;
|
||||||
|
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -32,7 +34,8 @@ import java.util.Deque;
|
|||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
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
|
* 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
|
* current entry to an internal log Collection of the latest entries. The {@link #getLog()} method is used to obtain
|
||||||
* access to this collection.
|
* 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.
|
// 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);
|
private static final AtomicInteger count = new AtomicInteger(0);
|
||||||
@ -49,20 +53,15 @@ public class LogEntry
|
|||||||
private static final int MAX_LOG_SIZE = 10;
|
private static final int MAX_LOG_SIZE = 10;
|
||||||
private static final SimpleDateFormat HH_MM_SS = new SimpleDateFormat("HH:mm:ss");
|
private static final SimpleDateFormat HH_MM_SS = new SimpleDateFormat("HH:mm:ss");
|
||||||
|
|
||||||
private static ThreadLocal<LogEntry> currentLogEntry = new ThreadLocal<LogEntry>()
|
private static final ThreadLocal<LogEntry> currentLogEntry = ThreadLocal.withInitial(() -> {
|
||||||
{
|
LogEntry logEntry = new LogEntry();
|
||||||
@Override
|
if (log.size() >= MAX_LOG_SIZE)
|
||||||
protected LogEntry initialValue()
|
|
||||||
{
|
{
|
||||||
LogEntry logEntry = new LogEntry();
|
log.removeLast();
|
||||||
if (log.size() >= MAX_LOG_SIZE)
|
|
||||||
{
|
|
||||||
log.removeLast();
|
|
||||||
}
|
|
||||||
log.addFirst(logEntry);
|
|
||||||
return logEntry;
|
|
||||||
}
|
}
|
||||||
};
|
log.addFirst(logEntry);
|
||||||
|
return logEntry;
|
||||||
|
});
|
||||||
|
|
||||||
private final int id = count.incrementAndGet();
|
private final int id = count.incrementAndGet();
|
||||||
private final long start = System.currentTimeMillis();
|
private final long start = System.currentTimeMillis();
|
||||||
@ -204,9 +203,9 @@ public class LogEntry
|
|||||||
}
|
}
|
||||||
currentLogEntry.remove();
|
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)
|
private String size(long size)
|
||||||
{
|
{
|
||||||
|
// TODO fix numeric overflow in TB expression
|
||||||
return size == -1 ? "" : size(size, "1 byte",
|
return size == -1 ? "" : size(size, "1 byte",
|
||||||
new String[] { "bytes", " KB", " MB", " GB", " TB" },
|
new String[] { "bytes", " KB", " MB", " GB", " TB" },
|
||||||
new long[] { 1024, 1024*1024, 1024*1024*1024, 1024*1024*1024*1024, Long.MAX_VALUE });
|
new long[] { 1024, 1024*1024, 1024*1024*1024, 1024*1024*1024*1024, Long.MAX_VALUE });
|
@ -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" ;
|
||||||
|
}
|
@ -23,7 +23,10 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -35,6 +38,9 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
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.alfresco.util.TempFileProvider;
|
||||||
import org.apache.commons.logging.Log;
|
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>
|
* <li>maxTransformSeconds - the maximum time for a transformation, including failed ones.</li>
|
||||||
* </ul>
|
* </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 sourceFilename;
|
||||||
private final String targetFilename;
|
private final String targetFilename;
|
||||||
private final long minExpectedLength;
|
private final long minExpectedLength;
|
||||||
@ -70,22 +76,32 @@ abstract class ProbeTestTransform
|
|||||||
|
|
||||||
private final Log logger;
|
private final Log logger;
|
||||||
|
|
||||||
int livenessPercent;
|
private int livenessPercent;
|
||||||
long probeCount;
|
private long probeCount;
|
||||||
int transCount;
|
private int transCount;
|
||||||
long normalTime;
|
private long normalTime;
|
||||||
long maxTime = Long.MAX_VALUE;
|
private long maxTime = Long.MAX_VALUE;
|
||||||
long nextTransformTime;
|
private long nextTransformTime;
|
||||||
|
|
||||||
private final boolean livenessTransformEnabled;
|
private final boolean livenessTransformEnabled;
|
||||||
private final long livenessTransformPeriod;
|
private final long livenessTransformPeriod;
|
||||||
private long maxTransformCount = Long.MAX_VALUE;
|
private final long maxTransformCount;
|
||||||
private long maxTransformTime;
|
private long maxTransformTime;
|
||||||
|
|
||||||
private AtomicBoolean initialised = new AtomicBoolean(false);
|
private final AtomicBoolean initialised = new AtomicBoolean(false);
|
||||||
private AtomicBoolean readySent = new AtomicBoolean(false);
|
private final AtomicBoolean readySent = new AtomicBoolean(false);
|
||||||
private AtomicLong transformCount = new AtomicLong(0);
|
private final AtomicLong transformCount = new AtomicLong(0);
|
||||||
private AtomicBoolean die = new AtomicBoolean(false);
|
private final AtomicBoolean die = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public int getLivenessPercent()
|
||||||
|
{
|
||||||
|
return livenessPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getMaxTime()
|
||||||
|
{
|
||||||
|
return maxTime;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See Probes.md for more info.
|
* 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 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.
|
* @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,
|
String sourceFilename, String targetFilename, long expectedLength, long plusOrMinus,
|
||||||
int livenessPercent, long maxTransforms, long maxTransformSeconds,
|
int livenessPercent, long maxTransforms, long maxTransformSeconds,
|
||||||
long livenessTransformPeriodSeconds)
|
long livenessTransformPeriodSeconds)
|
||||||
{
|
{
|
||||||
logger = controller.logger;
|
this.logger = logger;
|
||||||
|
|
||||||
this.sourceFilename = sourceFilename;
|
this.sourceFilename = sourceFilename;
|
||||||
this.targetFilename = targetFilename;
|
this.targetFilename = targetFilename;
|
||||||
@ -128,7 +144,7 @@ abstract class ProbeTestTransform
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected long getPositiveLongEnv(String name, long defaultValue)
|
private long getPositiveLongEnv(String name, long defaultValue)
|
||||||
{
|
{
|
||||||
long l = -1;
|
long l = -1;
|
||||||
String env = System.getenv(name);
|
String env = System.getenv(name);
|
||||||
@ -171,7 +187,7 @@ abstract class ProbeTestTransform
|
|||||||
{
|
{
|
||||||
String probeMessage = getProbeMessage(isLiveProbe);
|
String probeMessage = getProbeMessage(isLiveProbe);
|
||||||
String message = "Success - No transform.";
|
String message = "Success - No transform.";
|
||||||
LogEntry.setStatusCodeAndMessage(200, probeMessage+message);
|
LogEntry.setStatusCodeAndMessage(200, probeMessage + message);
|
||||||
if (!isLiveProbe && !readySent.getAndSet(true))
|
if (!isLiveProbe && !readySent.getAndSet(true))
|
||||||
{
|
{
|
||||||
logger.info(probeMessage+message);
|
logger.info(probeMessage+message);
|
||||||
@ -179,7 +195,7 @@ abstract class ProbeTestTransform
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
String doTransform(HttpServletRequest request, boolean isLiveProbe)
|
private String doTransform(HttpServletRequest request, boolean isLiveProbe)
|
||||||
{
|
{
|
||||||
checkMaxTransformTimeAndCount(isLiveProbe);
|
checkMaxTransformTimeAndCount(isLiveProbe);
|
||||||
|
|
||||||
@ -207,9 +223,9 @@ abstract class ProbeTestTransform
|
|||||||
|
|
||||||
if (time > maxTime)
|
if (time > maxTime)
|
||||||
{
|
{
|
||||||
throw new TransformException(500, getMessagePrefix(isLiveProbe)+
|
throw new TransformException(500, getMessagePrefix(isLiveProbe) +
|
||||||
message+" which is more than "+ livenessPercent +
|
message + " which is more than " + livenessPercent +
|
||||||
"% slower than the normal value of "+normalTime+"ms");
|
"% slower than the normal value of " + normalTime + "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't care if the ready or live probe works out if we are 'ready' to take requests.
|
// We don't care if the ready or live probe works out if we are 'ready' to take requests.
|
||||||
@ -237,11 +253,11 @@ abstract class ProbeTestTransform
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File getSourceFile(HttpServletRequest request, boolean isLiveProbe)
|
private File getSourceFile(HttpServletRequest request, boolean isLiveProbe)
|
||||||
{
|
{
|
||||||
incrementTransformerCount();
|
incrementTransformerCount();
|
||||||
File sourceFile = TempFileProvider.createTempFile("source_", "_"+ sourceFilename);
|
File sourceFile = TempFileProvider.createTempFile("source_", "_"+ sourceFilename);
|
||||||
request.setAttribute(AbstractTransformerController.SOURCE_FILE, sourceFile);
|
request.setAttribute(SOURCE_FILE, sourceFile);
|
||||||
try (InputStream inputStream = this.getClass().getResourceAsStream('/'+sourceFilename))
|
try (InputStream inputStream = this.getClass().getResourceAsStream('/'+sourceFilename))
|
||||||
{
|
{
|
||||||
Files.copy(inputStream, sourceFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(inputStream, sourceFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
@ -256,15 +272,15 @@ abstract class ProbeTestTransform
|
|||||||
return sourceFile;
|
return sourceFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
File getTargetFile(HttpServletRequest request)
|
private File getTargetFile(HttpServletRequest request)
|
||||||
{
|
{
|
||||||
File targetFile = TempFileProvider.createTempFile("target_", "_"+targetFilename);
|
File targetFile = TempFileProvider.createTempFile("target_", "_"+targetFilename);
|
||||||
request.setAttribute(AbstractTransformerController.TARGET_FILE, targetFile);
|
request.setAttribute(TARGET_FILE, targetFile);
|
||||||
LogEntry.setTarget(targetFilename);
|
LogEntry.setTarget(targetFilename);
|
||||||
return targetFile;
|
return targetFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recordTransformTime(long time)
|
public void recordTransformTime(long time)
|
||||||
{
|
{
|
||||||
if (maxTransformTime > 0 && time > maxTransformTime)
|
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)
|
if (transCount <= AVERAGE_OVER_TRANSFORMS)
|
||||||
{
|
{
|
||||||
@ -331,4 +347,15 @@ abstract class ProbeTestTransform
|
|||||||
{
|
{
|
||||||
transformCount.incrementAndGet();
|
transformCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLivenessPercent(int livenessPercent)
|
||||||
|
{
|
||||||
|
this.livenessPercent = livenessPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getNormalTime()
|
||||||
|
{
|
||||||
|
|
||||||
|
return normalTime;
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -32,8 +32,6 @@ import org.springframework.boot.test.web.client.TestRestTemplate;
|
|||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
||||||
|
|
||||||
@ -54,7 +52,7 @@ public abstract class AbstractHttpRequestTest
|
|||||||
protected abstract String getSourceExtension();
|
protected abstract String getSourceExtension();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPageExists() throws Exception
|
public void testPageExists()
|
||||||
{
|
{
|
||||||
String result = restTemplate.getForObject("http://localhost:" + port + "/", String.class);
|
String result = restTemplate.getForObject("http://localhost:" + port + "/", String.class);
|
||||||
|
|
||||||
@ -63,7 +61,7 @@ public abstract class AbstractHttpRequestTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void logPageExists() throws Exception
|
public void logPageExists()
|
||||||
{
|
{
|
||||||
String result = restTemplate.getForObject("http://localhost:" + port + "/log", String.class);
|
String result = restTemplate.getForObject("http://localhost:" + port + "/log", String.class);
|
||||||
|
|
||||||
@ -72,7 +70,7 @@ public abstract class AbstractHttpRequestTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void errorPageExists() throws Exception
|
public void errorPageExists()
|
||||||
{
|
{
|
||||||
String result = restTemplate.getForObject("http://localhost:" + port + "/error", String.class);
|
String result = restTemplate.getForObject("http://localhost:" + port + "/error", String.class);
|
||||||
|
|
||||||
@ -81,7 +79,7 @@ public abstract class AbstractHttpRequestTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noFileError() throws Exception
|
public void noFileError()
|
||||||
{
|
{
|
||||||
// Transformer name is not part of the title as this is checked by another handler
|
// Transformer name is not part of the title as this is checked by another handler
|
||||||
assertTransformError(false,
|
assertTransformError(false,
|
||||||
@ -94,22 +92,22 @@ public abstract class AbstractHttpRequestTest
|
|||||||
assertMissingParameter("targetExtension");
|
assertMissingParameter("targetExtension");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertMissingParameter(String name) throws IOException
|
private void assertMissingParameter(String name)
|
||||||
{
|
{
|
||||||
assertTransformError(true,
|
assertTransformError(true,
|
||||||
getTransformerName() + " - Request parameter " + name + " is missing");
|
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)
|
if (addFile)
|
||||||
{
|
{
|
||||||
parameters.add("file", new org.springframework.core.io.ClassPathResource("quick."+getSourceExtension()));
|
parameters.add("file", new org.springframework.core.io.ClassPathResource("quick."+getSourceExtension()));
|
||||||
}
|
}
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
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, "");
|
ResponseEntity<String> response = restTemplate.exchange("/transform", HttpMethod.POST, entity, String.class, "");
|
||||||
assertEquals(errorMessage, getErrorMessage(response.getBody()));
|
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
|
// 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.
|
// 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.
|
// 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 = "";
|
String message = "";
|
||||||
int i = content.indexOf("\"message\":\"");
|
int i = content.indexOf("\"message\":\"");
|
||||||
|
@ -27,12 +27,7 @@ package org.alfresco.transformer;
|
|||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
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.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
@ -44,33 +39,21 @@ import java.io.IOException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.file.Files;
|
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.TransformReply;
|
||||||
import org.alfresco.transform.client.model.TransformRequest;
|
import org.alfresco.transform.client.model.TransformRequest;
|
||||||
import org.alfresco.transformer.model.FileRefEntity;
|
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
|
||||||
import org.alfresco.transformer.model.FileRefResponse;
|
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||||
import org.alfresco.util.exec.RuntimeExec;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.mock.web.MockMultipartFile;
|
import org.springframework.mock.web.MockMultipartFile;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
@ -85,18 +68,9 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
@Autowired
|
@Autowired
|
||||||
protected ObjectMapper objectMapper;
|
protected ObjectMapper objectMapper;
|
||||||
|
|
||||||
@Mock
|
@MockBean
|
||||||
private RuntimeExec mockTransformCommand;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RuntimeExec mockCheckCommand;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
|
protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
|
||||||
|
|
||||||
@Mock
|
|
||||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
|
||||||
|
|
||||||
protected String sourceExtension;
|
protected String sourceExtension;
|
||||||
protected String targetExtension;
|
protected String targetExtension;
|
||||||
protected String sourceMimetype;
|
protected String sourceMimetype;
|
||||||
@ -108,88 +82,14 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
protected byte[] expectedSourceFileBytes;
|
protected byte[] expectedSourceFileBytes;
|
||||||
protected byte[] expectedTargetFileBytes;
|
protected byte[] expectedTargetFileBytes;
|
||||||
|
|
||||||
protected AbstractTransformerController controller;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void before() throws Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by sub class
|
// Called by sub class
|
||||||
public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension,
|
protected abstract void mockTransformCommand(String sourceExtension,
|
||||||
String targetExtension, String sourceMimetype,
|
String targetExtension, String sourceMimetype,
|
||||||
boolean readTargetFileBytes) throws IOException
|
boolean readTargetFileBytes) throws IOException;
|
||||||
{
|
|
||||||
this.controller = controller;
|
|
||||||
this.sourceExtension = sourceExtension;
|
|
||||||
this.targetExtension = targetExtension;
|
|
||||||
this.sourceMimetype = sourceMimetype;
|
|
||||||
|
|
||||||
expectedOptions = null;
|
protected abstract AbstractTransformerController getController();
|
||||||
expectedSourceSuffix = null;
|
|
||||||
expectedSourceFileBytes = readTestFile(sourceExtension);
|
|
||||||
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
|
|
||||||
sourceFile = new MockMultipartFile("file", "quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
|
||||||
|
|
||||||
controller.setTransformCommand(mockTransformCommand);
|
protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method ends up being the core of the mock.
|
* This method ends up being the core of the mock.
|
||||||
@ -197,8 +97,8 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
* in order to simulate a successful transformation.
|
* in order to simulate a successful transformation.
|
||||||
*
|
*
|
||||||
* @param actualTargetExtension Requested extension.
|
* @param actualTargetExtension Requested extension.
|
||||||
* @param testFile The test file (transformed) - basically the result.
|
* @param testFile The test file (transformed) - basically the result.
|
||||||
* @param targetFile The location where the content from the testFile should be copied
|
* @param targetFile The location where the content from the testFile should be copied
|
||||||
* @throws IOException in case of any errors.
|
* @throws IOException in case of any errors.
|
||||||
*/
|
*/
|
||||||
void generateTargetFileFromResourceFile(String actualTargetExtension, File testFile,
|
void generateTargetFileFromResourceFile(String actualTargetExtension, File testFile,
|
||||||
@ -224,7 +124,7 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
|
|
||||||
protected byte[] readTestFile(String extension) throws IOException
|
protected byte[] readTestFile(String extension) throws IOException
|
||||||
{
|
{
|
||||||
return Files.readAllBytes(getTestFile("quick."+extension, true).toPath());
|
return Files.readAllBytes(getTestFile("quick." + extension, true).toPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected File getTestFile(String testFilename, boolean required) throws IOException
|
protected File getTestFile(String testFilename, boolean required) throws IOException
|
||||||
@ -233,22 +133,22 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
URL testFileUrl = classLoader.getResource(testFilename);
|
URL testFileUrl = classLoader.getResource(testFilename);
|
||||||
if (required && testFileUrl == null)
|
if (required && testFileUrl == null)
|
||||||
{
|
{
|
||||||
throw new IOException("The test file "+testFilename+" does not exist in the resources directory");
|
throw new IOException("The test file " + testFilename + " does not exist in the resources directory");
|
||||||
}
|
}
|
||||||
return testFileUrl == null ? null : new File(testFileUrl.getFile());
|
return testFileUrl == null ? null : new File(testFileUrl.getFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MockHttpServletRequestBuilder mockMvcRequest(String url, MockMultipartFile sourceFile, String... params)
|
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)
|
if (params.length % 2 != 0)
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("each param should have a name and value.");
|
throw new IllegalArgumentException("each param should have a name and value.");
|
||||||
}
|
}
|
||||||
for (int i=0; i<params.length; i+=2)
|
for (int i = 0; i < params.length; i += 2)
|
||||||
{
|
{
|
||||||
builder = builder.param(params[i], params[i+1]);
|
builder = builder.param(params[i], params[i + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
@ -258,9 +158,9 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
public void simpleTransformTest() throws Exception
|
public void simpleTransformTest() throws Exception
|
||||||
{
|
{
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||||
.andExpect(status().is(200))
|
.andExpect(status().is(200))
|
||||||
.andExpect(content().bytes(expectedTargetFileBytes))
|
.andExpect(content().bytes(expectedTargetFileBytes))
|
||||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
|
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -268,42 +168,32 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
{
|
{
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension, "testDelay", "400"))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension, "testDelay", "400"))
|
||||||
.andExpect(status().is(200))
|
.andExpect(status().is(200))
|
||||||
.andExpect(content().bytes(expectedTargetFileBytes))
|
.andExpect(content().bytes(expectedTargetFileBytes))
|
||||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
|
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||||
long ms = System.currentTimeMillis()- start;
|
long ms = System.currentTimeMillis() - start;
|
||||||
System.out.println("Transform incluing test delay was "+ms);
|
System.out.println("Transform incluing test delay was " + ms);
|
||||||
assertTrue("Delay sending the result back was too small "+ms, ms >= 400);
|
assertTrue("Delay sending the result back was too small " + ms, ms >= 400);
|
||||||
assertTrue("Delay sending the result back was too big "+ms, ms <= 500);
|
assertTrue("Delay sending the result back was too big " + ms, ms <= 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noTargetFileTest() throws Exception
|
public void noTargetFileTest() throws Exception
|
||||||
{
|
{
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
|
||||||
.andExpect(status().is(500));
|
.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
|
@Test
|
||||||
// Looks dangerous but is okay as we only use the final filename
|
// Looks dangerous but is okay as we only use the final filename
|
||||||
public void dotDotSourceFilenameTest() throws Exception
|
public void dotDotSourceFilenameTest() throws Exception
|
||||||
{
|
{
|
||||||
sourceFile = new MockMultipartFile("file", "../quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
sourceFile = new MockMultipartFile("file", "../quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||||
|
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||||
.andExpect(status().is(200))
|
.andExpect(status().is(200))
|
||||||
.andExpect(content().bytes(expectedTargetFileBytes))
|
.andExpect(content().bytes(expectedTargetFileBytes))
|
||||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
|
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -313,9 +203,9 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
sourceFile = new MockMultipartFile("file", "../quick", sourceMimetype, expectedSourceFileBytes);
|
sourceFile = new MockMultipartFile("file", "../quick", sourceMimetype, expectedSourceFileBytes);
|
||||||
|
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||||
.andExpect(status().is(200))
|
.andExpect(status().is(200))
|
||||||
.andExpect(content().bytes(expectedTargetFileBytes))
|
.andExpect(content().bytes(expectedTargetFileBytes))
|
||||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
|
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -325,8 +215,8 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
sourceFile = new MockMultipartFile("file", "abc/", sourceMimetype, expectedSourceFileBytes);
|
sourceFile = new MockMultipartFile("file", "abc/", sourceMimetype, expectedSourceFileBytes);
|
||||||
|
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||||
.andExpect(status().is(400))
|
.andExpect(status().is(400))
|
||||||
.andExpect(status().reason(containsString("The source filename was not supplied")));
|
.andExpect(status().reason(containsString("The source filename was not supplied")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -335,113 +225,47 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
sourceFile = new MockMultipartFile("file", "", sourceMimetype, expectedSourceFileBytes);
|
sourceFile = new MockMultipartFile("file", "", sourceMimetype, expectedSourceFileBytes);
|
||||||
|
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||||
.andExpect(status().is(400))
|
.andExpect(status().is(400))
|
||||||
.andExpect(status().reason(containsString("The source filename was not supplied")));
|
.andExpect(status().reason(containsString("The source filename was not supplied")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noTargetExtensionTest() throws Exception
|
public void noTargetExtensionTest() throws Exception
|
||||||
{
|
{
|
||||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile))
|
mockMvc.perform(mockMvcRequest("/transform", sourceFile))
|
||||||
.andExpect(status().is(400))
|
.andExpect(status().is(400))
|
||||||
.andExpect(status().reason(containsString("Request parameter targetExtension is missing")));
|
.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
|
@Test
|
||||||
public void calculateMaxTime() throws Exception
|
public void calculateMaxTime() throws Exception
|
||||||
{
|
{
|
||||||
ProbeTestTransform probeTestTransform = controller.getProbeTestTransform();
|
ProbeTestTransform probeTestTransform = getController().getProbeTestTransform();
|
||||||
probeTestTransform.livenessPercent = 110;
|
probeTestTransform.setLivenessPercent(110);
|
||||||
|
|
||||||
long [][] values = new long[][] {
|
long[][] values = new long[][]{
|
||||||
{5000, 0, Long.MAX_VALUE}, // 1st transform is ignored
|
{5000, 0, Long.MAX_VALUE}, // 1st transform is ignored
|
||||||
{1000, 1000, 2100}, // 1000 + 1000*1.1
|
{1000, 1000, 2100}, // 1000 + 1000*1.1
|
||||||
{3000, 2000, 4200}, // 2000 + 2000*1.1
|
{3000, 2000, 4200}, // 2000 + 2000*1.1
|
||||||
{2000, 2000, 4200},
|
{2000, 2000, 4200},
|
||||||
{6000, 3000, 6300},
|
{6000, 3000, 6300},
|
||||||
{8000, 4000, 8400},
|
{8000, 4000, 8400},
|
||||||
{4444, 4000, 8400}, // no longer in the first few, so normal and max times don't change
|
{4444, 4000, 8400}, // no longer in the first few, so normal and max times don't change
|
||||||
{5555, 4000, 8400}
|
{5555, 4000, 8400}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (long[] v: values)
|
for (long[] v : values)
|
||||||
{
|
{
|
||||||
long time = v[0];
|
long time = v[0];
|
||||||
long expectedNormalTime = v[1];
|
long expectedNormalTime = v[1];
|
||||||
long expectedMaxTime = v[2];
|
long expectedMaxTime = v[2];
|
||||||
|
|
||||||
probeTestTransform.calculateMaxTime(time, true);
|
probeTestTransform.calculateMaxTime(time, true);
|
||||||
assertEquals("", expectedNormalTime, probeTestTransform.normalTime);
|
assertEquals("", expectedNormalTime, probeTestTransform.getNormalTime());
|
||||||
assertEquals("", expectedMaxTime, probeTestTransform.maxTime);
|
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
|
@Test
|
||||||
public void testEmptyPojoTransform() throws Exception
|
public void testEmptyPojoTransform() throws Exception
|
||||||
{
|
{
|
||||||
@ -450,9 +274,12 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
|
|
||||||
// Serialize and call the transformer
|
// Serialize and call the transformer
|
||||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||||
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
|
String transformationReplyAsString = mockMvc
|
||||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
.perform(MockMvcRequestBuilders
|
||||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
|
.post("/transform")
|
||||||
|
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
.content(tr))
|
||||||
.andExpect(status().is(HttpStatus.BAD_REQUEST.value()))
|
.andExpect(status().is(HttpStatus.BAD_REQUEST.value()))
|
||||||
.andReturn().getResponse().getContentAsString();
|
.andReturn().getResponse().getContentAsString();
|
||||||
|
|
||||||
@ -461,6 +288,4 @@ public abstract class AbstractTransformerControllerTest
|
|||||||
// Assert the reply
|
// Assert the reply
|
||||||
assertEquals(HttpStatus.BAD_REQUEST.value(), transformReply.getStatus());
|
assertEquals(HttpStatus.BAD_REQUEST.value(), transformReply.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest);
|
|
||||||
}
|
}
|
||||||
|
32
pom.xml
32
pom.xml
@ -3,9 +3,10 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>alfresco-super-pom</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>9</version>
|
<version>2.0.5.RELEASE</version>
|
||||||
|
<relativePath />
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
@ -44,13 +45,6 @@
|
|||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<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>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-core</artifactId>
|
<artifactId>logback-core</artifactId>
|
||||||
@ -121,4 +115,22 @@
|
|||||||
</snapshotRepository>
|
</snapshotRepository>
|
||||||
</distributionManagement>
|
</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>
|
</project>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user