From ba972872883d92f3c9f9930880ea73c543306d9b Mon Sep 17 00:00:00 2001 From: alandavis Date: Wed, 13 Jul 2022 12:53:00 +0100 Subject: [PATCH] Save point: [skip ci] * Extract TransformHandler from TransformController --- .../transform/coreaio/AIOTransformEngine.java | 3 - .../transform/base/QueueTransformService.java | 31 +- .../transform/base/TransformController.java | 531 +--------------- .../transform/base/TransformEngine.java | 2 +- .../transform/base/TransformHandler.java | 577 ++++++++++++++++++ .../base/config/WebApplicationConfig.java | 4 - .../transform/base/fs/FileManager.java | 121 +--- .../base/AbstractTransformControllerTest.java | 1 - .../base/QueueTransformServiceTest.java | 52 +- .../transform/misc/MiscTransformEngine.java | 3 - .../PdfRendererTransformEngine.java | 1 - 11 files changed, 651 insertions(+), 675 deletions(-) create mode 100644 engines/base/src/main/java/org/alfresco/transform/base/TransformHandler.java diff --git a/engines/aio/src/main/java/org/alfresco/transform/coreaio/AIOTransformEngine.java b/engines/aio/src/main/java/org/alfresco/transform/coreaio/AIOTransformEngine.java index 944a1adc..365918d7 100644 --- a/engines/aio/src/main/java/org/alfresco/transform/coreaio/AIOTransformEngine.java +++ b/engines/aio/src/main/java/org/alfresco/transform/coreaio/AIOTransformEngine.java @@ -27,19 +27,16 @@ package org.alfresco.transform.coreaio; import org.alfresco.transform.base.TransformEngine; -import org.alfresco.transform.base.TransformRegistryImpl; import org.alfresco.transform.base.probes.ProbeTestTransform; import org.alfresco.transform.config.TransformConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import static org.alfresco.transform.base.logging.StandardMessages.COMMUNITY_LICENCE; -import static org.alfresco.transform.common.Mimetype.MIMETYPE_HTML; import static org.alfresco.transform.common.Mimetype.MIMETYPE_PDF; import static org.alfresco.transform.common.Mimetype.MIMETYPE_TEXT_PLAIN; diff --git a/engines/base/src/main/java/org/alfresco/transform/base/QueueTransformService.java b/engines/base/src/main/java/org/alfresco/transform/base/QueueTransformService.java index fb500916..e6f06355 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/QueueTransformService.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/QueueTransformService.java @@ -26,20 +26,11 @@ */ package org.alfresco.transform.base; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; - -import java.util.Optional; - -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; - +import org.alfresco.transform.base.messaging.TransformMessageConverter; +import org.alfresco.transform.base.messaging.TransformReplySender; import org.alfresco.transform.client.model.TransformReply; import org.alfresco.transform.client.model.TransformRequest; import org.alfresco.transform.common.TransformException; -import org.alfresco.transform.base.messaging.TransformMessageConverter; -import org.alfresco.transform.base.messaging.TransformReplySender; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -49,6 +40,14 @@ import org.springframework.jms.annotation.JmsListener; import org.springframework.jms.support.converter.MessageConversionException; import org.springframework.stereotype.Component; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import java.util.Optional; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; + /** * Queue Transformer service. * This service reads all the requests for the particular engine, forwards them to the worker @@ -64,14 +63,10 @@ public class QueueTransformService { private static final Logger logger = LoggerFactory.getLogger(QueueTransformService.class); - // TODO: I know this is not smart but all the the transformation logic is in the Controller. - // The controller also manages the probes. There's tons of refactoring needed there, hence this. Sorry. @Autowired - private TransformController transformController; - + private TransformHandler transformHandler; @Autowired private TransformMessageConverter transformMessageConverter; - @Autowired private TransformReplySender transformReplySender; @@ -129,9 +124,7 @@ public class QueueTransformService return; } - TransformReply reply = transformController.transform(transformRequest.get(), null) - .getBody(); - + TransformReply reply = transformHandler.handleMessageRequest(transformRequest.get(), null).getBody(); transformReplySender.send(replyToDestinationQueue, reply); } diff --git a/engines/base/src/main/java/org/alfresco/transform/base/TransformController.java b/engines/base/src/main/java/org/alfresco/transform/base/TransformController.java index 43abe0f6..468b1151 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/TransformController.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/TransformController.java @@ -26,19 +26,12 @@ */ package org.alfresco.transform.base; -import org.alfresco.transform.base.clients.AlfrescoSharedFileStoreClient; -import org.alfresco.transform.base.fs.FileManager; import org.alfresco.transform.base.logging.LogEntry; -import org.alfresco.transform.base.model.FileRefResponse; import org.alfresco.transform.base.probes.ProbeTestTransform; -import org.alfresco.transform.base.util.OutputStreamLengthRecorder; -import org.alfresco.transform.client.model.InternalContext; import org.alfresco.transform.client.model.TransformReply; import org.alfresco.transform.client.model.TransformRequest; import org.alfresco.transform.common.TransformException; -import org.alfresco.transform.common.TransformerDebug; import org.alfresco.transform.config.TransformConfig; -import org.alfresco.transform.messages.TransformRequestValidator; import org.alfresco.transform.registry.TransformServiceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,24 +40,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -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.servlet.ModelAndView; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; @@ -72,50 +57,29 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Arrays; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; import static java.text.MessageFormat.format; -import static java.util.stream.Collectors.joining; -import static org.alfresco.transform.base.fs.FileManager.TempFileProvider.createTempFile; -import static org.alfresco.transform.base.fs.FileManager.createTargetFile; -import static org.alfresco.transform.base.fs.FileManager.deleteFile; -import static org.alfresco.transform.base.fs.FileManager.getDirectAccessUrlInputStream; -import static org.alfresco.transform.base.fs.FileManager.getFilenameFromContentDisposition; -import static org.alfresco.transform.base.fs.FileManager.save; import static org.alfresco.transform.common.RequestParamMap.CONFIG_VERSION; import static org.alfresco.transform.common.RequestParamMap.CONFIG_VERSION_DEFAULT; -import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL; import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_ERROR; import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_LOG; import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_ROOT; +import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_TEST; import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_TRANSFORM; import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_TRANSFORM_CONFIG; -import static org.alfresco.transform.common.RequestParamMap.ENDPOINT_TEST; import static org.alfresco.transform.common.RequestParamMap.FILE; -import static org.alfresco.transform.common.RequestParamMap.SOURCE_ENCODING; -import static org.alfresco.transform.common.RequestParamMap.SOURCE_EXTENSION; import static org.alfresco.transform.common.RequestParamMap.SOURCE_MIMETYPE; -import static org.alfresco.transform.common.RequestParamMap.TARGET_ENCODING; import static org.alfresco.transform.common.RequestParamMap.TARGET_MIMETYPE; import static org.alfresco.transform.config.CoreVersionDecorator.setOrClearCoreVersion; import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.OK; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; -import static org.springframework.util.StringUtils.getFilenameExtension; /** * Provides the main endpoints into the t-engine. @@ -124,82 +88,26 @@ import static org.springframework.util.StringUtils.getFilenameExtension; public class TransformController { private static final Logger logger = LoggerFactory.getLogger(TransformController.class); - private static final List NON_TRANSFORM_OPTION_REQUEST_PARAMETERS = Arrays.asList(SOURCE_EXTENSION, - TARGET_MIMETYPE, SOURCE_MIMETYPE, DIRECT_ACCESS_URL); @Autowired(required = false) private List transformEngines; - - @Autowired(required = false) - private List customTransformers; - - @Autowired - private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient; - @Autowired - private TransformRequestValidator transformRequestValidator; @Autowired private TransformServiceRegistry transformRegistry; @Autowired - private TransformerDebug transformerDebug; + private TransformHandler transformHandler; @Value("${transform.core.version}") private String coreVersion; private TransformEngine transformEngine; ProbeTestTransform probeTestTransform; - private Map customTransformersByName = new HashMap<>(); - private AtomicInteger httpRequestCount = new AtomicInteger(1); @PostConstruct - public void init() + private void init() { - initTransformEngine(); - initProbeTestTransform(); - initCustomTransformersByName(); + transformEngine = transformHandler.getTransformEngine(); + probeTestTransform = transformHandler.getProbeTestTransform(); } - private void initTransformEngine() - { - if (transformEngines != null) - { - // Normally there is just one TransformEngine per t-engine, but we also want to be able to amalgamate the - // CustomTransform code from many t-engines into a single t-engine. In this case, there should be a wrapper - // TransformEngine (it has no TransformConfig of its own). - transformEngine = transformEngines.stream() - .filter(transformEngine -> transformEngine.getTransformConfig() == null) - .findFirst() - .orElse(transformEngines.get(0)); - - logger.info("TransformEngine: " + transformEngine.getTransformEngineName()); - transformEngines.stream() - .filter(te -> te != transformEngine) - .sorted(Comparator.comparing(TransformEngine::getTransformEngineName)) - .map(transformEngine -> " "+transformEngine.getTransformEngineName()).forEach(logger::info); - } - } - - private void initProbeTestTransform() - { - if (transformEngine != null) - { - probeTestTransform = transformEngine.getLivenessAndReadinessProbeTestTransform(); - } - } - - private void initCustomTransformersByName() - { - if (customTransformers != null) - { - customTransformers.forEach(customTransformer -> - customTransformersByName.put(customTransformer.getTransformerName(), customTransformer)); - - logger.info("Transformers:"); - customTransformers.stream() - .sorted(Comparator.comparing(CustomTransformer::getTransformerName)) - .map(customTransformer -> " "+customTransformer.getTransformerName()).forEach(logger::info); - } - } - - @EventListener(ApplicationReadyEvent.class) public void startup() { @@ -288,13 +196,26 @@ public class TransformController return new ResponseEntity<>(transformConfig, OK); } + @PostMapping(value = ENDPOINT_TRANSFORM, consumes = MULTIPART_FORM_DATA_VALUE) + public StreamingResponseBody transform(HttpServletRequest request, + @RequestParam(value = FILE, required = false) MultipartFile sourceMultipartFile, + @RequestParam(value = SOURCE_MIMETYPE, required = false) String sourceMimetype, + @RequestParam(value = TARGET_MIMETYPE, required = false) String targetMimetype, + @RequestParam Map requestParameters) + { + return transformHandler.handleHttpRequest(request, sourceMultipartFile, sourceMimetype, + targetMimetype, requestParameters); + } + @PostMapping(value = ENDPOINT_TEST, consumes = MULTIPART_FORM_DATA_VALUE) - public StreamingResponseBody test(HttpServletRequest request, + public StreamingResponseBody testTransform(HttpServletRequest request, @RequestParam(value = FILE, required = false) MultipartFile sourceMultipartFile, @RequestParam(value = SOURCE_MIMETYPE, required = false) String sourceMimetype, @RequestParam(value = TARGET_MIMETYPE, required = false) String targetMimetype, @RequestParam Map origRequestParameters) { + // Remaps request parameters from test.html and hands them off to the normal transform endpoint. + // There are name and value parameters which allow dynamic names and values to be used. Map requestParameters = new HashMap<>(); sourceMimetype = overrideMimetypeFromExtension(origRequestParameters, SOURCE_MIMETYPE, sourceMimetype); targetMimetype = overrideMimetypeFromExtension(origRequestParameters, TARGET_MIMETYPE, targetMimetype); @@ -328,420 +249,6 @@ public class TransformController return value; } - @PostMapping(value = ENDPOINT_TRANSFORM, consumes = MULTIPART_FORM_DATA_VALUE) - public StreamingResponseBody transform(HttpServletRequest request, - @RequestParam(value = FILE, required = false) MultipartFile sourceMultipartFile, - @RequestParam(value = SOURCE_MIMETYPE, required = false) String sourceMimetype, - @RequestParam(value = TARGET_MIMETYPE, required = false) String targetMimetype, - @RequestParam Map requestParameters) - { - if (logger.isDebugEnabled()) - { - logger.debug("Processing request via HTTP endpoint. Params: sourceMimetype: '{}', targetMimetype: '{}', " - + "requestParameters: {}", sourceMimetype, targetMimetype, requestParameters); - } - probeTestTransform.incrementTransformerCount(); - - // Obtain the source - final String directUrl = requestParameters.getOrDefault(DIRECT_ACCESS_URL, ""); - InputStream inputStream = directUrl.isBlank() - ? FileManager.getMultipartFileInputStream(sourceMultipartFile) - : getDirectAccessUrlInputStream(directUrl); - long sourceSizeInBytes = -1L; // TODO pass in t-options or just ignore for http request as the repo will have checked. - Map transformOptions = getTransformOptions(requestParameters); - String transformName = getTransformerName(sourceSizeInBytes, sourceMimetype, targetMimetype, transformOptions); - CustomTransformer customTransformer = getCustomTransformer(transformName); - String sourceEncoding = transformOptions.get(SOURCE_ENCODING); - String targetEncoding = transformOptions.get(TARGET_ENCODING); // TODO not normally set - String reference = "e"+httpRequestCount.getAndIncrement(); - transformerDebug.pushTransform(reference, sourceMimetype, targetMimetype, sourceSizeInBytes, transformName); - transformerDebug.logOptions(reference, requestParameters); - - return os -> { - OutputStreamLengthRecorder outputStream = new OutputStreamLengthRecorder(os); - try - { - TransformManagerImpl transformManager = TransformManagerImpl.builder() - .withRequest(request) - .withSourceMimetype(sourceMimetype) - .withTargetMimetype(targetMimetype) - .withInputStream(inputStream) - .withOutputStream(outputStream) - .build(); - - customTransformer.transform(sourceMimetype, inputStream, - targetMimetype, outputStream, transformOptions, transformManager); - - transformManager.ifUsedCopyTargetFileToOutputStream(); - - LogEntry.setTargetSize(outputStream.getLength()); - long time = LogEntry.setStatusCodeAndMessage(OK.value(), "Success"); - - transformManager.deleteSourceFileIfExists(); - transformManager.deleteTargetFileIfExists(); - - probeTestTransform.recordTransformTime(time); - transformerDebug.popTransform(reference, time); - } - catch (Exception e) - { - transformerDebug.logFailure(reference, e.getMessage()); - throw new RuntimeException(e); - } - }; - } - - /** - * '/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 one which produces 'html' - * - * @param request The transformation request - * @param timeout Transformation timeout - * @return A transformation reply - */ - public ResponseEntity transform(@RequestBody TransformRequest request, - @RequestParam(value = "timeout", required = false) Long timeout) - { - long start = System.currentTimeMillis(); - logger.trace("Received {}, timeout {} ms", request, timeout); - probeTestTransform.incrementTransformerCount(); - TransformReply reply = createBasicTransformReply(request); - - if (isTransformRequestValid(request, reply) == false) - { - return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); - } - - InputStream inputStream = getInputStream(request, reply); - if (inputStream == null) - { - return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); - } - - String targetMimetype = request.getTargetMediaType(); - String sourceMimetype = request.getSourceMediaType(); - File targetFile = createTargetFile(null, sourceMimetype, targetMimetype); - transformerDebug.pushTransform(request); - - try - { - OutputStreamLengthRecorder outputStream = - new OutputStreamLengthRecorder(new BufferedOutputStream(new FileOutputStream(targetFile))); - - long sourceSizeInBytes = request.getSourceSize(); - Map transformOptions = getTransformOptions(request.getTransformRequestOptions()); - String sourceEncoding = transformOptions.get(SOURCE_ENCODING); - String targetEncoding = transformOptions.get(TARGET_ENCODING); // TODO not normally set - transformerDebug.logOptions(request); - String transformName = getTransformerName(sourceSizeInBytes, sourceMimetype, targetMimetype, transformOptions); - CustomTransformer customTransformer = getCustomTransformer(transformName); - - TransformManagerImpl transformManager = TransformManagerImpl.builder() - .withSourceMimetype(sourceMimetype) - .withTargetMimetype(targetMimetype) - .withInputStream(inputStream) - .withOutputStream(outputStream) - .withTargetFile(targetFile) - .build(); - - customTransformer.transform(sourceMimetype, inputStream, - targetMimetype, outputStream, transformOptions, transformManager); - - transformManager.ifUsedCopyTargetFileToOutputStream(); - - reply.getInternalContext().setCurrentSourceSize(outputStream.getLength()); - - if (saveTargetFileInSharedFileStore(targetFile, reply) == false) - { - return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); - } - - transformManager.deleteSourceFileIfExists(); - transformManager.deleteTargetFileIfExists(); - - probeTestTransform.recordTransformTime(System.currentTimeMillis()-start); - transformerDebug.popTransform(reply); - - logger.trace("Sending successful {}, timeout {} ms", reply, timeout); - return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); - } - catch (TransformException e) - { - reply.setStatus(e.getStatusCode()); - reply.setErrorDetails(messageWithCause("Failed at processing transformation", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to perform transform (TransformException), sending " + reply, e); - return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); - } - catch (Exception e) - { - reply.setStatus(INTERNAL_SERVER_ERROR.value()); - reply.setErrorDetails(messageWithCause("Failed at processing transformation", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to perform transform (Exception), sending " + reply, e); - return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); - } - } - - private boolean isTransformRequestValid(TransformRequest request, TransformReply reply) - { - final Errors errors = validateTransformRequest(request); - validateInternalContext(request, errors); - reply.setInternalContext(request.getInternalContext()); - if (!errors.getAllErrors().isEmpty()) - { - reply.setStatus(BAD_REQUEST.value()); - reply.setErrorDetails(errors - .getAllErrors() - .stream() - .map(Object::toString) - .collect(joining(", "))); - - transformerDebug.logFailure(reply); - logger.trace("Invalid request, sending {}", reply); - return false; - } - return true; - } - - private TransformReply createBasicTransformReply(TransformRequest request) - { - TransformReply reply = new TransformReply(); - reply.setRequestId(request.getRequestId()); - reply.setSourceReference(request.getSourceReference()); - reply.setSchema(request.getSchema()); - reply.setClientData(request.getClientData()); - reply.setInternalContext(request.getInternalContext()); - return reply; - } - - private Errors validateTransformRequest(final TransformRequest transformRequest) - { - DirectFieldBindingResult errors = new DirectFieldBindingResult(transformRequest, "request"); - transformRequestValidator.validate(transformRequest, errors); - return errors; - } - - private void validateInternalContext(TransformRequest request, Errors errors) - { - String errorMessage = InternalContext.checkForBasicErrors(request.getInternalContext(), "T-Request"); - if (errorMessage != null) - { - errors.rejectValue("internalContext", null, errorMessage); - } - initialiseContext(request); - } - - private void initialiseContext(TransformRequest request) - { - // If needed, initialise the context enough to allow logging to take place without NPE checks - request.setInternalContext(InternalContext.initialise(request.getInternalContext())); - } - - protected Map getTransformOptions(Map requestParameters) - { - Map transformOptions = new HashMap<>(requestParameters); - transformOptions.keySet().removeAll(NON_TRANSFORM_OPTION_REQUEST_PARAMETERS); - transformOptions.values().removeIf(v -> v.isEmpty()); - return transformOptions; - } - - private InputStream getSharedFileStoreInputStream(String sourceReference) - { - ResponseEntity responseEntity = alfrescoSharedFileStoreClient.retrieveFile(sourceReference); - final Resource body = responseEntity.getBody(); - if (body == null) - { - String message = "Source file with reference: " + sourceReference + " is null or empty."; - logger.warn(message); - throw new TransformException(BAD_REQUEST.value(), message); - } - - try - { - return body.getInputStream(); - } - catch (IOException e) - { - String message = "Shared File Store reference is invalid."; - logger.warn(message); - throw new TransformException(BAD_REQUEST.value(), message, e); - } - } - - private InputStream getInputStream(TransformRequest request, TransformReply reply) - { - final String directUrl = request.getTransformRequestOptions().getOrDefault(DIRECT_ACCESS_URL, ""); - InputStream inputStream = null; - try - { - inputStream = directUrl.isBlank() - ? getSharedFileStoreInputStream(request.getSourceReference()) - : getDirectAccessUrlInputStream(directUrl); - } - catch (TransformException e) - { - reply.setStatus(e.getStatusCode()); - reply.setErrorDetails(messageWithCause("Failed at reading the source file", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to load source file (TransformException), sending " + reply); - } - catch (HttpClientErrorException e) - { - reply.setStatus(e.getStatusCode().value()); - reply.setErrorDetails(messageWithCause("Failed at reading the source file", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to load source file (HttpClientErrorException), sending " + reply, e); - } - return inputStream; - } - - private boolean saveTargetFileInSharedFileStore(File targetFile, TransformReply reply) - { - FileRefResponse targetRef; - try - { - targetRef = alfrescoSharedFileStoreClient.saveFile(targetFile); - } - catch (TransformException e) - { - reply.setStatus(e.getStatusCode()); - reply.setErrorDetails(messageWithCause("Failed at writing the transformed file", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to save target file (TransformException), sending " + reply, e); - return false; - } - catch (HttpClientErrorException e) - { - reply.setStatus(e.getStatusCode().value()); - reply.setErrorDetails(messageWithCause("Failed at writing the transformed file. ", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to save target file (HttpClientErrorException), sending " + reply, e); - return false; - } - catch (Exception e) - { - reply.setStatus(INTERNAL_SERVER_ERROR.value()); - reply.setErrorDetails(messageWithCause("Failed at writing the transformed file. ", e)); - - transformerDebug.logFailure(reply); - logger.trace("Failed to save target file (Exception), sending " + reply, e); - return false; - } - - try - { - deleteFile(targetFile); - } - catch (Exception e) - { - logger.error("Failed to delete local temp target file. Error will be ignored ", e); - } - - reply.setTargetReference(targetRef.getEntry().getFileRef()); - reply.setStatus(CREATED.value()); - - return true; - } - - /** - * Loads the file with the specified sourceReference from Alfresco Shared File Store - * - * @param sourceReference reference to the file in Alfresco Shared File Store - * @param sourceExtension default extension if the file in Alfresco Shared File Store has none - * @return the file containing the source content for the transformation - */ - private File loadSourceFile(final String sourceReference, final String sourceExtension) - { - ResponseEntity responseEntity = alfrescoSharedFileStoreClient.retrieveFile(sourceReference); - - HttpHeaders headers = responseEntity.getHeaders(); - String filename = getFilenameFromContentDisposition(headers); - - String extension = getFilenameExtension(filename) != null ? getFilenameExtension(filename) : sourceExtension; - MediaType contentType = headers.getContentType(); - long size = headers.getContentLength(); - - final Resource body = responseEntity.getBody(); - if (body == null) - { - String message = "Source file with reference: " + sourceReference + " is null or empty. " - + "Transformation will fail and stop now as there is no content to be transformed."; - logger.warn(message); - throw new TransformException(BAD_REQUEST.value(), message); - } - final File file = createTempFile("source_", "." + extension); - - logger.debug("Read source content {} length={} contentType={}", - sourceReference, size, contentType); - - save(body, file); - LogEntry.setSource(filename, size); - return file; - } - - private static String messageWithCause(final String prefix, Throwable e) - { - final StringBuilder sb = new StringBuilder(); - sb.append(prefix).append(" - ") - .append(e.getClass().getSimpleName()).append(": ") - .append(e.getMessage()); - - while (e.getCause() != null) - { - e = e.getCause(); - sb.append(", cause ") - .append(e.getClass().getSimpleName()).append(": ") - .append(e.getMessage()); - } - - return sb.toString(); - } - - private String getTransformerName(long sourceSizeInBytes, final String sourceMimetype, - final String targetMimetype, final Map transformOptions) - { - // The transformOptions always contains sourceEncoding when sent to a T-Engine, even though it should not be - // used to select a transformer. Similar to source and target mimetypes and extensions, but these are not - // passed in transformOptions. - String sourceEncoding = transformOptions.remove(SOURCE_ENCODING); - try - { - final String transformerName = transformRegistry.findTransformerName(sourceMimetype, - sourceSizeInBytes, targetMimetype, transformOptions, null); - if (transformerName == null) - { - throw new TransformException(BAD_REQUEST.value(), "No transforms were able to handle the request"); - } - return transformerName; - } - finally - { - if (sourceEncoding != null) - { - transformOptions.put(SOURCE_ENCODING, sourceEncoding); - } - } - } - - private CustomTransformer getCustomTransformer(String transformName) - { - CustomTransformer customTransformer = customTransformersByName.get(transformName); - if (customTransformer == null) - { - throw new TransformException(BAD_REQUEST.value(), "Custom Transformer "+customTransformer+" not found"); - } - return customTransformer; - } - @ExceptionHandler(TypeMismatchException.class) public void handleParamsTypeMismatch(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException { diff --git a/engines/base/src/main/java/org/alfresco/transform/base/TransformEngine.java b/engines/base/src/main/java/org/alfresco/transform/base/TransformEngine.java index 34e84f3d..24be6395 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/TransformEngine.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/TransformEngine.java @@ -26,9 +26,9 @@ */ package org.alfresco.transform.base; +import org.alfresco.transform.base.probes.ProbeTestTransform; import org.alfresco.transform.common.TransformConfigResourceReader; import org.alfresco.transform.config.TransformConfig; -import org.alfresco.transform.base.probes.ProbeTestTransform; /** * Interface to be implemented by transform specific code. Provides information about the t-engine as a whole. diff --git a/engines/base/src/main/java/org/alfresco/transform/base/TransformHandler.java b/engines/base/src/main/java/org/alfresco/transform/base/TransformHandler.java new file mode 100644 index 00000000..49a79b23 --- /dev/null +++ b/engines/base/src/main/java/org/alfresco/transform/base/TransformHandler.java @@ -0,0 +1,577 @@ +/* + * #%L + * Alfresco Transform Core + * %% + * Copyright (C) 2005 - 2022 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 . + * #L% + */ +package org.alfresco.transform.base; + +import org.alfresco.transform.base.clients.AlfrescoSharedFileStoreClient; +import org.alfresco.transform.base.fs.FileManager; +import org.alfresco.transform.base.logging.LogEntry; +import org.alfresco.transform.base.model.FileRefResponse; +import org.alfresco.transform.base.probes.ProbeTestTransform; +import org.alfresco.transform.base.util.OutputStreamLengthRecorder; +import org.alfresco.transform.client.model.InternalContext; +import org.alfresco.transform.client.model.TransformReply; +import org.alfresco.transform.client.model.TransformRequest; +import org.alfresco.transform.common.TransformException; +import org.alfresco.transform.common.TransformerDebug; +import org.alfresco.transform.messages.TransformRequestValidator; +import org.alfresco.transform.registry.TransformServiceRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.validation.DirectFieldBindingResult; +import org.springframework.validation.Errors; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.util.stream.Collectors.joining; +import static org.alfresco.transform.base.fs.FileManager.TempFileProvider.createTempFile; +import static org.alfresco.transform.base.fs.FileManager.createTargetFile; +import static org.alfresco.transform.base.fs.FileManager.deleteFile; +import static org.alfresco.transform.base.fs.FileManager.getDirectAccessUrlInputStream; +import static org.alfresco.transform.base.fs.FileManager.getFilenameFromContentDisposition; +import static org.alfresco.transform.base.fs.FileManager.save; +import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL; +import static org.alfresco.transform.common.RequestParamMap.SOURCE_ENCODING; +import static org.alfresco.transform.common.RequestParamMap.SOURCE_EXTENSION; +import static org.alfresco.transform.common.RequestParamMap.SOURCE_MIMETYPE; +import static org.alfresco.transform.common.RequestParamMap.TARGET_ENCODING; +import static org.alfresco.transform.common.RequestParamMap.TARGET_MIMETYPE; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.util.StringUtils.getFilenameExtension; + +/** + * Handles the transform requests from either http or a message. + */ +@Component +public class TransformHandler +{ + private static final Logger logger = LoggerFactory.getLogger(TransformHandler.class); + private static final List NON_TRANSFORM_OPTION_REQUEST_PARAMETERS = Arrays.asList(SOURCE_EXTENSION, + TARGET_MIMETYPE, SOURCE_MIMETYPE, DIRECT_ACCESS_URL); + + @Autowired(required = false) + private List transformEngines; + + @Autowired(required = false) + private List customTransformers; + + @Autowired + private AlfrescoSharedFileStoreClient alfrescoSharedFileStoreClient; + @Autowired + private TransformRequestValidator transformRequestValidator; + @Autowired + private TransformServiceRegistry transformRegistry; + @Autowired + private TransformerDebug transformerDebug; + @Autowired + private TransformEngine transformEngine; + private AtomicInteger httpRequestCount = new AtomicInteger(1); + private ProbeTestTransform probeTestTransform; + private Map customTransformersByName = new HashMap<>(); + + @PostConstruct + public void init() + { + initTransformEngine(); + initProbeTestTransform(); + initCustomTransformersByName(); + } + + private void initTransformEngine() + { + if (transformEngines != null) + { + // Normally there is just one TransformEngine per t-engine, but we also want to be able to amalgamate the + // CustomTransform code from many t-engines into a single t-engine. In this case, there should be a wrapper + // TransformEngine (it has no TransformConfig of its own). + transformEngine = transformEngines.stream() + .filter(transformEngine -> transformEngine.getTransformConfig() == null) + .findFirst() + .orElse(transformEngines.get(0)); + + logger.info("TransformEngine: " + transformEngine.getTransformEngineName()); + transformEngines.stream() + .filter(te -> te != transformEngine) + .sorted(Comparator.comparing(TransformEngine::getTransformEngineName)) + .map(transformEngine -> " "+transformEngine.getTransformEngineName()).forEach(logger::info); + } + } + + private void initProbeTestTransform() + { + if (transformEngine != null) + { + probeTestTransform = transformEngine.getLivenessAndReadinessProbeTestTransform(); + } + } + + private void initCustomTransformersByName() + { + if (customTransformers != null) + { + customTransformers.forEach(customTransformer -> + customTransformersByName.put(customTransformer.getTransformerName(), customTransformer)); + + logger.info("Transformers:"); + customTransformers.stream() + .sorted(Comparator.comparing(CustomTransformer::getTransformerName)) + .map(customTransformer -> " "+customTransformer.getTransformerName()).forEach(logger::info); + } + } + + public TransformEngine getTransformEngine() + { + return transformEngine; + } + + public ProbeTestTransform getProbeTestTransform() + { + return probeTestTransform; + } + + public StreamingResponseBody handleHttpRequest(HttpServletRequest request, MultipartFile sourceMultipartFile, + String sourceMimetype, String targetMimetype, Map requestParameters) + { + if (logger.isDebugEnabled()) + { + logger.debug("Processing request via HTTP endpoint. Params: sourceMimetype: '{}', targetMimetype: '{}', " + + "requestParameters: {}", sourceMimetype, targetMimetype, requestParameters); + } + probeTestTransform.incrementTransformerCount(); + + // Obtain the source + final String directUrl = requestParameters.getOrDefault(DIRECT_ACCESS_URL, ""); + InputStream inputStream = directUrl.isBlank() + ? FileManager.getMultipartFileInputStream(sourceMultipartFile) + : getDirectAccessUrlInputStream(directUrl); + long sourceSizeInBytes = -1L; // TODO pass in t-options or just ignore for http request as the repo will have checked. + Map transformOptions = getTransformOptions(requestParameters); + String transformName = getTransformerName(sourceSizeInBytes, sourceMimetype, targetMimetype, transformOptions); + CustomTransformer customTransformer = getCustomTransformer(transformName); + String sourceEncoding = transformOptions.get(SOURCE_ENCODING); + String targetEncoding = transformOptions.get(TARGET_ENCODING); // TODO not normally set + String reference = "e"+httpRequestCount.getAndIncrement(); + transformerDebug.pushTransform(reference, sourceMimetype, targetMimetype, sourceSizeInBytes, transformName); + transformerDebug.logOptions(reference, requestParameters); + + return os -> { + OutputStreamLengthRecorder outputStream = new OutputStreamLengthRecorder(os); + try + { + TransformManagerImpl transformManager = TransformManagerImpl.builder() + .withRequest(request) + .withSourceMimetype(sourceMimetype) + .withTargetMimetype(targetMimetype) + .withInputStream(inputStream) + .withOutputStream(outputStream) + .build(); + + customTransformer.transform(sourceMimetype, inputStream, + targetMimetype, outputStream, transformOptions, transformManager); + + transformManager.ifUsedCopyTargetFileToOutputStream(); + + LogEntry.setTargetSize(outputStream.getLength()); + long time = LogEntry.setStatusCodeAndMessage(OK.value(), "Success"); + + transformManager.deleteSourceFileIfExists(); + transformManager.deleteTargetFileIfExists(); + + probeTestTransform.recordTransformTime(time); + transformerDebug.popTransform(reference, time); + } + catch (Exception e) + { + transformerDebug.logFailure(reference, e.getMessage()); + throw new RuntimeException(e); + } + }; + } + + public ResponseEntity handleMessageRequest(TransformRequest request, Long timeout) + { + long start = System.currentTimeMillis(); + logger.trace("Received {}, timeout {} ms", request, timeout); + probeTestTransform.incrementTransformerCount(); + TransformReply reply = createBasicTransformReply(request); + + if (isTransformRequestValid(request, reply) == false) + { + return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); + } + + InputStream inputStream = getInputStream(request, reply); + if (inputStream == null) + { + return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); + } + + String targetMimetype = request.getTargetMediaType(); + String sourceMimetype = request.getSourceMediaType(); + File targetFile = createTargetFile(null, sourceMimetype, targetMimetype); + transformerDebug.pushTransform(request); + + try + { + OutputStreamLengthRecorder outputStream = + new OutputStreamLengthRecorder(new BufferedOutputStream(new FileOutputStream(targetFile))); + + long sourceSizeInBytes = request.getSourceSize(); + Map transformOptions = getTransformOptions(request.getTransformRequestOptions()); + String sourceEncoding = transformOptions.get(SOURCE_ENCODING); + String targetEncoding = transformOptions.get(TARGET_ENCODING); // TODO not normally set + transformerDebug.logOptions(request); + String transformName = getTransformerName(sourceSizeInBytes, sourceMimetype, targetMimetype, transformOptions); + CustomTransformer customTransformer = getCustomTransformer(transformName); + + TransformManagerImpl transformManager = TransformManagerImpl.builder() + .withSourceMimetype(sourceMimetype) + .withTargetMimetype(targetMimetype) + .withInputStream(inputStream) + .withOutputStream(outputStream) + .withTargetFile(targetFile) + .build(); + + customTransformer.transform(sourceMimetype, inputStream, + targetMimetype, outputStream, transformOptions, transformManager); + + transformManager.ifUsedCopyTargetFileToOutputStream(); + + reply.getInternalContext().setCurrentSourceSize(outputStream.getLength()); + + if (saveTargetFileInSharedFileStore(targetFile, reply) == false) + { + return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); + } + + transformManager.deleteSourceFileIfExists(); + transformManager.deleteTargetFileIfExists(); + + probeTestTransform.recordTransformTime(System.currentTimeMillis()-start); + transformerDebug.popTransform(reply); + + logger.trace("Sending successful {}, timeout {} ms", reply, timeout); + return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); + } + catch (TransformException e) + { + reply.setStatus(e.getStatusCode()); + reply.setErrorDetails(messageWithCause("Failed at processing transformation", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to perform transform (TransformException), sending " + reply, e); + return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); + } + catch (Exception e) + { + reply.setStatus(INTERNAL_SERVER_ERROR.value()); + reply.setErrorDetails(messageWithCause("Failed at processing transformation", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to perform transform (Exception), sending " + reply, e); + return new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus())); + } + } + private boolean isTransformRequestValid(TransformRequest request, TransformReply reply) + { + final Errors errors = validateTransformRequest(request); + validateInternalContext(request, errors); + reply.setInternalContext(request.getInternalContext()); + if (!errors.getAllErrors().isEmpty()) + { + reply.setStatus(BAD_REQUEST.value()); + reply.setErrorDetails(errors + .getAllErrors() + .stream() + .map(Object::toString) + .collect(joining(", "))); + + transformerDebug.logFailure(reply); + logger.trace("Invalid request, sending {}", reply); + return false; + } + return true; + } + + private TransformReply createBasicTransformReply(TransformRequest request) + { + TransformReply reply = new TransformReply(); + reply.setRequestId(request.getRequestId()); + reply.setSourceReference(request.getSourceReference()); + reply.setSchema(request.getSchema()); + reply.setClientData(request.getClientData()); + reply.setInternalContext(request.getInternalContext()); + return reply; + } + + private Errors validateTransformRequest(final TransformRequest transformRequest) + { + DirectFieldBindingResult errors = new DirectFieldBindingResult(transformRequest, "request"); + transformRequestValidator.validate(transformRequest, errors); + return errors; + } + + private void validateInternalContext(TransformRequest request, Errors errors) + { + String errorMessage = InternalContext.checkForBasicErrors(request.getInternalContext(), "T-Request"); + if (errorMessage != null) + { + errors.rejectValue("internalContext", null, errorMessage); + } + initialiseContext(request); + } + + private void initialiseContext(TransformRequest request) + { + // If needed, initialise the context enough to allow logging to take place without NPE checks + request.setInternalContext(InternalContext.initialise(request.getInternalContext())); + } + + private Map getTransformOptions(Map requestParameters) + { + Map transformOptions = new HashMap<>(requestParameters); + transformOptions.keySet().removeAll(NON_TRANSFORM_OPTION_REQUEST_PARAMETERS); + transformOptions.values().removeIf(v -> v.isEmpty()); + return transformOptions; + } + + private InputStream getSharedFileStoreInputStream(String sourceReference) + { + ResponseEntity responseEntity = alfrescoSharedFileStoreClient.retrieveFile(sourceReference); + final Resource body = responseEntity.getBody(); + if (body == null) + { + String message = "Source file with reference: " + sourceReference + " is null or empty."; + logger.warn(message); + throw new TransformException(BAD_REQUEST.value(), message); + } + + try + { + return body.getInputStream(); + } + catch (IOException e) + { + String message = "Shared File Store reference is invalid."; + logger.warn(message); + throw new TransformException(BAD_REQUEST.value(), message, e); + } + } + + private InputStream getInputStream(TransformRequest request, TransformReply reply) + { + final String directUrl = request.getTransformRequestOptions().getOrDefault(DIRECT_ACCESS_URL, ""); + InputStream inputStream = null; + try + { + inputStream = directUrl.isBlank() + ? getSharedFileStoreInputStream(request.getSourceReference()) + : getDirectAccessUrlInputStream(directUrl); + } + catch (TransformException e) + { + reply.setStatus(e.getStatusCode()); + reply.setErrorDetails(messageWithCause("Failed at reading the source file", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to load source file (TransformException), sending " + reply); + } + catch (HttpClientErrorException e) + { + reply.setStatus(e.getStatusCode().value()); + reply.setErrorDetails(messageWithCause("Failed at reading the source file", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to load source file (HttpClientErrorException), sending " + reply, e); + } + return inputStream; + } + + private boolean saveTargetFileInSharedFileStore(File targetFile, TransformReply reply) + { + FileRefResponse targetRef; + try + { + targetRef = alfrescoSharedFileStoreClient.saveFile(targetFile); + } + catch (TransformException e) + { + reply.setStatus(e.getStatusCode()); + reply.setErrorDetails(messageWithCause("Failed at writing the transformed file", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to save target file (TransformException), sending " + reply, e); + return false; + } + catch (HttpClientErrorException e) + { + reply.setStatus(e.getStatusCode().value()); + reply.setErrorDetails(messageWithCause("Failed at writing the transformed file. ", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to save target file (HttpClientErrorException), sending " + reply, e); + return false; + } + catch (Exception e) + { + reply.setStatus(INTERNAL_SERVER_ERROR.value()); + reply.setErrorDetails(messageWithCause("Failed at writing the transformed file. ", e)); + + transformerDebug.logFailure(reply); + logger.trace("Failed to save target file (Exception), sending " + reply, e); + return false; + } + + try + { + deleteFile(targetFile); + } + catch (Exception e) + { + logger.error("Failed to delete local temp target file. Error will be ignored ", e); + } + + reply.setTargetReference(targetRef.getEntry().getFileRef()); + reply.setStatus(CREATED.value()); + + return true; + } + + /** + * Loads the file with the specified sourceReference from Alfresco Shared File Store + * + * @param sourceReference reference to the file in Alfresco Shared File Store + * @param sourceExtension default extension if the file in Alfresco Shared File Store has none + * @return the file containing the source content for the transformation + */ + private File loadSourceFile(final String sourceReference, final String sourceExtension) + { + ResponseEntity responseEntity = alfrescoSharedFileStoreClient.retrieveFile(sourceReference); + + HttpHeaders headers = responseEntity.getHeaders(); + String filename = getFilenameFromContentDisposition(headers); + + String extension = getFilenameExtension(filename) != null ? getFilenameExtension(filename) : sourceExtension; + MediaType contentType = headers.getContentType(); + long size = headers.getContentLength(); + + final Resource body = responseEntity.getBody(); + if (body == null) + { + String message = "Source file with reference: " + sourceReference + " is null or empty. " + + "Transformation will fail and stop now as there is no content to be transformed."; + logger.warn(message); + throw new TransformException(BAD_REQUEST.value(), message); + } + final File file = createTempFile("source_", "." + extension); + + logger.debug("Read source content {} length={} contentType={}", + sourceReference, size, contentType); + + save(body, file); + LogEntry.setSource(filename, size); + return file; + } + + private static String messageWithCause(final String prefix, Throwable e) + { + final StringBuilder sb = new StringBuilder(); + sb.append(prefix).append(" - ") + .append(e.getClass().getSimpleName()).append(": ") + .append(e.getMessage()); + + while (e.getCause() != null) + { + e = e.getCause(); + sb.append(", cause ") + .append(e.getClass().getSimpleName()).append(": ") + .append(e.getMessage()); + } + + return sb.toString(); + } + + private String getTransformerName(long sourceSizeInBytes, final String sourceMimetype, + final String targetMimetype, final Map transformOptions) + { + // The transformOptions always contains sourceEncoding when sent to a T-Engine, even though it should not be + // used to select a transformer. Similar to source and target mimetypes and extensions, but these are not + // passed in transformOptions. + String sourceEncoding = transformOptions.remove(SOURCE_ENCODING); + try + { + final String transformerName = transformRegistry.findTransformerName(sourceMimetype, + sourceSizeInBytes, targetMimetype, transformOptions, null); + if (transformerName == null) + { + throw new TransformException(BAD_REQUEST.value(), "No transforms were able to handle the request"); + } + return transformerName; + } + finally + { + if (sourceEncoding != null) + { + transformOptions.put(SOURCE_ENCODING, sourceEncoding); + } + } + } + + private CustomTransformer getCustomTransformer(String transformName) + { + CustomTransformer customTransformer = customTransformersByName.get(transformName); + if (customTransformer == null) + { + throw new TransformException(BAD_REQUEST.value(), "Custom Transformer "+customTransformer+" not found"); + } + return customTransformer; + } +} diff --git a/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java b/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java index b122d692..59c2ac83 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java @@ -32,11 +32,9 @@ import org.alfresco.transform.base.clients.AlfrescoSharedFileStoreClient; import org.alfresco.transform.common.TransformerDebug; import org.alfresco.transform.messages.TransformRequestValidator; import org.alfresco.transform.registry.TransformServiceRegistry; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -79,8 +77,6 @@ public class WebApplicationConfig implements WebMvcConfigurer return new TransformRequestValidator(); } - @Autowired Environment env; - @Bean public TransformServiceRegistry transformRegistry() { diff --git a/engines/base/src/main/java/org/alfresco/transform/base/fs/FileManager.java b/engines/base/src/main/java/org/alfresco/transform/base/fs/FileManager.java index 213a292d..eb8bdf85 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/fs/FileManager.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/fs/FileManager.java @@ -26,35 +26,28 @@ */ package org.alfresco.transform.base.fs; +import org.alfresco.transform.base.logging.LogEntry; +import org.alfresco.transform.common.ExtensionService; +import org.alfresco.transform.common.TransformException; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.file.Files; +import java.util.Arrays; + import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.alfresco.transform.common.ExtensionService.getExtensionForMimetype; import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.INSUFFICIENT_STORAGE; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; -import static org.springframework.util.StringUtils.getFilename; -import static org.springframework.util.StringUtils.getFilenameExtension; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.util.Arrays; - -import javax.servlet.http.HttpServletRequest; - -import org.alfresco.transform.common.ExtensionService; -import org.alfresco.transform.common.TransformException; -import org.alfresco.transform.base.logging.LogEntry; -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.web.multipart.MultipartFile; -import org.springframework.web.util.UriUtils; public class FileManager { @@ -101,13 +94,6 @@ public class FileManager } } - public static File buildFile(String filename) - { - filename = checkFilename(false, filename); - LogEntry.setTarget(filename); - return TempFileProvider.createTempFile("target_", "_" + filename); - } - public static void deleteFile(final File file) throws Exception { if (!file.delete()) @@ -116,32 +102,6 @@ public class FileManager } } - private static String checkFilename(boolean source, String filename) - { - filename = getFilename(filename); - if (filename == null || filename.isEmpty()) - { - String sourceOrTarget = source ? "source" : "target"; - int statusCode = source ? BAD_REQUEST.value() : INTERNAL_SERVER_ERROR.value(); - 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(), REPLACE_EXISTING); - } - catch (IOException e) - { - throw new TransformException(INSUFFICIENT_STORAGE.value(), - "Failed to store the source file", e); - } - } - public static void save(Resource body, File file) { try @@ -155,28 +115,6 @@ public class FileManager } } - private static Resource load(File file) - { - try - { - Resource resource = new UrlResource(file.toURI()); - if (resource.exists() || resource.isReadable()) - { - return resource; - } - else - { - throw new TransformException(INTERNAL_SERVER_ERROR.value(), - "Could not read the target file: " + file.getPath()); - } - } - catch (MalformedURLException e) - { - throw new TransformException(INTERNAL_SERVER_ERROR.value(), - "The target filename was malformed: " + file.getPath(), e); - } - } - public static String getFilenameFromContentDisposition(HttpHeaders headers) { String filename = ""; @@ -193,24 +131,6 @@ public class FileManager return filename; } - public static String createTargetFileName(final String fileName, String sourceMimetype, String targetMimetype) - { - String targetExtension = ExtensionService.getExtensionForTargetMimetype(targetMimetype, sourceMimetype); - final String sourceFilename = getFilename(fileName); - - if (sourceFilename == null || sourceFilename.isEmpty()) - { - return null; - } - - final String ext = getFilenameExtension(sourceFilename); - if (ext == null || ext.isEmpty()) - { - return sourceFilename + '.' + targetExtension; - } - return sourceFilename.substring(0, sourceFilename.length() - ext.length() - 1) + '.' + targetExtension; - } - public static InputStream getMultipartFileInputStream(MultipartFile sourceMultipartFile) { InputStream inputStream; @@ -267,15 +187,6 @@ public class FileManager } } - public static ResponseEntity createAttachment(String targetFilename, File - targetFile) - { - Resource targetResource = load(targetFile); - targetFilename = UriUtils.encodePath(getFilename(targetFilename), "UTF-8"); - return ResponseEntity.ok().header(CONTENT_DISPOSITION, - "attachment; filename*= UTF-8''" + targetFilename).body(targetResource); - } - /** * TempFileProvider - Duplicated and adapted from alfresco-core. */ diff --git a/engines/base/src/test/java/org/alfresco/transform/base/AbstractTransformControllerTest.java b/engines/base/src/test/java/org/alfresco/transform/base/AbstractTransformControllerTest.java index 0dee3aca..f1b280ab 100644 --- a/engines/base/src/test/java/org/alfresco/transform/base/AbstractTransformControllerTest.java +++ b/engines/base/src/test/java/org/alfresco/transform/base/AbstractTransformControllerTest.java @@ -80,7 +80,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.core.io.ClassPathResource; diff --git a/engines/base/src/test/java/org/alfresco/transform/base/QueueTransformServiceTest.java b/engines/base/src/test/java/org/alfresco/transform/base/QueueTransformServiceTest.java index 3e28ec9c..ae60a0d9 100644 --- a/engines/base/src/test/java/org/alfresco/transform/base/QueueTransformServiceTest.java +++ b/engines/base/src/test/java/org/alfresco/transform/base/QueueTransformServiceTest.java @@ -27,23 +27,10 @@ package org.alfresco.transform.base; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.CREATED; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; - -import javax.jms.Destination; -import javax.jms.JMSException; -import javax.jms.Message; - -import org.alfresco.transform.client.model.TransformReply; -import org.alfresco.transform.client.model.TransformRequest; import org.alfresco.transform.base.messaging.TransformMessageConverter; import org.alfresco.transform.base.messaging.TransformReplySender; +import org.alfresco.transform.client.model.TransformReply; +import org.alfresco.transform.client.model.TransformRequest; import org.apache.activemq.command.ActiveMQObjectMessage; import org.apache.activemq.command.ActiveMQQueue; import org.junit.jupiter.api.BeforeEach; @@ -55,10 +42,23 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.jms.support.converter.MessageConversionException; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; + public class QueueTransformServiceTest { @Mock - private TransformController transformController; + private TransformHandler transformHandler; @Mock private TransformMessageConverter transformMessageConverter; @Mock @@ -78,7 +78,7 @@ public class QueueTransformServiceTest { queueTransformService.receive(null); - verifyNoMoreInteractions(transformController); + verifyNoMoreInteractions(transformHandler); verifyNoMoreInteractions(transformMessageConverter); verifyNoMoreInteractions(transformReplySender); } @@ -88,7 +88,7 @@ public class QueueTransformServiceTest { queueTransformService.receive(new ActiveMQObjectMessage()); - verifyNoMoreInteractions(transformController); + verifyNoMoreInteractions(transformHandler); verifyNoMoreInteractions(transformMessageConverter); verifyNoMoreInteractions(transformReplySender); } @@ -116,7 +116,7 @@ public class QueueTransformServiceTest verify(transformMessageConverter).fromMessage(msg); verify(transformReplySender).send(destination, reply, msg.getCorrelationId()); - verifyNoMoreInteractions(transformController); + verifyNoMoreInteractions(transformHandler); } @Test @@ -143,7 +143,7 @@ public class QueueTransformServiceTest verify(transformMessageConverter).fromMessage(msg); verify(transformReplySender).send(destination, reply, msg.getCorrelationId()); - verifyNoMoreInteractions(transformController); + verifyNoMoreInteractions(transformHandler); } @Test @@ -170,7 +170,7 @@ public class QueueTransformServiceTest verify(transformMessageConverter).fromMessage(msg); verify(transformReplySender).send(destination, reply, msg.getCorrelationId()); - verifyNoMoreInteractions(transformController); + verifyNoMoreInteractions(transformHandler); } @Test @@ -188,12 +188,12 @@ public class QueueTransformServiceTest doReturn(request).when(transformMessageConverter).fromMessage(msg); doReturn(new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()))) - .when(transformController).transform(request, null); + .when(transformHandler).handleMessageRequest(request, null); queueTransformService.receive(msg); verify(transformMessageConverter).fromMessage(msg); - verify(transformController).transform(request, null); + verify(transformHandler).handleMessageRequest(request, null); verify(transformReplySender).send(destination, reply); } @@ -206,7 +206,7 @@ public class QueueTransformServiceTest queueTransformService.receive(msg); - verifyNoMoreInteractions(transformController); + verifyNoMoreInteractions(transformHandler); verifyNoMoreInteractions(transformMessageConverter); verifyNoMoreInteractions(transformReplySender); } @@ -229,12 +229,12 @@ public class QueueTransformServiceTest doReturn(request).when(transformMessageConverter).fromMessage(msg); doReturn(new ResponseEntity<>(reply, HttpStatus.valueOf(reply.getStatus()))) - .when(transformController).transform(request, null); + .when(transformHandler).handleMessageRequest(request, null); queueTransformService.receive(msg); verify(transformMessageConverter).fromMessage(msg); - verify(transformController).transform(request, null); + verify(transformHandler).handleMessageRequest(request, null); verify(transformReplySender).send(destination, reply); } } diff --git a/engines/misc/src/main/java/org/alfresco/transform/misc/MiscTransformEngine.java b/engines/misc/src/main/java/org/alfresco/transform/misc/MiscTransformEngine.java index 4de32439..c53f02a7 100644 --- a/engines/misc/src/main/java/org/alfresco/transform/misc/MiscTransformEngine.java +++ b/engines/misc/src/main/java/org/alfresco/transform/misc/MiscTransformEngine.java @@ -34,13 +34,10 @@ import org.alfresco.transform.config.TransformConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; import static org.alfresco.transform.base.logging.StandardMessages.COMMUNITY_LICENCE; import static org.alfresco.transform.common.Mimetype.MIMETYPE_HTML; -import static org.alfresco.transform.common.Mimetype.MIMETYPE_PDF; import static org.alfresco.transform.common.Mimetype.MIMETYPE_TEXT_PLAIN; import static org.alfresco.transform.common.RequestParamMap.SOURCE_ENCODING; diff --git a/engines/pdfrenderer/src/main/java/org/alfresco/transform/pdfrenderer/PdfRendererTransformEngine.java b/engines/pdfrenderer/src/main/java/org/alfresco/transform/pdfrenderer/PdfRendererTransformEngine.java index 27d070af..e9dfa85f 100644 --- a/engines/pdfrenderer/src/main/java/org/alfresco/transform/pdfrenderer/PdfRendererTransformEngine.java +++ b/engines/pdfrenderer/src/main/java/org/alfresco/transform/pdfrenderer/PdfRendererTransformEngine.java @@ -38,7 +38,6 @@ import java.util.Collections; import static org.alfresco.transform.base.logging.StandardMessages.COMMUNITY_LICENCE; import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_PNG; import static org.alfresco.transform.common.Mimetype.MIMETYPE_PDF; -import static org.alfresco.transform.common.Mimetype.MIMETYPE_TEXT_PLAIN; @Component public class PdfRendererTransformEngine implements TransformEngine