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;
|
||||
|
||||
import static org.alfresco.transformer.fs.FileManager.createAttachment;
|
||||
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
|
||||
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.alfresco.transformer.executors.PdfRendererCommandExecutor;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
@ -52,69 +61,49 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
@Controller
|
||||
public class AlfrescoPdfRendererController extends AbstractTransformerController
|
||||
{
|
||||
private static final String EXE = "/usr/bin/alfresco-pdf-renderer";
|
||||
private static final Log logger = LogFactory.getLog(AlfrescoPdfRendererController.class);
|
||||
|
||||
@Autowired
|
||||
private PdfRendererCommandExecutor commandExecutor;
|
||||
|
||||
@Autowired
|
||||
public AlfrescoPdfRendererController()
|
||||
{
|
||||
logger = LogFactory.getLog(AlfrescoPdfRendererController.class);
|
||||
logger.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
logEnterpriseLicenseMessage();
|
||||
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
|
||||
logger.info("alfresco-pdf-renderer uses the PDFium library from Google Inc. See the license at https://pdfium.googlesource.com/pdfium/+/master/LICENSE or in /pdfium.txt");
|
||||
logger.info("-----------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
setTransformCommand(createTransformCommand());
|
||||
setCheckCommand(createCheckCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTransformerName()
|
||||
public String getTransformerName()
|
||||
{
|
||||
return "Alfresco PDF Renderer";
|
||||
}
|
||||
|
||||
private static RuntimeExec createTransformCommand()
|
||||
@Override
|
||||
public String version()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*", new String[]{EXE, "SPLIT:${options}", "${source}", "${target}"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
|
||||
Map<String, String> defaultProperties = new HashMap<>();
|
||||
defaultProperties.put("key", null);
|
||||
runtimeExec.setDefaultProperties(defaultProperties);
|
||||
|
||||
runtimeExec.setErrorCodes("1");
|
||||
|
||||
return runtimeExec;
|
||||
}
|
||||
|
||||
private static RuntimeExec createCheckCommand()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*", new String[]{EXE, "--version"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
|
||||
return runtimeExec;
|
||||
return commandExecutor.version();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProbeTestTransform getProbeTestTransform()
|
||||
public ProbeTestTransform getProbeTestTransform()
|
||||
{
|
||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
||||
return new ProbeTestTransform(this,"quick.pdf", "quick.png",
|
||||
return new ProbeTestTransform(this, logger, "quick.pdf", "quick.png",
|
||||
7455, 1024, 150, 10240, 60*20+1, 60*15-15)
|
||||
{
|
||||
@Override
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
||||
{
|
||||
AlfrescoPdfRendererController.this.executeTransformCommand("", sourceFile, targetFile, null);
|
||||
commandExecutor.run("", sourceFile, targetFile, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processTransform(File sourceFile, File targetFile,
|
||||
public void processTransform(File sourceFile, File targetFile,
|
||||
Map<String, String> transformOptions, Long timeout)
|
||||
{
|
||||
String page = transformOptions.get("page");
|
||||
@ -137,7 +126,7 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
|
||||
String options = buildTransformOptions(pageOption, widthOption, heightOption,
|
||||
allowEnlargementOption, maintainAspectRatioOption);
|
||||
|
||||
executeTransformCommand(options, sourceFile, targetFile, timeout);
|
||||
commandExecutor.run(options, sourceFile, targetFile, timeout);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@ -155,19 +144,26 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
|
||||
@RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio)
|
||||
{
|
||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||
getProbeTestTransform().incrementTransformerCount();
|
||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||
File targetFile = createTargetFile(request, targetFilename);
|
||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||
|
||||
String options = buildTransformOptions(page, width, height, allowEnlargement,
|
||||
maintainAspectRatio);
|
||||
executeTransformCommand(options, sourceFile, targetFile, timeout);
|
||||
commandExecutor.run(options, sourceFile, targetFile, timeout);
|
||||
|
||||
return createAttachment(targetFilename, targetFile, testDelay);
|
||||
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
|
||||
LogEntry.setTargetSize(targetFile.length());
|
||||
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
time += LogEntry.addDelay(testDelay);
|
||||
getProbeTestTransform().recordTransformTime(time);
|
||||
return body;
|
||||
}
|
||||
|
||||
|
||||
public String buildTransformOptions(Integer page,Integer width,Integer height,Boolean allowEnlargement,Boolean maintainAspectRatio)
|
||||
private static String buildTransformOptions(Integer page,Integer width,Integer height,Boolean
|
||||
allowEnlargement,Boolean maintainAspectRatio)
|
||||
{
|
||||
StringJoiner args = new StringJoiner(" ");
|
||||
if (width != null && width >= 0)
|
||||
|
@ -12,6 +12,8 @@
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.alfresco.transformer.executors.PdfRendererCommandExecutor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
@ -27,10 +29,16 @@ public class Application
|
||||
@Value("${container.name}")
|
||||
private String containerName;
|
||||
|
||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
@Bean
|
||||
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
return registry -> registry.config().commonTags("containerName", containerName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PdfRendererCommandExecutor commandExecutor() {
|
||||
return new PdfRendererCommandExecutor();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
SpringApplication.run(Application.class, args);
|
||||
|
@ -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;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transformer.executors.PdfRendererCommandExecutor;
|
||||
import org.alfresco.transformer.model.FileRefEntity;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Test the AlfrescoPdfRendererController without a server.
|
||||
@ -47,22 +76,111 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
@WebMvcTest(AlfrescoPdfRendererController.class)
|
||||
public class AlfrescoPdfRendererControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
@Mock
|
||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockTransformCommand;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockCheckCommand;
|
||||
|
||||
@SpyBean
|
||||
private PdfRendererCommandExecutor commandExecutor;
|
||||
|
||||
@SpyBean
|
||||
private AlfrescoPdfRendererController controller;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception
|
||||
public void before() throws IOException
|
||||
{
|
||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
||||
super.controller = controller;
|
||||
super.mockTransformCommand(controller, "pdf", "png", "application/pdf", true);
|
||||
commandExecutor.setTransformCommand(mockTransformCommand);
|
||||
commandExecutor.setCheckCommand(mockCheckCommand);
|
||||
|
||||
mockTransformCommand("pdf", "png", "application/pdf", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mockTransformCommand(String sourceExtension,
|
||||
String targetExtension, String sourceMimetype,
|
||||
boolean readTargetFileBytes) throws IOException
|
||||
{
|
||||
this.sourceExtension = sourceExtension;
|
||||
this.targetExtension = targetExtension;
|
||||
this.sourceMimetype = sourceMimetype;
|
||||
|
||||
expectedOptions = null;
|
||||
expectedSourceSuffix = null;
|
||||
expectedSourceFileBytes = readTestFile(sourceExtension);
|
||||
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
|
||||
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
|
||||
when(mockTransformCommand.execute(any(), anyLong())).thenAnswer(
|
||||
(Answer<RuntimeExec.ExecutionResult>) invocation -> {
|
||||
Map<String, String> actualProperties = invocation.getArgument(0);
|
||||
assertEquals("There should be 3 properties", 3, actualProperties.size());
|
||||
|
||||
String actualOptions = actualProperties.get("options");
|
||||
String actualSource = actualProperties.get("source");
|
||||
String actualTarget = actualProperties.get("target");
|
||||
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
|
||||
|
||||
assertNotNull(actualSource);
|
||||
assertNotNull(actualTarget);
|
||||
if (expectedSourceSuffix != null)
|
||||
{
|
||||
assertTrue("The source file \""+actualSource+"\" should have ended in \""+expectedSourceSuffix+"\"", actualSource.endsWith(expectedSourceSuffix));
|
||||
actualSource = actualSource.substring(0, actualSource.length()-expectedSourceSuffix.length());
|
||||
}
|
||||
|
||||
assertNotNull(actualOptions);
|
||||
if (expectedOptions != null)
|
||||
{
|
||||
assertEquals("expectedOptions", expectedOptions, actualOptions);
|
||||
}
|
||||
|
||||
Long actualTimeout = invocation.getArgument(1);
|
||||
assertNotNull(actualTimeout);
|
||||
if (expectedTimeout != null)
|
||||
{
|
||||
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
|
||||
}
|
||||
|
||||
// Copy a test file into the target file location if it exists
|
||||
int i = actualTarget.lastIndexOf('_');
|
||||
if (i >= 0)
|
||||
{
|
||||
String testFilename = actualTarget.substring(i+1);
|
||||
File testFile = getTestFile(testFilename, false);
|
||||
File targetFile = new File(actualTarget);
|
||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
|
||||
targetFile);
|
||||
}
|
||||
|
||||
// Check the supplied source file has not been changed.
|
||||
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
|
||||
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||
|
||||
return mockExecutionResult;
|
||||
});
|
||||
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
|
||||
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractTransformerController getController()
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void optionsTest() throws Exception
|
||||
{
|
||||
expectedOptions = "--width=321 --height=654 --allow-enlargement --maintain-aspect-ratio --page=2";
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
|
||||
@ -82,7 +200,7 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
|
||||
public void optionsNegateBooleansTest() throws Exception
|
||||
{
|
||||
expectedOptions = "--width=321 --height=654 --page=2";
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
|
||||
@ -106,4 +224,64 @@ public class AlfrescoPdfRendererControllerTest extends AbstractTransformerContro
|
||||
transformRequest.setSourceMediaType(MediaType.APPLICATION_PDF_VALUE);
|
||||
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badExitCodeTest() throws Exception
|
||||
{
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(1);
|
||||
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
|
||||
.andExpect(status().is(400))
|
||||
.andExpect(status().reason(containsString("Transformer exit code was not 0: \nSTDERR")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoTransform() throws Exception
|
||||
{
|
||||
// Files
|
||||
String sourceFileRef = UUID.randomUUID().toString();
|
||||
File sourceFile = getTestFile("quick." + sourceExtension, true);
|
||||
String targetFileRef = UUID.randomUUID().toString();
|
||||
|
||||
|
||||
// Transformation Request POJO
|
||||
TransformRequest transformRequest = new TransformRequest();
|
||||
transformRequest.setRequestId("1");
|
||||
transformRequest.setSchema(1);
|
||||
transformRequest.setClientData("Alfresco Digital Business Platform");
|
||||
transformRequest.setTransformRequestOptions(new HashMap<>());
|
||||
transformRequest.setSourceReference(sourceFileRef);
|
||||
transformRequest.setSourceExtension(sourceExtension);
|
||||
transformRequest.setSourceSize(sourceFile.length());
|
||||
transformRequest.setTargetExtension(targetExtension);
|
||||
|
||||
// HTTP Request
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=quick." + sourceExtension);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new FileSystemResource(
|
||||
sourceFile), headers, HttpStatus.OK);
|
||||
|
||||
when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response);
|
||||
when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(new FileRefResponse(new FileRefEntity(targetFileRef)));
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
|
||||
// Update the Transformation Request with any specific params before sending it
|
||||
updateTransformRequestWithSpecificOptions(transformRequest);
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.andExpect(status().is(HttpStatus.CREATED.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
TransformReply transformReply = objectMapper.readValue(transformationReplyAsString, TransformReply.class);
|
||||
|
||||
// Assert the reply
|
||||
assertEquals(transformRequest.getRequestId(), transformReply.getRequestId());
|
||||
assertEquals(transformRequest.getClientData(), transformReply.getClientData());
|
||||
assertEquals(transformRequest.getSchema(), transformReply.getSchema());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
@ -48,5 +47,5 @@ public class AlfrescoPdfRendererHttpRequestTest extends AbstractHttpRequestTest
|
||||
protected String getSourceExtension()
|
||||
{
|
||||
return "pdf";
|
||||
};
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.alfresco.transformer.executors.ImageMagickCommandExecutor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
@ -27,10 +29,16 @@ public class Application
|
||||
@Value("${container.name}")
|
||||
private String containerName;
|
||||
|
||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
@Bean
|
||||
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
return registry -> registry.config().commonTags("containerName", containerName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ImageMagickCommandExecutor commandExecutor() {
|
||||
return new ImageMagickCommandExecutor();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
SpringApplication.run(Application.class, args);
|
||||
|
@ -11,16 +11,27 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import static org.alfresco.transformer.fs.FileManager.createAttachment;
|
||||
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
|
||||
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
|
||||
import static org.alfresco.transformer.util.Util.stringToBoolean;
|
||||
import static org.alfresco.transformer.util.Util.stringToInteger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.alfresco.transformer.exceptions.TransformException;
|
||||
import org.alfresco.transformer.executors.ImageMagickCommandExecutor;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
@ -56,73 +67,46 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
@Controller
|
||||
public class ImageMagickController extends AbstractTransformerController
|
||||
{
|
||||
private static final String ROOT = "/usr/lib64/ImageMagick-7.0.7";
|
||||
private static final String DYN = ROOT+"/lib";
|
||||
private static final String EXE = "/usr/bin/convert";
|
||||
private static final Log logger = LogFactory.getLog(ImageMagickController.class);
|
||||
|
||||
private static final List<String> GRAVITY_VALUES = Arrays.asList(
|
||||
"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center");
|
||||
|
||||
@Autowired
|
||||
private ImageMagickCommandExecutor commandExecutor;
|
||||
|
||||
@Autowired
|
||||
public ImageMagickController()
|
||||
{
|
||||
logger = LogFactory.getLog(ImageMagickController.class);
|
||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
logEnterpriseLicenseMessage();
|
||||
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
|
||||
logger.info("This transformer uses ImageMagick from ImageMagick Studio LLC. See the license at http://www.imagemagick.org/script/license.php or in /ImageMagick-license.txt");
|
||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
setTransformCommand(createTransformCommand());
|
||||
setCheckCommand(createCheckCommand());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTransformerName()
|
||||
public String getTransformerName()
|
||||
{
|
||||
return "ImageMagick";
|
||||
}
|
||||
|
||||
private static RuntimeExec createTransformCommand()
|
||||
@Override
|
||||
public String version()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*", new String[]{EXE, "${source}", "SPLIT:${options}", "-strip", "-quiet", "${target}"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
|
||||
Map<String, String> processProperties = new HashMap<>();
|
||||
processProperties.put("MAGICK_HOME", ROOT);
|
||||
processProperties.put("DYLD_FALLBACK_LIBRARY_PATH", DYN);
|
||||
processProperties.put("LD_LIBRARY_PATH", DYN);
|
||||
runtimeExec.setProcessProperties(processProperties);
|
||||
|
||||
Map<String, String> defaultProperties = new HashMap<>();
|
||||
defaultProperties.put("options", null);
|
||||
runtimeExec.setDefaultProperties(defaultProperties);
|
||||
|
||||
runtimeExec.setErrorCodes("1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799");
|
||||
|
||||
return runtimeExec;
|
||||
}
|
||||
|
||||
private static RuntimeExec createCheckCommand()
|
||||
{
|
||||
RuntimeExec runtimeExec = new RuntimeExec();
|
||||
Map<String, String[]> commandsAndArguments = new HashMap<>();
|
||||
commandsAndArguments.put(".*", new String[]{EXE, "-version"});
|
||||
runtimeExec.setCommandsAndArguments(commandsAndArguments);
|
||||
|
||||
return runtimeExec;
|
||||
return commandExecutor.version();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProbeTestTransform getProbeTestTransform()
|
||||
public ProbeTestTransform getProbeTestTransform()
|
||||
{
|
||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
||||
return new ProbeTestTransform(this, "quick.jpg", "quick.png",
|
||||
35593, 1024, 150, 1024, 60*15+1,60*15+0)
|
||||
return new ProbeTestTransform(this, logger, "quick.jpg", "quick.png",
|
||||
35593, 1024, 150, 1024, 60*15+1,60*15)
|
||||
{
|
||||
@Override
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
||||
{
|
||||
ImageMagickController.this.executeTransformCommand("", sourceFile, "", targetFile, null);
|
||||
commandExecutor.run("", sourceFile, "", targetFile, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -168,6 +152,7 @@ public class ImageMagickController extends AbstractTransformerController
|
||||
{
|
||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(),
|
||||
targetExtension);
|
||||
getProbeTestTransform().incrementTransformerCount();
|
||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||
File targetFile = createTargetFile(request, targetFilename);
|
||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||
@ -176,14 +161,20 @@ public class ImageMagickController extends AbstractTransformerController
|
||||
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions);
|
||||
String pageRange = calculatePageRange(startPage, endPage);
|
||||
|
||||
executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout);
|
||||
commandExecutor.run(options, sourceFile, pageRange, targetFile,
|
||||
timeout);
|
||||
|
||||
return createAttachment(targetFilename, targetFile, testDelay);
|
||||
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
|
||||
LogEntry.setTargetSize(targetFile.length());
|
||||
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
time += LogEntry.addDelay(testDelay);
|
||||
getProbeTestTransform().recordTransformTime(time);
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processTransform(File sourceFile, File targetFile,
|
||||
Map<String, String> transformOptions, Long timeout)
|
||||
public void processTransform(final File sourceFile, final File targetFile,
|
||||
final Map<String, String> transformOptions, final Long timeout)
|
||||
{
|
||||
Integer startPage = stringToInteger(transformOptions.get("startPage"));
|
||||
Integer endPage = stringToInteger(transformOptions.get("endPage"));
|
||||
@ -201,28 +192,17 @@ public class ImageMagickController extends AbstractTransformerController
|
||||
Boolean resizePercentage = stringToBoolean(transformOptions.get("resizePercentage"));
|
||||
Boolean allowEnlargement = stringToBoolean(transformOptions.get("allowEnlargement"));
|
||||
Boolean maintainAspectRatio = stringToBoolean(transformOptions.get("maintainAspectRatio"));
|
||||
String commandOptions = transformOptions.get("commandOptions");
|
||||
|
||||
String options = buildTransformOptions(startPage, endPage , alphaRemove, autoOrient, cropGravity, cropWidth, cropHeight, cropPercentage,
|
||||
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, commandOptions);
|
||||
String pageRange = calculatePageRange(startPage, endPage);
|
||||
final String options = buildTransformOptions(startPage, endPage, alphaRemove, autoOrient,
|
||||
cropGravity, cropWidth, cropHeight, cropPercentage,
|
||||
cropXOffset, cropYOffset, thumbnail, resizeWidth, resizeHeight, resizePercentage, allowEnlargement, maintainAspectRatio, null);
|
||||
final String pageRange = calculatePageRange(startPage, endPage);
|
||||
|
||||
executeTransformCommand(options, sourceFile, pageRange, targetFile, timeout);
|
||||
commandExecutor.run(options, sourceFile, pageRange, targetFile,
|
||||
timeout);
|
||||
}
|
||||
|
||||
private void executeTransformCommand(String options, File sourceFile, String pageRange, File targetFile, Long timeout)
|
||||
{
|
||||
LogEntry.setOptions(pageRange+(pageRange.isEmpty() ? "" : " ")+options);
|
||||
|
||||
Map<String, String> properties = new HashMap<String, String>(5);
|
||||
properties.put("options", options);
|
||||
properties.put("source", sourceFile.getAbsolutePath()+pageRange);
|
||||
properties.put("target", targetFile.getAbsolutePath());
|
||||
|
||||
executeTransformCommand(properties, targetFile, timeout);
|
||||
}
|
||||
|
||||
private String buildTransformOptions(Integer startPage, Integer endPage, Boolean alphaRemove,
|
||||
private static String buildTransformOptions(Integer startPage, Integer endPage, Boolean alphaRemove,
|
||||
Boolean autoOrient, String cropGravity, Integer cropWidth, Integer cropHeight,
|
||||
Boolean cropPercentage, Integer cropXOffset, Integer cropYOffset, Boolean thumbnail,
|
||||
Integer resizeWidth, Integer resizeHeight, Boolean resizePercentage,
|
||||
@ -261,7 +241,7 @@ public class ImageMagickController extends AbstractTransformerController
|
||||
args.add(cropGravity);
|
||||
}
|
||||
|
||||
StringBuilder crop = new StringBuilder("");
|
||||
StringBuilder crop = new StringBuilder();
|
||||
if (cropWidth != null && cropWidth >= 0)
|
||||
{
|
||||
crop.append(cropWidth);
|
||||
@ -303,7 +283,7 @@ public class ImageMagickController extends AbstractTransformerController
|
||||
if (resizeHeight != null || resizeWidth != null || resizePercentage !=null || maintainAspectRatio != null)
|
||||
{
|
||||
args.add(thumbnail != null && thumbnail ? "-thumbnail" : "-resize");
|
||||
StringBuilder resize = new StringBuilder("");
|
||||
StringBuilder resize = new StringBuilder();
|
||||
if (resizeWidth != null && resizeWidth >= 0)
|
||||
{
|
||||
resize.append(resizeWidth);
|
||||
@ -335,10 +315,9 @@ public class ImageMagickController extends AbstractTransformerController
|
||||
args.toString();
|
||||
}
|
||||
|
||||
private String calculatePageRange(Integer startPage, Integer endPage)
|
||||
private static String calculatePageRange(Integer startPage, Integer endPage)
|
||||
{
|
||||
return
|
||||
startPage == null
|
||||
return startPage == null
|
||||
? endPage == null
|
||||
? ""
|
||||
: "["+endPage+']'
|
||||
|
@ -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;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transformer.executors.ImageMagickCommandExecutor;
|
||||
import org.alfresco.transformer.model.FileRefEntity;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Test the ImageMagickController without a server.
|
||||
@ -49,16 +76,104 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
@WebMvcTest(ImageMagickController.class)
|
||||
public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
@Mock
|
||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockTransformCommand;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockCheckCommand;
|
||||
|
||||
@SpyBean
|
||||
private ImageMagickCommandExecutor commandExecutor;
|
||||
|
||||
@SpyBean
|
||||
private ImageMagickController controller;
|
||||
|
||||
@Before
|
||||
public void before() throws IOException
|
||||
{
|
||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
||||
super.controller = controller;
|
||||
commandExecutor.setTransformCommand(mockTransformCommand);
|
||||
commandExecutor.setCheckCommand(mockCheckCommand);
|
||||
|
||||
super.mockTransformCommand(controller, "jpg", "png", "image/jpg", true);
|
||||
mockTransformCommand("jpg", "png", "image/jpg", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mockTransformCommand(String sourceExtension,
|
||||
String targetExtension, String sourceMimetype,
|
||||
boolean readTargetFileBytes) throws IOException
|
||||
{
|
||||
this.sourceExtension = sourceExtension;
|
||||
this.targetExtension = targetExtension;
|
||||
this.sourceMimetype = sourceMimetype;
|
||||
|
||||
expectedOptions = null;
|
||||
expectedSourceSuffix = null;
|
||||
expectedSourceFileBytes = readTestFile(sourceExtension);
|
||||
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
|
||||
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
|
||||
when(mockTransformCommand.execute(any(), anyLong())).thenAnswer(
|
||||
(Answer<RuntimeExec.ExecutionResult>) invocation -> {
|
||||
Map<String, String> actualProperties = invocation.getArgument(0);
|
||||
assertEquals("There should be 3 properties", 3, actualProperties.size());
|
||||
|
||||
String actualOptions = actualProperties.get("options");
|
||||
String actualSource = actualProperties.get("source");
|
||||
String actualTarget = actualProperties.get("target");
|
||||
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
|
||||
|
||||
assertNotNull(actualSource);
|
||||
assertNotNull(actualTarget);
|
||||
if (expectedSourceSuffix != null)
|
||||
{
|
||||
assertTrue("The source file \""+actualSource+"\" should have ended in \""+expectedSourceSuffix+"\"", actualSource.endsWith(expectedSourceSuffix));
|
||||
actualSource = actualSource.substring(0, actualSource.length()-expectedSourceSuffix.length());
|
||||
}
|
||||
|
||||
assertNotNull(actualOptions);
|
||||
if (expectedOptions != null)
|
||||
{
|
||||
assertEquals("expectedOptions", expectedOptions, actualOptions);
|
||||
}
|
||||
|
||||
Long actualTimeout = invocation.getArgument(1);
|
||||
assertNotNull(actualTimeout);
|
||||
if (expectedTimeout != null)
|
||||
{
|
||||
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
|
||||
}
|
||||
|
||||
// Copy a test file into the target file location if it exists
|
||||
int i = actualTarget.lastIndexOf('_');
|
||||
if (i >= 0)
|
||||
{
|
||||
String testFilename = actualTarget.substring(i+1);
|
||||
File testFile = getTestFile(testFilename, false);
|
||||
File targetFile = new File(actualTarget);
|
||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
|
||||
targetFile);
|
||||
}
|
||||
|
||||
// Check the supplied source file has not been changed.
|
||||
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
|
||||
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||
|
||||
return mockExecutionResult;
|
||||
});
|
||||
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
|
||||
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected AbstractTransformerController getController()
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -67,7 +182,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
for (String value: new String[] {"North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center"})
|
||||
{
|
||||
expectedOptions = "-gravity "+value+" +repage";
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
.param("cropGravity", value))
|
||||
@ -80,7 +195,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
@Test
|
||||
public void cropGravityBadTest() throws Exception
|
||||
{
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
.param("cropGravity", "badValue"))
|
||||
@ -92,7 +207,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
expectedOptions = "-alpha remove -gravity SouthEast -crop 123x456%+90+12 +repage -thumbnail 321x654%!";
|
||||
expectedSourceSuffix = "[2-3]";
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
|
||||
@ -126,7 +241,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
expectedOptions = "-auto-orient -gravity SouthEast -crop 123x456+90+12 +repage -resize 321x654>";
|
||||
expectedSourceSuffix = "[2-3]";
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
|
||||
@ -160,7 +275,7 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
// Example of why the commandOptions parameter is a bad idea.
|
||||
expectedOptions = "( horrible command / ); -resize 321x654>";
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", targetExtension)
|
||||
.param("thumbnail", "false")
|
||||
@ -180,4 +295,64 @@ public class ImageMagickControllerTest extends AbstractTransformerControllerTest
|
||||
transformRequest.setSourceMediaType(MediaType.IMAGE_PNG_VALUE);
|
||||
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badExitCodeTest() throws Exception
|
||||
{
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(1);
|
||||
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
|
||||
.andExpect(status().is(400))
|
||||
.andExpect(status().reason(containsString("Transformer exit code was not 0: \nSTDERR")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoTransform() throws Exception
|
||||
{
|
||||
// Files
|
||||
String sourceFileRef = UUID.randomUUID().toString();
|
||||
File sourceFile = getTestFile("quick." + sourceExtension, true);
|
||||
String targetFileRef = UUID.randomUUID().toString();
|
||||
|
||||
|
||||
// Transformation Request POJO
|
||||
TransformRequest transformRequest = new TransformRequest();
|
||||
transformRequest.setRequestId("1");
|
||||
transformRequest.setSchema(1);
|
||||
transformRequest.setClientData("Alfresco Digital Business Platform");
|
||||
transformRequest.setTransformRequestOptions(new HashMap<>());
|
||||
transformRequest.setSourceReference(sourceFileRef);
|
||||
transformRequest.setSourceExtension(sourceExtension);
|
||||
transformRequest.setSourceSize(sourceFile.length());
|
||||
transformRequest.setTargetExtension(targetExtension);
|
||||
|
||||
// HTTP Request
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=quick." + sourceExtension);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new FileSystemResource(
|
||||
sourceFile), headers, HttpStatus.OK);
|
||||
|
||||
when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response);
|
||||
when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(new FileRefResponse(new FileRefEntity(targetFileRef)));
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
|
||||
// Update the Transformation Request with any specific params before sending it
|
||||
updateTransformRequestWithSpecificOptions(transformRequest);
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.andExpect(status().is(HttpStatus.CREATED.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
TransformReply transformReply = objectMapper.readValue(transformationReplyAsString, TransformReply.class);
|
||||
|
||||
// Assert the reply
|
||||
assertEquals(transformRequest.getRequestId(), transformReply.getRequestId());
|
||||
assertEquals(transformRequest.getClientData(), transformReply.getClientData());
|
||||
assertEquals(transformRequest.getSchema(), transformReply.getSchema());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
@ -48,5 +47,5 @@ public class ImageMagickHttpRequestTest extends AbstractHttpRequestTest
|
||||
protected String getSourceExtension()
|
||||
{
|
||||
return "jpg";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
@ -27,10 +29,17 @@ public class Application
|
||||
@Value("${container.name}")
|
||||
private String containerName;
|
||||
|
||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
@Bean
|
||||
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
return registry -> registry.config().commonTags("containerName", containerName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LibreOfficeJavaExecutor javaExecutor()
|
||||
{
|
||||
return new LibreOfficeJavaExecutor();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
SpringApplication.run(Application.class, args);
|
||||
|
@ -11,19 +11,23 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import static org.alfresco.transformer.fs.FileManager.createAttachment;
|
||||
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
|
||||
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.artofsolving.jodconverter.OfficeDocumentConverter;
|
||||
import org.artofsolving.jodconverter.office.OfficeException;
|
||||
import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
@ -33,8 +37,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.sun.star.task.ErrorCodeIOException;
|
||||
|
||||
/**
|
||||
* Controller for the Docker based LibreOffice transformer.
|
||||
*
|
||||
@ -59,86 +61,48 @@ import com.sun.star.task.ErrorCodeIOException;
|
||||
@Controller
|
||||
public class LibreOfficeController extends AbstractTransformerController
|
||||
{
|
||||
private static final String OFFICE_HOME = "/opt/libreoffice5.4";
|
||||
|
||||
private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088;
|
||||
|
||||
private JodConverter jodconverter;
|
||||
private static final Log logger = LogFactory.getLog(LibreOfficeController.class);
|
||||
|
||||
@Autowired
|
||||
public LibreOfficeController() throws Exception
|
||||
private LibreOfficeJavaExecutor javaExecutor;
|
||||
|
||||
@Autowired
|
||||
public LibreOfficeController()
|
||||
{
|
||||
logger = LogFactory.getLog(LibreOfficeController.class);
|
||||
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
logEnterpriseLicenseMessage();
|
||||
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
|
||||
logger.info("This transformer uses LibreOffice from The Document Foundation. See the license at https://www.libreoffice.org/download/license/ or in /libreoffice.txt");
|
||||
logger.info("-------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
private static JodConverter createJodConverter(Long taskExecutionTimeout)
|
||||
{
|
||||
String timeout = taskExecutionTimeout == null || taskExecutionTimeout <= 0 ? "120000" : taskExecutionTimeout.toString();
|
||||
|
||||
JodConverterSharedInstance jodconverter = new JodConverterSharedInstance();
|
||||
|
||||
jodconverter.setOfficeHome(OFFICE_HOME); // jodconverter.officeHome
|
||||
jodconverter.setMaxTasksPerProcess("200"); // jodconverter.maxTasksPerProcess
|
||||
jodconverter.setTaskExecutionTimeout(timeout); // jodconverter.maxTaskExecutionTimeout
|
||||
jodconverter.setTaskQueueTimeout("30000"); // jodconverter.taskQueueTimeout
|
||||
jodconverter.setConnectTimeout("28000"); // jodconverter.connectTimeout
|
||||
jodconverter.setPortNumbers("8100"); // jodconverter.portNumbers
|
||||
jodconverter.setTemplateProfileDir(""); // jodconverter.templateProfileDir
|
||||
jodconverter.setEnabled("true"); // jodconverter.enabled
|
||||
jodconverter.afterPropertiesSet();
|
||||
|
||||
return jodconverter;
|
||||
}
|
||||
|
||||
public void setJodConverter(JodConverter jodconverter)
|
||||
{
|
||||
this.jodconverter = jodconverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Jodconverter timeouts are per OfficeManager, so we would need multiple OfficeManagers if we
|
||||
* have different timeouts. Alfresco only has one. So we delay building it until the first request.
|
||||
* This was not done previously.
|
||||
*/
|
||||
private synchronized void setJodConverterOnFirstRequest(Long timeout)
|
||||
{
|
||||
if (jodconverter == null)
|
||||
{
|
||||
setJodConverter(createJodConverter(timeout));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTransformerName()
|
||||
public String getTransformerName()
|
||||
{
|
||||
return "LibreOffice";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String version()
|
||||
public String version()
|
||||
{
|
||||
return "LibreOffice available";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProbeTestTransform getProbeTestTransform()
|
||||
public ProbeTestTransform getProbeTestTransform()
|
||||
{
|
||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
||||
return new ProbeTestTransform(this, "quick.doc", "quick.pdf",
|
||||
return new ProbeTestTransform(this, logger, "quick.doc", "quick.pdf",
|
||||
11817, 1024, 150, 10240, 60*30+1, 60*15+20)
|
||||
{
|
||||
@Override
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
||||
{
|
||||
LibreOfficeController.this.executeTransformCommand(sourceFile, targetFile, null);
|
||||
javaExecutor.call(sourceFile, targetFile);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//todo: the "timeout" request parameter is ignored; the timeout is preset at JodConverter creation
|
||||
@PostMapping(value = "/transform", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
||||
@RequestParam("file") MultipartFile sourceMultipartFile,
|
||||
@ -147,122 +111,25 @@ public class LibreOfficeController extends AbstractTransformerController
|
||||
@RequestParam(value = "testDelay", required = false) Long testDelay)
|
||||
{
|
||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||
getProbeTestTransform().incrementTransformerCount();
|
||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||
File targetFile = createTargetFile(request, targetFilename);
|
||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||
|
||||
executeTransformCommand(sourceFile, targetFile, timeout);
|
||||
javaExecutor.call(sourceFile, targetFile);
|
||||
|
||||
return createAttachment(targetFilename, targetFile, testDelay);
|
||||
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
|
||||
LogEntry.setTargetSize(targetFile.length());
|
||||
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
time += LogEntry.addDelay(testDelay);
|
||||
getProbeTestTransform().recordTransformTime(time);
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processTransform(File sourceFile, File targetFile,
|
||||
public void processTransform(File sourceFile, File targetFile,
|
||||
Map<String, String> transformOptions, Long timeout)
|
||||
{
|
||||
executeTransformCommand(sourceFile, targetFile, timeout);
|
||||
}
|
||||
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile, Long timeout)
|
||||
{
|
||||
timeout = timeout != null && timeout > 0 ? timeout : 0;
|
||||
|
||||
try
|
||||
{
|
||||
convert(sourceFile, targetFile, timeout);
|
||||
}
|
||||
catch (OfficeException e)
|
||||
{
|
||||
throw new TransformException(400, "LibreOffice server conversion failed: \n"+
|
||||
" from file: " + sourceFile + "\n" +
|
||||
" to file: " + targetFile,
|
||||
e);
|
||||
}
|
||||
catch (Throwable throwable)
|
||||
{
|
||||
// Because of the known bug with empty Spreadsheets in JodConverter try to catch exception and produce empty pdf file
|
||||
if (throwable.getCause() instanceof ErrorCodeIOException &&
|
||||
((ErrorCodeIOException) throwable.getCause()).ErrCode == JODCONVERTER_TRANSFORMATION_ERROR_CODE)
|
||||
{
|
||||
logger.warn("Transformation failed: \n" +
|
||||
"from file: " + sourceFile + "\n" +
|
||||
"to file: " + targetFile +
|
||||
"Source file " + sourceFile + " has no content");
|
||||
produceEmptyPdfFile(targetFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetFile.exists() || targetFile.length() == 0L)
|
||||
{
|
||||
throw new TransformException(500, "Transformer failed to create an output file");
|
||||
}
|
||||
}
|
||||
|
||||
void convert(File sourceFile, File targetFile, long timeout)
|
||||
{
|
||||
setJodConverterOnFirstRequest(timeout);
|
||||
OfficeManager officeManager = jodconverter.getOfficeManager();
|
||||
OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
|
||||
converter.convert(sourceFile, targetFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method produces an empty PDF file at the specified File location.
|
||||
* Apache's PDFBox is used to create the PDF file.
|
||||
*/
|
||||
private void produceEmptyPdfFile(File targetFile)
|
||||
{
|
||||
// If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
|
||||
// org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
|
||||
// https://issues.apache.org/jira/browse/PDFBOX-914
|
||||
|
||||
PDDocument pdfDoc = null;
|
||||
PDPageContentStream contentStream = null;
|
||||
try
|
||||
{
|
||||
pdfDoc = new PDDocument();
|
||||
PDPage pdfPage = new PDPage();
|
||||
// Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
|
||||
// that have literally nothing in them. So we'll put a content stream in it.
|
||||
contentStream = new PDPageContentStream(pdfDoc, pdfPage);
|
||||
pdfDoc.addPage(pdfPage);
|
||||
|
||||
// Now write the in-memory PDF document into the temporary file.
|
||||
pdfDoc.save(targetFile.getAbsolutePath());
|
||||
|
||||
}
|
||||
catch (IOException iox)
|
||||
{
|
||||
throw new TransformException(500, "Error creating empty PDF file", iox);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (contentStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
contentStream.close();
|
||||
}
|
||||
catch (IOException ignored)
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
}
|
||||
if (pdfDoc != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
pdfDoc.close();
|
||||
}
|
||||
catch (IOException ignored)
|
||||
{
|
||||
// Intentionally empty.
|
||||
}
|
||||
}
|
||||
}
|
||||
javaExecutor.call(sourceFile, targetFile);
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,11 @@
|
||||
* agreement is prohibited.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
|
||||
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
|
||||
public interface JodConverter
|
||||
{
|
||||
@ -21,11 +21,11 @@ public interface JodConverter
|
||||
* Gets the JodConverter OfficeManager.
|
||||
* @return
|
||||
*/
|
||||
public abstract OfficeManager getOfficeManager();
|
||||
OfficeManager getOfficeManager();
|
||||
|
||||
/**
|
||||
* This method returns a boolean indicating whether the JodConverter connection to OOo is available.
|
||||
* @return <code>true</code> if available, else <code>false</code>
|
||||
*/
|
||||
public abstract boolean isAvailable();
|
||||
boolean isAvailable();
|
||||
}
|
@ -23,12 +23,11 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
@ -41,7 +40,7 @@ import org.artofsolving.jodconverter.office.OfficeManager;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
|
||||
/**
|
||||
* Makes use of the JodConverter library and an installed
|
||||
@ -51,10 +50,10 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
*/
|
||||
public class JodConverterSharedInstance implements InitializingBean, DisposableBean, JodConverter
|
||||
{
|
||||
private static Log logger = LogFactory.getLog(JodConverterSharedInstance.class);
|
||||
private static final Log logger = LogFactory.getLog(JodConverterSharedInstance.class);
|
||||
|
||||
private OfficeManager officeManager;
|
||||
boolean isAvailable = false;
|
||||
private boolean isAvailable = false;
|
||||
|
||||
// JodConverter's built-in configuration settings.
|
||||
//
|
||||
@ -82,7 +81,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
private Boolean deprecatedOooEnabled;
|
||||
private int[] deprecatedOooPortNumbers;
|
||||
|
||||
public void setMaxTasksPerProcess(String maxTasksPerProcess)
|
||||
void setMaxTasksPerProcess(String maxTasksPerProcess)
|
||||
{
|
||||
Long l = parseStringForLong(maxTasksPerProcess.trim());
|
||||
if (l != null)
|
||||
@ -96,7 +95,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public void setOfficeHome(String officeHome)
|
||||
void setOfficeHome(String officeHome)
|
||||
{
|
||||
this.officeHome = officeHome == null ? "" : officeHome.trim();
|
||||
}
|
||||
@ -106,7 +105,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
this.deprecatedOooExe = deprecatedOooExe == null ? "" : deprecatedOooExe.trim();
|
||||
}
|
||||
|
||||
public void setPortNumbers(String s)
|
||||
void setPortNumbers(String s)
|
||||
{
|
||||
portNumbers = parsePortNumbers(s, "jodconverter");
|
||||
}
|
||||
@ -147,12 +146,12 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
return portNumbers;
|
||||
}
|
||||
|
||||
public void setTaskExecutionTimeout(String taskExecutionTimeout)
|
||||
void setTaskExecutionTimeout(String taskExecutionTimeout)
|
||||
{
|
||||
this.taskExecutionTimeout = parseStringForLong(taskExecutionTimeout.trim());
|
||||
}
|
||||
|
||||
public void setTemplateProfileDir(String templateProfileDir)
|
||||
void setTemplateProfileDir(String templateProfileDir)
|
||||
{
|
||||
if (templateProfileDir == null || templateProfileDir.trim().length() == 0)
|
||||
{
|
||||
@ -169,26 +168,26 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
}
|
||||
}
|
||||
|
||||
public void setTaskQueueTimeout(String taskQueueTimeout)
|
||||
void setTaskQueueTimeout(String taskQueueTimeout)
|
||||
{
|
||||
this.taskQueueTimeout = parseStringForLong(taskQueueTimeout.trim());
|
||||
}
|
||||
|
||||
public void setConnectTimeout(String connectTimeout)
|
||||
void setConnectTimeout(String connectTimeout)
|
||||
{
|
||||
this.connectTimeout = parseStringForLong(connectTimeout.trim());
|
||||
}
|
||||
|
||||
public void setEnabled(String enabled)
|
||||
void setEnabled(final String enabledStr)
|
||||
{
|
||||
this.enabled = parseEnabled(enabled);
|
||||
enabled = parseEnabled(enabledStr);
|
||||
|
||||
// If this is a request from the Enterprise Admin console to disable the JodConverter.
|
||||
if (this.enabled == false && (deprecatedOooEnabled == null || deprecatedOooEnabled == false))
|
||||
if (!enabled && (deprecatedOooEnabled == null || !deprecatedOooEnabled))
|
||||
{
|
||||
// We need to change isAvailable to false so we don't make calls to a previously started OfficeManger.
|
||||
// In the case of Enterprise it is very unlikely that ooo.enabled will have been set to true.
|
||||
this.isAvailable = false;
|
||||
isAvailable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +206,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
// So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated
|
||||
// ooo.exe setting rather than the jodconverter.officeHome setting if we don't have the jod setting as
|
||||
// oooDirect was replaced by jodconverter after this release.
|
||||
String getOfficeHome()
|
||||
private String getOfficeHome()
|
||||
{
|
||||
String officeHome = this.officeHome;
|
||||
if ((officeHome == null || officeHome.isEmpty()) && (deprecatedOooExe != null && !deprecatedOooExe.isEmpty()))
|
||||
@ -243,7 +242,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
// Community set properties via alfresco-global.properties.
|
||||
// Enterprise may do the same but may also reset jodconverter.enabled them via the Admin console.
|
||||
// In the case of Enterprise it is very unlikely that ooo.enabled will be set to true.
|
||||
boolean isEnabled()
|
||||
private boolean isEnabled()
|
||||
{
|
||||
return (deprecatedOooEnabled != null && deprecatedOooEnabled) || (enabled != null && enabled);
|
||||
}
|
||||
@ -251,7 +250,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
// So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated
|
||||
// ooo.port setting rather than the jodconverter.portNumbers if ooo.enabled is true and jodconverter.enabled
|
||||
// is false.
|
||||
int[] getPortNumbers()
|
||||
private int[] getPortNumbers()
|
||||
{
|
||||
return (enabled == null || !enabled) && deprecatedOooEnabled != null && deprecatedOooEnabled
|
||||
? deprecatedOooPortNumbers
|
||||
@ -260,11 +259,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
|
||||
private Long parseStringForLong(String string)
|
||||
{
|
||||
Long result = null;
|
||||
try
|
||||
{
|
||||
long l = Long.parseLong(string);
|
||||
result = new Long(l);
|
||||
return Long.parseLong(string);
|
||||
}
|
||||
catch (NumberFormatException nfe)
|
||||
{
|
||||
@ -272,9 +269,8 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
{
|
||||
logger.debug("Cannot parse numerical value from " + string);
|
||||
}
|
||||
// else intentionally empty
|
||||
}
|
||||
return result;
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -283,14 +279,14 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
*/
|
||||
public boolean isAvailable()
|
||||
{
|
||||
final boolean result = isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
|
||||
return result;
|
||||
return isAvailable && (officeManager != null || (url != null && !url.isEmpty()));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet()
|
||||
{
|
||||
// isAvailable defaults to false afterPropertiesSet. It only becomes true on successful completion of this method.
|
||||
@ -318,7 +314,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
}
|
||||
|
||||
// Only start the JodConverter instance(s) if the subsystem is enabled.
|
||||
if (isEnabled() == false)
|
||||
if (!isEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -418,7 +414,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
|
||||
private void logAllSofficeFilesUnderOfficeHome()
|
||||
{
|
||||
if (logger.isDebugEnabled() == false)
|
||||
if (!logger.isDebugEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -430,7 +426,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
|
||||
logFileInfo(requestedOfficeHome);
|
||||
|
||||
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<File>(), 2))
|
||||
for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList<>(), 2))
|
||||
{
|
||||
logFileInfo(f);
|
||||
}
|
||||
@ -449,26 +445,11 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
return results;
|
||||
}
|
||||
|
||||
File[] matchingFiles = searchRoot.listFiles(new FilenameFilter()
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File dir, String name)
|
||||
{
|
||||
return name.startsWith("soffice");
|
||||
}
|
||||
});
|
||||
for (File f : matchingFiles)
|
||||
{
|
||||
results.add(f);
|
||||
}
|
||||
File[] matchingFiles = searchRoot.listFiles((dir, name) -> name.startsWith("soffice"));
|
||||
Arrays.stream(matchingFiles)
|
||||
.forEach(results::add);
|
||||
|
||||
for (File dir : searchRoot.listFiles(new FileFilter()
|
||||
{
|
||||
@Override
|
||||
public boolean accept(File f) {
|
||||
return f.isDirectory();
|
||||
}
|
||||
}))
|
||||
for (File dir : searchRoot.listFiles(File::isDirectory))
|
||||
{
|
||||
findSofficePrograms(dir, results, currentRecursionDepth + 1, maxRecursionDepth);
|
||||
}
|
||||
@ -482,7 +463,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
*/
|
||||
private void logFileInfo(File f)
|
||||
{
|
||||
if (logger.isDebugEnabled() == false)
|
||||
if (!logger.isDebugEnabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -512,7 +493,9 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.DisposableBean#destroy()
|
||||
*/
|
||||
public void destroy() throws Exception {
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
this.isAvailable = false;
|
||||
if (officeManager != null)
|
||||
{
|
||||
@ -530,6 +513,7 @@ public class JodConverterSharedInstance implements InitializingBean, DisposableB
|
||||
/* (non-Javadoc)
|
||||
* @see org.alfresco.repo.content.JodConverterWorker#getOfficeManager()
|
||||
*/
|
||||
@Override
|
||||
public OfficeManager getOfficeManager()
|
||||
{
|
||||
return officeManager;
|
@ -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.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transformer.executors.LibreOfficeJavaExecutor;
|
||||
import org.alfresco.transformer.model.FileRefEntity;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.artofsolving.jodconverter.office.OfficeException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.mockito.Mock;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
@ -62,15 +74,18 @@ import org.springframework.util.StringUtils;
|
||||
@WebMvcTest(LibreOfficeControllerTest.class)
|
||||
public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
@Mock
|
||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||
|
||||
@SpyBean
|
||||
private LibreOfficeJavaExecutor javaExecutor;
|
||||
|
||||
@SpyBean
|
||||
private LibreOfficeController controller;
|
||||
|
||||
@Before
|
||||
public void before() throws IOException
|
||||
{
|
||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
||||
super.controller = controller;
|
||||
|
||||
sourceExtension = "doc";
|
||||
targetExtension = "pdf";
|
||||
sourceMimetype = "application/msword";
|
||||
@ -78,11 +93,11 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
// The following is based on super.mockTransformCommand(...)
|
||||
// This is because LibreOffice used JodConverter rather than a RuntimeExec
|
||||
|
||||
expectedSourceFileBytes = Files.readAllBytes(getTestFile("quick."+sourceExtension, true).toPath());
|
||||
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick."+targetExtension, true).toPath());
|
||||
sourceFile = new MockMultipartFile("file", "quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
expectedSourceFileBytes = Files.readAllBytes(getTestFile("quick." + sourceExtension, true).toPath());
|
||||
expectedTargetFileBytes = Files.readAllBytes(getTestFile("quick." + targetExtension, true).toPath());
|
||||
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
|
||||
doAnswer((Answer) invocation ->
|
||||
doAnswer(invocation ->
|
||||
{
|
||||
File sourceFile = invocation.getArgument(0);
|
||||
File targetFile = invocation.getArgument(1);
|
||||
@ -91,19 +106,12 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
assertNotNull(sourceFile);
|
||||
assertNotNull(targetFile);
|
||||
|
||||
Long actualTimeout = invocation.getArgument(2);
|
||||
assertNotNull(actualTimeout);
|
||||
if (expectedTimeout != null)
|
||||
{
|
||||
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
|
||||
}
|
||||
|
||||
// Copy a test file into the target file location if it exists
|
||||
String actualTarget = targetFile.getAbsolutePath();
|
||||
int i = actualTarget.lastIndexOf('_');
|
||||
if (i >= 0)
|
||||
{
|
||||
String testFilename = actualTarget.substring(i+1);
|
||||
String testFilename = actualTarget.substring(i + 1);
|
||||
File testFile = getTestFile(testFilename, false);
|
||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile, targetFile);
|
||||
}
|
||||
@ -113,16 +121,28 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||
|
||||
return null;
|
||||
}).when(controller).convert(any(), any(), anyLong());
|
||||
}).when(javaExecutor).convert(any(), any());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mockTransformCommand(String sourceExtension, String targetExtension,
|
||||
String sourceMimetype, boolean readTargetFileBytes)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractTransformerController getController()
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void badExitCodeTest() throws Exception
|
||||
{
|
||||
doThrow(OfficeException.class).when(controller).convert(any(), any(), anyLong());
|
||||
doThrow(OfficeException.class).when(javaExecutor).convert(any(), any());
|
||||
|
||||
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/transform")
|
||||
mockMvc.perform(MockMvcRequestBuilders.multipart("/transform")
|
||||
.file(sourceFile)
|
||||
.param("targetExtension", "xxx"))
|
||||
.andExpect(status().is(400))
|
||||
@ -137,4 +157,58 @@ public class LibreOfficeControllerTest extends AbstractTransformerControllerTest
|
||||
transformRequest.setSourceMediaType("application/msword");
|
||||
transformRequest.setTargetMediaType(MediaType.IMAGE_PNG_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoTransform() throws Exception
|
||||
{
|
||||
// Files
|
||||
String sourceFileRef = UUID.randomUUID().toString();
|
||||
File sourceFile = getTestFile("quick." + sourceExtension, true);
|
||||
String targetFileRef = UUID.randomUUID().toString();
|
||||
|
||||
// Transformation Request POJO
|
||||
TransformRequest transformRequest = new TransformRequest();
|
||||
transformRequest.setRequestId("1");
|
||||
transformRequest.setSchema(1);
|
||||
transformRequest.setClientData("Alfresco Digital Business Platform");
|
||||
transformRequest.setTransformRequestOptions(new HashMap<>());
|
||||
transformRequest.setSourceReference(sourceFileRef);
|
||||
transformRequest.setSourceExtension(sourceExtension);
|
||||
transformRequest.setSourceSize(sourceFile.length());
|
||||
transformRequest.setTargetExtension(targetExtension);
|
||||
|
||||
// HTTP Request
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=quick." + sourceExtension);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new FileSystemResource(
|
||||
sourceFile), headers, HttpStatus.OK);
|
||||
|
||||
when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response);
|
||||
when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(
|
||||
new FileRefResponse(new FileRefEntity(targetFileRef)));
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
|
||||
// Update the Transformation Request with any specific params before sending it
|
||||
updateTransformRequestWithSpecificOptions(transformRequest);
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(
|
||||
MockMvcRequestBuilders.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.andExpect(
|
||||
status().is(HttpStatus.CREATED.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
TransformReply transformReply = objectMapper.readValue(transformationReplyAsString,
|
||||
TransformReply.class);
|
||||
|
||||
// Assert the reply
|
||||
assertEquals(transformRequest.getRequestId(), transformReply.getRequestId());
|
||||
assertEquals(transformRequest.getClientData(), transformReply.getClientData());
|
||||
assertEquals(transformRequest.getSchema(), transformReply.getSchema());
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import org.alfresco.transformer.AbstractHttpRequestTest;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
@ -47,5 +46,5 @@ public class LibreOfficeHttpRequestTest extends AbstractHttpRequestTest
|
||||
protected String getSourceExtension()
|
||||
{
|
||||
return "doc";
|
||||
};
|
||||
}
|
||||
}
|
@ -11,15 +11,17 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.alfresco.transformer.executors.TikaJavaExecutor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
|
||||
public class Application
|
||||
@ -27,10 +29,18 @@ public class Application
|
||||
@Value("${container.name}")
|
||||
private String containerName;
|
||||
|
||||
@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
|
||||
@Bean
|
||||
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags()
|
||||
{
|
||||
return registry -> registry.config().commonTags("containerName", containerName);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TikaJavaExecutor javaExecutor() throws Exception
|
||||
{
|
||||
return new TikaJavaExecutor();
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
SpringApplication.run(Application.class, args);
|
||||
|
@ -12,21 +12,31 @@
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN;
|
||||
import static org.alfresco.transformer.Tika.INCLUDE_CONTENTS;
|
||||
import static org.alfresco.transformer.Tika.NOT_EXTRACT_BOOKMARKS_TEXT;
|
||||
import static org.alfresco.transformer.Tika.PDF_BOX;
|
||||
import static org.alfresco.transformer.Tika.TARGET_ENCODING;
|
||||
import static org.alfresco.transformer.Tika.TARGET_MIMETYPE;
|
||||
import static org.alfresco.transformer.Tika.TRANSFORM_NAMES;
|
||||
import static org.alfresco.transformer.executors.Tika.INCLUDE_CONTENTS;
|
||||
import static org.alfresco.transformer.executors.Tika.NOT_EXTRACT_BOOKMARKS_TEXT;
|
||||
import static org.alfresco.transformer.executors.Tika.PDF_BOX;
|
||||
import static org.alfresco.transformer.executors.Tika.TARGET_ENCODING;
|
||||
import static org.alfresco.transformer.executors.Tika.TARGET_MIMETYPE;
|
||||
import static org.alfresco.transformer.executors.Tika.TRANSFORM_NAMES;
|
||||
import static org.alfresco.transformer.fs.FileManager.createAttachment;
|
||||
import static org.alfresco.transformer.fs.FileManager.createSourceFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
|
||||
import static org.alfresco.transformer.logging.StandardMessages.ENTERPRISE_LICENCE;
|
||||
import static org.alfresco.transformer.util.Util.stringToBoolean;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.transformer.exceptions.TransformException;
|
||||
import org.alfresco.transformer.executors.TikaJavaExecutor;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
@ -35,7 +45,6 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* Controller for the Docker based Tika transformers.
|
||||
@ -61,51 +70,45 @@ import org.xml.sax.SAXException;
|
||||
@Controller
|
||||
public class TikaController extends AbstractTransformerController
|
||||
{
|
||||
private Tika tika;
|
||||
private static final Log logger = LogFactory.getLog(TikaController.class);
|
||||
|
||||
@Autowired
|
||||
public TikaController() throws TikaException, IOException, SAXException
|
||||
private TikaJavaExecutor javaExecutor;
|
||||
|
||||
@Autowired
|
||||
public TikaController()
|
||||
{
|
||||
logger = LogFactory.getLog(TikaController.class);
|
||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
logEnterpriseLicenseMessage();
|
||||
Arrays.stream(ENTERPRISE_LICENCE.split("\\n")).forEach(logger::info);
|
||||
logger.info("Tika is from Apache. See the license at http://www.apache.org/licenses/LICENSE-2.0. or in /Apache\\ 2.0.txt");
|
||||
logger.info("--------------------------------------------------------------------------------------------------------------------------------------------------------------");
|
||||
|
||||
tika = new Tika();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTransformerName()
|
||||
public String getTransformerName()
|
||||
{
|
||||
return "Tika";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void callTransform(String... args)
|
||||
{
|
||||
tika.transform(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String version()
|
||||
public String version()
|
||||
{
|
||||
return "Tika available";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProbeTestTransform getProbeTestTransform()
|
||||
public ProbeTestTransform getProbeTestTransform()
|
||||
{
|
||||
// See the Javadoc on this method and Probes.md for the choice of these values.
|
||||
// the livenessPercentage is a little large as Tika does tend to suffer from slow transforms that class with a gc.
|
||||
return new ProbeTestTransform(this, "quick.pdf", "quick.txt",
|
||||
60, 16, 400, 10240, 60*30+1, 60*15+20)
|
||||
return new ProbeTestTransform(this, logger, "quick.pdf", "quick.txt",
|
||||
60, 16, 400, 10240, 60 * 30 + 1, 60 * 15 + 20)
|
||||
{
|
||||
@Override
|
||||
protected void executeTransformCommand(File sourceFile, File targetFile)
|
||||
{
|
||||
TikaController.this.callTransform(sourceFile, targetFile, PDF_BOX,
|
||||
TARGET_MIMETYPE+MIMETYPE_TEXT_PLAIN, TARGET_ENCODING+"UTF-8");
|
||||
javaExecutor.call(sourceFile, targetFile, PDF_BOX,
|
||||
TARGET_MIMETYPE + MIMETYPE_TEXT_PLAIN, TARGET_ENCODING + "UTF-8");
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -121,9 +124,8 @@ public class TikaController extends AbstractTransformerController
|
||||
@RequestParam(value = "testDelay", required = false) Long testDelay,
|
||||
|
||||
@RequestParam(value = "transform") String transform,
|
||||
@RequestParam(value="includeContents", required = false) Boolean includeContents,
|
||||
@RequestParam(value="notExtractBookmarksText", required = false) Boolean notExtractBookmarksText)
|
||||
|
||||
@RequestParam(value = "includeContents", required = false) Boolean includeContents,
|
||||
@RequestParam(value = "notExtractBookmarksText", required = false) Boolean notExtractBookmarksText)
|
||||
{
|
||||
if (!TRANSFORM_NAMES.contains(transform))
|
||||
{
|
||||
@ -131,6 +133,7 @@ public class TikaController extends AbstractTransformerController
|
||||
}
|
||||
|
||||
String targetFilename = createTargetFileName(sourceMultipartFile.getOriginalFilename(), targetExtension);
|
||||
getProbeTestTransform().incrementTransformerCount();
|
||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||
File targetFile = createTargetFile(request, targetFilename);
|
||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||
@ -138,16 +141,21 @@ public class TikaController extends AbstractTransformerController
|
||||
// TODO Consider streaming the request and response rather than using temporary files
|
||||
// https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/streaming-response-body.html
|
||||
|
||||
callTransform(sourceFile, targetFile, transform,
|
||||
javaExecutor.call(sourceFile, targetFile, transform,
|
||||
includeContents != null && includeContents ? INCLUDE_CONTENTS : null,
|
||||
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT: null,
|
||||
TARGET_MIMETYPE+targetMimetype, TARGET_ENCODING+targetEncoding);
|
||||
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT : null,
|
||||
TARGET_MIMETYPE + targetMimetype, TARGET_ENCODING + targetEncoding);
|
||||
|
||||
return createAttachment(targetFilename, targetFile, testDelay);
|
||||
final ResponseEntity<Resource> body = createAttachment(targetFilename, targetFile);
|
||||
LogEntry.setTargetSize(targetFile.length());
|
||||
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
time += LogEntry.addDelay(testDelay);
|
||||
getProbeTestTransform().recordTransformTime(time);
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processTransform(File sourceFile, File targetFile,
|
||||
public void processTransform(File sourceFile, File targetFile,
|
||||
Map<String, String> transformOptions, Long timeout)
|
||||
{
|
||||
|
||||
@ -157,9 +165,9 @@ public class TikaController extends AbstractTransformerController
|
||||
String targetMimetype = transformOptions.get("targetMimetype");
|
||||
String targetEncoding = transformOptions.get("targetEncoding");
|
||||
|
||||
callTransform(sourceFile, targetFile, transform,
|
||||
javaExecutor.call(sourceFile, targetFile, transform,
|
||||
includeContents != null && includeContents ? INCLUDE_CONTENTS : null,
|
||||
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT: null,
|
||||
notExtractBookmarksText != null && notExtractBookmarksText ? NOT_EXTRACT_BOOKMARKS_TEXT : null,
|
||||
TARGET_MIMETYPE + targetMimetype, TARGET_ENCODING + targetEncoding);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,36 @@
|
||||
* agreement is prohibited.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_HTML;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_IMAGE_JPEG;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_IMAGE_PNG;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_IMAGE_TIFF;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_CSV;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_TEXT_PLAIN;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XHTML;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XML;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.sax.SAXTransformerFactory;
|
||||
import javax.xml.transform.sax.TransformerHandler;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.apache.tika.config.TikaConfig;
|
||||
import org.apache.tika.exception.TikaException;
|
||||
@ -30,19 +59,6 @@ import org.xml.sax.Attributes;
|
||||
import org.xml.sax.ContentHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.TransformerConfigurationException;
|
||||
import javax.xml.transform.sax.SAXTransformerFactory;
|
||||
import javax.xml.transform.sax.TransformerHandler;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.alfresco.repo.content.MimetypeMap.*;
|
||||
|
||||
/**
|
||||
* Stripped down command line Tika transformers. Not actually run as a separate process, but the code fits the patten
|
||||
* used by transformers that do.
|
||||
@ -424,7 +440,7 @@ public class Tika
|
||||
public static final String TIKA_AUTO = "TikaAuto";
|
||||
public static final String TEXT_MINING = "TextMining";
|
||||
|
||||
public static final List<String> TRANSFORM_NAMES = Arrays.asList(
|
||||
public static final List<String> TRANSFORM_NAMES = asList(
|
||||
ARCHIVE, OUTLOOK_MSG, PDF_BOX, POI_OFFICE, POI, POI_OO_XML, TIKA_AUTO, TEXT_MINING);
|
||||
|
||||
public static final String TARGET_MIMETYPE = "--targetMimetype=";
|
||||
@ -445,17 +461,17 @@ public class Tika
|
||||
public static final String XML = "xml";
|
||||
public static final String ZIP = "zip";
|
||||
|
||||
private Parser packageParser = new PackageParser();
|
||||
private Parser pdfParser = new PDFParser();
|
||||
private Parser officeParser = new OfficeParser();
|
||||
private Parser autoDetectParser;
|
||||
private Parser ooXmlParser = new OOXMLParser();
|
||||
private Parser tikaOfficeDetectParser = new TikaOfficeDetectParser();
|
||||
private PDFParserConfig pdfParserConfig = new PDFParserConfig();
|
||||
private final Parser packageParser = new PackageParser();
|
||||
private final Parser pdfParser = new PDFParser();
|
||||
private final Parser officeParser = new OfficeParser();
|
||||
private final Parser autoDetectParser;
|
||||
private final Parser ooXmlParser = new OOXMLParser();
|
||||
private final Parser tikaOfficeDetectParser = new TikaOfficeDetectParser();
|
||||
private final PDFParserConfig pdfParserConfig = new PDFParserConfig();
|
||||
|
||||
private DocumentSelector pdfBoxEmbededDocumentSelector = new DocumentSelector()
|
||||
{
|
||||
private List<String> disabledMediaTypes = Arrays.asList(new String[] {MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_TIFF, MIMETYPE_IMAGE_PNG});
|
||||
private final List<String> disabledMediaTypes = asList(MIMETYPE_IMAGE_JPEG, MIMETYPE_IMAGE_TIFF, MIMETYPE_IMAGE_PNG);
|
||||
|
||||
@Override
|
||||
public boolean select(Metadata metadata)
|
||||
@ -628,17 +644,14 @@ public class Tika
|
||||
String sourceFilename,
|
||||
String targetFilename, String targetMimetype, String targetEncoding)
|
||||
{
|
||||
InputStream is = null;
|
||||
OutputStream os = null;
|
||||
Writer ow = null;
|
||||
|
||||
try
|
||||
try (InputStream is = new BufferedInputStream(new FileInputStream(sourceFilename));
|
||||
OutputStream os = new FileOutputStream(targetFilename);
|
||||
Writer ow = new BufferedWriter(new OutputStreamWriter(os, targetEncoding)))
|
||||
{
|
||||
is = new BufferedInputStream(new FileInputStream(sourceFilename));
|
||||
os = new FileOutputStream(targetFilename);
|
||||
ow = new BufferedWriter(new OutputStreamWriter(os, targetEncoding));
|
||||
Metadata metadata = new Metadata();
|
||||
ParseContext context = buildParseContext(documentSelector, includeContents, notExtractBookmarksText);
|
||||
ParseContext context = buildParseContext(documentSelector, includeContents,
|
||||
notExtractBookmarksText);
|
||||
ContentHandler handler = getContentHandler(targetMimetype, ow);
|
||||
|
||||
parser.parse(is, handler, metadata, context);
|
||||
@ -647,24 +660,9 @@ public class Tika
|
||||
{
|
||||
throw new IllegalStateException(e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (is != null)
|
||||
{
|
||||
try { is.close(); } catch (Throwable e) {}
|
||||
}
|
||||
if (os != null)
|
||||
{
|
||||
try { os.close(); } catch (Throwable e) {}
|
||||
}
|
||||
if (ow != null)
|
||||
{
|
||||
try { ow.close(); } catch (Throwable e) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ContentHandler getContentHandler(String targetMimetype, Writer output)
|
||||
private ContentHandler getContentHandler(String targetMimetype, Writer output)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -676,7 +674,7 @@ public class Tika
|
||||
else
|
||||
{
|
||||
SAXTransformerFactory factory = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
|
||||
TransformerHandler transformerHandler = null;
|
||||
TransformerHandler transformerHandler;
|
||||
transformerHandler = factory.newTransformerHandler();
|
||||
transformerHandler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
transformerHandler.setResult(new StreamResult(output));
|
||||
@ -792,7 +790,8 @@ public class Tika
|
||||
}
|
||||
}
|
||||
|
||||
protected ParseContext buildParseContext(DocumentSelector documentSelector, Boolean includeContents, Boolean notExtractBookmarksText)
|
||||
private ParseContext buildParseContext(DocumentSelector documentSelector,
|
||||
Boolean includeContents, Boolean notExtractBookmarksText)
|
||||
{
|
||||
ParseContext context = new ParseContext();
|
||||
|
@ -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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.executors;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -43,7 +43,7 @@ import org.apache.tika.parser.microsoft.ooxml.OOXMLParser;
|
||||
import org.xml.sax.ContentHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
///////// THIS FILE WAS A COPY OF THE CODE IN alfresco-repository /////////////
|
||||
|
||||
/**
|
||||
* <a href="http://tika.apache.org/Apache Tika">Apache Tika</a> assumes that
|
||||
@ -58,11 +58,11 @@ import org.xml.sax.SAXException;
|
||||
* @author Nick Burch
|
||||
*/
|
||||
public class TikaOfficeDetectParser implements Parser {
|
||||
private Parser ole2Parser = new OfficeParser();
|
||||
private Parser ooxmlParser = new OOXMLParser();
|
||||
private final Parser ole2Parser = new OfficeParser();
|
||||
private final Parser ooxmlParser = new OOXMLParser();
|
||||
|
||||
public Set<MediaType> getSupportedTypes(ParseContext parseContext) {
|
||||
Set<MediaType> types = new HashSet<MediaType>();
|
||||
Set<MediaType> types = new HashSet<>();
|
||||
types.addAll(ole2Parser.getSupportedTypes(parseContext));
|
||||
types.addAll(ooxmlParser.getSupportedTypes(parseContext));
|
||||
return types;
|
@ -37,41 +37,68 @@ import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_WORD;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XHTML;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_XML;
|
||||
import static org.alfresco.repo.content.MimetypeMap.MIMETYPE_ZIP;
|
||||
import static org.alfresco.transformer.Tika.ARCHIVE;
|
||||
import static org.alfresco.transformer.Tika.CSV;
|
||||
import static org.alfresco.transformer.Tika.DOC;
|
||||
import static org.alfresco.transformer.Tika.DOCX;
|
||||
import static org.alfresco.transformer.Tika.HTML;
|
||||
import static org.alfresco.transformer.Tika.MSG;
|
||||
import static org.alfresco.transformer.Tika.OUTLOOK_MSG;
|
||||
import static org.alfresco.transformer.Tika.PDF;
|
||||
import static org.alfresco.transformer.Tika.PDF_BOX;
|
||||
import static org.alfresco.transformer.Tika.POI;
|
||||
import static org.alfresco.transformer.Tika.POI_OFFICE;
|
||||
import static org.alfresco.transformer.Tika.POI_OO_XML;
|
||||
import static org.alfresco.transformer.Tika.PPTX;
|
||||
import static org.alfresco.transformer.Tika.TEXT_MINING;
|
||||
import static org.alfresco.transformer.Tika.TIKA_AUTO;
|
||||
import static org.alfresco.transformer.Tika.TXT;
|
||||
import static org.alfresco.transformer.Tika.XHTML;
|
||||
import static org.alfresco.transformer.Tika.XML;
|
||||
import static org.alfresco.transformer.Tika.XSLX;
|
||||
import static org.alfresco.transformer.Tika.ZIP;
|
||||
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
||||
import static org.alfresco.transformer.executors.Tika.ARCHIVE;
|
||||
import static org.alfresco.transformer.executors.Tika.CSV;
|
||||
import static org.alfresco.transformer.executors.Tika.DOC;
|
||||
import static org.alfresco.transformer.executors.Tika.DOCX;
|
||||
import static org.alfresco.transformer.executors.Tika.HTML;
|
||||
import static org.alfresco.transformer.executors.Tika.MSG;
|
||||
import static org.alfresco.transformer.executors.Tika.OUTLOOK_MSG;
|
||||
import static org.alfresco.transformer.executors.Tika.PDF;
|
||||
import static org.alfresco.transformer.executors.Tika.PDF_BOX;
|
||||
import static org.alfresco.transformer.executors.Tika.POI;
|
||||
import static org.alfresco.transformer.executors.Tika.POI_OFFICE;
|
||||
import static org.alfresco.transformer.executors.Tika.POI_OO_XML;
|
||||
import static org.alfresco.transformer.executors.Tika.PPTX;
|
||||
import static org.alfresco.transformer.executors.Tika.TEXT_MINING;
|
||||
import static org.alfresco.transformer.executors.Tika.TIKA_AUTO;
|
||||
import static org.alfresco.transformer.executors.Tika.TXT;
|
||||
import static org.alfresco.transformer.executors.Tika.XHTML;
|
||||
import static org.alfresco.transformer.executors.Tika.XML;
|
||||
import static org.alfresco.transformer.executors.Tika.XSLX;
|
||||
import static org.alfresco.transformer.executors.Tika.ZIP;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transformer.executors.TikaJavaExecutor;
|
||||
import org.alfresco.transformer.model.FileRefEntity;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Test the TikaController without a server.
|
||||
@ -81,37 +108,126 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
|
||||
@WebMvcTest(TikaController.class)
|
||||
public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
{
|
||||
public static final String EXPECTED_XHTML_CONTENT_CONTAINS = "<p>The quick brown fox jumps over the lazy dog</p>";
|
||||
public static final String EXPECTED_TEXT_CONTENT_CONTAINS = "The quick brown fox jumps over the lazy dog";
|
||||
public static final String EXPECTED_MSG_CONTENT_CONTAINS = "Recipients\n" +
|
||||
private static final String EXPECTED_XHTML_CONTENT_CONTAINS = "<p>The quick brown fox jumps over the lazy dog</p>";
|
||||
private static final String EXPECTED_TEXT_CONTENT_CONTAINS = "The quick brown fox jumps over the lazy dog";
|
||||
private static final String EXPECTED_MSG_CONTENT_CONTAINS = "Recipients\n" +
|
||||
"\tmark.rogers@alfresco.com; speedy@quick.com; mrquick@nowhere.com\n" +
|
||||
"\n" +
|
||||
"The quick brown fox jumps over the lazy dogs";
|
||||
public static final String EXPECTED_CSV_CONTENT_CONTAINS = "\"The\",\"quick\",\"brown\",\"fox\"";
|
||||
private static final String EXPECTED_CSV_CONTENT_CONTAINS = "\"The\",\"quick\",\"brown\",\"fox\"";
|
||||
|
||||
@Mock
|
||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockTransformCommand;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockCheckCommand;
|
||||
|
||||
@SpyBean
|
||||
private TikaJavaExecutor javaExecutor;
|
||||
|
||||
@SpyBean
|
||||
private TikaController controller;
|
||||
|
||||
String transform = PDF_BOX;
|
||||
String targetEncoding = "UTF-8";
|
||||
String targetMimetype = MIMETYPE_TEXT_PLAIN;
|
||||
private String transform = PDF_BOX;
|
||||
private String targetEncoding = "UTF-8";
|
||||
private String targetMimetype = MIMETYPE_TEXT_PLAIN;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception
|
||||
public void before()
|
||||
{
|
||||
controller.setAlfrescoSharedFileStoreClient(alfrescoSharedFileStoreClient);
|
||||
super.controller = controller;
|
||||
|
||||
sourceExtension = "pdf";
|
||||
targetExtension = "txt";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mockTransformCommand(String sourceExtension,
|
||||
String targetExtension, String sourceMimetype,
|
||||
boolean readTargetFileBytes) throws IOException
|
||||
{
|
||||
this.sourceExtension = sourceExtension;
|
||||
this.targetExtension = targetExtension;
|
||||
this.sourceMimetype = sourceMimetype;
|
||||
|
||||
expectedOptions = null;
|
||||
expectedSourceSuffix = null;
|
||||
expectedSourceFileBytes = readTestFile(sourceExtension);
|
||||
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
|
||||
sourceFile = new MockMultipartFile("file", "quick." + sourceExtension, sourceMimetype,
|
||||
expectedSourceFileBytes);
|
||||
|
||||
when(mockTransformCommand.execute(any(), anyLong())).thenAnswer(
|
||||
(Answer<RuntimeExec.ExecutionResult>) invocation -> {
|
||||
Map<String, String> actualProperties = invocation.getArgument(0);
|
||||
assertEquals("There should be 3 properties", 3, actualProperties.size());
|
||||
|
||||
String actualOptions = actualProperties.get("options");
|
||||
String actualSource = actualProperties.get("source");
|
||||
String actualTarget = actualProperties.get("target");
|
||||
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
|
||||
|
||||
assertNotNull(actualSource);
|
||||
assertNotNull(actualTarget);
|
||||
if (expectedSourceSuffix != null)
|
||||
{
|
||||
assertTrue(
|
||||
"The source file \"" + actualSource + "\" should have ended in \"" + expectedSourceSuffix + "\"",
|
||||
actualSource.endsWith(expectedSourceSuffix));
|
||||
actualSource = actualSource.substring(0,
|
||||
actualSource.length() - expectedSourceSuffix.length());
|
||||
}
|
||||
|
||||
assertNotNull(actualOptions);
|
||||
if (expectedOptions != null)
|
||||
{
|
||||
assertEquals("expectedOptions", expectedOptions, actualOptions);
|
||||
}
|
||||
|
||||
Long actualTimeout = invocation.getArgument(1);
|
||||
assertNotNull(actualTimeout);
|
||||
if (expectedTimeout != null)
|
||||
{
|
||||
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
|
||||
}
|
||||
|
||||
// Copy a test file into the target file location if it exists
|
||||
int i = actualTarget.lastIndexOf('_');
|
||||
if (i >= 0)
|
||||
{
|
||||
String testFilename = actualTarget.substring(i + 1);
|
||||
File testFile = getTestFile(testFilename, false);
|
||||
File targetFile = new File(actualTarget);
|
||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
|
||||
targetFile);
|
||||
}
|
||||
|
||||
// Check the supplied source file has not been changed.
|
||||
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
|
||||
assertTrue("Source file is not the same",
|
||||
Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||
|
||||
return mockExecutionResult;
|
||||
});
|
||||
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
|
||||
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractTransformerController getController()
|
||||
{
|
||||
return controller;
|
||||
}
|
||||
|
||||
private void transform(String transform, String sourceExtension, String targetExtension,
|
||||
String sourceMimetype, String targetMimetype,
|
||||
Boolean includeContents, String expectedContentContains) throws Exception
|
||||
{
|
||||
// We don't use targetFileBytes as some of the transforms contain different date text based on the os being used.
|
||||
super.mockTransformCommand(controller, sourceExtension, targetExtension, sourceMimetype, false);
|
||||
mockTransformCommand(sourceExtension, targetExtension, sourceMimetype, false);
|
||||
this.transform = transform;
|
||||
this.targetMimetype = targetMimetype;
|
||||
|
||||
@ -141,7 +257,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void simpleTransformTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.simpleTransformTest();
|
||||
}
|
||||
|
||||
@ -149,21 +265,13 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void testDelayTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.testDelayTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void badExitCodeTest() throws Exception
|
||||
{
|
||||
// Ignore the test in super class as the Tika transforms are real rather than mocked up.
|
||||
// It is the mock that returns a non zero exit code.
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void noTargetFileTest() throws Exception
|
||||
public void noTargetFileTest()
|
||||
{
|
||||
// Ignore the test in super class as the Tika transforms are real rather than mocked up.
|
||||
// It is the mock that returns a zero length file for other transformers, when we supply an invalid targetExtension.
|
||||
@ -175,7 +283,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void dotDotSourceFilenameTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.dotDotSourceFilenameTest();
|
||||
}
|
||||
|
||||
@ -183,7 +291,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void noExtensionSourceFilenameTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.noExtensionSourceFilenameTest();
|
||||
}
|
||||
|
||||
@ -191,7 +299,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void badSourceFilenameTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.badSourceFilenameTest();
|
||||
}
|
||||
|
||||
@ -199,7 +307,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void blankSourceFilenameTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.blankSourceFilenameTest();
|
||||
}
|
||||
|
||||
@ -207,7 +315,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void noTargetExtensionTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.noTargetExtensionTest();
|
||||
}
|
||||
|
||||
@ -215,7 +323,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Override
|
||||
public void calculateMaxTime() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
super.calculateMaxTime();
|
||||
}
|
||||
|
||||
@ -224,7 +332,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Test
|
||||
public void badEncodingTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
targetEncoding = "rubbish";
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||
.andExpect(status().is(500));
|
||||
@ -388,7 +496,7 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
@Test
|
||||
public void pdfToTxtExtractBookmarksTest() throws Exception
|
||||
{
|
||||
super.mockTransformCommand(controller, PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockTransformCommand(PDF, TXT, MIMETYPE_PDF, true);
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension).param("notExtractBookmarksText", "true"))
|
||||
.andExpect(status().is(200))
|
||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||
@ -405,4 +513,54 @@ public class TikaControllerTest extends AbstractTransformerControllerTest
|
||||
transformRequest.getTransformRequestOptions().put("targetMimetype", MediaType.TEXT_PLAIN_VALUE);
|
||||
transformRequest.getTransformRequestOptions().put("targetEncoding", "UTF-8");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoTransform() throws Exception
|
||||
{
|
||||
// Files
|
||||
String sourceFileRef = UUID.randomUUID().toString();
|
||||
File sourceFile = getTestFile("quick." + sourceExtension, true);
|
||||
String targetFileRef = UUID.randomUUID().toString();
|
||||
|
||||
|
||||
// Transformation Request POJO
|
||||
TransformRequest transformRequest = new TransformRequest();
|
||||
transformRequest.setRequestId("1");
|
||||
transformRequest.setSchema(1);
|
||||
transformRequest.setClientData("Alfresco Digital Business Platform");
|
||||
transformRequest.setTransformRequestOptions(new HashMap<>());
|
||||
transformRequest.setSourceReference(sourceFileRef);
|
||||
transformRequest.setSourceExtension(sourceExtension);
|
||||
transformRequest.setSourceSize(sourceFile.length());
|
||||
transformRequest.setTargetExtension(targetExtension);
|
||||
|
||||
// HTTP Request
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=quick." + sourceExtension);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new FileSystemResource(
|
||||
sourceFile), headers, HttpStatus.OK);
|
||||
|
||||
when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response);
|
||||
when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(new FileRefResponse(new FileRefEntity(targetFileRef)));
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
|
||||
// Update the Transformation Request with any specific params before sending it
|
||||
updateTransformRequestWithSpecificOptions(transformRequest);
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.andExpect(status().is(HttpStatus.CREATED.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
TransformReply transformReply = objectMapper.readValue(transformationReplyAsString, TransformReply.class);
|
||||
|
||||
// Assert the reply
|
||||
assertEquals(transformRequest.getRequestId(), transformReply.getRequestId());
|
||||
assertEquals(transformRequest.getClientData(), transformReply.getClientData());
|
||||
assertEquals(transformRequest.getSchema(), transformReply.getSchema());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ public class AlfrescoPdfRendererController extends AbstractTransformerController
|
||||
String options = args.toString();
|
||||
LogEntry.setOptions(options);
|
||||
|
||||
Map<String, String> properties = new HashMap<String, String>(5);
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
properties.put("options", options);
|
||||
properties.put("source", sourceFile.getAbsolutePath());
|
||||
properties.put("target", targetFile.getAbsolutePath());
|
||||
|
@ -25,55 +25,39 @@
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
|
||||
import static org.alfresco.transformer.fs.FileManager.buildFile;
|
||||
import static org.alfresco.transformer.fs.FileManager.createTargetFileName;
|
||||
import static org.alfresco.transformer.fs.FileManager.getFilenameFromContentDisposition;
|
||||
import static org.alfresco.transformer.fs.FileManager.save;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transform.client.model.TransformRequestValidator;
|
||||
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
|
||||
import org.alfresco.transformer.exceptions.TransformException;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.DirectFieldBindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* <p>Abstract Controller, provides structure and helper methods to sub-class transformer controllers.</p>
|
||||
@ -103,11 +87,9 @@ import org.springframework.web.util.UriUtils;
|
||||
* <p>Provides methods to help super classes perform /transform requests. Also responses to /version, /ready and /live
|
||||
* requests.</p>
|
||||
*/
|
||||
public abstract class AbstractTransformerController
|
||||
public abstract class AbstractTransformerController implements TransformController
|
||||
{
|
||||
public static final String SOURCE_FILE = "sourceFile";
|
||||
public static final String TARGET_FILE = "targetFile";
|
||||
public static final String FILENAME = "filename=";
|
||||
private static final Log logger = LogFactory.getLog(AbstractTransformerController.class);
|
||||
|
||||
@Autowired
|
||||
private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
|
||||
@ -115,123 +97,90 @@ public abstract class AbstractTransformerController
|
||||
@Autowired
|
||||
private TransformRequestValidator transformRequestValidator;
|
||||
|
||||
protected static Log logger;
|
||||
|
||||
protected RuntimeExec transformCommand;
|
||||
private RuntimeExec checkCommand;
|
||||
|
||||
private ProbeTestTransform probeTestTransform = null;
|
||||
|
||||
public void setTransformCommand(RuntimeExec runtimeExec)
|
||||
{
|
||||
transformCommand = runtimeExec;
|
||||
}
|
||||
|
||||
public void setCheckCommand(RuntimeExec runtimeExec)
|
||||
{
|
||||
checkCommand = runtimeExec;
|
||||
}
|
||||
|
||||
protected void logEnterpriseLicenseMessage()
|
||||
{
|
||||
logger.info("This image is only intended to be used with the Alfresco Enterprise Content Repository which is covered by ");
|
||||
logger.info("https://www.alfresco.com/legal/agreements and https://www.alfresco.com/terms-use");
|
||||
logger.info("");
|
||||
logger.info("License rights for this program may be obtained from Alfresco Software, Ltd. pursuant to a written agreement");
|
||||
logger.info("and any use of this program without such an agreement is prohibited.");
|
||||
logger.info("");
|
||||
}
|
||||
|
||||
protected abstract String getTransformerName();
|
||||
|
||||
/**
|
||||
* '/transform' endpoint which consumes and produces 'application/json'
|
||||
*
|
||||
* This is the way to tell Spring to redirect the request to this endpoint
|
||||
* instead of the older one, which produces 'html'
|
||||
*
|
||||
* @param transformRequest The transformation request
|
||||
* @param request The transformation request
|
||||
* @param timeout Transformation timeout
|
||||
* @return A transformation reply
|
||||
*/
|
||||
@PostMapping(value = "/transform", produces = APPLICATION_JSON_VALUE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<TransformReply> transform(@RequestBody TransformRequest transformRequest,
|
||||
public ResponseEntity<TransformReply> transform(@RequestBody TransformRequest request,
|
||||
@RequestParam(value = "timeout", required = false) Long timeout)
|
||||
{
|
||||
TransformReply transformReply = new TransformReply();
|
||||
transformReply.setRequestId(transformRequest.getRequestId());
|
||||
transformReply.setSourceReference(transformRequest.getSourceReference());
|
||||
transformReply.setSchema(transformRequest.getSchema());
|
||||
transformReply.setClientData(transformRequest.getClientData());
|
||||
final TransformReply reply = new TransformReply();
|
||||
reply.setRequestId(request.getRequestId());
|
||||
reply.setSourceReference(request.getSourceReference());
|
||||
reply.setSchema(request.getSchema());
|
||||
reply.setClientData(request.getClientData());
|
||||
|
||||
Errors errors = validateTransformRequest(transformRequest);
|
||||
final Errors errors = validateTransformRequest(request);
|
||||
if (!errors.getAllErrors().isEmpty())
|
||||
{
|
||||
transformReply.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
transformReply.setErrorDetails(errors.getAllErrors().stream().map(Object::toString)
|
||||
reply.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||
reply.setErrorDetails(errors.getAllErrors().stream().map(Object::toString)
|
||||
.collect(Collectors.joining(", ")));
|
||||
|
||||
return new ResponseEntity<>(transformReply,
|
||||
HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply,
|
||||
HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
|
||||
// Load the source file
|
||||
File sourceFile;
|
||||
try
|
||||
{
|
||||
sourceFile = loadSourceFile(transformRequest.getSourceReference());
|
||||
sourceFile = loadSourceFile(request.getSourceReference());
|
||||
}
|
||||
catch (TransformException te)
|
||||
{
|
||||
transformReply.setStatus(te.getStatusCode());
|
||||
transformReply
|
||||
.setErrorDetails("Failed at reading the source file. " + te.getMessage());
|
||||
reply.setStatus(te.getStatusCode());
|
||||
reply .setErrorDetails("Failed at reading the source file. " + te.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
catch (HttpClientErrorException hcee)
|
||||
{
|
||||
transformReply.setStatus(hcee.getStatusCode().value());
|
||||
transformReply
|
||||
.setErrorDetails("Failed at reading the source file. " + hcee.getMessage());
|
||||
reply.setStatus(hcee.getStatusCode().value());
|
||||
reply .setErrorDetails("Failed at reading the source file. " + hcee.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
transformReply.setStatus(500);
|
||||
transformReply.setErrorDetails("Failed at reading the source file. " + e.getMessage());
|
||||
reply.setStatus(500);
|
||||
reply.setErrorDetails("Failed at reading the source file. " + e.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
|
||||
// Create local temp target file in order to run the transformation
|
||||
String targetFilename = createTargetFileName(sourceFile.getName(),
|
||||
transformRequest.getTargetExtension());
|
||||
request.getTargetExtension());
|
||||
File targetFile = buildFile(targetFilename);
|
||||
|
||||
// Run the transformation
|
||||
try
|
||||
{
|
||||
processTransform(sourceFile, targetFile,
|
||||
transformRequest.getTransformRequestOptions(), timeout);
|
||||
request.getTransformRequestOptions(), timeout);
|
||||
}
|
||||
catch (TransformException te)
|
||||
{
|
||||
transformReply.setStatus(te.getStatusCode());
|
||||
transformReply
|
||||
.setErrorDetails("Failed at processing transformation. " + te.getMessage());
|
||||
reply.setStatus(te.getStatusCode());
|
||||
reply.setErrorDetails("Failed at processing transformation. " + te.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
transformReply.setStatus(500);
|
||||
transformReply
|
||||
.setErrorDetails("Failed at processing transformation. " + e.getMessage());
|
||||
reply.setStatus(500);
|
||||
reply.setErrorDetails("Failed at processing transformation. " + e.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
|
||||
// Write the target file
|
||||
@ -242,211 +191,50 @@ public abstract class AbstractTransformerController
|
||||
}
|
||||
catch (TransformException te)
|
||||
{
|
||||
transformReply.setStatus(te.getStatusCode());
|
||||
transformReply
|
||||
.setErrorDetails("Failed at writing the transformed file. " + te.getMessage());
|
||||
reply.setStatus(te.getStatusCode());
|
||||
reply.setErrorDetails("Failed at writing the transformed file. " + te.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
catch (HttpClientErrorException hcee)
|
||||
{
|
||||
transformReply.setStatus(hcee.getStatusCode().value());
|
||||
transformReply
|
||||
.setErrorDetails("Failed at writing the transformed file. " + hcee.getMessage());
|
||||
reply.setStatus(hcee.getStatusCode().value());
|
||||
reply.setErrorDetails("Failed at writing the transformed file. " + hcee.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
transformReply.setStatus(500);
|
||||
transformReply
|
||||
.setErrorDetails("Failed at writing the transformed file. " + e.getMessage());
|
||||
reply.setStatus(500);
|
||||
reply.setErrorDetails("Failed at writing the transformed file. " + e.getMessage());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
|
||||
transformReply.setTargetReference(targetRef.getEntry().getFileRef());
|
||||
transformReply.setStatus(HttpStatus.CREATED.value());
|
||||
reply.setTargetReference(targetRef.getEntry().getFileRef());
|
||||
reply.setStatus(HttpStatus.CREATED.value());
|
||||
|
||||
return new ResponseEntity<>(transformReply, HttpStatus.valueOf(transformReply.getStatus()));
|
||||
return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()));
|
||||
}
|
||||
|
||||
private Errors validateTransformRequest(TransformRequest transformRequest)
|
||||
private Errors validateTransformRequest(final TransformRequest transformRequest)
|
||||
{
|
||||
DirectFieldBindingResult errors = new DirectFieldBindingResult(transformRequest, "request");
|
||||
transformRequestValidator.validate(transformRequest, errors);
|
||||
return errors;
|
||||
}
|
||||
|
||||
protected abstract void processTransform(File sourceFile, File targetFile,
|
||||
Map<String, String> transformOptions, Long timeout);
|
||||
|
||||
@RequestMapping("/version")
|
||||
@ResponseBody
|
||||
protected String version()
|
||||
{
|
||||
String version = "Version not checked";
|
||||
if (checkCommand != null)
|
||||
{
|
||||
RuntimeExec.ExecutionResult result = checkCommand.execute();
|
||||
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
|
||||
{
|
||||
throw new TransformException(500, "Transformer version check exit code was not 0: \n" + result);
|
||||
}
|
||||
|
||||
version = result.getStdOut().trim();
|
||||
if (version.isEmpty())
|
||||
{
|
||||
throw new TransformException(500, "Transformer version check failed to create any output");
|
||||
}
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
@GetMapping("/ready")
|
||||
@ResponseBody
|
||||
public String ready(HttpServletRequest request)
|
||||
{
|
||||
return probe(request, false);
|
||||
}
|
||||
|
||||
@GetMapping("/live")
|
||||
@ResponseBody
|
||||
public String live(HttpServletRequest request)
|
||||
{
|
||||
return probe(request, true);
|
||||
}
|
||||
|
||||
private String probe(HttpServletRequest request, boolean isLiveProbe)
|
||||
{
|
||||
return getProbeTestTransformInternal().doTransformOrNothing(request, isLiveProbe);
|
||||
}
|
||||
|
||||
private ProbeTestTransform getProbeTestTransformInternal()
|
||||
{
|
||||
if (probeTestTransform == null)
|
||||
{
|
||||
probeTestTransform = getProbeTestTransform();
|
||||
}
|
||||
return probeTestTransform;
|
||||
}
|
||||
|
||||
abstract ProbeTestTransform getProbeTestTransform();
|
||||
|
||||
@GetMapping("/")
|
||||
public String transformForm(Model model)
|
||||
{
|
||||
return "transformForm"; // the name of the template
|
||||
}
|
||||
|
||||
@GetMapping("/log")
|
||||
public String log(Model model)
|
||||
{
|
||||
model.addAttribute("title", getTransformerName() + " Log Entries");
|
||||
Collection<LogEntry> log = LogEntry.getLog();
|
||||
if (!log.isEmpty())
|
||||
{
|
||||
model.addAttribute("log", log);
|
||||
}
|
||||
return "log"; // the name of the template
|
||||
}
|
||||
|
||||
@GetMapping("/error")
|
||||
public String error()
|
||||
{
|
||||
return "error"; // the name of the template
|
||||
}
|
||||
|
||||
@ExceptionHandler(TypeMismatchException.class)
|
||||
public void handleParamsTypeMismatch(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException
|
||||
{
|
||||
String transformerName = getTransformerName();
|
||||
String name = e.getParameterName();
|
||||
String message = "Request parameter " + name + " is of the wrong type";
|
||||
int statusCode = 400;
|
||||
|
||||
if (logger != null && logger.isErrorEnabled())
|
||||
{
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
LogEntry.setStatusCodeAndMessage(statusCode, message);
|
||||
|
||||
response.sendError(statusCode, transformerName+" - "+message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public void handleMissingParams(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException
|
||||
{
|
||||
String transformerName = getTransformerName();
|
||||
String name = e.getParameterName();
|
||||
String message = "Request parameter " + name + " is missing";
|
||||
int statusCode = 400;
|
||||
|
||||
if (logger != null && logger.isErrorEnabled())
|
||||
{
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
LogEntry.setStatusCodeAndMessage(statusCode, message);
|
||||
|
||||
response.sendError(statusCode, transformerName+" - "+message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(TransformException.class)
|
||||
public void transformExceptionWithMessage(HttpServletResponse response, TransformException e) throws IOException
|
||||
{
|
||||
String transformerName = getTransformerName();
|
||||
String message = e.getMessage();
|
||||
int statusCode = e.getStatusCode();
|
||||
|
||||
if (logger != null && logger.isErrorEnabled())
|
||||
{
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
long time = LogEntry.setStatusCodeAndMessage(statusCode, message);
|
||||
getProbeTestTransformInternal().recordTransformTime(time);
|
||||
|
||||
// Forced to include the transformer name in the message (see commented out version of this method)
|
||||
response.sendError(statusCode, transformerName+" - "+message);
|
||||
}
|
||||
|
||||
// Results in HTML rather than json but there is an error in the log about "template might not exist or might
|
||||
// not be accessible by any of the configured Template Resolvers" for the transformer.html (which is correct
|
||||
// because that failed). Looks like Spring only supports returning json or XML when returning an Object or even
|
||||
// a ResponseEntity without this logged exception, which is a shame as it would have been nicer to have just
|
||||
// added the transformerName to the Object.
|
||||
// @ExceptionHandler(TransformException.class)
|
||||
// public final Map<String, Object> transformExceptionWithMessage(HttpServletResponse response, TransformException e, WebRequest request)
|
||||
// {
|
||||
// String transformerName = getTransformerName();
|
||||
// String message = e.getMessage();
|
||||
// int statusCode = e.getStatusCode();
|
||||
//
|
||||
// LogEntry.setStatusCodeAndMessage(statusCode, message);
|
||||
//
|
||||
// Map<String, Object> errorAttributes = new HashMap<>();
|
||||
// errorAttributes.put("title", transformerName);
|
||||
// errorAttributes.put("message", message);
|
||||
// errorAttributes.put("status", Integer.toString(statusCode));
|
||||
// errorAttributes.put("error", HttpStatus.valueOf(statusCode).getReasonPhrase());
|
||||
// return errorAttributes;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Loads the file with the specified sourceReference from Alfresco Shared File Store
|
||||
*
|
||||
* @param sourceReference reference to the file in Alfresco Shared File Store
|
||||
* @return the file containing the source content for the transformation
|
||||
*/
|
||||
protected File loadSourceFile(String sourceReference)
|
||||
private File loadSourceFile(final String sourceReference)
|
||||
{
|
||||
|
||||
ResponseEntity<Resource> responseEntity = alfrescoSharedFileStoreClient
|
||||
.retrieveFile(sourceReference);
|
||||
getProbeTestTransformInternal().incrementTransformerCount();
|
||||
getProbeTestTransform().incrementTransformerCount();
|
||||
|
||||
HttpHeaders headers = responseEntity.getHeaders();
|
||||
String filename = getFilenameFromContentDisposition(headers);
|
||||
@ -468,299 +256,4 @@ public abstract class AbstractTransformerController
|
||||
LogEntry.setSource(filename, size);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
private String getFilenameFromContentDisposition(HttpHeaders headers)
|
||||
{
|
||||
String filename = "";
|
||||
String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);
|
||||
if (contentDisposition != null)
|
||||
{
|
||||
String[] strings = contentDisposition.split("; *");
|
||||
for (String string: strings)
|
||||
{
|
||||
if (string.startsWith(FILENAME))
|
||||
{
|
||||
filename = string.substring(FILENAME.length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file name for the target file
|
||||
*
|
||||
* @param fileName Desired file name
|
||||
* @param targetExtension File extension
|
||||
* @return Target file name
|
||||
*/
|
||||
protected String createTargetFileName(String fileName, String targetExtension)
|
||||
{
|
||||
String targetFilename = null;
|
||||
String sourceFilename = fileName;
|
||||
sourceFilename = StringUtils.getFilename(sourceFilename);
|
||||
if (sourceFilename != null && !sourceFilename.isEmpty())
|
||||
{
|
||||
String ext = StringUtils.getFilenameExtension(sourceFilename);
|
||||
targetFilename = (ext != null && !ext.isEmpty()
|
||||
? sourceFilename.substring(0, sourceFilename.length()-ext.length()-1)
|
||||
: sourceFilename)+
|
||||
'.'+targetExtension;
|
||||
}
|
||||
return targetFilename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File that holds the source content for a transformation.
|
||||
*
|
||||
* @param request
|
||||
* @param multipartFile from the request
|
||||
* @return a temporary File.
|
||||
* @throws TransformException if there was no source filename.
|
||||
*/
|
||||
protected File createSourceFile(HttpServletRequest request, MultipartFile multipartFile)
|
||||
{
|
||||
getProbeTestTransformInternal().incrementTransformerCount();
|
||||
String filename = multipartFile.getOriginalFilename();
|
||||
long size = multipartFile.getSize();
|
||||
filename = checkFilename( true, filename);
|
||||
File file = TempFileProvider.createTempFile("source_", "_" + filename);
|
||||
request.setAttribute(SOURCE_FILE, file);
|
||||
save(multipartFile, file);
|
||||
LogEntry.setSource(filename, size);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a File to be used to store the result of a transformation.
|
||||
*
|
||||
* @param request
|
||||
* @param filename The targetFilename supplied in the request. Only the filename if a path is used as part of the
|
||||
* temporary filename.
|
||||
* @return a temporary File.
|
||||
* @throws TransformException if there was no target filename.
|
||||
*/
|
||||
protected File createTargetFile(HttpServletRequest request, String filename)
|
||||
{
|
||||
File file = buildFile(filename);
|
||||
request.setAttribute(TARGET_FILE, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
private File buildFile(String filename)
|
||||
{
|
||||
filename = checkFilename( false, filename);
|
||||
LogEntry.setTarget(filename);
|
||||
return TempFileProvider.createTempFile("target_", "_" + filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the filename is okay to uses in a temporary file name.
|
||||
*
|
||||
* @param filename or path to be checked.
|
||||
* @return the filename part of the supplied filename if it was a path.
|
||||
* @throws TransformException if there was no target filename.
|
||||
*/
|
||||
private String checkFilename(boolean source, String filename)
|
||||
{
|
||||
filename = StringUtils.getFilename(filename);
|
||||
if (filename == null || filename.isEmpty())
|
||||
{
|
||||
String sourceOrTarget = source ? "source" : "target";
|
||||
int statusCode = source ? 400 : 500;
|
||||
throw new TransformException(statusCode, "The " + sourceOrTarget + " filename was not supplied");
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
private void save(MultipartFile multipartFile, File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
Files.copy(multipartFile.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new TransformException(507, "Failed to store the source file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void save(Resource body, File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
InputStream inputStream = body == null ? null : body.getInputStream();
|
||||
Files.copy(inputStream, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new TransformException(507, "Failed to store the source file", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Resource load(File file)
|
||||
{
|
||||
try
|
||||
{
|
||||
Resource resource = new UrlResource(file.toURI());
|
||||
if (resource.exists() || resource.isReadable())
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TransformException(500, "Could not read the target file: " + file.getPath());
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e)
|
||||
{
|
||||
throw new TransformException(500, "The target filename was malformed: " + file.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void callTransform(File sourceFile, File targetFile, String... args) throws TransformException
|
||||
{
|
||||
args = buildArgs(sourceFile, targetFile, args);
|
||||
try
|
||||
{
|
||||
callTransform(args);
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
throw new TransformException(400, getMessage(e));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TransformException(500, getMessage(e));
|
||||
}
|
||||
if (!targetFile.exists() || targetFile.length() == 0)
|
||||
{
|
||||
throw new TransformException(500, "Transformer failed to create an output file");
|
||||
}
|
||||
}
|
||||
|
||||
private String getMessage(Exception e)
|
||||
{
|
||||
return e.getMessage() == null ? e.getClass().getSimpleName(): e.getMessage();
|
||||
}
|
||||
|
||||
protected void callTransform(String[] args)
|
||||
{
|
||||
// Overridden when the transform is done in the JVM rather than in an external command.
|
||||
}
|
||||
|
||||
protected String[] buildArgs(File sourceFile, File targetFile, String[] args)
|
||||
{
|
||||
ArrayList<String> methodArgs = new ArrayList<>(args.length+2);
|
||||
StringJoiner sj = new StringJoiner(" ");
|
||||
for (String arg: args)
|
||||
{
|
||||
addArg(methodArgs, sj, arg);
|
||||
}
|
||||
|
||||
addFileArg(methodArgs, sj, sourceFile);
|
||||
addFileArg(methodArgs, sj, targetFile);
|
||||
|
||||
LogEntry.setOptions(sj.toString());
|
||||
|
||||
return methodArgs.toArray(new String[methodArgs.size()]);
|
||||
}
|
||||
|
||||
private void addArg(ArrayList<String> methodArgs, StringJoiner sj, String arg)
|
||||
{
|
||||
if (arg != null)
|
||||
{
|
||||
sj.add(arg);
|
||||
methodArgs.add(arg);
|
||||
}
|
||||
}
|
||||
|
||||
private void addFileArg(ArrayList<String> methodArgs, StringJoiner sj, File arg)
|
||||
{
|
||||
if (arg != null)
|
||||
{
|
||||
String path = arg.getAbsolutePath();
|
||||
int i = path.lastIndexOf('.');
|
||||
String ext = i == -1 ? "???" : path.substring(i+1);
|
||||
sj.add(ext);
|
||||
methodArgs.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeTransformCommand(String options, File sourceFile, File targetFile, Long timeout)
|
||||
{
|
||||
LogEntry.setOptions(options);
|
||||
|
||||
Map<String, String> properties = new HashMap<String, String>(5);
|
||||
properties.put("options", options);
|
||||
properties.put("source", sourceFile.getAbsolutePath());
|
||||
properties.put("target", targetFile.getAbsolutePath());
|
||||
|
||||
executeTransformCommand(properties, targetFile, timeout);
|
||||
}
|
||||
|
||||
public void executeTransformCommand(Map<String, String> properties, File targetFile, Long timeout)
|
||||
{
|
||||
timeout = timeout != null && timeout > 0 ? timeout : 0;
|
||||
RuntimeExec.ExecutionResult result = transformCommand.execute(properties, timeout);
|
||||
|
||||
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
|
||||
{
|
||||
throw new TransformException(400, "Transformer exit code was not 0: \n" + result.getStdErr());
|
||||
}
|
||||
|
||||
if (!targetFile.exists() || targetFile.length() == 0)
|
||||
{
|
||||
throw new TransformException(500, "Transformer failed to create an output file");
|
||||
}
|
||||
}
|
||||
|
||||
protected ResponseEntity<Resource> createAttachment(String targetFilename, File targetFile, Long testDelay)
|
||||
{
|
||||
Resource targetResource = load(targetFile);
|
||||
targetFilename = UriUtils.encodePath(StringUtils.getFilename(targetFilename), "UTF-8");
|
||||
ResponseEntity<Resource> body = ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename*= UTF-8''" + targetFilename).body(targetResource);
|
||||
LogEntry.setTargetSize(targetFile.length());
|
||||
long time = LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
time += LogEntry.addDelay(testDelay);
|
||||
getProbeTestTransformInternal().recordTransformTime(time);
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely converts a {@link String} to an {@link Integer}
|
||||
*
|
||||
* @param param String to be converted
|
||||
* @return Null if param is null or converted value as {@link Integer}
|
||||
*/
|
||||
protected Integer stringToInteger(String param)
|
||||
{
|
||||
return param == null ? null : Integer.parseInt(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely converts a {@link String} to an {@link Integer}
|
||||
*
|
||||
* @param param String to be converted
|
||||
* @return Null if param is null or converted value as {@link Boolean}
|
||||
*/
|
||||
protected Boolean stringToBoolean(String param)
|
||||
{
|
||||
return param == null? null : Boolean.parseBoolean(param);
|
||||
}
|
||||
|
||||
public AlfrescoSharedFileStoreClient getAlfrescoSharedFileStoreClient()
|
||||
{
|
||||
return alfrescoSharedFileStoreClient;
|
||||
}
|
||||
|
||||
public void setAlfrescoSharedFileStoreClient(
|
||||
AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient)
|
||||
{
|
||||
this.alfrescoSharedFileStoreClient = alfrescoSharedFileStoreClient;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
import static org.alfresco.transformer.fs.FileManager.SOURCE_FILE;
|
||||
import static org.alfresco.transformer.fs.FileManager.TARGET_FILE;
|
||||
import static org.alfresco.transformer.fs.FileManager.deleteFile;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
|
||||
public class TransformInterceptor extends HandlerInterceptorAdapter
|
||||
{
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler) throws Exception
|
||||
HttpServletResponse response, Object handler)
|
||||
{
|
||||
LogEntry.start();
|
||||
return true;
|
||||
@ -45,21 +49,11 @@ public class TransformInterceptor extends HandlerInterceptorAdapter
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler, Exception ex)
|
||||
throws Exception
|
||||
{
|
||||
// TargetFile cannot be deleted until completion, otherwise 0 bytes are sent.
|
||||
deleteFile(request, AbstractTransformerController.SOURCE_FILE);
|
||||
deleteFile(request, AbstractTransformerController.TARGET_FILE);
|
||||
deleteFile(request, SOURCE_FILE);
|
||||
deleteFile(request, TARGET_FILE);
|
||||
|
||||
LogEntry.complete();
|
||||
}
|
||||
|
||||
private void deleteFile(HttpServletRequest request, String attributeName)
|
||||
{
|
||||
File file = (File) request.getAttribute(attributeName);
|
||||
if (file != null)
|
||||
{
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,10 +5,11 @@
|
||||
* pursuant to a written agreement and any use of this program without such an
|
||||
* agreement is prohibited.
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.clients;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.alfresco.transformer.exceptions.TransformException;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
@ -23,8 +23,10 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.config;
|
||||
|
||||
import org.alfresco.transformer.TransformInterceptor;
|
||||
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
@ -23,11 +23,11 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.exceptions;
|
||||
|
||||
public class TransformException extends RuntimeException
|
||||
{
|
||||
private int statusCode;
|
||||
private final int statusCode;
|
||||
|
||||
public TransformException(int statusCode, String message)
|
||||
{
|
@ -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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.logging;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collection;
|
||||
@ -32,7 +34,8 @@ import java.util.Deque;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Provides setter and getter methods to allow the current Thread to set various log properties and for these
|
||||
@ -40,8 +43,9 @@ import static java.lang.Math.max;
|
||||
* current entry to an internal log Collection of the latest entries. The {@link #getLog()} method is used to obtain
|
||||
* access to this collection.
|
||||
*/
|
||||
public class LogEntry
|
||||
public final class LogEntry
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(LogEntry.class);
|
||||
// TODO allow ProbeTestTransform to find out if there are any transforms running longer than the max time.
|
||||
|
||||
private static final AtomicInteger count = new AtomicInteger(0);
|
||||
@ -49,11 +53,7 @@ public class LogEntry
|
||||
private static final int MAX_LOG_SIZE = 10;
|
||||
private static final SimpleDateFormat HH_MM_SS = new SimpleDateFormat("HH:mm:ss");
|
||||
|
||||
private static ThreadLocal<LogEntry> currentLogEntry = new ThreadLocal<LogEntry>()
|
||||
{
|
||||
@Override
|
||||
protected LogEntry initialValue()
|
||||
{
|
||||
private static final ThreadLocal<LogEntry> currentLogEntry = ThreadLocal.withInitial(() -> {
|
||||
LogEntry logEntry = new LogEntry();
|
||||
if (log.size() >= MAX_LOG_SIZE)
|
||||
{
|
||||
@ -61,8 +61,7 @@ public class LogEntry
|
||||
}
|
||||
log.addFirst(logEntry);
|
||||
return logEntry;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
private final int id = count.incrementAndGet();
|
||||
private final long start = System.currentTimeMillis();
|
||||
@ -204,9 +203,9 @@ public class LogEntry
|
||||
}
|
||||
currentLogEntry.remove();
|
||||
|
||||
if (AbstractTransformerController.logger != null && AbstractTransformerController.logger.isDebugEnabled())
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
AbstractTransformerController.logger.debug(logEntry.toString());
|
||||
logger.debug(logEntry.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +278,7 @@ public class LogEntry
|
||||
|
||||
private String size(long size)
|
||||
{
|
||||
// TODO fix numeric overflow in TB expression
|
||||
return size == -1 ? "" : size(size, "1 byte",
|
||||
new String[] { "bytes", " KB", " MB", " GB", " TB" },
|
||||
new long[] { 1024, 1024*1024, 1024*1024*1024, 1024*1024*1024*1024, Long.MAX_VALUE });
|
@ -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/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.transformer;
|
||||
package org.alfresco.transformer.probes;
|
||||
|
||||
import static org.alfresco.transformer.fs.FileManager.SOURCE_FILE;
|
||||
import static org.alfresco.transformer.fs.FileManager.TARGET_FILE;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@ -35,6 +38,9 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.transformer.AbstractTransformerController;
|
||||
import org.alfresco.transformer.exceptions.TransformException;
|
||||
import org.alfresco.transformer.logging.LogEntry;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
@ -60,9 +66,9 @@ import org.apache.commons.logging.Log;
|
||||
* <li>maxTransformSeconds - the maximum time for a transformation, including failed ones.</li>
|
||||
* </ul>
|
||||
*/
|
||||
abstract class ProbeTestTransform
|
||||
public abstract class ProbeTestTransform
|
||||
{
|
||||
public static final int AVERAGE_OVER_TRANSFORMS = 5;
|
||||
private static final int AVERAGE_OVER_TRANSFORMS = 5;
|
||||
private final String sourceFilename;
|
||||
private final String targetFilename;
|
||||
private final long minExpectedLength;
|
||||
@ -70,22 +76,32 @@ abstract class ProbeTestTransform
|
||||
|
||||
private final Log logger;
|
||||
|
||||
int livenessPercent;
|
||||
long probeCount;
|
||||
int transCount;
|
||||
long normalTime;
|
||||
long maxTime = Long.MAX_VALUE;
|
||||
long nextTransformTime;
|
||||
private int livenessPercent;
|
||||
private long probeCount;
|
||||
private int transCount;
|
||||
private long normalTime;
|
||||
private long maxTime = Long.MAX_VALUE;
|
||||
private long nextTransformTime;
|
||||
|
||||
private final boolean livenessTransformEnabled;
|
||||
private final long livenessTransformPeriod;
|
||||
private long maxTransformCount = Long.MAX_VALUE;
|
||||
private final long maxTransformCount;
|
||||
private long maxTransformTime;
|
||||
|
||||
private AtomicBoolean initialised = new AtomicBoolean(false);
|
||||
private AtomicBoolean readySent = new AtomicBoolean(false);
|
||||
private AtomicLong transformCount = new AtomicLong(0);
|
||||
private AtomicBoolean die = new AtomicBoolean(false);
|
||||
private final AtomicBoolean initialised = new AtomicBoolean(false);
|
||||
private final AtomicBoolean readySent = new AtomicBoolean(false);
|
||||
private final AtomicLong transformCount = new AtomicLong(0);
|
||||
private final AtomicBoolean die = new AtomicBoolean(false);
|
||||
|
||||
public int getLivenessPercent()
|
||||
{
|
||||
return livenessPercent;
|
||||
}
|
||||
|
||||
public long getMaxTime()
|
||||
{
|
||||
return maxTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* See Probes.md for more info.
|
||||
@ -97,12 +113,12 @@ abstract class ProbeTestTransform
|
||||
* @param maxTransformSeconds default values normally supplied by helm. Not identical so we can be sure which value is used.
|
||||
* @param livenessTransformPeriodSeconds default values normally supplied by helm. Not identical so we can be sure which value is used.
|
||||
*/
|
||||
public ProbeTestTransform(AbstractTransformerController controller,
|
||||
public ProbeTestTransform(AbstractTransformerController controller, Log logger,
|
||||
String sourceFilename, String targetFilename, long expectedLength, long plusOrMinus,
|
||||
int livenessPercent, long maxTransforms, long maxTransformSeconds,
|
||||
long livenessTransformPeriodSeconds)
|
||||
{
|
||||
logger = controller.logger;
|
||||
this.logger = logger;
|
||||
|
||||
this.sourceFilename = sourceFilename;
|
||||
this.targetFilename = targetFilename;
|
||||
@ -128,7 +144,7 @@ abstract class ProbeTestTransform
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
protected long getPositiveLongEnv(String name, long defaultValue)
|
||||
private long getPositiveLongEnv(String name, long defaultValue)
|
||||
{
|
||||
long l = -1;
|
||||
String env = System.getenv(name);
|
||||
@ -171,7 +187,7 @@ abstract class ProbeTestTransform
|
||||
{
|
||||
String probeMessage = getProbeMessage(isLiveProbe);
|
||||
String message = "Success - No transform.";
|
||||
LogEntry.setStatusCodeAndMessage(200, probeMessage+message);
|
||||
LogEntry.setStatusCodeAndMessage(200, probeMessage + message);
|
||||
if (!isLiveProbe && !readySent.getAndSet(true))
|
||||
{
|
||||
logger.info(probeMessage+message);
|
||||
@ -179,7 +195,7 @@ abstract class ProbeTestTransform
|
||||
return message;
|
||||
}
|
||||
|
||||
String doTransform(HttpServletRequest request, boolean isLiveProbe)
|
||||
private String doTransform(HttpServletRequest request, boolean isLiveProbe)
|
||||
{
|
||||
checkMaxTransformTimeAndCount(isLiveProbe);
|
||||
|
||||
@ -207,9 +223,9 @@ abstract class ProbeTestTransform
|
||||
|
||||
if (time > maxTime)
|
||||
{
|
||||
throw new TransformException(500, getMessagePrefix(isLiveProbe)+
|
||||
message+" which is more than "+ livenessPercent +
|
||||
"% slower than the normal value of "+normalTime+"ms");
|
||||
throw new TransformException(500, getMessagePrefix(isLiveProbe) +
|
||||
message + " which is more than " + livenessPercent +
|
||||
"% 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.
|
||||
@ -237,11 +253,11 @@ abstract class ProbeTestTransform
|
||||
}
|
||||
}
|
||||
|
||||
File getSourceFile(HttpServletRequest request, boolean isLiveProbe)
|
||||
private File getSourceFile(HttpServletRequest request, boolean isLiveProbe)
|
||||
{
|
||||
incrementTransformerCount();
|
||||
File sourceFile = TempFileProvider.createTempFile("source_", "_"+ sourceFilename);
|
||||
request.setAttribute(AbstractTransformerController.SOURCE_FILE, sourceFile);
|
||||
request.setAttribute(SOURCE_FILE, sourceFile);
|
||||
try (InputStream inputStream = this.getClass().getResourceAsStream('/'+sourceFilename))
|
||||
{
|
||||
Files.copy(inputStream, sourceFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
@ -256,15 +272,15 @@ abstract class ProbeTestTransform
|
||||
return sourceFile;
|
||||
}
|
||||
|
||||
File getTargetFile(HttpServletRequest request)
|
||||
private File getTargetFile(HttpServletRequest request)
|
||||
{
|
||||
File targetFile = TempFileProvider.createTempFile("target_", "_"+targetFilename);
|
||||
request.setAttribute(AbstractTransformerController.TARGET_FILE, targetFile);
|
||||
request.setAttribute(TARGET_FILE, targetFile);
|
||||
LogEntry.setTarget(targetFilename);
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
void recordTransformTime(long time)
|
||||
public void recordTransformTime(long time)
|
||||
{
|
||||
if (maxTransformTime > 0 && time > maxTransformTime)
|
||||
{
|
||||
@ -272,7 +288,7 @@ abstract class ProbeTestTransform
|
||||
}
|
||||
}
|
||||
|
||||
void calculateMaxTime(long time, boolean isLiveProbe)
|
||||
public void calculateMaxTime(long time, boolean isLiveProbe)
|
||||
{
|
||||
if (transCount <= AVERAGE_OVER_TRANSFORMS)
|
||||
{
|
||||
@ -331,4 +347,15 @@ abstract class ProbeTestTransform
|
||||
{
|
||||
transformCount.incrementAndGet();
|
||||
}
|
||||
|
||||
public void setLivenessPercent(int livenessPercent)
|
||||
{
|
||||
this.livenessPercent = livenessPercent;
|
||||
}
|
||||
|
||||
public long getNormalTime()
|
||||
{
|
||||
|
||||
return normalTime;
|
||||
}
|
||||
}
|
@ -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.util.LinkedMultiValueMap;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.test.util.AssertionErrors.assertTrue;
|
||||
|
||||
@ -54,7 +52,7 @@ public abstract class AbstractHttpRequestTest
|
||||
protected abstract String getSourceExtension();
|
||||
|
||||
@Test
|
||||
public void testPageExists() throws Exception
|
||||
public void testPageExists()
|
||||
{
|
||||
String result = restTemplate.getForObject("http://localhost:" + port + "/", String.class);
|
||||
|
||||
@ -63,7 +61,7 @@ public abstract class AbstractHttpRequestTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void logPageExists() throws Exception
|
||||
public void logPageExists()
|
||||
{
|
||||
String result = restTemplate.getForObject("http://localhost:" + port + "/log", String.class);
|
||||
|
||||
@ -72,7 +70,7 @@ public abstract class AbstractHttpRequestTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void errorPageExists() throws Exception
|
||||
public void errorPageExists()
|
||||
{
|
||||
String result = restTemplate.getForObject("http://localhost:" + port + "/error", String.class);
|
||||
|
||||
@ -81,7 +79,7 @@ public abstract class AbstractHttpRequestTest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noFileError() throws Exception
|
||||
public void noFileError()
|
||||
{
|
||||
// Transformer name is not part of the title as this is checked by another handler
|
||||
assertTransformError(false,
|
||||
@ -94,22 +92,22 @@ public abstract class AbstractHttpRequestTest
|
||||
assertMissingParameter("targetExtension");
|
||||
}
|
||||
|
||||
protected void assertMissingParameter(String name) throws IOException
|
||||
private void assertMissingParameter(String name)
|
||||
{
|
||||
assertTransformError(true,
|
||||
getTransformerName() + " - Request parameter " + name + " is missing");
|
||||
}
|
||||
|
||||
protected void assertTransformError(boolean addFile, String errorMessage) throws IOException
|
||||
private void assertTransformError(boolean addFile, String errorMessage)
|
||||
{
|
||||
LinkedMultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>();
|
||||
LinkedMultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
|
||||
if (addFile)
|
||||
{
|
||||
parameters.add("file", new org.springframework.core.io.ClassPathResource("quick."+getSourceExtension()));
|
||||
}
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||
HttpEntity<LinkedMultiValueMap<String, Object>> entity = new HttpEntity<LinkedMultiValueMap<String, Object>>(parameters, headers);
|
||||
HttpEntity<LinkedMultiValueMap<String, Object>> entity = new HttpEntity<>(parameters, headers);
|
||||
ResponseEntity<String> response = restTemplate.exchange("/transform", HttpMethod.POST, entity, String.class, "");
|
||||
assertEquals(errorMessage, getErrorMessage(response.getBody()));
|
||||
}
|
||||
@ -117,7 +115,7 @@ public abstract class AbstractHttpRequestTest
|
||||
// Strip out just the error message from the returned json content body
|
||||
// Had been expecting the Error page to be returned, but we end up with the json in this test harness.
|
||||
// Is correct if run manually, so not worrying too much about this.
|
||||
private String getErrorMessage(String content) throws IOException
|
||||
private String getErrorMessage(String content)
|
||||
{
|
||||
String message = "";
|
||||
int i = content.indexOf("\"message\":\"");
|
||||
|
@ -27,12 +27,7 @@ package org.alfresco.transformer;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.anyObject;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@ -44,33 +39,21 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.alfresco.transform.client.model.TransformReply;
|
||||
import org.alfresco.transform.client.model.TransformRequest;
|
||||
import org.alfresco.transformer.model.FileRefEntity;
|
||||
import org.alfresco.transformer.model.FileRefResponse;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.junit.Before;
|
||||
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
|
||||
import org.alfresco.transformer.probes.ProbeTestTransform;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@ -85,18 +68,9 @@ public abstract class AbstractTransformerControllerTest
|
||||
@Autowired
|
||||
protected ObjectMapper objectMapper;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockTransformCommand;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec mockCheckCommand;
|
||||
|
||||
@Mock
|
||||
@MockBean
|
||||
protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
|
||||
|
||||
@Mock
|
||||
private RuntimeExec.ExecutionResult mockExecutionResult;
|
||||
|
||||
protected String sourceExtension;
|
||||
protected String targetExtension;
|
||||
protected String sourceMimetype;
|
||||
@ -108,88 +82,14 @@ public abstract class AbstractTransformerControllerTest
|
||||
protected byte[] expectedSourceFileBytes;
|
||||
protected byte[] expectedTargetFileBytes;
|
||||
|
||||
protected AbstractTransformerController controller;
|
||||
|
||||
@Before
|
||||
public void before() throws Exception
|
||||
{
|
||||
}
|
||||
|
||||
// Called by sub class
|
||||
public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension,
|
||||
protected abstract void mockTransformCommand(String sourceExtension,
|
||||
String targetExtension, String sourceMimetype,
|
||||
boolean readTargetFileBytes) throws IOException
|
||||
{
|
||||
this.controller = controller;
|
||||
this.sourceExtension = sourceExtension;
|
||||
this.targetExtension = targetExtension;
|
||||
this.sourceMimetype = sourceMimetype;
|
||||
boolean readTargetFileBytes) throws IOException;
|
||||
|
||||
expectedOptions = null;
|
||||
expectedSourceSuffix = null;
|
||||
expectedSourceFileBytes = readTestFile(sourceExtension);
|
||||
expectedTargetFileBytes = readTargetFileBytes ? readTestFile(targetExtension) : null;
|
||||
sourceFile = new MockMultipartFile("file", "quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
protected abstract AbstractTransformerController getController();
|
||||
|
||||
controller.setTransformCommand(mockTransformCommand);
|
||||
controller.setCheckCommand(mockCheckCommand);
|
||||
|
||||
when(mockTransformCommand.execute(anyObject(), anyLong())).thenAnswer(new Answer<RuntimeExec.ExecutionResult>()
|
||||
{
|
||||
public RuntimeExec.ExecutionResult answer(InvocationOnMock invocation) throws Throwable
|
||||
{
|
||||
Map<String, String> actualProperties = invocation.getArgument(0);
|
||||
assertEquals("There should be 3 properties", 3, actualProperties.size());
|
||||
|
||||
String actualOptions = actualProperties.get("options");
|
||||
String actualSource = actualProperties.get("source");
|
||||
String actualTarget = actualProperties.get("target");
|
||||
String actualTargetExtension = StringUtils.getFilenameExtension(actualTarget);
|
||||
|
||||
assertNotNull(actualSource);
|
||||
assertNotNull(actualTarget);
|
||||
if (expectedSourceSuffix != null)
|
||||
{
|
||||
assertTrue("The source file \""+actualSource+"\" should have ended in \""+expectedSourceSuffix+"\"", actualSource.endsWith(expectedSourceSuffix));
|
||||
actualSource = actualSource.substring(0, actualSource.length()-expectedSourceSuffix.length());
|
||||
}
|
||||
|
||||
assertNotNull(actualOptions);
|
||||
if (expectedOptions != null)
|
||||
{
|
||||
assertEquals("expectedOptions", expectedOptions, actualOptions);
|
||||
}
|
||||
|
||||
Long actualTimeout = invocation.getArgument(1);
|
||||
assertNotNull(actualTimeout);
|
||||
if (expectedTimeout != null)
|
||||
{
|
||||
assertEquals("expectedTimeout", expectedTimeout, actualTimeout);
|
||||
}
|
||||
|
||||
// Copy a test file into the target file location if it exists
|
||||
int i = actualTarget.lastIndexOf('_');
|
||||
if (i >= 0)
|
||||
{
|
||||
String testFilename = actualTarget.substring(i+1);
|
||||
File testFile = getTestFile(testFilename, false);
|
||||
File targetFile = new File(actualTarget);
|
||||
generateTargetFileFromResourceFile(actualTargetExtension, testFile,
|
||||
targetFile);
|
||||
}
|
||||
|
||||
// Check the supplied source file has not been changed.
|
||||
byte[] actualSourceFileBytes = Files.readAllBytes(new File(actualSource).toPath());
|
||||
assertTrue("Source file is not the same", Arrays.equals(expectedSourceFileBytes, actualSourceFileBytes));
|
||||
|
||||
return mockExecutionResult;
|
||||
}
|
||||
});
|
||||
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
when(mockExecutionResult.getStdErr()).thenReturn("STDERROR");
|
||||
when(mockExecutionResult.getStdOut()).thenReturn("STDOUT");
|
||||
}
|
||||
protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest);
|
||||
|
||||
/**
|
||||
* This method ends up being the core of the mock.
|
||||
@ -224,7 +124,7 @@ public abstract class AbstractTransformerControllerTest
|
||||
|
||||
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
|
||||
@ -233,22 +133,22 @@ public abstract class AbstractTransformerControllerTest
|
||||
URL testFileUrl = classLoader.getResource(testFilename);
|
||||
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());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
@ -260,7 +160,7 @@ public abstract class AbstractTransformerControllerTest
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||
.andExpect(status().is(200))
|
||||
.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
|
||||
@ -270,11 +170,11 @@ public abstract class AbstractTransformerControllerTest
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension, "testDelay", "400"))
|
||||
.andExpect(status().is(200))
|
||||
.andExpect(content().bytes(expectedTargetFileBytes))
|
||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick."+targetExtension));
|
||||
long ms = System.currentTimeMillis()- start;
|
||||
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 big "+ms, ms <= 500);
|
||||
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
|
||||
long ms = System.currentTimeMillis() - start;
|
||||
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 big " + ms, ms <= 500);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -284,26 +184,16 @@ public abstract class AbstractTransformerControllerTest
|
||||
.andExpect(status().is(500));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void badExitCodeTest() throws Exception
|
||||
{
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(1);
|
||||
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
|
||||
.andExpect(status().is(400))
|
||||
.andExpect(status().reason(containsString("Transformer exit code was not 0: \nSTDERR")));
|
||||
}
|
||||
|
||||
@Test
|
||||
// Looks dangerous but is okay as we only use the final filename
|
||||
public void dotDotSourceFilenameTest() throws Exception
|
||||
{
|
||||
sourceFile = new MockMultipartFile("file", "../quick."+sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
sourceFile = new MockMultipartFile("file", "../quick." + sourceExtension, sourceMimetype, expectedSourceFileBytes);
|
||||
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||
.andExpect(status().is(200))
|
||||
.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
|
||||
@ -315,7 +205,7 @@ public abstract class AbstractTransformerControllerTest
|
||||
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
|
||||
.andExpect(status().is(200))
|
||||
.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
|
||||
@ -347,30 +237,13 @@ public abstract class AbstractTransformerControllerTest
|
||||
.andExpect(status().reason(containsString("Request parameter targetExtension is missing")));
|
||||
}
|
||||
|
||||
// @Test
|
||||
// // Not a real test, but helpful for trying out the duration times in log code.
|
||||
// public void testTimes() throws InterruptedException
|
||||
// {
|
||||
// LogEntry.start();
|
||||
// Thread.sleep(50);
|
||||
// LogEntry.setSource("test File", 1234);
|
||||
// Thread.sleep(200);
|
||||
// LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
// LogEntry.addDelay(2000L);
|
||||
// for (LogEntry logEntry: LogEntry.getLog())
|
||||
// {
|
||||
// String str = logEntry.getDuration();
|
||||
// System.out.println(str);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void calculateMaxTime() throws Exception
|
||||
{
|
||||
ProbeTestTransform probeTestTransform = controller.getProbeTestTransform();
|
||||
probeTestTransform.livenessPercent = 110;
|
||||
ProbeTestTransform probeTestTransform = getController().getProbeTestTransform();
|
||||
probeTestTransform.setLivenessPercent(110);
|
||||
|
||||
long [][] values = new long[][] {
|
||||
long[][] values = new long[][]{
|
||||
{5000, 0, Long.MAX_VALUE}, // 1st transform is ignored
|
||||
{1000, 1000, 2100}, // 1000 + 1000*1.1
|
||||
{3000, 2000, 4200}, // 2000 + 2000*1.1
|
||||
@ -381,67 +254,18 @@ public abstract class AbstractTransformerControllerTest
|
||||
{5555, 4000, 8400}
|
||||
};
|
||||
|
||||
for (long[] v: values)
|
||||
for (long[] v : values)
|
||||
{
|
||||
long time = v[0];
|
||||
long expectedNormalTime = v[1];
|
||||
long expectedMaxTime = v[2];
|
||||
|
||||
probeTestTransform.calculateMaxTime(time, true);
|
||||
assertEquals("", expectedNormalTime, probeTestTransform.normalTime);
|
||||
assertEquals("", expectedMaxTime, probeTestTransform.maxTime);
|
||||
assertEquals("", expectedNormalTime, probeTestTransform.getNormalTime());
|
||||
assertEquals("", expectedMaxTime, probeTestTransform.getMaxTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPojoTransform() throws Exception
|
||||
{
|
||||
// Files
|
||||
String sourceFileRef = UUID.randomUUID().toString();
|
||||
File sourceFile = getTestFile("quick." + sourceExtension, true);
|
||||
String targetFileRef = UUID.randomUUID().toString();
|
||||
|
||||
|
||||
// Transformation Request POJO
|
||||
TransformRequest transformRequest = new TransformRequest();
|
||||
transformRequest.setRequestId("1");
|
||||
transformRequest.setSchema(1);
|
||||
transformRequest.setClientData("Alfresco Digital Business Platform");
|
||||
transformRequest.setTransformRequestOptions(new HashMap<>());
|
||||
transformRequest.setSourceReference(sourceFileRef);
|
||||
transformRequest.setSourceExtension(sourceExtension);
|
||||
transformRequest.setSourceSize(sourceFile.length());
|
||||
transformRequest.setTargetExtension(targetExtension);
|
||||
|
||||
// HTTP Request
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=quick." + sourceExtension);
|
||||
ResponseEntity<Resource> response = new ResponseEntity<>(new FileSystemResource(
|
||||
sourceFile), headers, HttpStatus.OK);
|
||||
|
||||
when(alfrescoSharedFileStoreClient.retrieveFile(sourceFileRef)).thenReturn(response);
|
||||
when(alfrescoSharedFileStoreClient.saveFile(any())).thenReturn(new FileRefResponse(new FileRefEntity(targetFileRef)));
|
||||
when(mockExecutionResult.getExitValue()).thenReturn(0);
|
||||
|
||||
// Update the Transformation Request with any specific params before sending it
|
||||
updateTransformRequestWithSpecificOptions(transformRequest);
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.andExpect(status().is(HttpStatus.CREATED.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
TransformReply transformReply = objectMapper.readValue(transformationReplyAsString, TransformReply.class);
|
||||
|
||||
// Assert the reply
|
||||
assertEquals(transformRequest.getRequestId(), transformReply.getRequestId());
|
||||
assertEquals(transformRequest.getClientData(), transformReply.getClientData());
|
||||
assertEquals(transformRequest.getSchema(), transformReply.getSchema());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyPojoTransform() throws Exception
|
||||
{
|
||||
@ -450,9 +274,12 @@ public abstract class AbstractTransformerControllerTest
|
||||
|
||||
// Serialize and call the transformer
|
||||
String tr = objectMapper.writeValueAsString(transformRequest);
|
||||
String transformationReplyAsString = mockMvc.perform(MockMvcRequestBuilders.post("/transform")
|
||||
String transformationReplyAsString = mockMvc
|
||||
.perform(MockMvcRequestBuilders
|
||||
.post("/transform")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.content(tr))
|
||||
.andExpect(status().is(HttpStatus.BAD_REQUEST.value()))
|
||||
.andReturn().getResponse().getContentAsString();
|
||||
|
||||
@ -461,6 +288,4 @@ public abstract class AbstractTransformerControllerTest
|
||||
// Assert the reply
|
||||
assertEquals(HttpStatus.BAD_REQUEST.value(), transformReply.getStatus());
|
||||
}
|
||||
|
||||
protected abstract void updateTransformRequestWithSpecificOptions(TransformRequest transformRequest);
|
||||
}
|
||||
|
32
pom.xml
32
pom.xml
@ -3,9 +3,10 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-super-pom</artifactId>
|
||||
<version>9</version>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.5.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<groupId>org.alfresco</groupId>
|
||||
@ -44,13 +45,6 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>${dependency.spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
@ -121,4 +115,22 @@
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
Loading…
x
Reference in New Issue
Block a user