ATS-175 : T-Engine code cleanup

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,10 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transformer;
package org.alfresco.transformer.probes;
import static org.alfresco.transformer.fs.FileManager.SOURCE_FILE;
import static org.alfresco.transformer.fs.FileManager.TARGET_FILE;
import java.io.File;
import java.io.IOException;
@@ -35,6 +38,9 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import org.alfresco.transformer.AbstractTransformerController;
import org.alfresco.transformer.exceptions.TransformException;
import org.alfresco.transformer.logging.LogEntry;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
@@ -60,9 +66,9 @@ import org.apache.commons.logging.Log;
* <li>maxTransformSeconds - the maximum time for a transformation, including failed ones.</li>
* </ul>
*/
abstract class ProbeTestTransform
public abstract class ProbeTestTransform
{
public static final int AVERAGE_OVER_TRANSFORMS = 5;
private static final int AVERAGE_OVER_TRANSFORMS = 5;
private final String sourceFilename;
private final String targetFilename;
private final long minExpectedLength;
@@ -70,22 +76,32 @@ abstract class ProbeTestTransform
private final Log logger;
int livenessPercent;
long probeCount;
int transCount;
long normalTime;
long maxTime = Long.MAX_VALUE;
long nextTransformTime;
private int livenessPercent;
private long probeCount;
private int transCount;
private long normalTime;
private long maxTime = Long.MAX_VALUE;
private long nextTransformTime;
private final boolean livenessTransformEnabled;
private final long livenessTransformPeriod;
private long maxTransformCount = Long.MAX_VALUE;
private final long maxTransformCount;
private long maxTransformTime;
private AtomicBoolean initialised = new AtomicBoolean(false);
private AtomicBoolean readySent = new AtomicBoolean(false);
private AtomicLong transformCount = new AtomicLong(0);
private AtomicBoolean die = new AtomicBoolean(false);
private final AtomicBoolean initialised = new AtomicBoolean(false);
private final AtomicBoolean readySent = new AtomicBoolean(false);
private final AtomicLong transformCount = new AtomicLong(0);
private final AtomicBoolean die = new AtomicBoolean(false);
public int getLivenessPercent()
{
return livenessPercent;
}
public long getMaxTime()
{
return maxTime;
}
/**
* See Probes.md for more info.
@@ -97,12 +113,12 @@ abstract class ProbeTestTransform
* @param maxTransformSeconds default values normally supplied by helm. Not identical so we can be sure which value is used.
* @param livenessTransformPeriodSeconds default values normally supplied by helm. Not identical so we can be sure which value is used.
*/
public ProbeTestTransform(AbstractTransformerController controller,
public ProbeTestTransform(AbstractTransformerController controller, Log logger,
String sourceFilename, String targetFilename, long expectedLength, long plusOrMinus,
int livenessPercent, long maxTransforms, long maxTransformSeconds,
long livenessTransformPeriodSeconds)
{
logger = controller.logger;
this.logger = logger;
this.sourceFilename = sourceFilename;
this.targetFilename = targetFilename;
@@ -128,7 +144,7 @@ abstract class ProbeTestTransform
return defaultValue;
}
protected long getPositiveLongEnv(String name, long defaultValue)
private long getPositiveLongEnv(String name, long defaultValue)
{
long l = -1;
String env = System.getenv(name);
@@ -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;
}
}

View File

@@ -0,0 +1,28 @@
package org.alfresco.transformer.util;
/**
*/
public class Util
{
/**
* Safely converts a {@link String} to an {@link Integer}
*
* @param param String to be converted
* @return Null if param is null or converted value as {@link Integer}
*/
public static Integer stringToInteger(String param)
{
return param == null ? null : Integer.parseInt(param);
}
/**
* Safely converts a {@link String} to an {@link Integer}
*
* @param param String to be converted
* @return Null if param is null or converted value as {@link Boolean}
*/
public static Boolean stringToBoolean(String param)
{
return param == null? null : Boolean.parseBoolean(param);
}
}

View File

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

View File

@@ -27,12 +27,7 @@ package org.alfresco.transformer;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -44,33 +39,21 @@ import java.io.IOException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.alfresco.transform.client.model.TransformReply;
import org.alfresco.transform.client.model.TransformRequest;
import org.alfresco.transformer.model.FileRefEntity;
import org.alfresco.transformer.model.FileRefResponse;
import org.alfresco.util.exec.RuntimeExec;
import org.junit.Before;
import org.alfresco.transformer.clients.AlfrescoSharedFileStoreClient;
import org.alfresco.transformer.probes.ProbeTestTransform;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -85,18 +68,9 @@ public abstract class AbstractTransformerControllerTest
@Autowired
protected ObjectMapper objectMapper;
@Mock
private RuntimeExec mockTransformCommand;
@Mock
private RuntimeExec mockCheckCommand;
@Mock
@MockBean
protected AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient;
@Mock
private RuntimeExec.ExecutionResult mockExecutionResult;
protected String sourceExtension;
protected String targetExtension;
protected String sourceMimetype;
@@ -108,88 +82,14 @@ public abstract class AbstractTransformerControllerTest
protected byte[] expectedSourceFileBytes;
protected byte[] expectedTargetFileBytes;
protected AbstractTransformerController controller;
@Before
public void before() throws Exception
{
}
// Called by sub class
public void mockTransformCommand(AbstractTransformerController controller, String sourceExtension,
String targetExtension, String sourceMimetype,
boolean readTargetFileBytes) throws IOException
{
this.controller = controller;
this.sourceExtension = sourceExtension;
this.targetExtension = targetExtension;
this.sourceMimetype = sourceMimetype;
protected abstract void mockTransformCommand(String sourceExtension,
String targetExtension, String 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.
@@ -197,8 +97,8 @@ public abstract class AbstractTransformerControllerTest
* in order to simulate a successful transformation.
*
* @param actualTargetExtension Requested extension.
* @param testFile The test file (transformed) - basically the result.
* @param targetFile The location where the content from the testFile should be copied
* @param testFile The test file (transformed) - basically the result.
* @param targetFile The location where the content from the testFile should be copied
* @throws IOException in case of any errors.
*/
void generateTargetFileFromResourceFile(String actualTargetExtension, File testFile,
@@ -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;
@@ -258,9 +158,9 @@ public abstract class AbstractTransformerControllerTest
public void simpleTransformTest() throws Exception
{
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(status().is(200))
.andExpect(content().bytes(expectedTargetFileBytes))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
}
@Test
@@ -268,42 +168,32 @@ public abstract class AbstractTransformerControllerTest
{
long start = System.currentTimeMillis();
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(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);
}
@Test
public void noTargetFileTest() throws Exception
{
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", "xxx"))
.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")));
.andExpect(status().is(500));
}
@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(status().is(200))
.andExpect(content().bytes(expectedTargetFileBytes))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
}
@Test
@@ -313,9 +203,9 @@ public abstract class AbstractTransformerControllerTest
sourceFile = new MockMultipartFile("file", "../quick", 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(status().is(200))
.andExpect(content().bytes(expectedTargetFileBytes))
.andExpect(header().string("Content-Disposition", "attachment; filename*= UTF-8''quick." + targetExtension));
}
@Test
@@ -325,8 +215,8 @@ public abstract class AbstractTransformerControllerTest
sourceFile = new MockMultipartFile("file", "abc/", sourceMimetype, expectedSourceFileBytes);
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
.andExpect(status().is(400))
.andExpect(status().reason(containsString("The source filename was not supplied")));
.andExpect(status().is(400))
.andExpect(status().reason(containsString("The source filename was not supplied")));
}
@Test
@@ -335,113 +225,47 @@ public abstract class AbstractTransformerControllerTest
sourceFile = new MockMultipartFile("file", "", sourceMimetype, expectedSourceFileBytes);
mockMvc.perform(mockMvcRequest("/transform", sourceFile, "targetExtension", targetExtension))
.andExpect(status().is(400))
.andExpect(status().reason(containsString("The source filename was not supplied")));
.andExpect(status().is(400))
.andExpect(status().reason(containsString("The source filename was not supplied")));
}
@Test
public void noTargetExtensionTest() throws Exception
{
mockMvc.perform(mockMvcRequest("/transform", sourceFile))
.andExpect(status().is(400))
.andExpect(status().reason(containsString("Request parameter targetExtension is missing")));
.andExpect(status().is(400))
.andExpect(status().reason(containsString("Request parameter targetExtension is missing")));
}
// @Test
// // Not a real test, but helpful for trying out the duration times in log code.
// public void testTimes() throws InterruptedException
// {
// LogEntry.start();
// Thread.sleep(50);
// LogEntry.setSource("test File", 1234);
// Thread.sleep(200);
// LogEntry.setStatusCodeAndMessage(200, "Success");
// LogEntry.addDelay(2000L);
// for (LogEntry logEntry: LogEntry.getLog())
// {
// String str = logEntry.getDuration();
// System.out.println(str);
// }
// }
@Test
public void calculateMaxTime() throws Exception
{
ProbeTestTransform probeTestTransform = controller.getProbeTestTransform();
probeTestTransform.livenessPercent = 110;
ProbeTestTransform probeTestTransform = getController().getProbeTestTransform();
probeTestTransform.setLivenessPercent(110);
long [][] values = new long[][] {
{5000, 0, Long.MAX_VALUE}, // 1st transform is ignored
{1000, 1000, 2100}, // 1000 + 1000*1.1
{3000, 2000, 4200}, // 2000 + 2000*1.1
{2000, 2000, 4200},
{6000, 3000, 6300},
{8000, 4000, 8400},
{4444, 4000, 8400}, // no longer in the first few, so normal and max times don't change
{5555, 4000, 8400}
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
{2000, 2000, 4200},
{6000, 3000, 6300},
{8000, 4000, 8400},
{4444, 4000, 8400}, // no longer in the first few, so normal and max times don't change
{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")
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).content(tr))
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.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);
}