REPO-5201 Update README on how to create a T-Engine (#261)

Reflects changes to AbstractTransformerController that simplify the number of methods that need to be implemented.
This commit is contained in:
Alan Davis 2020-06-17 13:00:33 +01:00 committed by GitHub
parent 76457cb6e8
commit 38f7a8cc9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -2,19 +2,17 @@
This project contains code that is common between all the ACS transformers that run within their own 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 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 streaming of content to and from the container.
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: A transformer project is expected to provide the following files:
~~~ ~~~
src/main/resources/templates/transformForm.html src/main/resources/templates/transformForm.html
src/main/java/org/alfresco/transformer/<XXX>Controller.java src/main/java/org/alfresco/transformer/<TransformerName>Controller.java
src/main/java/org/alfresco/transformer/Application.java src/main/java/org/alfresco/transformer/Application.java
~~~ ~~~
* transformerForm.html - A simple test page using [thymeleaf](http://www.thymeleaf.org) that gathers request * transformForm.html - A simple test page using [thymeleaf](http://www.thymeleaf.org) that gathers request
parameters so they may be used to test the transformer. parameters so they may be used to test the transformer.
~~~ ~~~
@ -25,12 +23,13 @@ src/main/java/org/alfresco/transformer/Application.java
<form method="POST" enctype="multipart/form-data" action="/transform"> <form method="POST" enctype="multipart/form-data" action="/transform">
<table> <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">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">file *</div></td><td><input type="file" name="file" /></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">sourceExtension *</div></td><td><input type="text" name="sourceExtension" 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">targetExtension *</div></td><td><input type="text" name="targetExtension" value="" /></td></tr>
<tr><td><div style="text-align:right">allowPdfEnlargement</div></td><td><input type="checkbox" name="allowPdfEnlargement" value="true" /></td></tr> <tr><td><div style="text-align:right">sourceMimetype *</div></td><td><input type="text" name="sourceMimetype" value="" /></td></tr>
<tr><td><div style="text-align:right">maintainPdfAspectRatio</div></td><td><input type="checkbox" name="maintainPdfAspectRatio" value="true" /></td></tr> <tr><td><div style="text-align:right">targetMimetype *</div></td><td><input type="text" name="targetMimetype" value="" /></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">abc:width</div></td><td><input type="text" name="width" value="" /></td></tr>
<tr><td><div style="text-align:right">abc:height</div></td><td><input type="text" name="height" value="" /></td></tr>
<tr><td><div style="text-align:right">timeout</div></td><td><input type="text" name="timeout" 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> <tr><td></td><td><input type="submit" value="Transform" /></td></tr>
</table> </table>
@ -44,131 +43,86 @@ src/main/java/org/alfresco/transformer/Application.java
~~~ ~~~
* *TransformerName*Controller.java - A [Spring Boot](https://projects.spring.io/spring-boot/) Controller that * *TransformerName*Controller.java - A [Spring Boot](https://projects.spring.io/spring-boot/) Controller that
extends AbstractTransformerController to handel a POST request to *"/transform"*. extends AbstractTransformerController to handel requests. It implements a few methods including *transformImpl*
which is intended to perform the actual transform. Generally the transform is done in a sub class of
*JavaExecutor*, when a Java library is being used or *AbstractCommandExecutor*, when an external process is used.
Both are sub interfaces of *Transformer*.
~~~ ~~~
... ...
@Controller @Controller
public class AlfrescoPdfRendererController extends AbstractTransformerController public class TransformerNameController extends AbstractTransformerController
{ {
... private static final Logger logger = LoggerFactory.getLogger(TransformerNameController.class);
@PostMapping("/transform") TransformerNameExecutor executor;
public ResponseEntity<Resource> transform(HttpServletRequest request,
@RequestParam("file") MultipartFile sourceMultipartFile, @PostConstruct
@RequestParam("targetFilename") String targetFilename, private void init()
@RequestParam(value = "width", required = false) Integer width,
@RequestParam(value = "height", required = false) Integer height,
@RequestParam(value = "allowPdfEnlargement", required = false) Boolean allowPdfEnlargement,
@RequestParam(value = "maintainPdfAspectRatio", required = false) Boolean maintainPdfAspectRatio,
@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "timeout", required = false) Long timeout)
{ {
try executor = new TransformerNameExecutor();
}
@Override
public String getTransformerName()
{
return "Transformer Name";
}
@Override
public String version()
{
return commandExecutor.version();
}
@Override
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",
7455, 1024, 150, 10240, 60 * 20 + 1, 60 * 15 - 15)
{ {
File sourceFile = createSourceFile(request, sourceMultipartFile); @Override
File targetFile = createTargetFile(request, targetFilename); protected void executeTransformCommand(File sourceFile, File targetFile)
// Both files are deleted by TransformInterceptor.afterCompletion {
transformImpl(null, null, null, Collections.emptyMap(), sourceFile, targetFile);
}
};
}
StringJoiner args = new StringJoiner(" "); @Override
if (width != null) public void transformImpl(String transformName, String sourceMimetype, String targetMimetype,
{ Map<String, String> transformOptions, File sourceFile, File targetFile)
args.add("--width=" + width); {
} executor.transform(sourceMimetype, targetMimetype, transformOptions, sourceFile, targetFile);
if (height != null)
{
args.add("--height=" + height);
}
if (allowPdfEnlargement != null && allowPdfEnlargement)
{
args.add("--allow-enlargement");
}
if (maintainPdfAspectRatio != null && maintainPdfAspectRatio)
{
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<>();
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);
}
} }
} }
~~~ ~~~
* *TransformerName*Controller#processTransform(File sourceFile, File targetFile, Map<String, String> transformOptions, Long timeout) * *TransformerName*Executer.java - *JavaExecutor* and *CommandExecutor* sub classes need to extract values from
*transformOptions* and use them in a call to an external process or as parameters to a library call.
### /transform (Consumes: `application/json`, Produces: `application/json`) ~~~
The new *consumes* and *produces* arguments have been specified in order to differentiate this endpoint from the previous one (which **consumes** `multipart/form-data`) ...
public class TransformerNameExecutor extends AbstractCommandExecutor
The endpoint should **always** receive a `TransformationRequest` and should **always** respond with a `TransformationReply`.
As specific transformers require specific arguments (e.g. `transform` for the **Tika transformer**) the request body should include this in the `transformRequestOptions` via the `Map<String,String> transformRequestOptions`.
**Example request body**
```javascript
var transformRequest = {
"requestId": "1",
"sourceReference": "2f9ed237-c734-4366-8c8b-6001819169a4",
"sourceMediaType": "pdf",
"sourceSize": 123456,
"sourceExtension": "pdf",
"targetMediaType": "txt",
"targetExtension": "txt",
"clientType": "ACS",
"clientData": "Yo No Soy Marinero, Soy Capitan, Soy Capitan!",
"schema": 1,
"transformRequestOptions": {
"targetMimetype": "text/plain",
"targetEncoding": "UTF-8",
"transform": "PdfBox"
}
}
```
**Example response body**
```javascript
var transformReply = {
"requestId": "1",
"status": 201,
"errorDetails": null,
"sourceReference": "2f9ed237-c734-4366-8c8b-6001819169a4",
"targetReference": "34d69ff0-7eaa-4741-8a9f-e1915e6995bf",
"clientType": "ACS",
"clientData": "Yo No Soy Marinero, Soy Capitan, Soy Capitan!",
"schema": 1
}
```
### processTransform method
```java
public abstract class AbstractTransformerController
{ {
void processTransform(File sourceFile, File targetFile, Map<String, String> transformOptions, Long timeout) { /* Perform the transformation*/ } ...
@Override
public void transform(String transformName, String sourceMimetype, String targetMimetype,
Map<String, String> transformOptions,
File sourceFile, File targetFile) throws TransformException
{
final String options = TransformerNameOptionsBuilder
.builder()
.withWidth(transformOptions.get(WIDTH_REQUEST_PARAM))
.withHeight(transformOptions.get(HEIGHT_REQUEST_PARAM))
.build();
Long timeout = stringToLong(transformOptions.get(TIMEOUT));
run(options, sourceFile, targetFile, timeout);
}
} }
``` ~~~
The **abstract** method is declared in the *AbstractTransformerController* and must be implemented by the specific controllers.
This method is called by the *AbstractTransformerController* directly in the **new** `/transform` endpoint which **consumes** `application/json` and **produces** `application/json`.
The method is responsible for performing the transformation. Upon a **successful** transformation it updates the `targetFile` parameter.
* Application.java - [Spring Boot](https://projects.spring.io/spring-boot/) expects to find an Application in * Application.java - [Spring Boot](https://projects.spring.io/spring-boot/) expects to find an Application in
a project's source files. The following may be used: a project's source files. The following may be used:
@ -189,6 +143,49 @@ public class Application
} }
~~~ ~~~
Transform requests are handled by the *AbstractTransformerController*, but are either:
* POST requests (a direct http request from a client) where the transform options are passed as parameters, the source is supplied as a multipart file and
the response is a file download.
* POST request (a request via a message queue) where the transform options are supplied as JSON and the response is also JSON.
The source and target content is read from a location accessible to both the client and the transfomer.
**Example JSON request body**
```javascript
var transformRequest = {
"requestId": "1",
"sourceReference": "2f9ed237-c734-4366-8c8b-6001819169a4",
"sourceMediaType": "application/pdf",
"sourceSize": 123456,
"sourceExtension": "pdf",
"targetMediaType": "text/plain",
"targetExtension": "txt",
"clientType": "ACS",
"clientData": "Yo No Soy Marinero, Soy Capitan, Soy Capitan!",
"schema": 1,
"transformRequestOptions": {
"targetMimetype": "text/plain",
"targetEncoding": "UTF-8",
"abc:width": "120",
"abc:height": "200"
}
}
```
**Example JSON response body**
```javascript
var transformReply = {
"requestId": "1",
"status": 201,
"errorDetails": null,
"sourceReference": "2f9ed237-c734-4366-8c8b-6001819169a4",
"targetReference": "34d69ff0-7eaa-4741-8a9f-e1915e6995bf",
"clientType": "ACS",
"clientData": "Yo No Soy Marinero, Soy Capitan, Soy Capitan!",
"schema": 1
}
```
## Building and testing ## Building and testing
The project can be built by running the Maven command: The project can be built by running the Maven command: