mirror of
https://github.com/Alfresco/alfresco-transform-core.git
synced 2025-08-07 17:48:35 +00:00
Initial commit
This commit is contained in:
170
alfresco-transformer-base/README.md
Normal file
170
alfresco-transformer-base/README.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Common code for Docker based ACS transformers
|
||||
|
||||
This project contains code that is common between all the ACS transformers that run within their own
|
||||
Docker containers. It performs common actions such as logging, throttling requests and handling the
|
||||
streaming of content to and from the container. It also provides structure and hook points to allow
|
||||
specific transformers to simply check request parameter and perform the transformation using either
|
||||
files or a pair of InputStream and OutputStream.
|
||||
|
||||
A transformer project is expected to provide the following files:
|
||||
|
||||
~~~
|
||||
src/main/resources/templates/transformForm.html
|
||||
src/main/java/org/alfresco/transformer/<XXX>Controller.java
|
||||
src/main/java/org/alfresco/transformer/Application.java
|
||||
~~~
|
||||
|
||||
* transformerForm.html - A simple test page using [thymeleaf](http://www.thymeleaf.org) that gathers request
|
||||
parameters so they may be used to test the transformer.
|
||||
|
||||
~~~
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
<div>
|
||||
<h2>Test Transformation</h2>
|
||||
<form method="POST" enctype="multipart/form-data" action="/transform">
|
||||
<table>
|
||||
<tr><td><div style="text-align:right">file *</div></td><td><input type="file" name="file" /></td></tr>
|
||||
<tr><td><div style="text-align:right">targetFilename *</div></td><td><input type="text" name="targetFilename" value="" /></td></tr>
|
||||
<tr><td><div style="text-align:right">width</div></td><td><input type="text" name="width" value="" /></td></tr>
|
||||
<tr><td><div style="text-align:right">height</div></td><td><input type="text" name="height" value="" /></td></tr>
|
||||
<tr><td><div style="text-align:right">allowEnlargement</div></td><td><input type="checkbox" name="allowEnlargement" value="true" /></td></tr>
|
||||
<tr><td><div style="text-align:right">maintainAspectRatio</div></td><td><input type="checkbox" name="maintainAspectRatio" value="true" /></td></tr>
|
||||
<tr><td><div style="text-align:right">page</div></td><td><input type="text" name="page" value="" /></td></tr>
|
||||
<tr><td><div style="text-align:right">timeout</div></td><td><input type="text" name="timeout" value="" /></td></tr>
|
||||
<tr><td></td><td><input type="submit" value="Transform" /></td></tr>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/log">Log entries</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
~~~
|
||||
|
||||
* *TransformerName*Controller.java - A [Spring Boot](https://projects.spring.io/spring-boot/) Controller that
|
||||
extends AbstractTransformerController to handel a POST request to *"/transform"*.
|
||||
|
||||
~~~
|
||||
...
|
||||
@Controller
|
||||
public class AlfrescoPdfRendererController extends AbstractTransformerController
|
||||
{
|
||||
...
|
||||
|
||||
@PostMapping("/transform")
|
||||
public ResponseEntity<Resource> transform(HttpServletRequest request,
|
||||
@RequestParam("file") MultipartFile sourceMultipartFile,
|
||||
@RequestParam("targetFilename") String targetFilename,
|
||||
@RequestParam(value = "width", required = false) Integer width,
|
||||
@RequestParam(value = "height", required = false) Integer height,
|
||||
@RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement,
|
||||
@RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio,
|
||||
@RequestParam(value = "page", required = false) Integer page,
|
||||
@RequestParam(value = "timeout", required = false) Long timeout)
|
||||
{
|
||||
try
|
||||
{
|
||||
File sourceFile = createSourceFile(request, sourceMultipartFile);
|
||||
File targetFile = createTargetFile(request, targetFilename);
|
||||
// Both files are deleted by TransformInterceptor.afterCompletion
|
||||
|
||||
StringJoiner args = new StringJoiner(" ");
|
||||
if (width != null)
|
||||
{
|
||||
args.add("--width=" + width);
|
||||
}
|
||||
if (height != null)
|
||||
{
|
||||
args.add("--height=" + height);
|
||||
}
|
||||
if (allowEnlargement != null && allowEnlargement)
|
||||
{
|
||||
args.add("--allow-enlargement");
|
||||
}
|
||||
if (maintainAspectRatio != null && maintainAspectRatio)
|
||||
{
|
||||
args.add("--maintain-aspect-ratio");
|
||||
}
|
||||
if (page != null)
|
||||
{
|
||||
args.add("--page=" + page);
|
||||
}
|
||||
String options = args.toString();
|
||||
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);
|
||||
|
||||
return createAttachment(targetFilename, targetFile);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new TransformException(500, "Filename encoding error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
* Application.java - [Spring Boot](https://projects.spring.io/spring-boot/) expects to find an Application in
|
||||
a project's source files. The following may be used:
|
||||
|
||||
~~~
|
||||
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);
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## Building and testing
|
||||
|
||||
The project can be built by running the Maven command:
|
||||
|
||||
~~~
|
||||
mvn clean install
|
||||
~~~
|
||||
|
||||
## Artifacts
|
||||
|
||||
The artifacts can be obtained by:
|
||||
|
||||
* downloading from the [Alfresco repository](https://artifacts.alfresco.com/nexus/content/groups/public/)
|
||||
* Adding a Maven dependency to your pom file.
|
||||
|
||||
~~~
|
||||
<dependency>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-transformer-base</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
~~~
|
||||
|
||||
and the Alfresco Maven repository:
|
||||
|
||||
~~~
|
||||
<repository>
|
||||
<id>alfresco-maven-repo</id>
|
||||
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
|
||||
</repository>
|
||||
~~~
|
||||
|
||||
The build plan in Bamboo is PLAT-TB
|
||||
|
||||
## Contributing guide
|
||||
|
||||
Please use [this guide](https://github.com/Alfresco/alfresco-jodconverter/blob/master/CONTRIBUTING.md) to make a
|
||||
contribution to the project.
|
44
alfresco-transformer-base/pom.xml
Normal file
44
alfresco-transformer-base/pom.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-docker-transformers</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-transformer-base</artifactId>
|
||||
<version>0.1-SNAPSHOT</version>
|
||||
|
||||
<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>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-core</artifactId>
|
||||
<version>${dependency.alfresco-core.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* #%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,330 @@
|
||||
/*
|
||||
* #%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.base;
|
||||
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.alfresco.util.exec.RuntimeExec;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract Controller, provides structure and helper methods to sub-class transformer controllers.
|
||||
*
|
||||
* Status Codes:
|
||||
*
|
||||
* 200 Success
|
||||
* 400 Bad Request: Request parameter <name> is missing (missing mandatory parameter)
|
||||
* 400 Bad Request: Request parameter <name> is of the wrong type
|
||||
* 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file)
|
||||
* 400 Bad Request: The source filename was not supplied
|
||||
* 500 Internal Server Error: (no message with low level IO problems)
|
||||
* 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked)
|
||||
* 500 Internal Server Error: Transformer version check exit code was not 0
|
||||
* 500 Internal Server Error: Transformer version check failed to create any output
|
||||
* 500 Internal Server Error: Could not read the target file
|
||||
* 500 Internal Server Error: The target filename was malformed (should not happen because of other checks)
|
||||
* 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content)
|
||||
* 500 Internal Server Error: Filename encoding error
|
||||
* 507 Insufficient Storage: Failed to store the source file
|
||||
*
|
||||
* 408 Request Timeout -- TODO implement general timeout mechanism rather than depend on transformer timeout (might be possible for external processes)
|
||||
* 415 Unsupported Media Type -- TODO possibly implement a check on supported source and target mimetypes (probably not)
|
||||
* 429 Too Many Requests -- TODO implement general throttling mechanism (needs to be done)
|
||||
*/
|
||||
public abstract class AbstractTransformerController
|
||||
{
|
||||
public static final String SOURCE_FILE = "sourceFile";
|
||||
public static final String TARGET_FILE = "targetFile";
|
||||
|
||||
protected static Log logger;
|
||||
|
||||
protected RuntimeExec transformCommand;
|
||||
private RuntimeExec checkCommand;
|
||||
|
||||
public void setTransformCommand(RuntimeExec runtimeExec)
|
||||
{
|
||||
transformCommand = runtimeExec;
|
||||
}
|
||||
|
||||
public void setCheckCommand(RuntimeExec runtimeExec)
|
||||
{
|
||||
checkCommand = runtimeExec;
|
||||
}
|
||||
|
||||
@RequestMapping("/version")
|
||||
@ResponseBody
|
||||
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("/")
|
||||
public String transformForm(Model model)
|
||||
{
|
||||
return "transformForm"; // the name of the template
|
||||
}
|
||||
|
||||
@GetMapping("/log")
|
||||
public String log(Model model)
|
||||
{
|
||||
Collection<LogEntry> log = LogEntry.getLog();
|
||||
if (!log.isEmpty())
|
||||
{
|
||||
model.addAttribute("log", log);
|
||||
}
|
||||
return "log"; // the name of the template
|
||||
}
|
||||
|
||||
@ExceptionHandler(TypeMismatchException.class)
|
||||
public void handleParamsTypeMismatch(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException
|
||||
{
|
||||
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, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public void handleMissingParams(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException
|
||||
{
|
||||
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, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(TransformException.class)
|
||||
public void transformExceptionWithMessage(HttpServletResponse response, TransformException e) throws IOException
|
||||
{
|
||||
String message = e.getMessage();
|
||||
int statusCode = e.getStatusCode();
|
||||
|
||||
if (logger != null && logger.isErrorEnabled())
|
||||
{
|
||||
logger.error(message);
|
||||
}
|
||||
|
||||
LogEntry.setStatusCodeAndMessage(statusCode, message);
|
||||
|
||||
response.sendError(statusCode, message);
|
||||
}
|
||||
|
||||
protected String createTargetFileName(MultipartFile sourceMultipartFile, String targetExtension)
|
||||
{
|
||||
String targetFilename = null;
|
||||
String sourceFilename = sourceMultipartFile.getOriginalFilename();
|
||||
sourceFilename = StringUtils.getFilename(sourceFilename);
|
||||
if (sourceFilename != null && !sourceFilename.isEmpty())
|
||||
{
|
||||
String ext = StringUtils.getFilenameExtension(sourceFilename);
|
||||
if (ext != null && !ext.isEmpty())
|
||||
{
|
||||
targetFilename =sourceFilename.substring(0, sourceFilename.length()-ext.length()-1)+'.'+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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
filename = checkFilename( false, filename);
|
||||
LogEntry.setTarget(filename);
|
||||
File file = TempFileProvider.createTempFile("target_", "_" + filename);
|
||||
request.setAttribute(TARGET_FILE, file);
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
protected void executeTransformCommand(Map<String, String> properties, File targetFile, Long timeout)
|
||||
{
|
||||
long timeoutMs = timeout != null && timeout > 0 ? timeout : 0;
|
||||
RuntimeExec.ExecutionResult result = transformCommand.execute(properties, timeoutMs);
|
||||
|
||||
if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0)
|
||||
{
|
||||
throw new TransformException(400, "Transformer exit code was not 0: \n" + result);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
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());
|
||||
LogEntry.setStatusCodeAndMessage(200, "Success");
|
||||
return body;
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
throw new TransformException(500, "Filename encoding error", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* #%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.base;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Provides setter and getter methods to allow the current Thread to set various log properties and for these
|
||||
* values to be retrieved. The {@link #complete()} method should be called at the end of a request to flush the
|
||||
* 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
|
||||
{
|
||||
private static final AtomicInteger count = new AtomicInteger(0);
|
||||
private static final Deque<LogEntry> log = new ConcurrentLinkedDeque<>();
|
||||
private static final int MAX_LOG_SIZE = 10;
|
||||
|
||||
private static ThreadLocal<LogEntry> currentLogEntry = new ThreadLocal<LogEntry>()
|
||||
{
|
||||
@Override
|
||||
protected LogEntry initialValue()
|
||||
{
|
||||
LogEntry logEntry = new LogEntry();
|
||||
if (log.size() >= MAX_LOG_SIZE)
|
||||
{
|
||||
log.removeLast();
|
||||
}
|
||||
log.addFirst(logEntry);
|
||||
return logEntry;
|
||||
}
|
||||
};
|
||||
|
||||
private final int id = count.incrementAndGet();
|
||||
private final long start = System.currentTimeMillis();
|
||||
private int statusCode;
|
||||
|
||||
private long durationStreamIn;
|
||||
private long durationTransform;
|
||||
private long durationStreamOut;
|
||||
|
||||
private String source;
|
||||
private long sourceSize;
|
||||
private String target;
|
||||
private long targetSize;
|
||||
private String options;
|
||||
private String message;
|
||||
|
||||
public static Collection<LogEntry> getLog()
|
||||
{
|
||||
return log;
|
||||
}
|
||||
|
||||
public static void start()
|
||||
{
|
||||
currentLogEntry.get();
|
||||
}
|
||||
|
||||
public static void setSource(String source, long sourceSize)
|
||||
{
|
||||
LogEntry logEntry = currentLogEntry.get();
|
||||
logEntry.source = getExtension(source);
|
||||
logEntry.sourceSize = sourceSize;
|
||||
logEntry.durationStreamIn = System.currentTimeMillis() - logEntry.start;
|
||||
}
|
||||
|
||||
public static void setTarget(String target)
|
||||
{
|
||||
currentLogEntry.get().target = getExtension(target);
|
||||
}
|
||||
|
||||
private static String getExtension(String filename)
|
||||
{
|
||||
int i = filename.lastIndexOf('.');
|
||||
if (i != -1)
|
||||
{
|
||||
filename = filename.substring(i+1);
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
public static void setTargetSize(long targetSize)
|
||||
{
|
||||
currentLogEntry.get().targetSize = targetSize;
|
||||
}
|
||||
|
||||
public static void setOptions(String options)
|
||||
{
|
||||
currentLogEntry.get().options = options;
|
||||
}
|
||||
|
||||
public static void setStatusCodeAndMessage(int statusCode, String message)
|
||||
{
|
||||
LogEntry logEntry = currentLogEntry.get();
|
||||
logEntry.statusCode = statusCode;
|
||||
logEntry.message = message;
|
||||
logEntry.durationTransform = System.currentTimeMillis() - logEntry.start - logEntry.durationStreamIn;
|
||||
}
|
||||
|
||||
public static void complete()
|
||||
{
|
||||
LogEntry logEntry = currentLogEntry.get();
|
||||
logEntry.durationStreamOut = System.currentTimeMillis() - logEntry.start - logEntry.durationStreamIn - logEntry.durationTransform;
|
||||
currentLogEntry.remove();
|
||||
}
|
||||
|
||||
public int getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public Date getDate()
|
||||
{
|
||||
return new Date(start);
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public String getDuration()
|
||||
{
|
||||
return time(durationStreamIn + durationTransform + durationStreamOut)+" ("+
|
||||
time(durationStreamIn)+' '+time(durationTransform)+' '+time(durationStreamOut)+")";
|
||||
}
|
||||
|
||||
public long getDurationStreamIn()
|
||||
{
|
||||
return durationStreamIn;
|
||||
}
|
||||
|
||||
public long getDurationTransform()
|
||||
{
|
||||
return durationTransform;
|
||||
}
|
||||
|
||||
public long getDurationStreamOut()
|
||||
{
|
||||
return durationStreamOut;
|
||||
}
|
||||
|
||||
public String getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
public String getSourceSize()
|
||||
{
|
||||
return size(sourceSize);
|
||||
}
|
||||
|
||||
public String getTarget()
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
public String getTargetSize()
|
||||
{
|
||||
return size(targetSize);
|
||||
}
|
||||
|
||||
public String getOptions()
|
||||
{
|
||||
return options;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
private String time(long ms)
|
||||
{
|
||||
return size(ms, "1 ms",
|
||||
new String[] { "ms", "s", "min", "hr" },
|
||||
new long[] { 1000, 60*1000, 60*60*1000, Long.MAX_VALUE});
|
||||
}
|
||||
|
||||
private String size(long size)
|
||||
{
|
||||
return 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 });
|
||||
}
|
||||
|
||||
private String size(long size, String singleValue, String[] units, long[] dividers)
|
||||
{
|
||||
if (size == 1)
|
||||
{
|
||||
return singleValue;
|
||||
}
|
||||
long divider = 1;
|
||||
for(int i = 0; i < units.length-1; i++)
|
||||
{
|
||||
long nextDivider = dividers[i];
|
||||
if(size < nextDivider)
|
||||
{
|
||||
return unitFormat(size, divider, units[i]);
|
||||
}
|
||||
divider = nextDivider;
|
||||
}
|
||||
return unitFormat(size, divider, units[units.length-1]);
|
||||
}
|
||||
|
||||
private String unitFormat(long size, long divider, String unit)
|
||||
{
|
||||
size = size * 10 / divider;
|
||||
int decimalPoint = (int) size % 10;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(size/10);
|
||||
if (decimalPoint != 0)
|
||||
{
|
||||
sb.append(".");
|
||||
sb.append(decimalPoint);
|
||||
}
|
||||
sb.append(' ');
|
||||
sb.append(unit);
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* #%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.base;
|
||||
|
||||
public class TransformException extends RuntimeException
|
||||
{
|
||||
private int statusCode;
|
||||
|
||||
public TransformException(int statusCode, String message)
|
||||
{
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
public TransformException(int statusCode, String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public int getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* #%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.base;
|
||||
|
||||
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class TransformInterceptor extends HandlerInterceptorAdapter
|
||||
{
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request,
|
||||
HttpServletResponse response, Object handler) throws Exception
|
||||
{
|
||||
LogEntry.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
@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, "sourceFile");
|
||||
deleteFile(request, "targetFile");
|
||||
|
||||
LogEntry.complete();
|
||||
}
|
||||
|
||||
private void deleteFile(HttpServletRequest request, String attributeName)
|
||||
{
|
||||
File file = (File) request.getAttribute(attributeName);
|
||||
if (file != null)
|
||||
{
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* #%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.base;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
|
||||
@Configuration
|
||||
public class WebApplicationConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(transformInterceptor()).addPathPatterns("/transform");;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TransformInterceptor transformInterceptor() {
|
||||
return new TransformInterceptor();
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
spring.http.multipart.max-file-size=8192MB
|
||||
spring.http.multipart.max-request-size=8192MB
|
||||
server.port = 8090
|
||||
|
||||
logging.level.org.alfresco.util.exec.RuntimeExec=debug
|
||||
logging.level.org.alfresco.transformer.LibreOfficeController=debug
|
||||
logging.level.org.alfresco.transformer.JodConverterSharedInstance=debug
|
@@ -0,0 +1,45 @@
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<body>
|
||||
|
||||
<div th:if="${message}">
|
||||
<h2 th:text="${message}"/>
|
||||
</div>
|
||||
|
||||
<h2>Log entries</h2>
|
||||
<div th:if="${log}">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Time</th>
|
||||
<th>Status Code</th>
|
||||
<th>Duration (ms)</th>
|
||||
<th>Source</th>
|
||||
<th></th>
|
||||
<th>Target</th>
|
||||
<th></th>
|
||||
<th>Options</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
<tr th:each="entry : ${log}">
|
||||
<td th:text="${entry.id}"></td>
|
||||
<td th:text="${#dates.format(entry.date, 'HH:mm:ss')}"></td>
|
||||
<td th:text="${entry.statusCode}"></td>
|
||||
<td th:text="${entry.duration}"></td>
|
||||
<td th:text="${entry.source}"></td>
|
||||
<td th:text="${entry.sourceSize}"></td>
|
||||
<td th:text="${entry.target}"></td>
|
||||
<td th:text="${entry.targetSize}"></td>
|
||||
<td th:text="${entry.options}"></td>
|
||||
<td th:text="${entry.message}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<br/>
|
||||
<a href="/">Test Transformation</a>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user