commit 42e7e192b5b8d963e231b7742d032e99e1b40bbd Author: Alan Davis Date: Wed Mar 7 14:39:07 2018 +0000 Initial commit diff --git a/.gitbugtraq b/.gitbugtraq new file mode 100644 index 00000000..bacffb70 --- /dev/null +++ b/.gitbugtraq @@ -0,0 +1,4 @@ +# For SmartGit +[bugtraq "jira"] + url = https://issues.alfresco.com/jira/browse/%BUGID% + logRegex = ([A-Z]+-\\d+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ff04b28c --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +*.class + +# Eclipse +.classpath +.settings +.project + +# Intellij +.idea/ +*.iml +*.iws + +# Mac +.DS_Store + +# Maven +target +*.log +*.log.* + +# Mobile Tools for Java (J2ME) + +.mtj +.tmp/ + +# Package Files # + +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml + +hs_err_pid* +alf_data +/src/main/resources/alfresco-global.properties +/src/main/resources/alfresco/extension/custom-log4j.properties diff --git a/README.md b/README.md new file mode 100644 index 00000000..9fc94e42 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Project that generates Docker images to perform ACS transformations + +* alfresco-transformer-base - contains code that is common to all the transformers. This includes + the streaming of content to and from the docker images. +* alfresco-docker- - contains code to generate an image for a transformer called . + +## Building and testing + +The project can be built by running the Maven command: + +~~~ +mvn clean install +~~~ + +The build plan in Bamboo is PLAT-TRANS + +## Contributing guide + +Please use [this guide](https://github.com/Alfresco/alfresco-jodconverter/blob/master/CONTRIBUTING.md) to make a +contribution to the project. diff --git a/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/pom.xml b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/pom.xml new file mode 100644 index 00000000..23b84b34 --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/pom.xml @@ -0,0 +1,66 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformer-alfresco-pdf-renderer + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-transformer-alfresco-pdf-renderer + 0.1-SNAPSHOT + jar + + + + + org.springframework.boot + spring-boot-starter-parent + ${dependency.spring-boot.version} + pom + import + + + + + + + org.alfresco + alfresco-transformer-base + ${dependency.alfresco-transformer-base.version} + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + org.alfresco + alfresco-core + ${dependency.alfresco-core.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.0.0.RELEASE + + + + repackage + + + + + + + + diff --git a/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java new file mode 100644 index 00000000..d9fe8948 --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/AlfrescoPdfRendererController.java @@ -0,0 +1,155 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.transformer; + +import org.alfresco.transformer.base.AbstractTransformerController; +import org.alfresco.transformer.base.LogEntry; +import org.alfresco.util.exec.RuntimeExec; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; + +/** + * Controller for the Docker based alfresco-pdf-renderer transformer. + * + * Status Codes: + * + * 200 Success + * 400 Bad Request: Request parameter is missing (missing mandatory parameter) + * 400 Bad Request: Request parameter is of the wrong type + * 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file) + * 400 Bad Request: The source filename was not supplied + * 500 Internal Server Error: (no message with low level IO problems) + * 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked) + * 500 Internal Server Error: Transformer version check exit code was not 0 + * 500 Internal Server Error: Transformer version check failed to create any output + * 500 Internal Server Error: Could not read the target file + * 500 Internal Server Error: The target filename was malformed (should not happen because of other checks) + * 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content) + * 500 Internal Server Error: Filename encoding error + * 507 Insufficient Storage: Failed to store the source file + */ +@Controller +public class AlfrescoPdfRendererController extends AbstractTransformerController +{ + private static final String EXE = "/usr/bin/alfresco-pdf-renderer"; + + @Autowired + public AlfrescoPdfRendererController() + { + logger = LogFactory.getLog(AlfrescoPdfRendererController.class); + setTransformCommand(createTransformCommand()); + setCheckCommand(createCheckCommand()); + } + + private static RuntimeExec createTransformCommand() + { + RuntimeExec runtimeExec = new RuntimeExec(); + Map commandsAndArguments = new HashMap<>(); + commandsAndArguments.put(".*", new String[]{EXE, "SPLIT:${options}", "${source}", "${target}"}); + runtimeExec.setCommandsAndArguments(commandsAndArguments); + + Map defaultProperties = new HashMap<>(); + defaultProperties.put("key", null); + runtimeExec.setDefaultProperties(defaultProperties); + + runtimeExec.setErrorCodes("1"); + + return runtimeExec; + } + + private static RuntimeExec createCheckCommand() + { + RuntimeExec runtimeExec = new RuntimeExec(); + Map commandsAndArguments = new HashMap<>(); + commandsAndArguments.put(".*", new String[]{EXE, "--version"}); + runtimeExec.setCommandsAndArguments(commandsAndArguments); + + return runtimeExec; + } + + @PostMapping("/transform") + public ResponseEntity transform(HttpServletRequest request, + @RequestParam("file") MultipartFile sourceMultipartFile, + @RequestParam("targetExtension") String targetExtension, + @RequestParam(value = "timeout", required = false) Long timeout, + + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "width", required = false) Integer width, + @RequestParam(value = "height", required = false) Integer height, + @RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement, + @RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio) + { + String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); + File sourceFile = createSourceFile(request, sourceMultipartFile); + File targetFile = createTargetFile(request, targetFilename); + // Both files are deleted by TransformInterceptor.afterCompletion + + StringJoiner args = new StringJoiner(" "); + if (width != null && width >= 0) + { + args.add("--width=" + width); + } + if (height != null && height >= 0) + { + args.add("--height=" + height); + } + if (allowEnlargement != null && allowEnlargement) + { + args.add("--allow-enlargement"); + } + if (maintainAspectRatio != null && maintainAspectRatio) + { + args.add("--maintain-aspect-ratio"); + } + if (page != null && page >= 0) + { + args.add("--page=" + page); + } + String options = args.toString(); + LogEntry.setOptions(options); + + Map properties = new HashMap(5); + properties.put("options", options); + properties.put("source", sourceFile.getAbsolutePath()); + properties.put("target", targetFile.getAbsolutePath()); + + executeTransformCommand(properties, targetFile, timeout); + + return createAttachment(targetFilename, targetFile); + } +} diff --git a/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/Application.java b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/Application.java new file mode 100644 index 00000000..2d2ad7f8 --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/java/org/alfresco/transformer/Application.java @@ -0,0 +1,38 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #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); + } +} diff --git a/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/resources/templates/transformForm.html b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/resources/templates/transformForm.html new file mode 100644 index 00000000..5f6b20ea --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/alfresco-transformer-alfresco-pdf-renderer/src/main/resources/templates/transformForm.html @@ -0,0 +1,33 @@ + + + +
+

+

+ +
+

Alfresco PDF Renderer Test Transformation

+
+ + + + + + + + + + + + + +
file *
targetExtension *
timeout
page
width
height
allowEnlargement
maintainAspectRatio
+
+
+ + + + + diff --git a/alfresco-docker-alfresco-pdf-renderer/docker/Dockerfile b/alfresco-docker-alfresco-pdf-renderer/docker/Dockerfile new file mode 100644 index 00000000..a971f750 --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/docker/Dockerfile @@ -0,0 +1,20 @@ +# Image provides a container in which to run alfresco-pdf-renderer transformations for Alfresco Content Services. + +FROM quay.io/alfresco/alfresco-base-java:9 + +ENV ALFRESCO_PDF_RENDERER_LIB_RPM_URL=https://nexus.alfresco.com/nexus/service/local/repositories/releases/content/org/alfresco/alfresco-pdf-renderer/1.1/alfresco-pdf-renderer-1.1-linux.tgz + +COPY target/alfresco-transformer-alfresco-pdf-renderer*.jar /usr/bin + +RUN ln /usr/bin/alfresco-transformer-alfresco-pdf-renderer*.jar /usr/bin/alfresco-transformer-alfresco-pdf-renderer.jar && \ + yum install -y wget && \ + wget $ALFRESCO_PDF_RENDERER_LIB_RPM_URL && \ + tar xf alfresco-pdf-renderer-*-linux.tgz -C /usr/bin && \ + rm -f alfresco-pdf-renderer-*-linux.tgz && \ + yum remove -y wget && \ + yum clean all + +EXPOSE 8090 + +ENTRYPOINT java -jar /usr/bin/alfresco-transformer-alfresco-pdf-renderer.jar + diff --git a/alfresco-docker-alfresco-pdf-renderer/docker/LICENSE b/alfresco-docker-alfresco-pdf-renderer/docker/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/docker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/alfresco-docker-alfresco-pdf-renderer/docker/README.md b/alfresco-docker-alfresco-pdf-renderer/docker/README.md new file mode 100644 index 00000000..01d4bd0a --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/docker/README.md @@ -0,0 +1,3 @@ +# Welcome + +This repository contains the Dockerfile that performs afresc-pdf-renderer transformations for the the ACS Repository. diff --git a/alfresco-docker-alfresco-pdf-renderer/docker/pom.xml b/alfresco-docker-alfresco-pdf-renderer/docker/pom.xml new file mode 100644 index 00000000..be130103 --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/docker/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformer-alfresco-pdf-renderer + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-docker-builder-alfresco-pdf-renderer + Alfresco Content Services alfresco-pdf-renderer Docker image builder + pom + + + ${project.version} + + + + + org.alfresco + alfresco-transformer-alfresco-pdf-renderer + ${dependency.alfresco-transformer-alfresco-pdf-renderer.version} + jar + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + copy-resources + process-resources + + copy + + + + + org.alfresco + alfresco-transformer-alfresco-pdf-renderer + ${dependency.alfresco-transformer-alfresco-pdf-renderer.version} + jar + false + ${project.build.directory} + + + + + + + + + + diff --git a/alfresco-docker-alfresco-pdf-renderer/pom.xml b/alfresco-docker-alfresco-pdf-renderer/pom.xml new file mode 100644 index 00000000..eeaf2511 --- /dev/null +++ b/alfresco-docker-alfresco-pdf-renderer/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformers + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-docker-transformer-alfresco-pdf-renderer + Alfresco Docker Transformer Alfresco PDF Renderer + 0.1-SNAPSHOT + pom + + + alfresco-transformer-alfresco-pdf-renderer + docker + + + diff --git a/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/pom.xml b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/pom.xml new file mode 100644 index 00000000..764e6f0d --- /dev/null +++ b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/pom.xml @@ -0,0 +1,66 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformer-imagemagick + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-transformer-imagemagick + 0.1-SNAPSHOT + jar + + + + + org.springframework.boot + spring-boot-starter-parent + ${dependency.spring-boot.version} + pom + import + + + + + + + org.alfresco + alfresco-transformer-base + ${dependency.alfresco-transformer-base.version} + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + org.alfresco + alfresco-core + ${dependency.alfresco-core.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.0.0.RELEASE + + + + repackage + + + + + + + + diff --git a/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/java/org/alfresco/transformer/Application.java b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/java/org/alfresco/transformer/Application.java new file mode 100644 index 00000000..2d2ad7f8 --- /dev/null +++ b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/java/org/alfresco/transformer/Application.java @@ -0,0 +1,38 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #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); + } +} diff --git a/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java new file mode 100644 index 00000000..c6eb5a3c --- /dev/null +++ b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/java/org/alfresco/transformer/ImageMagickController.java @@ -0,0 +1,270 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.transformer; + +import org.alfresco.transformer.base.AbstractTransformerController; +import org.alfresco.transformer.base.LogEntry; +import org.alfresco.transformer.base.TransformException; +import org.alfresco.util.exec.RuntimeExec; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +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.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.util.*; + +/** + * Controller for the Docker based ImageMagick transformer. + * + * + * Status Codes: + * + * 200 Success + * 400 Bad Request: Invalid cropGravity value (North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest, Center) + * 400 Bad Request: Request parameter is missing (missing mandatory parameter) + * 400 Bad Request: Request parameter is of the wrong type + * 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file) + * 400 Bad Request: The source filename was not supplied + * 500 Internal Server Error: (no message with low level IO problems) + * 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked) + * 500 Internal Server Error: Transformer version check exit code was not 0 + * 500 Internal Server Error: Transformer version check failed to create any output + * 500 Internal Server Error: Could not read the target file + * 500 Internal Server Error: The target filename was malformed (should not happen because of other checks) + * 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content) + * 500 Internal Server Error: Filename encoding error + * 507 Insufficient Storage: Failed to store the source file + */ +@Controller +public class ImageMagickController extends AbstractTransformerController +{ + private static final String ROOT = "/usr/lib64/ImageMagick-7.0.7"; + private static final String DYN = ROOT+"/lib"; + private static final String EXE = "/usr/bin/convert"; + private static final List GRAVITY_VALUES = Arrays.asList( + "North", "NorthEast", "East", "SouthEast", "South", "SouthWest", "West", "NorthWest", "Center"); + + @Autowired + public ImageMagickController() + { + logger = LogFactory.getLog(ImageMagickController.class); + setTransformCommand(createTransformCommand()); + setCheckCommand(createCheckCommand()); + } + + private static RuntimeExec createTransformCommand() + { + RuntimeExec runtimeExec = new RuntimeExec(); + Map commandsAndArguments = new HashMap<>(); + commandsAndArguments.put(".*", new String[]{EXE, "${source}", "SPLIT:${options}", "-strip", "-quiet", "${target}"}); + runtimeExec.setCommandsAndArguments(commandsAndArguments); + + Map processProperties = new HashMap<>(); + processProperties.put("MAGICK_HOME", ROOT); + processProperties.put("DYLD_FALLBACK_LIBRARY_PATH", DYN); + processProperties.put("LD_LIBRARY_PATH", DYN); + runtimeExec.setProcessProperties(processProperties); + + Map defaultProperties = new HashMap<>(); + defaultProperties.put("options", null); + runtimeExec.setDefaultProperties(defaultProperties); + + runtimeExec.setErrorCodes("1,2,255,400,405,410,415,420,425,430,435,440,450,455,460,465,470,475,480,485,490,495,499,700,705,710,715,720,725,730,735,740,750,755,760,765,770,775,780,785,790,795,799"); + + return runtimeExec; + } + + private static RuntimeExec createCheckCommand() + { + RuntimeExec runtimeExec = new RuntimeExec(); + Map commandsAndArguments = new HashMap<>(); + commandsAndArguments.put(".*", new String[]{EXE, "-version"}); + runtimeExec.setCommandsAndArguments(commandsAndArguments); + + return runtimeExec; + } + + @PostMapping("/transform") + public ResponseEntity transform(HttpServletRequest request, + @RequestParam("file") MultipartFile sourceMultipartFile, + @RequestParam("targetExtension") String targetExtension, + @RequestParam(value = "timeout", required = false) Long timeout, + + @RequestParam(value = "startPage", required = false) Integer startPage, + @RequestParam(value = "endPage", required = false) Integer endPage, + + @RequestParam(value = "alphaRemove", required = false) Boolean alphaRemove, + @RequestParam(value = "autoOrient", required = false) Boolean autoOrient, + + @RequestParam(value = "cropGravity", required = false) String cropGravity, + @RequestParam(value = "cropWidth", required = false) Integer cropWidth, + @RequestParam(value = "cropHeight", required = false) Integer cropHeight, + @RequestParam(value = "cropPercentage", required = false) Boolean cropPercentage, + @RequestParam(value = "cropXOffset", required = false) Integer cropXOffset, + @RequestParam(value = "cropYOffset", required = false) Integer cropYOffset, + + @RequestParam(value = "thumbnail", required = false) Boolean thumbnail, + @RequestParam(value = "resizeWidth", required = false) Integer resizeWidth, + @RequestParam(value = "resizeHeight", required = false) Integer resizeHeight, + @RequestParam(value = "resizePercentage", required = false) Boolean resizePercentage, + @RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement, + @RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio) + { + if (cropGravity != null) + { + cropGravity = cropGravity.trim(); + if (!cropGravity.isEmpty() && !GRAVITY_VALUES.contains(cropGravity)) + { + throw new TransformException(400, "Invalid cropGravity value"); + } + } + + String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); + File sourceFile = createSourceFile(request, sourceMultipartFile); + File targetFile = createTargetFile(request, targetFilename); + // Both files are deleted by TransformInterceptor.afterCompletion + + StringJoiner args = new StringJoiner(" "); + if (alphaRemove != null && alphaRemove) + { + args.add("-alpha"); + args.add(("remove")); + } + if (autoOrient != null && autoOrient) + { + args.add("-auto-orient"); + } + + if (cropGravity != null || cropWidth != null || cropHeight != null || cropPercentage != null || + cropXOffset != null || cropYOffset != null) + { + if (cropGravity != null) + { + args.add("-gravity"); + args.add(cropGravity); + } + + StringBuilder crop = new StringBuilder(""); + if (cropWidth != null && cropWidth >= 0) + { + crop.append(cropWidth); + } + if (cropHeight != null && cropHeight >= 0) + { + crop.append('x'); + crop.append(cropHeight); + } + if (cropPercentage != null && cropPercentage) + { + crop.append('%'); + } + if (cropXOffset != null) + { + if (cropXOffset >= 0) + { + crop.append('+'); + } + crop.append(cropXOffset); + } + if (cropYOffset != null) + { + if (cropYOffset >= 0) + { + crop.append('+'); + } + crop.append(cropYOffset); + } + if (crop.length() > 1) + { + args.add("-crop"); + args.add(crop); + } + + args.add("+repage"); + } + + if (resizeHeight != null || resizeWidth != null || resizePercentage !=null || maintainAspectRatio != null) + { + args.add(thumbnail != null && thumbnail ? "-thumbnail" : "-resize"); + StringBuilder resize = new StringBuilder(""); + if (resizeWidth != null && resizeWidth >= 0) + { + resize.append(resizeWidth); + } + if (resizeHeight != null && resizeHeight >= 0) + { + resize.append('x'); + resize.append(resizeHeight); + } + if (resizePercentage != null && resizePercentage) + { + resize.append('%'); + } + if (allowEnlargement == null || !allowEnlargement) + { + resize.append('>'); + } + if (maintainAspectRatio != null && maintainAspectRatio) + { + resize.append('!'); + } + if (resize.length() > 1) + { + args.add(resize); + } + } + + String pageRange = + startPage == null + ? endPage == null + ? "" + : "["+endPage+']' + : endPage == null || startPage.equals(endPage) + ? "["+startPage+']' + : "["+startPage+'-'+endPage+']'; + + String options = args.toString(); + LogEntry.setOptions(pageRange+(pageRange.isEmpty() ? "" : " ")+options); + + Map properties = new HashMap(5); + properties.put("options", options); + properties.put("source", sourceFile.getAbsolutePath()+pageRange); + properties.put("target", targetFile.getAbsolutePath()); + + executeTransformCommand(properties, targetFile, timeout); + + return createAttachment(targetFilename, targetFile); + } +} diff --git a/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/resources/templates/transformForm.html b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/resources/templates/transformForm.html new file mode 100644 index 00000000..b849eee4 --- /dev/null +++ b/alfresco-docker-imagemagick/alfresco-transformer-imagemagick/src/main/resources/templates/transformForm.html @@ -0,0 +1,46 @@ + + + +
+

+

+ +
+

ImageMagick Test Transformation

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
file *
targetExtension *
timeout
startPage
endPage
alphaRemove
autoOrient
cropGravity
North, NorthEast...Center
cropWidth
cropHeight
cropPercentage
cropXOffset
cropYOffset
thumbnail
resizeWidth
resizeHeight
resizePercentage
allowEnlargement
maintainAspectRatio
+
+
+ + + + + diff --git a/alfresco-docker-imagemagick/docker/Dockerfile b/alfresco-docker-imagemagick/docker/Dockerfile new file mode 100644 index 00000000..b9b9ea6f --- /dev/null +++ b/alfresco-docker-imagemagick/docker/Dockerfile @@ -0,0 +1,21 @@ +# Image provides a container in which to run ImageMagick transformations for Alfresco Content Services. + +FROM quay.io/alfresco/alfresco-base-java:9 + +ENV IMAGEMAGICK_RPM_URL=https://www.imagemagick.org/download/linux/CentOS/x86_64/ImageMagick-7.0.7-25.x86_64.rpm +ENV IMAGEMAGICK_LIB_RPM_URL=https://www.imagemagick.org/download/linux/CentOS/x86_64/ImageMagick-libs-7.0.7-25.x86_64.rpm + +COPY target/alfresco-transformer-imagemagick*.jar /usr/bin + +RUN ln /usr/bin/alfresco-transformer-imagemagick*.jar /usr/bin/alfresco-transformer-imagemagick.jar && \ + yum install -y wget && \ + wget $IMAGEMAGICK_RPM_URL && \ + wget $IMAGEMAGICK_LIB_RPM_URL && \ + yum localinstall -y ImageMagick-*.x86_64.rpm && \ + rm -f ImageMagick-*.x86_64.rpm && \ + yum remove -y wget && \ + yum clean all + +EXPOSE 8090 + +ENTRYPOINT java -jar /usr/bin/alfresco-transformer-imagemagick.jar diff --git a/alfresco-docker-imagemagick/docker/LICENSE b/alfresco-docker-imagemagick/docker/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/alfresco-docker-imagemagick/docker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/alfresco-docker-imagemagick/docker/README.md b/alfresco-docker-imagemagick/docker/README.md new file mode 100644 index 00000000..1750d496 --- /dev/null +++ b/alfresco-docker-imagemagick/docker/README.md @@ -0,0 +1,3 @@ +# Welcome + +This repository contains the Dockerfile that performs ImageMagick transformations for the the ACS Repository. diff --git a/alfresco-docker-imagemagick/docker/pom.xml b/alfresco-docker-imagemagick/docker/pom.xml new file mode 100644 index 00000000..b74c931d --- /dev/null +++ b/alfresco-docker-imagemagick/docker/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformer-imagemagick + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-docker-builder-imagemagick + Alfresco Content Services ImageMagick Docker image builder + pom + + + ${project.version} + + + + + org.alfresco + alfresco-transformer-imagemagick + ${dependency.alfresco-transformer-imagemagick.version} + jar + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + copy-resources + process-resources + + copy + + + + + org.alfresco + alfresco-transformer-imagemagick + ${dependency.alfresco-transformer-imagemagick.version} + jar + false + ${project.build.directory} + + + + + + + + + + diff --git a/alfresco-docker-imagemagick/pom.xml b/alfresco-docker-imagemagick/pom.xml new file mode 100644 index 00000000..34a7febb --- /dev/null +++ b/alfresco-docker-imagemagick/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformers + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-docker-transformer-imagemagick + Alfresco Docker Transformer ImageMagick + 0.1-SNAPSHOT + pom + + + alfresco-transformer-imagemagick + docker + + + diff --git a/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/pom.xml b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/pom.xml new file mode 100644 index 00000000..77070c4f --- /dev/null +++ b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/pom.xml @@ -0,0 +1,76 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformer-libreoffice + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-transformer-libreoffice + 0.1-SNAPSHOT + jar + + + + + org.springframework.boot + spring-boot-starter-parent + ${dependency.spring-boot.version} + pom + import + + + + + + + org.alfresco + alfresco-transformer-base + ${dependency.alfresco-transformer-base.version} + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + org.alfresco + alfresco-core + ${dependency.alfresco-core.version} + + + org.alfresco + alfresco-jodconverter-core + 3.0.1.1 + + + org.apache.pdfbox + pdfbox + 2.0.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.0.0.RELEASE + + + + repackage + + + + + + + + diff --git a/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/Application.java b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/Application.java new file mode 100644 index 00000000..2d2ad7f8 --- /dev/null +++ b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/Application.java @@ -0,0 +1,38 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #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); + } +} diff --git a/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/JodConverter.java b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/JodConverter.java new file mode 100644 index 00000000..2fad484c --- /dev/null +++ b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/JodConverter.java @@ -0,0 +1,45 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transformer; + +import org.artofsolving.jodconverter.office.OfficeManager; + +///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository ///////////// + +public interface JodConverter +{ + /** + * Gets the JodConverter OfficeManager. + * @return + */ + public abstract OfficeManager getOfficeManager(); + + /** + * This method returns a boolean indicating whether the JodConverter connection to OOo is available. + * @return true if available, else false + */ + public abstract boolean isAvailable(); +} diff --git a/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/JodConverterSharedInstance.java b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/JodConverterSharedInstance.java new file mode 100644 index 00000000..def2ddb1 --- /dev/null +++ b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/JodConverterSharedInstance.java @@ -0,0 +1,526 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.transformer; + +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration; +import org.artofsolving.jodconverter.office.OfficeException; +import org.artofsolving.jodconverter.office.OfficeManager; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; + +///////// THIS FILE IS A COPY OF THE CODE IN alfresco-repository ///////////// + +/** + * Makes use of the JodConverter library and an installed + * OpenOffice application to perform OpenOffice-driven conversions. + * + * @author Neil McErlean + */ +public class JodConverterSharedInstance implements InitializingBean, DisposableBean, JodConverter +{ + private static Log logger = LogFactory.getLog(JodConverterSharedInstance.class); + + private OfficeManager officeManager; + boolean isAvailable = false; + + // JodConverter's built-in configuration settings. + // + // These properties are set by Spring dependency injection at system startup in the usual way. + // If the values are changed via the JMX console at runtime, then the subsystem will be stopped + // and can be restarted with the new values - meaning that JodConverter will also be stopped and restarted. + // Therefore there is no special handling required for changes to e.g. portNumbers which determines + // the number of OOo instances there should be in the pool. + // + // Numeric parameters have to be handled as Strings, as that is what Spring gives us for missing values + // e.g. if jodconverter.maxTasksPerProcess is not specified in the properties file, the value + // "${jodconverter.maxTasksPerProcess}" will be injected. + + private Integer maxTasksPerProcess; + private String officeHome; + private int[] portNumbers; + private Long taskExecutionTimeout; + private Long taskQueueTimeout; + private File templateProfileDir; + private Boolean enabled; + private Long connectTimeout; + + private String deprecatedOooExe; + private Boolean deprecatedOooEnabled; + private int[] deprecatedOooPortNumbers; + + public void setMaxTasksPerProcess(String maxTasksPerProcess) + { + Long l = parseStringForLong(maxTasksPerProcess.trim()); + if (l != null) + { + this.maxTasksPerProcess = l.intValue(); + } + } + + public void setOfficeHome(String officeHome) + { + this.officeHome = officeHome == null ? "" : officeHome.trim(); + } + + public void setDeprecatedOooExe(String deprecatedOooExe) + { + this.deprecatedOooExe = deprecatedOooExe == null ? "" : deprecatedOooExe.trim(); + } + + public void setPortNumbers(String s) + { + portNumbers = parsePortNumbers(s, "jodconverter"); + } + + public void setDeprecatedOooPort(String s) + { + deprecatedOooPortNumbers = parsePortNumbers(s, "ooo"); + } + + private int[] parsePortNumbers(String s, String sys) + { + int[] portNumbers = null; + s = s == null ? null : s.trim(); + if (s != null && !s.isEmpty()) + { + StringTokenizer tokenizer = new StringTokenizer(s, ","); + int tokenCount = tokenizer.countTokens(); + portNumbers = new int[tokenCount]; + for (int i = 0;tokenizer.hasMoreTokens();i++) + { + try + { + portNumbers[i] = Integer.parseInt(tokenizer.nextToken().trim()); + } + catch (NumberFormatException e) + { + // Logging this as an error as this property would prevent JodConverter & therefore + // OOo from starting as specified + if (logger.isErrorEnabled()) + { + logger.error("Unparseable value for property '" + sys + ".portNumbers': " + s); + } + // We'll not rethrow the exception, instead allowing the problem to be picked up + // when the OOoJodConverter subsystem is started. + } + } + } + return portNumbers; + } + + public void setTaskExecutionTimeout(String taskExecutionTimeout) + { + this.taskExecutionTimeout = parseStringForLong(taskExecutionTimeout.trim()); + } + + public void setTemplateProfileDir(String templateProfileDir) + { + if (templateProfileDir == null || templateProfileDir.trim().length() == 0) + { + this.templateProfileDir = null; + } + else + { + File tmp = new File(templateProfileDir); + if (!tmp.isDirectory()) + { + throw new AlfrescoRuntimeException("OpenOffice template profile directory "+templateProfileDir+" does not exist."); + } + this.templateProfileDir = tmp; + } + } + + public void setTaskQueueTimeout(String taskQueueTimeout) + { + this.taskQueueTimeout = parseStringForLong(taskQueueTimeout.trim()); + } + + public void setConnectTimeout(String connectTimeout) + { + this.connectTimeout = parseStringForLong(connectTimeout.trim()); + } + + public void setEnabled(String enabled) + { + this.enabled = parseEnabled(enabled); + + // If this is a request from the Enterprise Admin console to disable the JodConverter. + if (this.enabled == false && (deprecatedOooEnabled == null || deprecatedOooEnabled == false)) + { + // We need to change isAvailable to false so we don't make calls to a previously started OfficeManger. + // In the case of Enterprise it is very unlikely that ooo.enabled will have been set to true. + this.isAvailable = false; + } + } + + public void setDeprecatedOooEnabled(String deprecatedOooEnabled) + { + this.deprecatedOooEnabled = parseEnabled(deprecatedOooEnabled); + // No need to worry about isAvailable as this setting cannot be changed via the Admin console. + } + + private Boolean parseEnabled(String enabled) + { + enabled = enabled == null ? "" : enabled.trim(); + return Boolean.parseBoolean(enabled); + } + + // So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated + // ooo.exe setting rather than the jodconverter.officeHome setting if we don't have the jod setting as + // oooDirect was replaced by jodconverter after this release. + String getOfficeHome() + { + String officeHome = this.officeHome; + if ((officeHome == null || officeHome.isEmpty()) && (deprecatedOooExe != null && !deprecatedOooExe.isEmpty())) + { + // It will only be possible to use the ooo.exe value if it includes a path, which itself has the officeHome + // value in it. + + // jodconverter.officeHome=/opt/libreoffice5.4/ + // ooo.exe=/opt/libreoffice5.4/program/soffice.bin + + // jodconverter.officeHome=C:/noscan/installs/521~1.1/LIBREO~1/App/libreoffice + // ooo.exe=C:/noscan/installs/COMMUN~1.0-E/LIBREO~1/App/libreoffice/program/soffice.exe + + File oooExe = new File(deprecatedOooExe); + File parent = oooExe.getParentFile(); + if (parent != null && "program".equals(parent.getName())) + { + File grandparent = parent.getParentFile(); + if (grandparent != null) + { + officeHome = grandparent.getPath(); + } + } + } + return officeHome; + } + + // So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated + // ooo.enabled setting if true rather than the jodconverter.enabled setting as oooDirect was replaced by + // jodconverter after this release. + // If ooo.enabled is true the JodConverter will be enabled. + // If ooo.enabled is false or unset the jodconverter.enabled value is used. + // Community set properties via alfresco-global.properties. + // Enterprise may do the same but may also reset jodconverter.enabled them via the Admin console. + // In the case of Enterprise it is very unlikely that ooo.enabled will be set to true. + boolean isEnabled() + { + return (deprecatedOooEnabled != null && deprecatedOooEnabled) || (enabled != null && enabled); + } + + // So that Community systems <= Alfresco 6.0.1-ea keep working on upgrade, we may need to use the deprecated + // ooo.port setting rather than the jodconverter.portNumbers if ooo.enabled is true and jodconverter.enabled + // is false. + int[] getPortNumbers() + { + return (enabled == null || !enabled) && deprecatedOooEnabled != null && deprecatedOooEnabled + ? deprecatedOooPortNumbers + : portNumbers; + } + + private Long parseStringForLong(String string) + { + Long result = null; + try + { + long l = Long.parseLong(string); + result = new Long(l); + } + catch (NumberFormatException nfe) + { + if (logger.isDebugEnabled()) + { + logger.debug("Cannot parse numerical value from " + string); + } + // else intentionally empty + } + return result; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.content.JodConverter#isAvailable() + */ + public boolean isAvailable() + { + final boolean result = isAvailable && officeManager != null; + return result; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // isAvailable defaults to false afterPropertiesSet. It only becomes true on successful completion of this method. + this.isAvailable = false; + + int[] portNumbers = getPortNumbers(); + String officeHome = getOfficeHome(); + if (logger.isDebugEnabled()) + { + logger.debug("JodConverter settings (null settings will be replaced by jodconverter defaults):"); + logger.debug(" officeHome = " + officeHome); + logger.debug(" enabled = " + isEnabled()); + logger.debug(" portNumbers = " + getString(portNumbers)); + logger.debug(" ooo.exe = " + deprecatedOooExe); + logger.debug(" ooo.enabled = " + deprecatedOooEnabled); + logger.debug(" ooo.port = " + getString(deprecatedOooPortNumbers)); + logger.debug(" jodConverter.enabled = " + enabled); + logger.debug(" jodconverter.portNumbers = " + getString(this.portNumbers)); + logger.debug(" jodconverter.officeHome = " + this.officeHome); + logger.debug(" jodconverter.maxTasksPerProcess = " + maxTasksPerProcess); + logger.debug(" jodconverter.taskExecutionTimeout = " + taskExecutionTimeout); + logger.debug(" jodconverter.taskQueueTimeout = " + taskQueueTimeout); + logger.debug(" jodconverter.connectTimeout = " + connectTimeout); + } + + // Only start the JodConverter instance(s) if the subsystem is enabled. + if (isEnabled() == false) + { + return; + } + + logAllSofficeFilesUnderOfficeHome(); + + try + { + DefaultOfficeManagerConfiguration defaultOfficeMgrConfig = new DefaultOfficeManagerConfiguration(); + if (maxTasksPerProcess != null && maxTasksPerProcess > 0) + { + defaultOfficeMgrConfig.setMaxTasksPerProcess(maxTasksPerProcess); + } + if (officeHome != null) + { + defaultOfficeMgrConfig.setOfficeHome(officeHome); + } + if (portNumbers != null && portNumbers.length != 0) + { + defaultOfficeMgrConfig.setPortNumbers(portNumbers); + } + if (taskExecutionTimeout != null && taskExecutionTimeout > 0) + { + defaultOfficeMgrConfig.setTaskExecutionTimeout(taskExecutionTimeout); + } + if (taskQueueTimeout != null && taskQueueTimeout > 0) + { + defaultOfficeMgrConfig.setTaskQueueTimeout(taskQueueTimeout); + } + if (templateProfileDir != null) + { + defaultOfficeMgrConfig.setTemplateProfileDir(templateProfileDir); + } + if (connectTimeout != null) + { + defaultOfficeMgrConfig.setConnectTimeout(connectTimeout); + } + // Try to configure and start the JodConverter library. + officeManager = defaultOfficeMgrConfig.buildOfficeManager(); + officeManager.start(); + } + catch (IllegalStateException isx) + { + if (logger.isErrorEnabled()) + { + logger.error("Unable to pre-initialise JodConverter library. " + + "The following error is shown for informational purposes only.", isx); + } + return; + } + catch (OfficeException ox) + { + if (logger.isErrorEnabled()) + { + logger.error("Unable to start JodConverter library. " + + "The following error is shown for informational purposes only.", ox); + } + + // We need to let it continue (comment-out return statement) even if an error occurs. See MNT-13706 and associated issues. + //return; + } + catch (Exception x) + { + if (logger.isErrorEnabled()) + { + logger.error("Unexpected error in configuring or starting the JodConverter library." + + "The following error is shown for informational purposes only.",x); + } + return; + } + + // If any exceptions are thrown in the above code, then isAvailable + // should remain false, hence the return statements. + this.isAvailable = true; + } + + private String getString(int[] portNumbers) + { + StringBuilder portInfo = new StringBuilder(); + if (portNumbers != null) + { + for (int i = 0;i < portNumbers.length;i++) + { + portInfo.append(portNumbers[i]); + if (i < portNumbers.length - 1) + { + portInfo.append(", "); + } + } + } + return portInfo.toString(); + } + + private void logAllSofficeFilesUnderOfficeHome() + { + if (logger.isDebugEnabled() == false) + { + return; + } + + String officeHome = getOfficeHome(); + File requestedOfficeHome = new File(officeHome); + + logger.debug("Some information on soffice* files and their permissions"); + + logFileInfo(requestedOfficeHome); + + for (File f : findSofficePrograms(requestedOfficeHome, new ArrayList(), 2)) + { + logFileInfo(f); + } + } + + private List findSofficePrograms(File searchRoot, List results, int maxRecursionDepth) + { + return this.findSofficePrograms(searchRoot, results, 0, maxRecursionDepth); + } + + private List findSofficePrograms(File searchRoot, List results, + int currentRecursionDepth, int maxRecursionDepth) + { + if (currentRecursionDepth >= maxRecursionDepth) + { + return results; + } + + File[] matchingFiles = searchRoot.listFiles(new FilenameFilter() + { + @Override + public boolean accept(File dir, String name) + { + return name.startsWith("soffice"); + } + }); + for (File f : matchingFiles) + { + results.add(f); + } + + for (File dir : searchRoot.listFiles(new FileFilter() + { + @Override + public boolean accept(File f) { + return f.isDirectory(); + } + })) + { + findSofficePrograms(dir, results, currentRecursionDepth + 1, maxRecursionDepth); + } + + return results; + } + + /** + * Logs some information on the specified file, including name and r/w/x permissions. + * @param f the file to log. + */ + private void logFileInfo(File f) + { + if (logger.isDebugEnabled() == false) + { + return; + } + + StringBuilder msg = new StringBuilder(); + msg.append(f).append(" "); + if (f.exists()) + { + if (f.canRead()) + { + msg.append("(") + .append(f.isDirectory() ? "d" : "-") + .append(f.canRead() ? "r" : "-") + .append(f.canWrite() ? "w" : "-") + .append(f.canExecute() ? "x" : "-") + .append(")"); + } + } + else + { + msg.append("does not exist"); + } + logger.debug(msg.toString()); + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + this.isAvailable = false; + if (officeManager != null) + { + // If there is an OfficeException when stopping the officeManager below, then there is + // little that can be done other than logging the exception and carrying on. The JodConverter-based + // libraries will not be used in any case, as isAvailable is false. + // + // Any exception thrown out of this method will be logged and swallowed by Spring + // (see javadoc for method declaration). Therefore there is no handling here for + // exceptions from jodConverter. + officeManager.stop(); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.content.JodConverterWorker#getOfficeManager() + */ + public OfficeManager getOfficeManager() + { + return officeManager; + } +} diff --git a/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java new file mode 100644 index 00000000..d81c63bb --- /dev/null +++ b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/java/org/alfresco/transformer/LibreOfficeController.java @@ -0,0 +1,220 @@ +/* + * #%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 . + * #L% + */ +package org.alfresco.transformer; + +import com.sun.star.task.ErrorCodeIOException; +import org.alfresco.transformer.base.AbstractTransformerController; +import org.alfresco.transformer.base.TransformException; +import org.apache.commons.logging.LogFactory; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.artofsolving.jodconverter.OfficeDocumentConverter; +import org.artofsolving.jodconverter.office.OfficeException; +import org.artofsolving.jodconverter.office.OfficeManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.File; +import java.io.IOException; + +/** + * Controller for the Docker based LibreOffice transformer. + * + * + * Status Codes: + * + * 200 Success + * 400 Bad Request: Request parameter is missing (missing mandatory parameter) + * 400 Bad Request: Request parameter is of the wrong type + * 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file) + * 400 Bad Request: The source filename was not supplied + * 500 Internal Server Error: (no message with low level IO problems) + * 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked) + * 500 Internal Server Error: Transformer version check exit code was not 0 + * 500 Internal Server Error: Transformer version check failed to create any output + * 500 Internal Server Error: Could not read the target file + * 500 Internal Server Error: The target filename was malformed (should not happen because of other checks) + * 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content) + * 500 Internal Server Error: Filename encoding error + * 507 Insufficient Storage: Failed to store the source file + */ +@Controller +public class LibreOfficeController extends AbstractTransformerController +{ + private static final String OFFICE_HOME = "/opt/libreoffice5.4"; + + private static final int JODCONVERTER_TRANSFORMATION_ERROR_CODE = 3088; + + private JodConverter jodconverter; + + @Autowired + public LibreOfficeController() throws Exception + { + logger = LogFactory.getLog(LibreOfficeController.class); + + setJodConverter(createJodConverter()); + } + + private static JodConverter createJodConverter() throws Exception + { + JodConverterSharedInstance jodconverter = new JodConverterSharedInstance(); + + jodconverter.setOfficeHome(OFFICE_HOME); // jodconverter.officeHome + jodconverter.setMaxTasksPerProcess("200"); // jodconverter.maxTasksPerProcess + jodconverter.setTaskExecutionTimeout("120000"); // jodconverter.maxTasksPerProcess + jodconverter.setTaskQueueTimeout("30000"); // jodconverter.taskQueueTimeout + jodconverter.setConnectTimeout("28000"); // jodconverter.connectTimeout + jodconverter.setPortNumbers("8100"); // jodconverter.portNumbers + jodconverter.setTemplateProfileDir(""); // jodconverter.templateProfileDir + jodconverter.setEnabled("true"); // jodconverter.enabled + jodconverter.afterPropertiesSet(); + + return jodconverter; + } + + public void setJodConverter(JodConverter jodconverter) + { + this.jodconverter = jodconverter; + } + + @PostMapping("/transform") + public ResponseEntity transform(HttpServletRequest request, + @RequestParam("file") MultipartFile sourceMultipartFile, + @RequestParam("targetExtension") String targetExtension, + @RequestParam(value = "timeout", required = false) Long timeout) + { + String targetFilename = createTargetFileName(sourceMultipartFile, targetExtension); + File sourceFile = createSourceFile(request, sourceMultipartFile); + File targetFile = createTargetFile(request, targetFilename); + // Both files are deleted by TransformInterceptor.afterCompletion + + executeTransformCommand(sourceFile, targetFile); + + return createAttachment(targetFilename, targetFile); + } + + protected void executeTransformCommand(File sourceFile, File targetFile) + { + try + { + OfficeManager officeManager = jodconverter.getOfficeManager(); + OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager); + converter.convert(sourceFile, targetFile); + } + catch (OfficeException e) + { + throw new TransformException(500, "LibreOffice server conversion failed: \n"+ + " from file: " + sourceFile + "\n" + + " to file: " + targetFile, + e); + } + catch (Throwable throwable) + { + // Because of the known bug with empty Spreadsheets in JodConverter try to catch exception and produce empty pdf file + if (throwable.getCause() instanceof ErrorCodeIOException && + ((ErrorCodeIOException) throwable.getCause()).ErrCode == JODCONVERTER_TRANSFORMATION_ERROR_CODE) + { + logger.warn("Transformation failed: \n" + + "from file: " + sourceFile + "\n" + + "to file: " + targetFile + + "Source file " + sourceFile + " has no content"); + produceEmptyPdfFile(targetFile); + } + else + { + throw throwable; + } + } + + if (!targetFile.exists() || targetFile.length() == 0L) + { + throw new TransformException(500, "Transformer failed to create an output file"); + } + } + + /** + * This method produces an empty PDF file at the specified File location. + * Apache's PDFBox is used to create the PDF file. + */ + private void produceEmptyPdfFile(File targetFile) + { + // If improvement PDFBOX-914 is incorporated, we can do this with a straight call to + // org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader("")); + // https://issues.apache.org/jira/browse/PDFBOX-914 + + PDDocument pdfDoc = null; + PDPageContentStream contentStream = null; + try + { + pdfDoc = new PDDocument(); + PDPage pdfPage = new PDPage(); + // Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs + // that have literally nothing in them. So we'll put a content stream in it. + contentStream = new PDPageContentStream(pdfDoc, pdfPage); + pdfDoc.addPage(pdfPage); + + // Now write the in-memory PDF document into the temporary file. + pdfDoc.save(targetFile.getAbsolutePath()); + + } + catch (IOException iox) + { + throw new TransformException(500, "Error creating empty PDF file", iox); + } + finally + { + if (contentStream != null) + { + try + { + contentStream.close(); + } + catch (IOException ignored) + { + // Intentionally empty + } + } + if (pdfDoc != null) + { + try + { + pdfDoc.close(); + } + catch (IOException ignored) + { + // Intentionally empty. + } + } + } + } +} diff --git a/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/resources/templates/transformForm.html b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/resources/templates/transformForm.html new file mode 100644 index 00000000..b7e62be9 --- /dev/null +++ b/alfresco-docker-libreoffice/alfresco-transformer-libreoffice/src/main/resources/templates/transformForm.html @@ -0,0 +1,26 @@ + + + +
+

+

+ +
+

LiberOffice Test Transformation

+
+ + + + + + +
file *
targetExtension *
timeout
+
+
+ + + + + diff --git a/alfresco-docker-libreoffice/docker/Dockerfile b/alfresco-docker-libreoffice/docker/Dockerfile new file mode 100644 index 00000000..4973f6f7 --- /dev/null +++ b/alfresco-docker-libreoffice/docker/Dockerfile @@ -0,0 +1,21 @@ +# Image provides a container in which to run LibreOffice transformations for Alfresco Content Services. + +FROM quay.io/alfresco/alfresco-base-java:9 + +ENV LIBREOFFICE_RPM_URL=http://download.documentfoundation.org/libreoffice/stable/5.4.5/rpm/x86_64/LibreOffice_5.4.5_Linux_x86-64_rpm.tar.gz + +COPY target/alfresco-transformer-libreoffice*.jar /usr/bin + +RUN ln /usr/bin/alfresco-transformer-libreoffice*.jar /usr/bin/alfresco-transformer-libreoffice.jar && \ + yum install -y wget && \ + yum install -y cairo cups-libs libSM && \ + wget $LIBREOFFICE_RPM_URL && \ + tar xzf LibreOffice_*_Linux_x86-64_rpm.tar.gz && \ + yum localinstall -y LibreOffice*/RPMS/*.rpm && \ + rm -rf LibreOffice_*_Linux_x86-64_rpm.tar.gz LibreOffice_*_Linux_x86-64_rpm && \ + yum remove -y wget && \ + yum clean all + +EXPOSE 8090 + +ENTRYPOINT java -jar /usr/bin/alfresco-transformer-libreoffice.jar diff --git a/alfresco-docker-libreoffice/docker/LICENSE b/alfresco-docker-libreoffice/docker/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/alfresco-docker-libreoffice/docker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/alfresco-docker-libreoffice/docker/README.md b/alfresco-docker-libreoffice/docker/README.md new file mode 100644 index 00000000..905fda7b --- /dev/null +++ b/alfresco-docker-libreoffice/docker/README.md @@ -0,0 +1,3 @@ +# Welcome + +This repository contains the Dockerfile that performs LibreOffice transformations for the the ACS Repository. diff --git a/alfresco-docker-libreoffice/docker/pom.xml b/alfresco-docker-libreoffice/docker/pom.xml new file mode 100644 index 00000000..28cb3f93 --- /dev/null +++ b/alfresco-docker-libreoffice/docker/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformer-libreoffice + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-docker-builder-libreoffice + Alfresco Content Services LibreOffice Docker image builder + pom + + + ${project.version} + + + + + org.alfresco + alfresco-transformer-libreoffice + ${dependency.alfresco-transformer-libreoffice.version} + jar + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.2 + + + copy-resources + process-resources + + copy + + + + + org.alfresco + alfresco-transformer-libreoffice + ${dependency.alfresco-transformer-libreoffice.version} + jar + false + ${project.build.directory} + + + + + + + + + + diff --git a/alfresco-docker-libreoffice/pom.xml b/alfresco-docker-libreoffice/pom.xml new file mode 100644 index 00000000..d3d005ae --- /dev/null +++ b/alfresco-docker-libreoffice/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformers + 0.1-SNAPSHOT + ../pom.xml + + + alfresco-docker-transformer-libreoffice + Alfresco Docker Transformer LibreOffice + 0.1-SNAPSHOT + pom + + + alfresco-transformer-libreoffice + docker + + + diff --git a/alfresco-transformer-base/README.md b/alfresco-transformer-base/README.md new file mode 100644 index 00000000..5439183e --- /dev/null +++ b/alfresco-transformer-base/README.md @@ -0,0 +1,170 @@ +# Common code for Docker based ACS transformers + +This project contains code that is common between all the ACS transformers that run within their own +Docker containers. It performs common actions such as logging, throttling requests and handling the +streaming of content to and from the container. It also provides structure and hook points to allow +specific transformers to simply check request parameter and perform the transformation using either +files or a pair of InputStream and OutputStream. + +A transformer project is expected to provide the following files: + +~~~ +src/main/resources/templates/transformForm.html +src/main/java/org/alfresco/transformer/Controller.java +src/main/java/org/alfresco/transformer/Application.java +~~~ + +* transformerForm.html - A simple test page using [thymeleaf](http://www.thymeleaf.org) that gathers request + parameters so they may be used to test the transformer. + +~~~ + + +
+

Test Transformation

+
+ + + + + + + + + + +
file *
targetFilename *
width
height
allowEnlargement
maintainAspectRatio
page
timeout
+
+
+ + + +~~~ + +* *TransformerName*Controller.java - A [Spring Boot](https://projects.spring.io/spring-boot/) Controller that + extends AbstractTransformerController to handel a POST request to *"/transform"*. + +~~~ +... +@Controller +public class AlfrescoPdfRendererController extends AbstractTransformerController +{ + ... + + @PostMapping("/transform") + public ResponseEntity transform(HttpServletRequest request, + @RequestParam("file") MultipartFile sourceMultipartFile, + @RequestParam("targetFilename") String targetFilename, + @RequestParam(value = "width", required = false) Integer width, + @RequestParam(value = "height", required = false) Integer height, + @RequestParam(value = "allowEnlargement", required = false) Boolean allowEnlargement, + @RequestParam(value = "maintainAspectRatio", required = false) Boolean maintainAspectRatio, + @RequestParam(value = "page", required = false) Integer page, + @RequestParam(value = "timeout", required = false) Long timeout) + { + try + { + File sourceFile = createSourceFile(request, sourceMultipartFile); + File targetFile = createTargetFile(request, targetFilename); + // Both files are deleted by TransformInterceptor.afterCompletion + + StringJoiner args = new StringJoiner(" "); + if (width != null) + { + args.add("--width=" + width); + } + if (height != null) + { + args.add("--height=" + height); + } + if (allowEnlargement != null && allowEnlargement) + { + args.add("--allow-enlargement"); + } + if (maintainAspectRatio != null && maintainAspectRatio) + { + args.add("--maintain-aspect-ratio"); + } + if (page != null) + { + args.add("--page=" + page); + } + String options = args.toString(); + LogEntry.setOptions(options); + + Map properties = new HashMap(5); + properties.put("options", options); + properties.put("source", sourceFile.getAbsolutePath()); + properties.put("target", targetFile.getAbsolutePath()); + + executeTransformCommand(properties, targetFile, timeout); + + return createAttachment(targetFilename, targetFile); + } + catch (UnsupportedEncodingException e) + { + throw new TransformException(500, "Filename encoding error", e); + } + } +} +~~~ + +* Application.java - [Spring Boot](https://projects.spring.io/spring-boot/) expects to find an Application in + a project's source files. The following may be used: + +~~~ +package org.alfresco.transformer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application +{ + public static void main(String[] args) + { + SpringApplication.run(Application.class, args); + } +} +~~~ + +## Building and testing + +The project can be built by running the Maven command: + +~~~ +mvn clean install +~~~ + +## Artifacts + +The artifacts can be obtained by: + +* downloading from the [Alfresco repository](https://artifacts.alfresco.com/nexus/content/groups/public/) +* Adding a Maven dependency to your pom file. + +~~~ + + org.alfresco + alfresco-transformer-base + 1.0 + +~~~ + +and the Alfresco Maven repository: + +~~~ + + alfresco-maven-repo + https://artifacts.alfresco.com/nexus/content/groups/public + +~~~ + +The build plan in Bamboo is PLAT-TB + +## Contributing guide + +Please use [this guide](https://github.com/Alfresco/alfresco-jodconverter/blob/master/CONTRIBUTING.md) to make a +contribution to the project. diff --git a/alfresco-transformer-base/pom.xml b/alfresco-transformer-base/pom.xml new file mode 100644 index 00000000..07fabf4d --- /dev/null +++ b/alfresco-transformer-base/pom.xml @@ -0,0 +1,44 @@ + + 4.0.0 + + + org.alfresco + alfresco-docker-transformers + 0.1-SNAPSHOT + ../pom.xml + + + org.alfresco + alfresco-transformer-base + 0.1-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-parent + ${dependency.spring-boot.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + org.alfresco + alfresco-core + ${dependency.alfresco-core.version} + + + + diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java new file mode 100644 index 00000000..2d2ad7f8 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/Application.java @@ -0,0 +1,38 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #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); + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/AbstractTransformerController.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/AbstractTransformerController.java new file mode 100644 index 00000000..870555a7 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/AbstractTransformerController.java @@ -0,0 +1,330 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transformer.base; + +import org.alfresco.util.TempFileProvider; +import org.alfresco.util.exec.RuntimeExec; +import org.apache.commons.logging.Log; +import org.springframework.beans.TypeMismatchException; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.Model; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.util.UriUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Collection; +import java.util.Map; + +/** + * Abstract Controller, provides structure and helper methods to sub-class transformer controllers. + * + * Status Codes: + * + * 200 Success + * 400 Bad Request: Request parameter is missing (missing mandatory parameter) + * 400 Bad Request: Request parameter is of the wrong type + * 400 Bad Request: Transformer exit code was not 0 (possible problem with the source file) + * 400 Bad Request: The source filename was not supplied + * 500 Internal Server Error: (no message with low level IO problems) + * 500 Internal Server Error: The target filename was not supplied (should not happen as targetExtension is checked) + * 500 Internal Server Error: Transformer version check exit code was not 0 + * 500 Internal Server Error: Transformer version check failed to create any output + * 500 Internal Server Error: Could not read the target file + * 500 Internal Server Error: The target filename was malformed (should not happen because of other checks) + * 500 Internal Server Error: Transformer failed to create an output file (the exit code was 0, so there should be some content) + * 500 Internal Server Error: Filename encoding error + * 507 Insufficient Storage: Failed to store the source file + * + * 408 Request Timeout -- TODO implement general timeout mechanism rather than depend on transformer timeout (might be possible for external processes) + * 415 Unsupported Media Type -- TODO possibly implement a check on supported source and target mimetypes (probably not) + * 429 Too Many Requests -- TODO implement general throttling mechanism (needs to be done) + */ +public abstract class AbstractTransformerController +{ + public static final String SOURCE_FILE = "sourceFile"; + public static final String TARGET_FILE = "targetFile"; + + protected static Log logger; + + protected RuntimeExec transformCommand; + private RuntimeExec checkCommand; + + public void setTransformCommand(RuntimeExec runtimeExec) + { + transformCommand = runtimeExec; + } + + public void setCheckCommand(RuntimeExec runtimeExec) + { + checkCommand = runtimeExec; + } + + @RequestMapping("/version") + @ResponseBody + String version() + { + String version = "Version not checked"; + if (checkCommand != null) + { + RuntimeExec.ExecutionResult result = checkCommand.execute(); + if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0) + { + throw new TransformException(500, "Transformer version check exit code was not 0: \n" + result); + } + + version = result.getStdOut().trim(); + if (version.isEmpty()) + { + throw new TransformException(500, "Transformer version check failed to create any output"); + } + } + + return version; + } + + @GetMapping("/") + public String transformForm(Model model) + { + return "transformForm"; // the name of the template + } + + @GetMapping("/log") + public String log(Model model) + { + Collection log = LogEntry.getLog(); + if (!log.isEmpty()) + { + model.addAttribute("log", log); + } + return "log"; // the name of the template + } + + @ExceptionHandler(TypeMismatchException.class) + public void handleParamsTypeMismatch(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException + { + String name = e.getParameterName(); + String message = "Request parameter " + name + " is of the wrong type"; + int statusCode = 400; + + if (logger != null && logger.isErrorEnabled()) + { + logger.error(message); + } + + LogEntry.setStatusCodeAndMessage(statusCode, message); + + response.sendError(statusCode, message); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public void handleMissingParams(HttpServletResponse response, MissingServletRequestParameterException e) throws IOException + { + String name = e.getParameterName(); + String message = "Request parameter " + name + " is missing"; + int statusCode = 400; + + if (logger != null && logger.isErrorEnabled()) + { + logger.error(message); + } + + LogEntry.setStatusCodeAndMessage(statusCode, message); + + response.sendError(statusCode, message); + } + + @ExceptionHandler(TransformException.class) + public void transformExceptionWithMessage(HttpServletResponse response, TransformException e) throws IOException + { + String message = e.getMessage(); + int statusCode = e.getStatusCode(); + + if (logger != null && logger.isErrorEnabled()) + { + logger.error(message); + } + + LogEntry.setStatusCodeAndMessage(statusCode, message); + + response.sendError(statusCode, message); + } + + protected String createTargetFileName(MultipartFile sourceMultipartFile, String targetExtension) + { + String targetFilename = null; + String sourceFilename = sourceMultipartFile.getOriginalFilename(); + sourceFilename = StringUtils.getFilename(sourceFilename); + if (sourceFilename != null && !sourceFilename.isEmpty()) + { + String ext = StringUtils.getFilenameExtension(sourceFilename); + if (ext != null && !ext.isEmpty()) + { + targetFilename =sourceFilename.substring(0, sourceFilename.length()-ext.length()-1)+'.'+targetExtension; + } + } + return targetFilename; + } + + /** + * Returns a File that holds the source content for a transformation. + * + * @param request + * @param multipartFile from the request + * @return a temporary File. + * @throws TransformException if there was no source filename. + */ + protected File createSourceFile(HttpServletRequest request, MultipartFile multipartFile) + { + String filename = multipartFile.getOriginalFilename(); + long size = multipartFile.getSize(); + filename = checkFilename( true, filename); + File file = TempFileProvider.createTempFile("source_", "_" + filename); + request.setAttribute(SOURCE_FILE, file); + save(multipartFile, file); + LogEntry.setSource(filename, size); + return file; + } + + /** + * Returns a File to be used to store the result of a transformation. + * + * @param request + * @param filename The targetFilename supplied in the request. Only the filename if a path is used as part of the + * temporary filename. + * @return a temporary File. + * @throws TransformException if there was no target filename. + */ + protected File createTargetFile(HttpServletRequest request, String filename) + { + filename = checkFilename( false, filename); + LogEntry.setTarget(filename); + File file = TempFileProvider.createTempFile("target_", "_" + filename); + request.setAttribute(TARGET_FILE, file); + return file; + } + + /** + * Checks the filename is okay to uses in a temporary file name. + * + * @param filename or path to be checked. + * @return the filename part of the supplied filename if it was a path. + * @throws TransformException if there was no target filename. + */ + private String checkFilename(boolean source, String filename) + { + filename = StringUtils.getFilename(filename); + if (filename == null || filename.isEmpty()) + { + String sourceOrTarget = source ? "source" : "target"; + int statusCode = source ? 400 : 500; + throw new TransformException(statusCode, "The " + sourceOrTarget + " filename was not supplied"); + } + return filename; + } + + private void save(MultipartFile multipartFile, File file) + { + try + { + Files.copy(multipartFile.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + catch (IOException e) + { + throw new TransformException(507, "Failed to store the source file", e); + } + } + + private Resource load(File file) + { + try + { + Resource resource = new UrlResource(file.toURI()); + if (resource.exists() || resource.isReadable()) + { + return resource; + } + else + { + throw new TransformException(500, "Could not read the target file: " + file.getPath()); + + } + } + catch (MalformedURLException e) + { + throw new TransformException(500, "The target filename was malformed: " + file.getPath(), e); + } + } + + protected void executeTransformCommand(Map properties, File targetFile, Long timeout) + { + long timeoutMs = timeout != null && timeout > 0 ? timeout : 0; + RuntimeExec.ExecutionResult result = transformCommand.execute(properties, timeoutMs); + + if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0) + { + throw new TransformException(400, "Transformer exit code was not 0: \n" + result); + } + + if (!targetFile.exists() || targetFile.length() == 0) + { + throw new TransformException(500, "Transformer failed to create an output file"); + } + } + + protected ResponseEntity createAttachment(String targetFilename, File targetFile) + { + try + { + Resource targetResource = load(targetFile); + targetFilename = UriUtils.encodePath(StringUtils.getFilename(targetFilename), "UTF-8"); + ResponseEntity body = ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename*= UTF-8''" + targetFilename).body(targetResource); + LogEntry.setTargetSize(targetFile.length()); + LogEntry.setStatusCodeAndMessage(200, "Success"); + return body; + } + catch (UnsupportedEncodingException e) + { + throw new TransformException(500, "Filename encoding error", e); + } + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/LogEntry.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/LogEntry.java new file mode 100644 index 00000000..49358340 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/LogEntry.java @@ -0,0 +1,248 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transformer.base; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Provides setter and getter methods to allow the current Thread to set various log properties and for these + * values to be retrieved. The {@link #complete()} method should be called at the end of a request to flush the + * current entry to an internal log Collection of the latest entries. The {@link #getLog()} method is used to obtain + * access to this collection. + */ +public class LogEntry +{ + private static final AtomicInteger count = new AtomicInteger(0); + private static final Deque log = new ConcurrentLinkedDeque<>(); + private static final int MAX_LOG_SIZE = 10; + + private static ThreadLocal currentLogEntry = new ThreadLocal() + { + @Override + protected LogEntry initialValue() + { + LogEntry logEntry = new LogEntry(); + if (log.size() >= MAX_LOG_SIZE) + { + log.removeLast(); + } + log.addFirst(logEntry); + return logEntry; + } + }; + + private final int id = count.incrementAndGet(); + private final long start = System.currentTimeMillis(); + private int statusCode; + + private long durationStreamIn; + private long durationTransform; + private long durationStreamOut; + + private String source; + private long sourceSize; + private String target; + private long targetSize; + private String options; + private String message; + + public static Collection getLog() + { + return log; + } + + public static void start() + { + currentLogEntry.get(); + } + + public static void setSource(String source, long sourceSize) + { + LogEntry logEntry = currentLogEntry.get(); + logEntry.source = getExtension(source); + logEntry.sourceSize = sourceSize; + logEntry.durationStreamIn = System.currentTimeMillis() - logEntry.start; + } + + public static void setTarget(String target) + { + currentLogEntry.get().target = getExtension(target); + } + + private static String getExtension(String filename) + { + int i = filename.lastIndexOf('.'); + if (i != -1) + { + filename = filename.substring(i+1); + } + return filename; + } + + public static void setTargetSize(long targetSize) + { + currentLogEntry.get().targetSize = targetSize; + } + + public static void setOptions(String options) + { + currentLogEntry.get().options = options; + } + + public static void setStatusCodeAndMessage(int statusCode, String message) + { + LogEntry logEntry = currentLogEntry.get(); + logEntry.statusCode = statusCode; + logEntry.message = message; + logEntry.durationTransform = System.currentTimeMillis() - logEntry.start - logEntry.durationStreamIn; + } + + public static void complete() + { + LogEntry logEntry = currentLogEntry.get(); + logEntry.durationStreamOut = System.currentTimeMillis() - logEntry.start - logEntry.durationStreamIn - logEntry.durationTransform; + currentLogEntry.remove(); + } + + public int getId() + { + return id; + } + + public Date getDate() + { + return new Date(start); + } + + public int getStatusCode() + { + return statusCode; + } + + public String getDuration() + { + return time(durationStreamIn + durationTransform + durationStreamOut)+" ("+ + time(durationStreamIn)+' '+time(durationTransform)+' '+time(durationStreamOut)+")"; + } + + public long getDurationStreamIn() + { + return durationStreamIn; + } + + public long getDurationTransform() + { + return durationTransform; + } + + public long getDurationStreamOut() + { + return durationStreamOut; + } + + public String getSource() + { + return source; + } + + public String getSourceSize() + { + return size(sourceSize); + } + + public String getTarget() + { + return target; + } + + public String getTargetSize() + { + return size(targetSize); + } + + public String getOptions() + { + return options; + } + + public String getMessage() + { + return message; + } + + private String time(long ms) + { + return size(ms, "1 ms", + new String[] { "ms", "s", "min", "hr" }, + new long[] { 1000, 60*1000, 60*60*1000, Long.MAX_VALUE}); + } + + private String size(long size) + { + return size(size, "1 byte", + new String[] { "bytes", "KB", "MB", "GB", "TB" }, + new long[] { 1024, 1024*1024, 1024*1024*1024, 1024*1024*1024*1024, Long.MAX_VALUE }); + } + + private String size(long size, String singleValue, String[] units, long[] dividers) + { + if (size == 1) + { + return singleValue; + } + long divider = 1; + for(int i = 0; i < units.length-1; i++) + { + long nextDivider = dividers[i]; + if(size < nextDivider) + { + return unitFormat(size, divider, units[i]); + } + divider = nextDivider; + } + return unitFormat(size, divider, units[units.length-1]); + } + + private String unitFormat(long size, long divider, String unit) + { + size = size * 10 / divider; + int decimalPoint = (int) size % 10; + + StringBuilder sb = new StringBuilder(); + sb.append(size/10); + if (decimalPoint != 0) + { + sb.append("."); + sb.append(decimalPoint); + } + sb.append(' '); + sb.append(unit); + + return sb.toString(); + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/TransformException.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/TransformException.java new file mode 100644 index 00000000..e3a67172 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/TransformException.java @@ -0,0 +1,47 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transformer.base; + +public class TransformException extends RuntimeException +{ + private int statusCode; + + public TransformException(int statusCode, String message) + { + super(message); + this.statusCode = statusCode; + } + + public TransformException(int statusCode, String message, Throwable cause) + { + super(message, cause); + } + + public int getStatusCode() + { + return statusCode; + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/TransformInterceptor.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/TransformInterceptor.java new file mode 100644 index 00000000..4a80f35c --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/TransformInterceptor.java @@ -0,0 +1,65 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transformer.base; + +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; + + +public class TransformInterceptor extends HandlerInterceptorAdapter +{ + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, Object handler) throws Exception + { + LogEntry.start(); + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, Object handler, Exception ex) + throws Exception + { + // TargetFile cannot be deleted until completion, otherwise 0 bytes are sent. + deleteFile(request, "sourceFile"); + deleteFile(request, "targetFile"); + + LogEntry.complete(); + } + + private void deleteFile(HttpServletRequest request, String attributeName) + { + File file = (File) request.getAttribute(attributeName); + if (file != null) + { + file.delete(); + } + } +} diff --git a/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/WebApplicationConfig.java b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/WebApplicationConfig.java new file mode 100644 index 00000000..c9a47f43 --- /dev/null +++ b/alfresco-transformer-base/src/main/java/org/alfresco/transformer/base/WebApplicationConfig.java @@ -0,0 +1,45 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transformer.base; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +@Configuration +public class WebApplicationConfig extends WebMvcConfigurerAdapter { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(transformInterceptor()).addPathPatterns("/transform");; + } + + @Bean + public TransformInterceptor transformInterceptor() { + return new TransformInterceptor(); + } +} diff --git a/alfresco-transformer-base/src/main/resources/application.properties b/alfresco-transformer-base/src/main/resources/application.properties new file mode 100644 index 00000000..4f9bcf43 --- /dev/null +++ b/alfresco-transformer-base/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.http.multipart.max-file-size=8192MB +spring.http.multipart.max-request-size=8192MB +server.port = 8090 + +logging.level.org.alfresco.util.exec.RuntimeExec=debug +logging.level.org.alfresco.transformer.LibreOfficeController=debug +logging.level.org.alfresco.transformer.JodConverterSharedInstance=debug \ No newline at end of file diff --git a/alfresco-transformer-base/src/main/resources/templates/log.html b/alfresco-transformer-base/src/main/resources/templates/log.html new file mode 100644 index 00000000..5c9a1a2b --- /dev/null +++ b/alfresco-transformer-base/src/main/resources/templates/log.html @@ -0,0 +1,45 @@ + + + +
+

+

+ +

Log entries

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IdTimeStatus CodeDuration (ms)SourceTargetOptionsMessage
+
+ + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..a5110199 --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + org.alfresco + alfresco-super-pom + 9 + + + org.alfresco + alfresco-docker-transformers + 0.1-SNAPSHOT + pom + + + 1.8 + 1.5.10.RELEASE + 6.18 + ${project.version} + + + + alfresco-transformer-base + alfresco-docker-alfresco-pdf-renderer + alfresco-docker-imagemagick + alfresco-docker-libreoffice + + + + scm:git@git.alfresco.com:Repository/alfresco-docker-transformers.git + scm:git@git.alfresco.com:Repository/alfresco-docker-transformers.git + https://git.alfresco.com/Repository/alfresco-docker-transformers.git + HEAD + + + + + alfresco-internal + https://artifacts.alfresco.com/nexus/content/repositories/releases + + + alfresco-internal-snapshots + https://artifacts.alfresco.com/nexus/content/repositories/snapshots + + + +