ACS-2718 Streamline ATS t-model/t-core (#586)

* Merge alfresco-transform-model project into alfresco-transform-core - WITHOUT the git history
* Simplify Maven dependencies and build project
* Unify release process of combined t-model and t-core
This commit is contained in:
Alan Davis 2022-04-05 17:36:43 +01:00 committed by GitHub
parent 7cbb9ac949
commit 64742a619e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 9896 additions and 50 deletions

View File

@ -45,38 +45,36 @@ jobs:
before_script: travis_wait bash _ci/cache_artifacts.sh
install: travis_wait 20 mvn -B -U -q clean install -DadditionalOption=-Xdoclint:none -DskipTests -Dmaven.javadoc.skip=true -Dmaven.wagon.http.pool=false -Pbase
script: mvn -B -U clean deploy -DadditionalOption=-Xdoclint:none -Dmaven.javadoc.skip=true -Dmaven.wagon.http.pool=false -Pbase
- name: "ImageMagick"
before_script: travis_wait bash _ci/cache_artifacts.sh
install: _ci/build.sh imagemagick
script: bash _ci/test.sh imagemagick
- name: "LibreOffice"
before_script: travis_wait bash _ci/cache_artifacts.sh
install: _ci/build.sh libreoffice
script: bash _ci/test.sh libreoffice
- name: "Transform Misc"
before_script: travis_wait bash _ci/cache_artifacts.sh
install: _ci/build.sh misc
script: bash _ci/test.sh misc
- name: "PDF Renderer"
before_script: travis_wait bash _ci/cache_artifacts.sh
install: _ci/build.sh pdf-renderer
script: bash _ci/test.sh pdf-renderer
- name: "Tika"
before_script: travis_wait bash _ci/cache_artifacts.sh
install: _ci/build.sh tika
script: bash _ci/test.sh tika
- name: "All in One Transformer"
before_script: travis_wait bash _ci/cache_artifacts.sh
install: _ci/build.sh full-build
script: travis_wait 30 bash _ci/test.sh aio-test
# - name: "Static Analysis (SAST)"
# TODO ATS-721: comment out until it is possible to run concurrent SAST scans
# if: type != pull_request
# if: branch = master AND type != pull_request
# before_install:
# - bash _ci/static_analysis_init.sh
# - bash _ci/init.sh
# script: bash _ci/static_analysis.sh
- name: "Release"
stage: release

View File

@ -1,12 +1,16 @@
## Alfresco Transform Core
[![Build Status](https://travis-ci.com/Alfresco/alfresco-transform-core.svg?branch=master)](https://travis-ci.com/Alfresco/alfresco-transform-core)
Contains the common transformer (T-Engine) code, plus a few actual implementations.
Contains the common transformer (T-Engine) code, plus a few implementations.
### Sub-projects
* `alfresco-transform-model` - library packaged as a jar file which contains the data model of json
configuration files and messages sent between clients, T-Engines and T-Router. Also contains code to
work out which transform should be used for a combination of configuration files; see the sub-project's
[README](https://github.com/Alfresco/alfresco-transform-core/blob/master/alfresco-transform-model/README.md)
* `alfresco-transformer-base` - library packaged as a jar file which contains code that is common
to all the transformers; see the sub-project's
to all the transformers; see the sub-project's
[README](https://github.com/Alfresco/alfresco-transform-core/blob/master/alfresco-transformer-base/README.md)
* `alfresco-transform-<name>` - multiple T-Engines; each one of them builds both a SpringBoot fat jar
and a [Docker image](https://github.com/Alfresco/alfresco-transform-core#docker)
@ -37,6 +41,12 @@ The artifacts can be obtained by:
* downloading from [Alfresco repository](https://artifacts.alfresco.com/nexus/content/groups/public)
* getting as Maven dependency by adding the dependency to your pom file:
```xml
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-model</artifactId>
<version>version</version>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transformer-base</artifactId>

View File

@ -1,20 +0,0 @@
#!/usr/bin/env bash
echo "=========================== Starting Static Analysis Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
pushd "$(dirname "${BASH_SOURCE[0]}")/../"
# Run in a sandbox for every branch, run normally on master
[ "${TRAVIS_BRANCH}" != "master" ] && RUN_IN_SANDBOX="-sandboxname Transformers" || RUN_IN_SANDBOX=""
java -jar vosp-api-wrappers-java-$VERACODE_WRAPPER_VERSION.jar -vid $VERACODE_API_ID \
-vkey $VERACODE_API_KEY -action uploadandscan -appname "Transform Service" \
${RUN_IN_SANDBOX} -createprofile false \
-filepath \
alfresco-transform-core-aio/alfresco-transform-core-aio-boot/target/alfresco-transform-core-aio-boot*.jar \
-version "$TRAVIS_JOB_ID - $TRAVIS_JOB_NUMBER" -scantimeout 3600
popd
set +vex
echo "=========================== Finishing Static Analysis Script =========================="

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
echo "=========================== Starting Static Analysis Init Script ==========================="
PS4="\[\e[35m\]+ \[\e[m\]"
set -vex
pushd "$(dirname "${BASH_SOURCE[0]}")/../"
wget https://repo1.maven.org/maven2/com/veracode/vosp/api/wrappers/vosp-api-wrappers-java/$VERACODE_WRAPPER_VERSION/vosp-api-wrappers-java-$VERACODE_WRAPPER_VERSION.jar
sha1sum -c <<< "$VERACODE_WRAPPER_SHA1 vosp-api-wrappers-java-$VERACODE_WRAPPER_VERSION.jar"
popd
set +vex
echo "=========================== Finishing Static Analysis Init Script =========================="

View File

@ -0,0 +1,4 @@
# alfresco-transform-model
Alfresco Transform Model - Contains the data model of json configuration files
and messages sent between clients, T-Engines and T-Router. Also contains code to
work out which transform should be used for a combination of configuration files.

View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>Alfresco Transform Model</name>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-model</artifactId>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-core</artifactId>
<version>2.6.0-A2-SNAPSHOT</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
<version>3.8.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
<configuration>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
</configuration>
<executions>
<execution>
<id>default-test</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>2.0.1.alfresco-2</version>
<executions>
<execution>
<id>third-party-licenses</id>
<goals>
<goal>add-third-party</goal>
<goal>download-licenses</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<failOnMissing>true</failOnMissing>
<excludedScopes>provided,test</excludedScopes>
<excludedGroups>org.alfresco</excludedGroups>
<failIfWarning>true</failIfWarning>
<includedLicenses>https://raw.githubusercontent.com/Alfresco/third-party-license-overrides/master/includedLicenses.txt</includedLicenses>
<licenseMergesUrl>https://raw.githubusercontent.com/Alfresco/third-party-license-overrides/master/licenseMerges.txt</licenseMergesUrl>
<overrideUrl>https://raw.githubusercontent.com/Alfresco/third-party-license-overrides/master/override-THIRD-PARTY.properties</overrideUrl>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>license-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,174 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import org.alfresco.transform.router.TransformStack;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Holds required contextual information.
*
* @author Denis Ungureanu
* created on 10/01/2019
*/
public class InternalContext implements Serializable
{
private MultiStep multiStep;
private int attemptedRetries;
private String currentSourceMediaType;
private String currentTargetMediaType;
private String replyToDestination;
private Long currentSourceSize;
private Map<String, String> transformRequestOptions = new HashMap<>();
public MultiStep getMultiStep()
{
return multiStep;
}
public void setMultiStep(MultiStep multiStep)
{
this.multiStep = multiStep;
}
public int getAttemptedRetries()
{
return attemptedRetries;
}
public void setAttemptedRetries(int attemptedRetries)
{
this.attemptedRetries = attemptedRetries;
}
public String getCurrentSourceMediaType()
{
return currentSourceMediaType;
}
public void setCurrentSourceMediaType(String currentSourceMediaType)
{
this.currentSourceMediaType = currentSourceMediaType;
}
public String getCurrentTargetMediaType()
{
return currentTargetMediaType;
}
public void setCurrentTargetMediaType(String currentTargetMediaType)
{
this.currentTargetMediaType = currentTargetMediaType;
}
/**
* Gets the reply to destination name.
*
* @return replyToDestination
*/
public String getReplyToDestination()
{
return replyToDestination;
}
/**
* Sets the reply to destination name.
* Note: replyToDestination is populated from jmsMessage replyTo field sent by T-Client
*
* @param replyToDestination reply to destination name
*/
public void setReplyToDestination(String replyToDestination)
{
this.replyToDestination = replyToDestination;
}
public Long getCurrentSourceSize()
{
return currentSourceSize;
}
public void setCurrentSourceSize(Long currentSourceSize)
{
this.currentSourceSize = currentSourceSize;
}
public Map<String, String> getTransformRequestOptions()
{
return transformRequestOptions;
}
public void setTransformRequestOptions(
Map<String, String> transformRequestOptions)
{
this.transformRequestOptions = transformRequestOptions;
}
@Override public String toString()
{
return "InternalContext{" +
"multiStep=" + multiStep +
", attemptedRetries=" + attemptedRetries +
", currentSourceMediaType='" + currentSourceMediaType + '\'' +
", currentTargetMediaType='" + currentTargetMediaType + '\'' +
", replyToDestination='" + replyToDestination + '\'' +
", currentSourceSize=" + currentSourceSize +
", transformRequestOptions=" + transformRequestOptions +
'}';
}
// To avoid NPE checks, initialise expected bits of the structure
public static InternalContext initialise(InternalContext internalContext)
{
if (internalContext == null)
{
internalContext = new InternalContext();
}
if (internalContext.getMultiStep() == null)
{
internalContext.setMultiStep(new MultiStep());
}
if (internalContext.getMultiStep().getTransformsToBeDone() == null ||
internalContext.getMultiStep().getTransformsToBeDone().isEmpty()) // might be immutable
{
internalContext.getMultiStep().setTransformsToBeDone(new ArrayList<>());
}
return internalContext;
}
// To avoid additional checks
public static String checkForBasicErrors(InternalContext internalContext, String type)
{
return internalContext == null
? type + " InternalContext was null"
: internalContext.getMultiStep() == null
? type + " InternalContext did not have the MultiStep set"
: internalContext.getMultiStep().getTransformsToBeDone() == null
? type + " InternalContext did not have the TransformsToBeDone set"
: internalContext.getMultiStep().getInitialRequestId() == null
? type + " InternalContext did not have the InitialRequestId set"
: TransformStack.checkStructure(internalContext, type);
}
}

View File

@ -0,0 +1,364 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import static java.util.Arrays.stream;
import static java.util.Collections.unmodifiableSet;
import static java.util.stream.Collectors.toSet;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Objects;
import java.util.Set;
/**
* Mimetype
* <p>
* The same mimetypes defined in org.alfresco.repo.content.MimetypeMap
*/
public class Mimetype
{
//region Prefixes
public static final String PREFIX_APPLICATION = "application/";
public static final String PREFIX_AUDIO = "audio/";
public static final String PREFIX_IMAGE = "image/";
public static final String PREFIX_MESSAGE = "message/";
public static final String PREFIX_MODEL = "model/";
public static final String PREFIX_MULTIPART = "multipart/";
public static final String PREFIX_TEXT = "text/";
public static final String PREFIX_VIDEO = "video/";
public static final String EXTENSION_BINARY = "bin";
public static final String MACOS_RESOURCE_FORK_FILE_NAME_PREFIX = "._";
//endregion
//region Misc
public static final String MIMETYPE_MULTIPART_ALTERNATIVE = "multipart/alternative";
public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
public static final String MIMETYPE_TEXT_MEDIAWIKI = "text/mediawiki";
public static final String MIMETYPE_TEXT_CSS = "text/css";
public static final String MIMETYPE_TEXT_CSV = "text/csv";
public static final String MIMETYPE_TEXT_JAVASCRIPT = "text/javascript";
public static final String MIMETYPE_XML = "text/xml";
public static final String MIMETYPE_HTML = "text/html";
public static final String MIMETYPE_XHTML = "application/xhtml+xml";
public static final String MIMETYPE_PDF = "application/pdf";
public static final String MIMETYPE_JSON = "application/json";
public static final String MIMETYPE_WORD = "application/msword";
public static final String MIMETYPE_EXCEL = "application/vnd.ms-excel";
public static final String MIMETYPE_BINARY = "application/octet-stream";
public static final String MIMETYPE_PPT = "application/vnd.ms-powerpoint";
public static final String MIMETYPE_APP_DWG = "application/dwg";
public static final String MIMETYPE_IMG_DWG = "image/vnd.dwg";
public static final String MIMETYPE_VIDEO_AVI = "video/x-msvideo";
public static final String MIMETYPE_VIDEO_QUICKTIME = "video/quicktime";
public static final String MIMETYPE_VIDEO_WMV = "video/x-ms-wmv";
public static final String MIMETYPE_VIDEO_3GP = "video/3gpp";
public static final String MIMETYPE_VIDEO_3GP2 = "video/3gpp2";
public static final String MIMETYPE_DITA = "application/dita+xml";
//endregion
//region Flash
public static final String MIMETYPE_FLASH = "application/x-shockwave-flash";
public static final String MIMETYPE_VIDEO_FLV = "video/x-flv";
public static final String MIMETYPE_APPLICATION_FLA = "application/x-fla";
public static final String MIMETYPE_VIDEO_MPG = "video/mpeg";
public static final String MIMETYPE_VIDEO_MP4 = "video/mp4";
public static final String MIMETYPE_IMAGE_GIF = "image/gif";
public static final String MIMETYPE_IMAGE_JPEG = "image/jpeg";
public static final String MIMETYPE_IMAGE_RGB = "image/x-rgb";
public static final String MIMETYPE_IMAGE_SVG = "image/svg+xml";
public static final String MIMETYPE_IMAGE_PNG = "image/png";
public static final String MIMETYPE_IMAGE_TIFF = "image/tiff";
public static final String MIMETYPE_IMAGE_RAW_DNG = "image/x-raw-adobe";
public static final String MIMETYPE_IMAGE_RAW_3FR = "image/x-raw-hasselblad";
public static final String MIMETYPE_IMAGE_RAW_RAF = "image/x-raw-fuji";
public static final String MIMETYPE_IMAGE_RAW_CR2 = "image/x-raw-canon";
public static final String MIMETYPE_IMAGE_RAW_K25 = "image/x-raw-kodak";
public static final String MIMETYPE_IMAGE_RAW_MRW = "image/x-raw-minolta";
public static final String MIMETYPE_IMAGE_RAW_NEF = "image/x-raw-nikon";
public static final String MIMETYPE_IMAGE_RAW_ORF = "image/x-raw-olympus";
public static final String MIMETYPE_IMAGE_RAW_PEF = "image/x-raw-pentax";
public static final String MIMETYPE_IMAGE_RAW_ARW = "image/x-raw-sony";
public static final String MIMETYPE_IMAGE_RAW_X3F = "image/x-raw-sigma";
public static final String MIMETYPE_IMAGE_RAW_RW2 = "image/x-raw-panasonic";
public static final String MIMETYPE_IMAGE_RAW_RWL = "image/x-raw-leica";
public static final String MIMETYPE_IMAGE_RAW_R3D = "image/x-raw-red";
public static final String MIMETYPE_IMAGE_DWT = "image/x-dwt";
public static final String MIMETYPE_APPLICATION_EPS = "application/eps";
public static final String MIMETYPE_APPLICATION_PS = "application/postscript";
public static final String MIMETYPE_JAVASCRIPT = "application/x-javascript";
public static final String MIMETYPE_ZIP = "application/zip";
public static final String MIMETYPE_OPENSEARCH_DESCRIPTION = "application/opensearchdescription+xml";
public static final String MIMETYPE_ATOM = "application/atom+xml";
public static final String MIMETYPE_RSS = "application/rss+xml";
public static final String MIMETYPE_RFC822 = "message/rfc822";
public static final String MIMETYPE_OUTLOOK_MSG = "application/vnd.ms-outlook";
public static final String MIMETYPE_VISIO = "application/vnd.visio";
public static final String MIMETYPE_VISIO_2013 = "application/vnd.visio2013";
//endregion
//region Adobe
public static final String MIMETYPE_APPLICATION_ILLUSTRATOR = "application/illustrator";
public static final String MIMETYPE_APPLICATION_PHOTOSHOP = "image/vnd.adobe.photoshop";
//endregion
//region Encrypted office document
public static final String MIMETYPE_ENCRYPTED_OFFICE = "application/x-tika-ooxml-protected";
//endregion
//region Open Document
public static final String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text";
public static final String MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE = "application/vnd.oasis.opendocument.text-template";
public static final String MIMETYPE_OPENDOCUMENT_GRAPHICS = "application/vnd.oasis.opendocument.graphics";
public static final String MIMETYPE_OPENDOCUMENT_GRAPHICS_TEMPLATE = "application/vnd.oasis.opendocument.graphics-template";
public static final String MIMETYPE_OPENDOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation";
public static final String MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE = "application/vnd.oasis.opendocument.presentation-template";
public static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
public static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE = "application/vnd.oasis.opendocument.spreadsheet-template";
public static final String MIMETYPE_OPENDOCUMENT_CHART = "application/vnd.oasis.opendocument.chart";
public static final String MIMETYPE_OPENDOCUMENT_CHART_TEMPLATE = "applicationvnd.oasis.opendocument.chart-template";
public static final String MIMETYPE_OPENDOCUMENT_IMAGE = "application/vnd.oasis.opendocument.image";
public static final String MIMETYPE_OPENDOCUMENT_IMAGE_TEMPLATE = "applicationvnd.oasis.opendocument.image-template";
public static final String MIMETYPE_OPENDOCUMENT_FORMULA = "application/vnd.oasis.opendocument.formula";
public static final String MIMETYPE_OPENDOCUMENT_FORMULA_TEMPLATE = "applicationvnd.oasis.opendocument.formula-template";
public static final String MIMETYPE_OPENDOCUMENT_TEXT_MASTER = "application/vnd.oasis.opendocument.text-master";
public static final String MIMETYPE_OPENDOCUMENT_TEXT_WEB = "application/vnd.oasis.opendocument.text-web";
public static final String MIMETYPE_OPENDOCUMENT_DATABASE = "application/vnd.oasis.opendocument.database";
//endregion
//region Open Office
public static final String MIMETYPE_OPENOFFICE1_WRITER = "application/vnd.sun.xml.writer";
public static final String MIMETYPE_OPENOFFICE1_CALC = "application/vnd.sun.xml.calc";
public static final String MIMETYPE_OPENOFFICE1_DRAW = "application/vnd.sun.xml.draw";
public static final String MIMETYPE_OPENOFFICE1_IMPRESS = "application/vnd.sun.xml.impress";
//endregion
//region Open XML
public static final String MIMETYPE_OPENXML_WORDPROCESSING = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
public static final String MIMETYPE_OPENXML_WORDPROCESSING_MACRO = "application/vnd.ms-word.document.macroenabled.12";
public static final String MIMETYPE_OPENXML_WORD_TEMPLATE = "application/vnd.openxmlformats-officedocument.wordprocessingml.template";
public static final String MIMETYPE_OPENXML_WORD_TEMPLATE_MACRO = "application/vnd.ms-word.template.macroenabled.12";
public static final String MIMETYPE_OPENXML_SPREADSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
public static final String MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE = "application/vnd.openxmlformats-officedocument.spreadsheetml.template";
public static final String MIMETYPE_OPENXML_SPREADSHEET_MACRO = "application/vnd.ms-excel.sheet.macroenabled.12";
public static final String MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE_MACRO = "application/vnd.ms-excel.template.macroenabled.12";
public static final String MIMETYPE_OPENXML_SPREADSHEET_ADDIN_MACRO = "application/vnd.ms-excel.addin.macroenabled.12";
public static final String MIMETYPE_OPENXML_SPREADSHEET_BINARY_MACRO = "application/vnd.ms-excel.sheet.binary.macroenabled.12";
public static final String MIMETYPE_OPENXML_PRESENTATION = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
public static final String MIMETYPE_OPENXML_PRESENTATION_MACRO = "application/vnd.ms-powerpoint.presentation.macroenabled.12";
public static final String MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW = "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
public static final String MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW_MACRO = "application/vnd.ms-powerpoint.slideshow.macroenabled.12";
public static final String MIMETYPE_OPENXML_PRESENTATION_TEMPLATE = "application/vnd.openxmlformats-officedocument.presentationml.template";
public static final String MIMETYPE_OPENXML_PRESENTATION_TEMPLATE_MACRO = "application/vnd.ms-powerpoint.template.macroenabled.12";
public static final String MIMETYPE_OPENXML_PRESENTATION_ADDIN = "application/vnd.ms-powerpoint.addin.macroenabled.12";
public static final String MIMETYPE_OPENXML_PRESENTATION_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.slide";
public static final String MIMETYPE_OPENXML_PRESENTATION_SLIDE_MACRO = "application/vnd.ms-powerpoint.slide.macroenabled.12";
//endregion
//region Star Office
public static final String MIMETYPE_STAROFFICE5_DRAW = "application/vnd.stardivision.draw";
public static final String MIMETYPE_STAROFFICE5_CALC = "application/vnd.stardivision.calc";
public static final String MIMETYPE_STAROFFICE5_IMPRESS = "application/vnd.stardivision.impress";
public static final String MIMETYPE_STAROFFICE5_IMPRESS_PACKED = "application/vnd.stardivision.impress-packed";
public static final String MIMETYPE_STAROFFICE5_CHART = "application/vnd.stardivision.chart";
public static final String MIMETYPE_STAROFFICE5_WRITER = "application/vnd.stardivision.writer";
public static final String MIMETYPE_STAROFFICE5_WRITER_GLOBAL = "application/vnd.stardivision.writer-global";
public static final String MIMETYPE_STAROFFICE5_MATH = "application/vnd.stardivision.math";
//endregion
//region Apple iWorks
public static final String MIMETYPE_IWORK_KEYNOTE = "application/vnd.apple.keynote";
public static final String MIMETYPE_IWORK_NUMBERS = "application/vnd.apple.numbers";
public static final String MIMETYPE_IWORK_PAGES = "application/vnd.apple.pages";
//endregion
//region MACOS
public static final String MIMETYPE_APPLEFILE = "application/applefile";
//endregion
//region WordPerfect
public static final String MIMETYPE_WORDPERFECT = "application/wordperfect";
//endregion
//region Audio
public static final String MIMETYPE_MP3 = "audio/mpeg";
public static final String MIMETYPE_AUDIO_MP4 = "audio/mp4";
public static final String MIMETYPE_VORBIS = "audio/vorbis";
public static final String MIMETYPE_FLAC = "audio/x-flac";
//endregion
//region Alfresco
public static final String MIMETYPE_ACP = "application/acp";
//region other
public static final String MIMETYPE_PBM = "image/x-portable-bitmap";
public static final String MIMETYPE_PNM = "image/x-portable-anymap";
public static final String MIMETYPE_XBM = "image/x-xbitmap";
public static final String MIMETYPE_XPM = "image/x-xpixmap";
public static final String MIMETYPE_Z = "application/x-compress";
public static final String MIMETYPE_PPM = "image/x-portable-pixmap";
public static final String MIMETYPE_TAR = "application/x-tar";
public static final String MIMETYPE_OGG = "application/ogg";
//endregion
private static final Set<String> ALL_MIMETYPES;
static
{
ALL_MIMETYPES = unmodifiableSet(stream(Mimetype.class.getDeclaredFields())
.filter(f -> Modifier.isPublic(f.getModifiers()))
.filter(f -> Modifier.isStatic(f.getModifiers()))
.filter(f -> Modifier.isFinal(f.getModifiers()))
.filter(f -> f.getType().isAssignableFrom(String.class))
.filter(f -> f.getName().startsWith("MIMETYPE_"))
.peek(f -> f.setAccessible(true))
.map(Mimetype::getFieldValue)
.filter(Objects::nonNull)
.collect(toSet()));
}
private static String getFieldValue(final Field f)
{
try
{
return (String) f.get(null);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
public static Set<String> matchMimetypes(final String regex)
{
return unmodifiableSet(ALL_MIMETYPES
.stream()
.filter(t -> t.matches(regex))
.collect(toSet()));
}
}

View File

@ -0,0 +1,82 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Holds required contextual information for a multi-step transform.
*
* @author Lucian Tuca
* created on 19/12/2018
*/
public class MultiStep implements Serializable
{
private String initialRequestId;
private String initialSourceMediaType;
private List<String> transformsToBeDone = new ArrayList<>();
// regions [Accessors]
public String getInitialSourceMediaType()
{
return initialSourceMediaType;
}
public void setInitialSourceMediaType(String initialSourceMediaType)
{
this.initialSourceMediaType = initialSourceMediaType;
}
public String getInitialRequestId()
{
return initialRequestId;
}
public void setInitialRequestId(String initialRequestId)
{
this.initialRequestId = initialRequestId;
}
public List<String> getTransformsToBeDone()
{
return transformsToBeDone;
}
public void setTransformsToBeDone(List<String> transformsToBeDone)
{
this.transformsToBeDone = transformsToBeDone;
}
//endregion
@Override public String toString()
{
return "MultiStep{" +
"initialRequestId='" + initialRequestId + '\'' +
", initialSourceMediaType='" + initialSourceMediaType + '\'' +
", transformsToBeDone=" + transformsToBeDone +
'}';
}
}

View File

@ -0,0 +1,216 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import java.io.Serializable;
import java.util.Objects;
public class TransformReply implements Serializable
{
private String requestId;
private int status;
private String errorDetails;
private String sourceReference;
private String targetReference;
private String clientData;
private int schema;
private InternalContext internalContext;
//region [Accessors]
public String getRequestId()
{
return requestId;
}
public void setRequestId(String requestId)
{
this.requestId = requestId;
}
public int getStatus()
{
return status;
}
public void setStatus(int status)
{
this.status = status;
}
public String getErrorDetails()
{
return errorDetails;
}
public void setErrorDetails(String errorDetails)
{
this.errorDetails = errorDetails;
}
public String getSourceReference()
{
return sourceReference;
}
public void setSourceReference(String sourceReference)
{
this.sourceReference = sourceReference;
}
public String getTargetReference()
{
return targetReference;
}
public void setTargetReference(String targetReference)
{
this.targetReference = targetReference;
}
public String getClientData()
{
return clientData;
}
public void setClientData(String clientData)
{
this.clientData = clientData;
}
public int getSchema()
{
return schema;
}
public void setSchema(int schema)
{
this.schema = schema;
}
public InternalContext getInternalContext()
{
return internalContext;
}
public void setInternalContext(InternalContext internalContext)
{
this.internalContext = internalContext;
}
//endregion
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TransformReply that = (TransformReply) o;
return Objects.equals(requestId, that.requestId);
}
@Override
public int hashCode()
{
return Objects.hash(requestId);
}
@Override public String toString()
{
return "TransformReply{" +
"requestId='" + requestId + '\'' +
", status=" + status +
", errorDetails='" + errorDetails + '\'' +
", sourceReference='" + sourceReference + '\'' +
", targetReference='" + targetReference + '\'' +
", clientData='" + clientData + '\'' +
", schema=" + schema +
", internalContext=" + internalContext +
'}';
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private final TransformReply reply = new TransformReply();
private Builder() {}
public Builder withRequestId(final String requestId)
{
reply.requestId = requestId;
return this;
}
public Builder withStatus(final int status)
{
reply.status = status;
return this;
}
public Builder withErrorDetails(final String errorDetails)
{
reply.errorDetails = errorDetails;
return this;
}
public Builder withSourceReference(final String sourceReference)
{
reply.sourceReference = sourceReference;
return this;
}
public Builder withTargetReference(final String targetReference)
{
reply.targetReference = targetReference;
return this;
}
public Builder withClientData(final String clientData)
{
reply.clientData = clientData;
return this;
}
public Builder withSchema(final int schema)
{
reply.schema = schema;
return this;
}
public Builder withInternalContext(final InternalContext internalContext)
{
reply.internalContext = internalContext;
return this;
}
public TransformReply build()
{
return reply;
}
}
}

View File

@ -0,0 +1,273 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class TransformRequest implements Serializable
{
private String requestId;
private String sourceReference;
private String sourceMediaType;
private Long sourceSize;
private String sourceExtension;
private String targetMediaType;
private String targetExtension;
private String clientData;
private int schema;
private Map<String, String> transformRequestOptions = new HashMap<>();
private InternalContext internalContext;
// regions [Accessors]
public String getRequestId()
{
return requestId;
}
public void setRequestId(String requestId)
{
this.requestId = requestId;
}
public String getSourceReference()
{
return sourceReference;
}
public void setSourceReference(String sourceReference)
{
this.sourceReference = sourceReference;
}
public String getSourceMediaType()
{
return sourceMediaType;
}
public void setSourceMediaType(String sourceMediaType)
{
this.sourceMediaType = sourceMediaType;
}
public Long getSourceSize()
{
return sourceSize;
}
public void setSourceSize(Long sourceSize)
{
this.sourceSize = sourceSize;
}
public String getSourceExtension()
{
return sourceExtension;
}
public void setSourceExtension(String sourceExtension)
{
this.sourceExtension = sourceExtension;
}
public String getTargetMediaType()
{
return targetMediaType;
}
public void setTargetMediaType(String targetMediaType)
{
this.targetMediaType = targetMediaType;
}
public String getTargetExtension()
{
return targetExtension;
}
public void setTargetExtension(String targetExtension)
{
this.targetExtension = targetExtension;
}
public String getClientData()
{
return clientData;
}
public void setClientData(String clientData)
{
this.clientData = clientData;
}
public int getSchema()
{
return schema;
}
public void setSchema(int schema)
{
this.schema = schema;
}
public Map<String, String> getTransformRequestOptions()
{
return transformRequestOptions;
}
public void setTransformRequestOptions(Map<String, String> transformRequestOptions)
{
this.transformRequestOptions = transformRequestOptions;
}
public InternalContext getInternalContext()
{
return internalContext;
}
public void setInternalContext(InternalContext internalContext)
{
this.internalContext = internalContext;
}
//endregion
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TransformRequest that = (TransformRequest) o;
return Objects.equals(requestId, that.requestId);
}
@Override
public int hashCode()
{
return Objects.hash(requestId);
}
@Override public String toString()
{
return "TransformRequest{" +
"requestId='" + requestId + '\'' +
", sourceReference='" + sourceReference + '\'' +
", sourceMediaType='" + sourceMediaType + '\'' +
", sourceSize=" + sourceSize +
", sourceExtension='" + sourceExtension + '\'' +
", targetMediaType='" + targetMediaType + '\'' +
", targetExtension='" + targetExtension + '\'' +
", clientData='" + clientData + '\'' +
", schema=" + schema +
", transformRequestOptions=" + transformRequestOptions +
", internalContext=" + internalContext +
'}';
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private final TransformRequest request = new TransformRequest();
private Builder() {}
public Builder withRequestId(final String requestId)
{
request.requestId = requestId;
return this;
}
public Builder withInternalContext(final InternalContext internalContext)
{
request.internalContext = internalContext;
return this;
}
public Builder withSourceReference(final String sourceReference)
{
request.sourceReference = sourceReference;
return this;
}
public Builder withSourceMediaType(final String sourceMediaType)
{
request.sourceMediaType = sourceMediaType;
return this;
}
public Builder withSourceSize(final Long sourceSize)
{
request.sourceSize = sourceSize;
return this;
}
public Builder withSourceExtension(final String sourceExtension)
{
request.sourceExtension = sourceExtension;
return this;
}
public Builder withTargetMediaType(final String targetMediaType)
{
request.targetMediaType = targetMediaType;
return this;
}
public Builder withTargetExtension(final String targetExtension)
{
request.targetExtension = targetExtension;
return this;
}
public Builder withClientData(final String clientData)
{
request.clientData = clientData;
return this;
}
public Builder withTransformRequestOptions(
final Map<String, String> transformRequestOptions)
{
request.transformRequestOptions = transformRequestOptions;
return this;
}
public Builder withSchema(final int schema)
{
request.schema = schema;
return this;
}
public TransformRequest build()
{
return request;
}
}
}

View File

@ -0,0 +1,93 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* TransformRequestValidator
* <p>
* Transform request validator
*/
public class TransformRequestValidator implements Validator
{
@Override
public boolean supports(Class<?> aClass)
{
return aClass.isAssignableFrom(TransformRequest.class);
}
@Override
public void validate(Object o, Errors errors)
{
final TransformRequest request = (TransformRequest) o;
if (request == null)
{
errors.reject(null, "request cannot be null");
}
else
{
String requestId = request.getRequestId();
if (requestId == null || requestId.isEmpty())
{
errors.rejectValue("requestId", null, "requestId cannot be null or empty");
}
Long sourceSize = request.getSourceSize();
if (sourceSize == null || sourceSize <= 0)
{
errors.rejectValue("sourceSize", null,
"sourceSize cannot be null or have its value smaller than 0");
}
String sourceMediaType = request.getSourceMediaType();
if (sourceMediaType == null || sourceMediaType.isEmpty())
{
errors.rejectValue("sourceMediaType", null,
"sourceMediaType cannot be null or empty");
}
String targetMediaType = request.getTargetMediaType();
if (targetMediaType == null || targetMediaType.isEmpty())
{
errors.rejectValue("targetMediaType", null,
"targetMediaType cannot be null or empty");
}
String targetExtension = request.getTargetExtension();
if (targetExtension == null || targetExtension.isEmpty())
{
errors.rejectValue("targetExtension", null,
"targetExtension cannot be null or empty");
}
String clientData = request.getClientData();
if (clientData == null || clientData.isEmpty())
{
errors.rejectValue("clientData", String.valueOf(request.getSchema()),
"clientData cannot be null or empty");
}
if (request.getSchema() < 0)
{
errors.rejectValue("schema", String.valueOf(request.getSchema()),
"schema cannot be less than 0");
}
}
}
}

View File

@ -0,0 +1,76 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
/**
* Abstract implementation of TransformOption.
*/
public abstract class AbstractTransformOption implements TransformOption
{
private boolean required;
public AbstractTransformOption()
{
}
public AbstractTransformOption(boolean required)
{
this.required = required;
}
@Override
public boolean isRequired()
{
return required;
}
@Override
public void setRequired(boolean required)
{
this.required = required;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AbstractTransformOption that = (AbstractTransformOption) o;
return required == that.required;
}
@Override
public int hashCode()
{
return Objects.hash(required);
}
@Override
public String toString()
{
return "AbstractTransformOption{" +
"required=" + required +
'}';
}
}

View File

@ -0,0 +1,53 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
/**
* Holds information to add new {@link SupportedSourceAndTarget} objects to an existing {@link Transformer}.<p><br>
*
* <pre>
* "addSupported": [
* {
* "transformerName": "Archive",
* "sourceMediaType": "application/zip",
* "targetMediaType": "text/xml",
* "priority": 60,
* "maxSourceSizeBytes": 18874368
* }
* ]
* </pre>
*/
public class AddSupported extends TransformerTypesSizeAndPriority
{
public static Builder builder()
{
return new Builder();
}
public static class Builder extends TransformerTypesSizeAndPriority.Builder<Builder, AddSupported>
{
private Builder()
{
super(new AddSupported());
}
}
}

View File

@ -0,0 +1,81 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import org.apache.maven.artifact.versioning.ComparableVersion;
import static org.alfresco.transform.client.model.config.CoreFunction.Constants.NO_UPPER_VERSION;
import static org.alfresco.transform.client.model.config.CoreFunction.Constants.NO_VERSION;
/**
* Provides a mapping between a transform {@code coreVersion} and functionality (such as the use of Direct Access URLs)
* supported in that version of the {@code alfresco-transform-base}, so that clients know if they may use it.
*/
public enum CoreFunction
{
/** May provide a Direct Access URL rather than upload a file **/
DIRECT_ACCESS_URL("2.5.7", null),
/** May request a transform via ActiveMQ **/
// Original version was HTTP only. However none of these are still operational
ACTIVE_MQ("1", null),
/** Original way to talk to a T-Engine **/
// The toValue really should be null rather than "9999" but gives us an upper test value
HTTP(null, "99999");
private final ComparableVersion fromVersion;
private final ComparableVersion toVersion;
public boolean isSupported(String version)
{
ComparableVersion comparableVersion = newComparableVersion(version, Constants.NO_VERSION);
return comparableVersion.compareTo(fromVersion) >= 0 && comparableVersion.compareTo(toVersion) <= 0;
}
CoreFunction(String fromVersion, String toVersion)
{
this.fromVersion = newComparableVersion(fromVersion, NO_VERSION);
this.toVersion = newComparableVersion(toVersion, NO_UPPER_VERSION);
}
static ComparableVersion newComparableVersion(String version, ComparableVersion defaultValue)
{
if (version == null)
{
return defaultValue;
}
int i = version.indexOf('-');
version = i > 0
? version.substring(0, i)
: version;
return new ComparableVersion(version);
}
static class Constants
{
static final ComparableVersion NO_VERSION = new ComparableVersion("");
static final ComparableVersion NO_UPPER_VERSION = new ComparableVersion(Integer.toString(Integer.MAX_VALUE));
}
}

View File

@ -0,0 +1,182 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import org.apache.maven.artifact.versioning.ComparableVersion;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.function.Predicate.not;
import static org.alfresco.transform.client.model.config.CoreFunction.Constants.NO_VERSION;
import static org.alfresco.transform.client.model.config.CoreFunction.newComparableVersion;
import static org.alfresco.transform.client.util.RequestParamMap.DIRECT_ACCESS_URL;
/**
* <p>Class sets or clears the {@code coreVersion} property of {@link Transformer}s in a {@link TransformConfig}<p/>
*
* <p>Since alfresco-transform-core 5.2.7, the config returned by T-Engines and T-Router via their
* {@code "/transform/config"} endpoint has been decorated with an {@code coreVersion} element, indicating what core
* functionality is provided by each transformer as a result of extending the {@code AbstractTransformerController} in
* the {@code alfresco-transform-base}. This is automatically added, so need not be specified by the T-Engine developer.
* It was originally added to indicate that it was possible to use Direct Access URLs (DAU).</p>
*
* <p>This class provides methods to sets or clear the field with the version number of the
* {@code alfresco-transform-base}. No value indicates 5.2.6 or earlier.</p>
*
* <p>To allow older and newer version of the Repository, T-Router and T-Engines to work together, this field is only
* returned if requested by a client that also knows about the field. An optional {@code "configVersion"} parameter
* has been added to the endpoint. The config for T-Engines need only add the field to single-step-transforms. When
* configs are combined it is then possible to add this field to pipeline and failover transforms by using the lowest
* core value of any step transform.</p>
*
* <p>If the field is not requested in the T-Router or the all-in-one transformer endpoint, it may need to be stripped
* from the {@link TransformConfig} as some of the T-Engines may have supplied it.</p>
*
* @see CoreFunction
*/
public class CoreVersionDecorator
{
public static final int CONFIG_VERSION_INCLUDES_CORE_VERSION = 2;
private static final Set<TransformOption> DIRECT_ACCESS_URL_TRANSFORM_OPTIONS =
Set.of(new TransformOptionValue(false, DIRECT_ACCESS_URL));
/**
* Returns a {@link TransformConfig} that includes or excludes the {@code coreVersion} field.
*/
public static TransformConfig setOrClearCoreVersion(TransformConfig transformConfig, int configVersion)
{
boolean includeCoreVersion = configVersion >= 2;
Map<String, Set<TransformOption>> transformOptions = new HashMap<>(transformConfig.getTransformOptions());
return TransformConfig.builder()
// We may need to create new Transformers as we must not change the original.
.withTransformers(transformConfig.getTransformers().stream()
.map(transformer -> {
if (( includeCoreVersion && transformer.getCoreVersion() == null) ||
(!includeCoreVersion && transformer.getCoreVersion() != null))
{
transformer = Transformer.builder()
.withCoreVersion(includeCoreVersion ? transformer.getCoreVersion() : null)
.withTransformOptions(setOrClearCoreTransformOptions(
includeCoreVersion ? transformer.getCoreVersion() : null,
transformOptions, transformer.getTransformOptions()))
// Original values
.withTransformerName(transformer.getTransformerName())
.withTransformerPipeline(transformer.getTransformerPipeline())
.withTransformerFailover(transformer.getTransformerFailover())
.withSupportedSourceAndTargetList(transformer.getSupportedSourceAndTargetList())
.build();
}
return transformer;
})
.collect(Collectors.toList()))
.withTransformOptions(transformOptions)
// Original values
.withRemoveTransformers(transformConfig.getRemoveTransformers())
.withAddSupported(transformConfig.getAddSupported())
.withRemoveSupported(transformConfig.getRemoveSupported())
.withOverrideSupported(transformConfig.getOverrideSupported())
.withSupportedDefaults(transformConfig.getSupportedDefaults())
.build();
}
public static void setCoreVersionOnSingleStepTransformers(TransformConfig transformConfig, String coreVersion)
{
Map<String, Set<TransformOption>> transformOptions = transformConfig.getTransformOptions();
List<Transformer> transformers = transformConfig.getTransformers();
transformers.stream()
.filter(CoreVersionDecorator::isSingleStep)
.forEach(transformer -> {
transformer.setCoreVersion(coreVersion);
transformer.setTransformOptions(setOrClearCoreTransformOptions(coreVersion,
transformOptions, transformer.getTransformOptions()));
});
}
/**
* The list of {@code transformers} must not contain forward references
*/
public static void setCoreVersionOnMultiStepTransformers(Map<String, Set<TransformOption>> transformOptions,
List<Transformer> transformers)
{
Map<String, Transformer> transformersByName = transformers.stream()
.collect(Collectors.toMap(Transformer::getTransformerName, Function.identity()));
transformers.stream()
.filter(not(CoreVersionDecorator::isSingleStep))
.forEach(transformer -> {
// Create a list of step transformers
List<String> namesOfStepTransformers = transformer.getTransformerFailover().isEmpty()
? transformer.getTransformerPipeline().stream()
.map(TransformStep::getTransformerName)
.collect(Collectors.toList())
: transformer.getTransformerFailover();
// Set the coreVersion to the lowest step transformer value
ComparableVersion minCoreVersion = namesOfStepTransformers.stream()
.map(transformerName -> transformersByName.get(transformerName).getCoreVersion())
.map(coreVersion -> newComparableVersion(coreVersion, NO_VERSION))
.min(ComparableVersion::compareTo).orElse(NO_VERSION);
String coreVersion = NO_VERSION.equals(minCoreVersion) ? null : minCoreVersion.toString();
transformer.setCoreVersion(coreVersion);
transformer.setTransformOptions(setOrClearCoreTransformOptions(transformer.getCoreVersion(),
transformOptions, transformer.getTransformOptions()));
});
}
private static Set<String> setOrClearCoreTransformOptions(String coreVersion, Map<String,
Set<TransformOption>> transformOptions, Set<String> transformerTransformOptions)
{
// If we have more options being added in future, consider adding an interface that will be implemented by
// different implementations for each coreVersion and then iterate over them here.
transformerTransformOptions = new HashSet<>(transformerTransformOptions);
if (CoreFunction.DIRECT_ACCESS_URL.isSupported(coreVersion))
{
// Added to the Transform config if any Transformers support it.
transformOptions.put(DIRECT_ACCESS_URL, DIRECT_ACCESS_URL_TRANSFORM_OPTIONS);
// Add DIRECT_ACCESS_URL to a copy of this Transformer's transform options.
transformerTransformOptions.add(DIRECT_ACCESS_URL);
}
else
{
transformOptions.remove(DIRECT_ACCESS_URL);
transformerTransformOptions.remove(DIRECT_ACCESS_URL);
}
return transformerTransformOptions;
}
private static boolean isSingleStep(Transformer transformer)
{
return (transformer.getTransformerFailover() == null || transformer.getTransformerFailover().isEmpty()) &&
(transformer.getTransformerPipeline() == null || transformer.getTransformerPipeline().isEmpty());
}
}

View File

@ -0,0 +1,54 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
/**
* Holds information to overriding existing {@link SupportedSourceAndTarget} objects with new {@code priority} and
* {@code maxSourceSizeBytes} values.<p><br>
*
* <pre>
* "overrideSupported" : [
* {
* "transformerName": "Archive", // override and existing entry
* "sourceMediaType": "application/zip",
* "targetMediaType": "text/html",
* "priority": 60,
* "maxSourceSizeBytes": 18874368
* }
* ]
* </pre>
*/
public class OverrideSupported extends TransformerTypesSizeAndPriority
{
public static Builder builder()
{
return new Builder();
}
public static class Builder extends TransformerTypesSizeAndPriority.Builder<Builder, OverrideSupported>
{
private Builder()
{
super(new OverrideSupported());
}
}
}

View File

@ -0,0 +1,56 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
/**
* Holds information about existing {@link SupportedSourceAndTarget} objects that should be removed.<p><br>
*
* <pre>
* "removeSupported" : [
* {
* "transformerName": "Archive",
* "sourceMediaType": "application/zip",
* "targetMediaType": "text/xml"
* }
* ]
* </pre>
*/
public class RemoveSupported extends TransformerAndTypes
{
@Override
public String toString()
{
return "{"+super.toString()+"}";
}
public static Builder builder()
{
return new Builder();
}
public static class Builder extends TransformerAndTypes.Builder<Builder, RemoveSupported>
{
private Builder()
{
super(new RemoveSupported());
}
}
}

View File

@ -0,0 +1,182 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Holds information to provide default {@code priority} and / or {@code maxSourceSizeBytes} defaults. In precedence
* order from most specific to most general these are defined by combinations of {@code transformerName} and
* {@code sourceMediaType}:<p><br>
* <ul>
* <li><b>transformer and source media type default</b> {@code transformerName} + {@code sourceMediaType}</li>
* <li><b>transformer default</b> {@code transformerName}</li>
* <li><b>source media type default</b> {@code sourceMediaType}</li>
* <li><b>system wide default</b> none</li>
* </ul><br>
*
* Both {@code maxSourceSizeBytes} and {@code priority} may be specified in a {@code "supportedDefaults"} element, but
* if only one is specified it is only that value that is being defaulted at the level specified by the combination of
* {@code transformerName} and {@code sourceMediaType}.<p><br>
*
* <pre>
* "supportedDefaults" : [
* {
* "transformerName": "Office", // default for a source type within a transformer
* "sourceMediaType": "application/zip",
* "maxSourceSizeBytes": 18874368
* },
* {
* "sourceMediaType": "application/msword", // defaults for a source type
* "maxSourceSizeBytes": 4194304,
* "priority": 45
* },
* {
* "priority": 60 // system default
* }
* {
* "maxSourceSizeBytes": -1 // system default
* }
* ]
* </pre>
*/
public class SupportedDefaults
{
String transformerName;
String sourceMediaType;
Long maxSourceSizeBytes = null;
Integer priority = null;
public String getTransformerName()
{
return transformerName;
}
public void setTransformerName(String transformerName)
{
this.transformerName = transformerName;
}
public String getSourceMediaType()
{
return sourceMediaType;
}
public void setSourceMediaType(String sourceMediaType)
{
this.sourceMediaType = sourceMediaType;
}
public Long getMaxSourceSizeBytes()
{
return maxSourceSizeBytes;
}
public void setMaxSourceSizeBytes(long maxSourceSizeBytes)
{
this.maxSourceSizeBytes = maxSourceSizeBytes;
}
public Integer getPriority()
{
return priority;
}
public void setPriority(int priority)
{
this.priority = priority;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SupportedDefaults that = (SupportedDefaults)o;
return Objects.equals(transformerName, that.transformerName) &&
Objects.equals(sourceMediaType, that.sourceMediaType) &&
Objects.equals(maxSourceSizeBytes, that.maxSourceSizeBytes) &&
Objects.equals(priority, that.priority);
}
@Override
public int hashCode()
{
return Objects.hash(transformerName, sourceMediaType, maxSourceSizeBytes, priority);
}
@Override
public String toString()
{
StringJoiner sj = new StringJoiner(", ");
if (transformerName != null) sj.add("\"transformerName\": \""+transformerName+'"');
if (sourceMediaType != null) sj.add("\"sourceMediaType\": \""+sourceMediaType+'"');
if (maxSourceSizeBytes != null) sj.add("\"maxSourceSizeBytes\": \""+maxSourceSizeBytes+'"');
if (priority != null) sj.add("\"priority\": \""+priority+'"');
return "{" + sj.toString() + "}";
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
final SupportedDefaults supportedDefaults;
protected Builder()
{
this.supportedDefaults = new SupportedDefaults();
}
public SupportedDefaults build()
{
return supportedDefaults;
}
public Builder withTransformerName(final String transformerName)
{
supportedDefaults.transformerName = transformerName;
return this;
}
public Builder withSourceMediaType(final String sourceMediaType)
{
supportedDefaults.sourceMediaType = sourceMediaType;
return this;
}
public Builder withMaxSourceSizeBytes(final Long maxSourceSizeBytes)
{
supportedDefaults.maxSourceSizeBytes = maxSourceSizeBytes;
return this;
}
public Builder withPriority(final Integer priority)
{
supportedDefaults.priority = priority;
return this;
}
}
}

View File

@ -0,0 +1,112 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Represents a single source and target combination supported by a transformer. Each combination has an optional
* maximum size and priority.
*/
public class SupportedSourceAndTarget extends Types
{
Long maxSourceSizeBytes = null;
Integer priority = null;
public SupportedSourceAndTarget()
{
}
public Long getMaxSourceSizeBytes()
{
return maxSourceSizeBytes;
}
public void setMaxSourceSizeBytes(Long maxSourceSizeBytes)
{
this.maxSourceSizeBytes = maxSourceSizeBytes;
}
public Integer getPriority()
{
return priority;
}
public void setPriority(Integer priority)
{
this.priority = priority;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
SupportedSourceAndTarget that = (SupportedSourceAndTarget)o;
return Objects.equals(maxSourceSizeBytes, that.maxSourceSizeBytes) &&
Objects.equals(priority, that.priority);
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), maxSourceSizeBytes, priority);
}
@Override
public String toString()
{
StringJoiner sj = new StringJoiner(", ");
String superToString = super.toString();
if (superToString != null) sj.add(superToString);
if (maxSourceSizeBytes != null) sj.add("\"maxSourceSizeBytes\": \""+maxSourceSizeBytes+'"');
if (priority != null) sj.add("\"priority\": \""+priority+'"');
return "{" + sj.toString() + "}";
}
public static Builder builder()
{
return new Builder();
}
public static class Builder extends Types.Builder<SupportedSourceAndTarget.Builder, SupportedSourceAndTarget>
{
private Builder()
{
super(new SupportedSourceAndTarget());
}
public Builder withMaxSourceSizeBytes(final Long maxSourceSizeBytes)
{
t.setMaxSourceSizeBytes(maxSourceSizeBytes);
return this;
}
public Builder withPriority(final Integer priority)
{
t.setPriority(priority);
return this;
}
}
}

View File

@ -0,0 +1,186 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Transform Configuration with multiple {@link Transformer}s and {@link TransformOption}s.
* It can be used for one or more Transformers.
*/
public class TransformConfig
{
private Map<String, Set<TransformOption>> transformOptions = new HashMap<>();
private List<Transformer> transformers = new ArrayList<>();
private Set<String> removeTransformers = new HashSet<>();
private Set<AddSupported> addSupported = new HashSet<>();
private Set<RemoveSupported> removeSupported = new HashSet<>();
private Set<OverrideSupported> overrideSupported = new HashSet<>();
private Set<SupportedDefaults> supportedDefaults = new HashSet<>();
public Map<String, Set<TransformOption>> getTransformOptions()
{
return transformOptions;
}
public void setTransformOptions(Map<String, Set<TransformOption>> transformOptions)
{
this.transformOptions = transformOptions;
}
public List<Transformer> getTransformers()
{
return transformers;
}
public Set<String> getRemoveTransformers()
{
return removeTransformers;
}
public Set<AddSupported> getAddSupported()
{
return addSupported;
}
public Set<RemoveSupported> getRemoveSupported()
{
return removeSupported;
}
public Set<OverrideSupported> getOverrideSupported()
{
return overrideSupported;
}
public Set<SupportedDefaults> getSupportedDefaults()
{
return supportedDefaults;
}
public void setTransformers(List<Transformer> transformers)
{
this.transformers = transformers;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TransformConfig that = (TransformConfig) o;
return transformOptions.equals(that.transformOptions) &&
transformers.equals(that.transformers) &&
removeTransformers.equals(that.removeTransformers) &&
addSupported.equals(that.addSupported) &&
removeSupported.equals(that.removeSupported) &&
overrideSupported.equals(that.overrideSupported) &&
supportedDefaults.equals(that.supportedDefaults);
}
@Override
public int hashCode()
{
return Objects.hash(transformOptions, transformers, removeTransformers, addSupported, removeSupported,
overrideSupported, supportedDefaults);
}
@Override
public String toString()
{
return "TransformConfig{" +
"transformOptions=" + transformOptions +
", transformers=" + transformers +
'}';
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private final TransformConfig transformConfig = new TransformConfig();
private Builder() {}
public TransformConfig build()
{
return transformConfig;
}
public Builder withTransformOptions(final Map<String, Set<TransformOption>> transformOptions)
{
transformConfig.transformOptions = transformOptions;
return this;
}
public Builder withTransformers(final List<Transformer> transformers)
{
transformConfig.transformers = transformers;
return this;
}
public Builder withRemoveTransformers(final Set<String> removeTransformers)
{
transformConfig.removeTransformers = removeTransformers;
return this;
}
public Builder withAddSupported(final Set<AddSupported> addSupported)
{
transformConfig.addSupported = addSupported;
return this;
}
public Builder withRemoveSupported(final Set<RemoveSupported> removeSupported)
{
transformConfig.removeSupported = removeSupported;
return this;
}
public Builder withOverrideSupported(final Set<OverrideSupported> overrideSupported)
{
transformConfig.overrideSupported = overrideSupported;
return this;
}
public Builder withSupportedDefaults(final Set<SupportedDefaults> supportedDefaults)
{
transformConfig.supportedDefaults = supportedDefaults;
return this;
}
}
}

View File

@ -0,0 +1,39 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Represents an individual transformer option or group of options that are required or optional (default).
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({@JsonSubTypes.Type(value = TransformOptionValue.class, name = "value"),
@JsonSubTypes.Type(value = TransformOptionGroup.class, name = "group")})
public interface TransformOption
{
boolean isRequired();
void setRequired(boolean required);
}

View File

@ -0,0 +1,82 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* Represents a group of one or more options. If the group is optional, child options that are marked as required are
* only required if any child in the group is supplied by the client. If the group is required, child options are
* optional or required based on their own setting alone.
*
* In a pipeline transformation, a group of options
*/
public class TransformOptionGroup extends AbstractTransformOption
{
private Set<TransformOption> transformOptions = new HashSet<>();
public TransformOptionGroup()
{
}
public TransformOptionGroup(boolean required, Set<TransformOption> transformOptions)
{
super(required);
this.transformOptions = transformOptions;
}
public Set<TransformOption> getTransformOptions()
{
return transformOptions;
}
public void setTransformOptions(Set<TransformOption> transformOptions)
{
this.transformOptions = transformOptions;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TransformOptionGroup that = (TransformOptionGroup) o;
return Objects.equals(transformOptions, that.transformOptions);
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), transformOptions);
}
@Override
public String toString()
{
return "TransformOptionGroup{" +
"transformOptions=" + transformOptions +
'}';
}
}

View File

@ -0,0 +1,76 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
/**
* Represents a single transformation option.
*/
public class TransformOptionValue extends AbstractTransformOption
{
private String name;
public TransformOptionValue()
{
}
public TransformOptionValue(boolean required, String name)
{
super(required);
this.name = name;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TransformOptionValue that = (TransformOptionValue) o;
return Objects.equals(name, that.name);
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), name);
}
@Override
public String toString()
{
return "TransformOptionValue{" +
"name='" + name + '\'' +
'}';
}
}

View File

@ -0,0 +1,89 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
/**
* Represents a single transform step in a transform pipeline. The last step in the pipeline does not specify the
* target type as that is based on the supported types and what has been requested.
*/
public class TransformStep
{
private String transformerName;
private String targetMediaType;
public TransformStep()
{
}
public TransformStep(String transformerName, String targetMediaType)
{
this.transformerName = transformerName;
this.targetMediaType = targetMediaType;
}
public String getTransformerName()
{
return transformerName;
}
public void setTransformerName(String transformerName)
{
this.transformerName = transformerName;
}
public String getTargetMediaType()
{
return targetMediaType;
}
public void setTargetMediaType(String targetMediaType)
{
this.targetMediaType = targetMediaType;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TransformStep that = (TransformStep) o;
return Objects.equals(transformerName, that.transformerName) &&
Objects.equals(targetMediaType, that.targetMediaType);
}
@Override
public int hashCode()
{
return Objects.hash(transformerName, targetMediaType);
}
@Override
public String toString()
{
return "TransformStep{" +
"transformerName='" + transformerName + '\'' +
", targetMediaType='" + targetMediaType + '\'' +
'}';
}
}

View File

@ -0,0 +1,244 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import org.alfresco.transform.client.registry.TransformServiceRegistry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
* Represents a set of transformations supported by the Transform Service or Local Transform Service Registry that
* share the same transform options. Each may be an actual transformer, a pipeline of multiple transformers or a list
* of failover transforms. It is possible that more than one transformer may able to perform a transformation from one
* mimetype to another. The actual selection of transformer is up to the Transform Service or Local Transform Service
* Registry to decide. Clients may use
* {@link TransformServiceRegistry#isSupported(String, long, String, java.util.Map, String)} to decide
* if they should send a request to the service. As a result clients have a simple generic view of transformations which
* allows new transformations to be added without the need to change client data structures other than to define new
* name value pairs. For this to work the Transform Service defines unique names for each option.
* <ul>
* <li>transformerName - is optional but if supplied should be unique. The client should infer nothing from the name
* as it is simply a label, but the Local Transform Service Registry will use the name in pipelines.</lI>
* <li>transformOptions - a grouping of individual transformer transformOptions. The group may be optional and may
* contain nested transformOptions.</li>
* </ul>
* For local transforms, this structure is extended when defining a pipeline transform and failover transform.
* <ul>
* <li>transformerPipeline - an array of pairs of transformer name and target extension for each transformer in the
* pipeline. The last one should not have an extension as that is defined by the request and should be in the
* supported list.</li>
* <li>transformerFailover - an array of failover definitions used in case of a fail transformation to pass a document
* to a sequence of transforms until one succeeds.</li>
* <li>coreVersion - indicates the version of the T-Engine's base. See {@link CoreVersionDecorator} for more detail.</li>
* </ul>
*/
public class Transformer
{
private String transformerName;
private String coreVersion;
private Set<String> transformOptions = new HashSet<>();
private Set<SupportedSourceAndTarget> supportedSourceAndTargetList = new HashSet<>();
private List<TransformStep> transformerPipeline = new ArrayList<>();
private List<String> transformerFailover = new ArrayList<>();
public Transformer()
{
}
public Transformer(String transformerName, Set<String> transformOptions,
Set<SupportedSourceAndTarget> supportedSourceAndTargetList)
{
this.transformerName = transformerName;
this.transformOptions = transformOptions;
this.supportedSourceAndTargetList = supportedSourceAndTargetList;
}
public Transformer(String transformerName, Set<String> transformOptions,
Set<SupportedSourceAndTarget> supportedSourceAndTargetList,
List<TransformStep> transformerPipeline)
{
this(transformerName, transformOptions, supportedSourceAndTargetList);
this.transformerPipeline = transformerPipeline;
}
public Transformer(String transformerName, Set<String> transformOptions,
Set<SupportedSourceAndTarget> supportedSourceAndTargetList,
List<TransformStep> transformerPipeline, List<String> transformerFailover)
{
this(transformerName, transformOptions, supportedSourceAndTargetList, transformerPipeline);
this.transformerFailover = transformerFailover;
}
public String getTransformerName()
{
return transformerName;
}
public void setTransformerName(String transformerName)
{
this.transformerName = transformerName;
}
public String getCoreVersion()
{
return coreVersion;
}
public void setCoreVersion(String coreVersion)
{
this.coreVersion = coreVersion;
}
public List<TransformStep> getTransformerPipeline()
{
return transformerPipeline;
}
public void setTransformerPipeline(List<TransformStep> transformerPipeline)
{
this.transformerPipeline = transformerPipeline;
}
public List<String> getTransformerFailover()
{
return transformerFailover;
}
public void setTransformerFailover(List<String> transformerFailover)
{
this.transformerFailover = transformerFailover;
}
public Set<String> getTransformOptions()
{
return transformOptions;
}
public void setTransformOptions(Set<String> transformOptions)
{
this.transformOptions = transformOptions;
}
public Set<SupportedSourceAndTarget> getSupportedSourceAndTargetList()
{
return supportedSourceAndTargetList;
}
public void setSupportedSourceAndTargetList(
Set<SupportedSourceAndTarget> supportedSourceAndTargetList)
{
this.supportedSourceAndTargetList = supportedSourceAndTargetList;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Transformer that = (Transformer) o;
return Objects.equals(transformerName, that.transformerName) &&
Objects.equals(coreVersion, that.coreVersion) &&
Objects.equals(transformerPipeline, that.transformerPipeline) &&
Objects.equals(transformerFailover, that.transformerFailover) &&
Objects.equals(transformOptions, that.transformOptions) &&
Objects.equals(supportedSourceAndTargetList,
that.supportedSourceAndTargetList);
}
@Override
public int hashCode()
{
return Objects.hash(transformerName, coreVersion, transformerPipeline, transformerFailover, transformOptions,
supportedSourceAndTargetList);
}
@Override
public String toString()
{
return "Transformer{" +
"transformerName='" + transformerName + '\'' +
", coreVersion=" + coreVersion +
", transformerPipeline=" + transformerPipeline +
", transformerFailover=" + transformerFailover +
", transformOptions=" + transformOptions +
", supportedSourceAndTargetList=" + supportedSourceAndTargetList +
'}';
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private final Transformer transformer = new Transformer();
private Builder() {}
public Transformer build()
{
return transformer;
}
public Builder withTransformerName(final String transformerName)
{
transformer.transformerName = transformerName;
return this;
}
public Builder withCoreVersion(final String coreVersion)
{
transformer.coreVersion = coreVersion;
return this;
}
public Builder withTransformerPipeline(final List<TransformStep> transformerPipeline)
{
transformer.transformerPipeline = transformerPipeline;
return this;
}
public Builder withTransformerFailover(final List<String> transformerFailover)
{
transformer.transformerFailover = transformerFailover;
return this;
}
public Builder withTransformOptions(final Set<String> transformOptions)
{
transformer.transformOptions = transformOptions;
return this;
}
public Builder withSupportedSourceAndTargetList(
final Set<SupportedSourceAndTarget> supportedSourceAndTargetList)
{
transformer.supportedSourceAndTargetList = supportedSourceAndTargetList;
return this;
}
}
}

View File

@ -0,0 +1,90 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Base object with {@code transformerName}, {@code sourceMediaType}and {@code targetMediaType}.
* Used to identify supported transforms.
*/
public abstract class TransformerAndTypes extends Types
{
String transformerName;
protected TransformerAndTypes() {}
public String getTransformerName()
{
return transformerName;
}
public void setTransformerName(String transformerName)
{
this.transformerName = transformerName;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TransformerAndTypes that = (TransformerAndTypes) o;
return Objects.equals(transformerName, that.transformerName);
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), transformerName);
}
@Override
public String toString()
{
StringJoiner sj = new StringJoiner(", ");
String superToString = super.toString();
if (transformerName != null) sj.add("\"transformerName\": \""+transformerName+'"');
if (superToString != null) sj.add(superToString);
return sj.toString();
}
public static class Builder<B extends TransformerAndTypes.Builder, T extends TransformerAndTypes>
extends Types.Builder<B, T>
{
private final T t;
protected Builder(T t)
{
super(t);
this.t = t;
}
public B withTransformerName(final String transformerName)
{
t.transformerName = transformerName;
return (B)this;
}
}
}

View File

@ -0,0 +1,109 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Base object with {@code transformerName}, {@code sourceMediaType}, {@code targetMediaType},
* {@code maxSourceSizeBytes} and {@code priority}.
*/
public abstract class TransformerTypesSizeAndPriority extends TransformerAndTypes
{
Long maxSourceSizeBytes = null;
Integer priority = null;
protected TransformerTypesSizeAndPriority() {}
public Long getMaxSourceSizeBytes()
{
return maxSourceSizeBytes;
}
public void setMaxSourceSizeBytes(Long maxSourceSizeBytes)
{
this.maxSourceSizeBytes = maxSourceSizeBytes;
}
public Integer getPriority()
{
return priority;
}
public void setPriority(Integer priority)
{
this.priority = priority;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TransformerTypesSizeAndPriority that = (TransformerTypesSizeAndPriority)o;
return getMaxSourceSizeBytes().equals(that.getMaxSourceSizeBytes()) &&
getPriority() == that.getPriority();
}
@Override
public int hashCode()
{
return Objects.hash(super.hashCode(), transformerName);
}
@Override
public String toString()
{
StringJoiner sj = new StringJoiner(", ");
String superToString = super.toString();
if (superToString != null) sj.add(superToString);
if (maxSourceSizeBytes != null) sj.add("\"maxSourceSizeBytes\": \""+maxSourceSizeBytes+'"');
if (priority != null) sj.add("\"priority\": \""+priority+'"');
return "{"+sj.toString()+"}";
}
public static class Builder<B extends TransformerTypesSizeAndPriority.Builder, T extends TransformerTypesSizeAndPriority>
extends TransformerAndTypes.Builder<B, T>
{
private final T t;
protected Builder(T t)
{
super(t);
this.t = t;
}
public B withMaxSourceSizeBytes(final long maxSourceSizeBytes)
{
t.setMaxSourceSizeBytes(maxSourceSizeBytes);
return (B)this;
}
public B withPriority(final int priority)
{
t.setPriority(priority);
return (B)this;
}
}
}

View File

@ -0,0 +1,109 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Base object with {@code sourceMediaType} and {@code targetMediaType}.
* Used to identify supported transforms.
*/
public class Types
{
String sourceMediaType;
String targetMediaType;
protected Types() {}
public String getSourceMediaType()
{
return sourceMediaType;
}
public void setSourceMediaType(String sourceMediaType)
{
this.sourceMediaType = sourceMediaType;
}
public String getTargetMediaType()
{
return targetMediaType;
}
public void setTargetMediaType(String targetMediaType)
{
this.targetMediaType = targetMediaType;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Types that = (Types) o;
return Objects.equals(sourceMediaType, that.sourceMediaType) &&
Objects.equals(targetMediaType, that.targetMediaType);
}
@Override
public int hashCode()
{
return Objects.hash(sourceMediaType, targetMediaType);
}
@Override
public String toString()
{
StringJoiner sj = new StringJoiner(", ");
if (sourceMediaType != null) sj.add("\"sourceMediaType\": \""+sourceMediaType+'"');
if (targetMediaType != null) sj.add("\"targetMediaType\": \""+targetMediaType+'"');
return sj.toString();
}
public static abstract class Builder<B extends Types.Builder, T extends Types>
{
final T t;
protected Builder(T t)
{
this.t = t;
}
public T build()
{
return (T)t;
}
public B withSourceMediaType(final String sourceMediaType)
{
t.sourceMediaType = sourceMediaType;
return (B)this;
}
public B withTargetMediaType(final String targetMediaType)
{
t.targetMediaType = targetMediaType;
return (B)this;
}
}
}

View File

@ -0,0 +1,144 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import static org.alfresco.transform.client.registry.TransformRegistryHelper.retrieveTransformListBySize;
import static org.alfresco.transform.client.registry.TransformRegistryHelper.lookupTransformOptions;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.transform.client.model.config.CoreFunction;
import org.alfresco.transform.client.model.config.TransformOption;
import org.alfresco.transform.client.model.config.Transformer;
/**
* Used to work out if a transformation is supported. Sub classes should implement {@link #getData()} to return an
* instance of the {@link TransformCache} class. This allows sub classes to periodically replace the registry's data with newer
* values. They may also extend the Data class to include extra fields and methods.
*/
public abstract class AbstractTransformRegistry implements TransformServiceRegistry
{
/**
* Logs an error message if there is an error in the configuration.
*
* @param msg to be logged.
*/
protected abstract void logError(String msg);
/**
* Logs a warning message if there is a problem in the configuration.
*
* @param msg to be logged.
*/
protected void logWarn(String msg)
{
logError(msg);
}
/**
* Returns the data held by the registry. Sub classes may extend the base Data and replace it at run time.
*
* @return the Data object that contains the registry's data.
*/
public abstract TransformCache getData();
/**
* Registers a single transformer. This is an internal method called by
* {@link CombinedTransformConfig#registerCombinedTransformers(AbstractTransformRegistry)}.
*
* @param transformer to be registered
* @param transformOptions all the transform options
* @param baseUrl where the config was be read from. Only needed when it is remote. Is null when local.
* Does not need to be a URL. May just be a name.
* @param readFrom debug message for log messages, indicating what type of config was read.
*/
protected void register(final Transformer transformer,
final Map<String, Set<TransformOption>> transformOptions, final String baseUrl,
final String readFrom)
{
getData().incrementTransformerCount();
transformer
.getSupportedSourceAndTargetList()
.forEach(e -> getData().appendTransform(e.getSourceMediaType(), e.getTargetMediaType(),
new SupportedTransform(
transformer.getTransformerName(),
lookupTransformOptions(transformer.getTransformOptions(), transformOptions,
readFrom, this::logError),
e.getMaxSourceSizeBytes(),
e.getPriority()),
transformer.getTransformerName(),
transformer.getCoreVersion()));
}
/**
* Works out the name of the transformer (might not map to an actual transformer) that will be used to transform
* content of a given source mimetype and size into a target mimetype given a list of actual transform option names
* and values (Strings) plus the data contained in the Transform objects registered with this class.
*
* @param sourceMimetype the mimetype of the source content
* @param sourceSizeInBytes the size in bytes of the source content. Ignored if negative.
* @param targetMimetype the mimetype of the target
* @param actualOptions the actual name value pairs available that could be passed to the Transform Service.
* @param renditionName (optional) name for the set of options and target mimetype. If supplied is used to cache
* results to avoid having to work out if a given transformation is supported a second time.
* The sourceMimetype and sourceSizeInBytes may still change. In the case of ACS this is the
* rendition name.
*/
@Override
public String findTransformerName(final String sourceMimetype, final long sourceSizeInBytes,
final String targetMimetype, final Map<String, String> actualOptions,
final String renditionName)
{
return retrieveTransformListBySize(getData(), sourceMimetype, targetMimetype, actualOptions,
renditionName)
.stream()
.filter(t -> t.getMaxSourceSizeBytes() == -1 ||
t.getMaxSourceSizeBytes() >= sourceSizeInBytes)
.findFirst()
.map(SupportedTransform::getName)
.orElse(null);
}
@Override
public long findMaxSize(final String sourceMimetype, final String targetMimetype,
final Map<String, String> actualOptions, final String renditionName)
{
final List<SupportedTransform> supportedTransforms = retrieveTransformListBySize(getData(),
sourceMimetype, targetMimetype, actualOptions, renditionName);
return supportedTransforms.isEmpty() ? 0 :
supportedTransforms.get(supportedTransforms.size() - 1).getMaxSourceSizeBytes();
}
@Override
public boolean isSupported(CoreFunction function, String transformerName)
{
return function.isSupported(getData().getCoreVersion(transformerName));
}
// When testing, we need to be able to set the baseUrl when reading from a file.
public String getBaseUrlIfTesting(String name, String baseUrl)
{
return baseUrl;
}
}

View File

@ -0,0 +1,830 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import org.alfresco.transform.client.model.config.AddSupported;
import org.alfresco.transform.client.model.config.SupportedDefaults;
import org.alfresco.transform.client.model.config.OverrideSupported;
import org.alfresco.transform.client.model.config.RemoveSupported;
import org.alfresco.transform.client.model.config.SupportedSourceAndTarget;
import org.alfresco.transform.client.model.config.TransformConfig;
import org.alfresco.transform.client.model.config.TransformOption;
import org.alfresco.transform.client.model.config.TransformStep;
import org.alfresco.transform.client.model.config.Transformer;
import org.alfresco.transform.client.model.config.TransformerAndTypes;
import org.alfresco.transform.client.model.config.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.alfresco.transform.client.model.config.CoreVersionDecorator.setCoreVersionOnMultiStepTransformers;
/**
* This class combines one or more T-Engine config and local files and registers them as if they were all in one file.
* Transform options are shared between all sources.<p><br>
*
* The caller should make calls to {@link #addTransformConfig(TransformConfig, String, String, AbstractTransformRegistry)}
* followed by calls to {@link #combineTransformerConfig(AbstractTransformRegistry)} and then
* {@link #registerCombinedTransformers(AbstractTransformRegistry)}.<p><br>
*
* The helper method {@link #combineAndRegister(TransformConfig, String, String, AbstractTransformRegistry)} may be used
* when there is only one config.
*
* @author adavis
*/
public class CombinedTransformConfig
{
private static final String MIMETYPE_METADATA_EXTRACT = "alfresco-metadata-extract";
private static final String MIMETYPE_METADATA_EMBED = "alfresco-metadata-embed";
private static final String INTERMEDIATE_STEPS_SHOULD_HAVE_A_TARGET_MIMETYPE = "intermediate steps should have a target mimetype";
private static final String THE_FINAL_STEP_SHOULD_NOT_HAVE_A_TARGET_MIMETYPE = "the final step should not have a target mimetype";
private final Map<String, Set<TransformOption>> combinedTransformOptions = new HashMap<>();
private List<Origin<Transformer>> combinedTransformers = new ArrayList<>();
private final Defaults defaults = new Defaults();
public static void combineAndRegister(TransformConfig transformConfig, String readFrom, String baseUrl,
AbstractTransformRegistry registry)
{
CombinedTransformConfig combinedTransformConfig = new CombinedTransformConfig();
combinedTransformConfig.addTransformConfig(transformConfig, readFrom, baseUrl, registry);
combinedTransformConfig.combineTransformerConfig(registry);
combinedTransformConfig.registerCombinedTransformers(registry);
}
public void clear()
{
combinedTransformOptions.clear();
combinedTransformers.clear();
defaults.clear();
}
public void addTransformConfig(List<Origin<TransformConfig>> transformConfigList, AbstractTransformRegistry registry)
{
transformConfigList.forEach(tc -> addTransformConfig(tc.get(), tc.getReadFrom(), tc.getBaseUrl(), registry));
}
public void addTransformConfig(TransformConfig transformConfig, String readFrom, String baseUrl,
AbstractTransformRegistry registry)
{
removeTransformers(transformConfig.getRemoveTransformers(), readFrom, registry);
supportedDefaults(transformConfig.getSupportedDefaults(), readFrom, registry);
removeSupported(transformConfig.getRemoveSupported(), readFrom, registry);
addSupported(transformConfig.getAddSupported(), readFrom, registry);
overrideSupported(transformConfig.getOverrideSupported(), readFrom, registry);
// Add transform options and transformers from the new transformConfig
transformConfig.getTransformOptions().forEach(combinedTransformOptions::put);
transformConfig.getTransformers().forEach(t -> combinedTransformers.add(new Origin<>(t, baseUrl, readFrom)));
}
private void removeTransformers(Set<String> removeTransformersSet, String readFrom, AbstractTransformRegistry registry)
{
if (!removeTransformersSet.isEmpty())
{
Set<String> leftOver = new HashSet<>(removeTransformersSet);
combinedTransformers.removeIf(combinedTransformer ->
{
String transformerName = combinedTransformer.get().getTransformerName();
if (removeTransformersSet.contains(transformerName))
{
leftOver.remove(transformerName);
return true;
}
return false;
});
Set<String> quotedLeftOver = leftOver.stream().map(transformerName -> "\""+transformerName+'"').collect(toSet());
logWarn(quotedLeftOver, readFrom, registry, "removeTransformers");
}
}
private void supportedDefaults(Set<SupportedDefaults> supportedDefaults, String readFrom, AbstractTransformRegistry registry)
{
if (!supportedDefaults.isEmpty())
{
Set<SupportedDefaults> leftOver = new HashSet<>(supportedDefaults);
supportedDefaults.stream()
.filter(supportedDefault -> supportedDefault.getMaxSourceSizeBytes() != null ||
supportedDefault.getPriority() != null)
.forEach(supportedDefault ->
{
defaults.add(supportedDefault);
leftOver.remove(supportedDefault);
});
logWarn(leftOver, readFrom, registry, "supportedDefaults");
}
}
private <T extends TransformerAndTypes> void processSupported(
Set<T> tSet, String readFrom, AbstractTransformRegistry registry, String elementName,
ProcessSingleT<T> process)
{
if (!tSet.isEmpty())
{
Set<T> leftOver = new HashSet<>(tSet);
tSet.stream()
.filter(t -> t.getTransformerName() != null &&
t.getSourceMediaType() != null &&
t.getTargetMediaType() != null)
.forEach(t -> process.process(leftOver, t));
logWarn(leftOver, readFrom, registry, elementName);
}
}
private <T> void logWarn(Set<T> leftOver, String readFrom, AbstractTransformRegistry registry, String elementName)
{
if (!leftOver.isEmpty())
{
StringJoiner sj = new StringJoiner(", ",
"Unable to process \"" + elementName + "\": [", "]. Read from " + readFrom);
leftOver.forEach(removeSupported -> sj.add(removeSupported.toString()));
registry.logWarn(sj.toString());
}
}
private interface ProcessSingleT<T>
{
void process(Set<T> leftOver, T removeSupported);
}
private void removeSupported(Set<RemoveSupported> removeSupportedSet, String readFrom, AbstractTransformRegistry registry)
{
processSupported(removeSupportedSet, readFrom, registry, "removeSupported",
(leftOver, removeSupported) ->
combinedTransformers.stream()
.map(Origin::get)
.forEach(transformer ->
{
if (transformer.getTransformerName().equals(removeSupported.getTransformerName()) &&
transformer.getSupportedSourceAndTargetList().removeIf(supported ->
supported.getSourceMediaType().equals(removeSupported.getSourceMediaType()) &&
supported.getTargetMediaType().equals(removeSupported.getTargetMediaType())))
{
leftOver.remove(removeSupported);
}
}));
}
private void addSupported(Set<AddSupported> addSupportedSet, String readFrom, AbstractTransformRegistry registry)
{
processSupported(addSupportedSet, readFrom, registry, "addSupported",
(leftOver, addSupported) ->
combinedTransformers.stream()
.map(Origin::get)
.filter(transformer -> transformer.getTransformerName().equals(addSupported.getTransformerName()))
.forEach(transformerWithName ->
{
Set<SupportedSourceAndTarget> supportedSourceAndTargetList =
transformerWithName.getSupportedSourceAndTargetList();
SupportedSourceAndTarget existingSupported = getExistingSupported(
supportedSourceAndTargetList,
addSupported.getSourceMediaType(), addSupported.getTargetMediaType());
if (existingSupported == null)
{
SupportedSourceAndTarget newSupportedSourceAndTarget = SupportedSourceAndTarget.builder()
.withSourceMediaType(addSupported.getSourceMediaType())
.withTargetMediaType(addSupported.getTargetMediaType())
.withMaxSourceSizeBytes(addSupported.getMaxSourceSizeBytes())
.withPriority(addSupported.getPriority())
.build();
supportedSourceAndTargetList.add(newSupportedSourceAndTarget);
leftOver.remove(addSupported);
}}));
}
private void overrideSupported(Set<OverrideSupported> overrideSupportedSet, String readFrom, AbstractTransformRegistry registry)
{
processSupported(overrideSupportedSet, readFrom, registry, "overrideSupported",
(leftOver, overrideSupported) ->
combinedTransformers.stream().
map(Origin::get).
filter(transformer -> transformer.getTransformerName().equals(overrideSupported.getTransformerName())).
forEach(transformerWithName ->
{
Set<SupportedSourceAndTarget> supportedSourceAndTargetList =
transformerWithName.getSupportedSourceAndTargetList();
SupportedSourceAndTarget existingSupported = getExistingSupported(
supportedSourceAndTargetList,
overrideSupported.getSourceMediaType(), overrideSupported.getTargetMediaType());
if (existingSupported != null)
{
supportedSourceAndTargetList.remove(existingSupported);
existingSupported.setMaxSourceSizeBytes(overrideSupported.getMaxSourceSizeBytes());
existingSupported.setPriority(overrideSupported.getPriority());
supportedSourceAndTargetList.add(existingSupported);
leftOver.remove(overrideSupported);
}
}));
}
private SupportedSourceAndTarget getExistingSupported(Set<SupportedSourceAndTarget> supportedSourceAndTargetList,
String sourceMediaType, String targetMediaType)
{
return supportedSourceAndTargetList.stream().filter(supported ->
supported.getSourceMediaType().equals(sourceMediaType) &&
supported.getTargetMediaType().equals(targetMediaType))
.findFirst()
.orElse(null);
}
public void combineTransformerConfig(AbstractTransformRegistry registry)
{
removeInvalidTransformers(registry);
sortTransformers(registry);
applyDefaults();
addWildcardSupportedSourceAndTarget(registry);
setCoreVersionOnCombinedMultiStepTransformers();
}
public TransformConfig buildTransformConfig()
{
List<Transformer> transformers = new ArrayList<>();
combinedTransformers.forEach(ct->transformers.add(ct.get()));
Set<SupportedDefaults> supportedDefaults = defaults.getSupportedDefaults();
return TransformConfig
.builder()
.withTransformers(transformers)
.withTransformOptions(combinedTransformOptions)
.withSupportedDefaults(supportedDefaults)
.build();
}
public void registerCombinedTransformers(AbstractTransformRegistry registry)
{
combinedTransformers.forEach(ct ->
registry.register(ct.get(), combinedTransformOptions, ct.getBaseUrl(), ct.getReadFrom()));
}
/**
* Discards transformers that are invalid (e.g. transformers that have both pipeline and failover sections). Calls
* {@link #removeInvalidTransformer(int, List, AbstractTransformRegistry, Origin, Transformer, String, String,
* boolean, boolean)} for each transform, so that individual invalid transforms or overridden
* transforms may be discarded.
*
* @param registry that will hold the transforms.
*/
private void removeInvalidTransformers(AbstractTransformRegistry registry)
{
for (int i=0; i<combinedTransformers.size(); i++)
{
try
{
Origin<Transformer> transformAndItsOrigin = combinedTransformers.get(i);
Transformer transformer = transformAndItsOrigin.get();
String readFrom = transformAndItsOrigin.getReadFrom();
String name = transformer.getTransformerName();
List<TransformStep> pipeline = transformer.getTransformerPipeline();
List<String> failover = transformer.getTransformerFailover();
boolean isPipeline = pipeline != null && !pipeline.isEmpty();
boolean isFailover = failover != null && !failover.isEmpty();
if (isPipeline && isFailover)
{
throw new IllegalArgumentException("Transformer " + transformerName(name) +
" cannot have pipeline and failover sections. Read from " + readFrom);
}
// Remove transforms as they may override each other or be invalid
int indexToRemove = removeInvalidTransformer(i, combinedTransformers, registry, transformAndItsOrigin,
transformer, name, readFrom, isPipeline, isFailover);
// Remove an overridden transform
if (indexToRemove >= 0)
{
combinedTransformers.remove(indexToRemove);
// The current index i should be decremented so we don't skip one.
// Condition not really needed as IllegalArgumentException is thrown if the latest entry is removed.
if (i >= indexToRemove)
{
i--;
}
}
}
catch (IllegalStateException e)
{
String msg = e.getMessage();
registry.logWarn(msg);
combinedTransformers.remove(i--);
}
catch (IllegalArgumentException e)
{
String msg = e.getMessage();
registry.logError(msg);
combinedTransformers.remove(i--);
}
}
}
/**
* Discards a transformer that is
* 1) invalid:
* a) has no name
* b) the pass through transformer name is specified in a T-Engine
* c) specifies transform options that don't exist,
* d) has the same name as another T-Engine transform (i.e. there should be no duplicate names from t-engines),
* e) the pass through transformer name is specified in a pipeline file
* f) a single step transform defined outside a t-engine without it being an override,
* g) a pipeline or failover transform is being overridden by a single step transform. Invalid because we
* don't know if a t-engine will be able to do it.
* 2) an earlier transform with the same name (it is being overridden). If the overridden transform is from a
* T-Engine and the overriding transform is not a pipeline or a failover, we also copy the {@code baseUrl}
* from the overridden transform so that the original T-Engine will still be called.
*
* @param i the current transform's index into combinedTransformers.
* @param combinedTransformers the full list of transformers in the order they were read.
* @param registry that wil hold the transforms.
* @param transformAndItsOrigin the current combinedTransformers element.
* @param transformer the current transformer.
* @param name the current transformer's name.
* @param readFrom where the current transformer was read from.
* @param isPipeline if the current transform is a pipeline.
* @param isFailover if the current transform is a failover.
*
* @return the index of a transform to be removed. {@code -1} is returned if there should not be a remove.
* @throws IllegalArgumentException if the current transform has a problem and should be removed.
* @throws IllegalStateException if the current transform is dependent on config from another transform which
* is currently unavailable.
*/
private int removeInvalidTransformer(int i, List<Origin<Transformer>> combinedTransformers,
AbstractTransformRegistry registry,
Origin<Transformer> transformAndItsOrigin, Transformer transformer,
String name, String readFrom, boolean isPipeline, boolean isFailover)
{
int indexToRemove = -1;
if (name == null || "".equals(name.trim()))
{
throw new IllegalArgumentException("Transformer names may not be null. Read from " + readFrom);
}
// Get the baseUrl - test code might change it
String baseUrl = transformAndItsOrigin.getBaseUrl();
String testBaseUrl = registry.getBaseUrlIfTesting(name, baseUrl);
if (!nullSafeEquals(baseUrl, testBaseUrl))
{
baseUrl = testBaseUrl;
transformAndItsOrigin = new Origin<>(transformer, baseUrl, readFrom);
combinedTransformers.set(i, transformAndItsOrigin);
}
boolean isTEngineTransform = baseUrl != null;
boolean isPassThroughTransform = isPassThroughTransformName(name);
if (isPassThroughTransform && isTEngineTransform)
{
throw new IllegalArgumentException("T-Engines should not use " + transformerName(name) +
" as a transform name. Read from " + readFrom);
}
for (String transformOptionsLabel : transformer.getTransformOptions())
{
if (!combinedTransformOptions.containsKey(transformOptionsLabel))
{
throw new IllegalStateException("Transformer " + transformerName(name) +
" references \"" + transformOptionsLabel +
"\" which do not exist. Read from " + readFrom);
}
}
boolean isOneStepTransform = !isPipeline && !isFailover && !isPassThroughTransform;
// Check to see if the name has been used before.
int j = lastIndexOf(name, combinedTransformers, i);
if (j >= 0)
{
if (isTEngineTransform)
{
// We currently don't allow different t-engines to override each others single step transforms,
// but we could if the order is defined in which they are read. Would need to check the baseUrl
// is different.
throw new IllegalArgumentException("Transformer " + transformerName(name) +
" must be a unique name. Read from " + readFrom);
}
if (isPassThroughTransform)
{
throw new IllegalArgumentException("Pipeline files should not use " + transformerName(name) +
" as a transform name. Read from " + readFrom);
}
if (isOneStepTransform)
{
Origin<Transformer> overriddenTransformAndItsOrigin = combinedTransformers.get(j);
Transformer overriddenTransformer = overriddenTransformAndItsOrigin.get();
List<TransformStep> overriddenPipeline = overriddenTransformer.getTransformerPipeline();
List<String> overriddenFailover = overriddenTransformer.getTransformerFailover();
boolean isOverriddenPipeline = overriddenPipeline != null && !overriddenPipeline.isEmpty();
boolean isOverriddenFailover = overriddenFailover != null && !overriddenFailover.isEmpty();
if (isOverriddenPipeline || isOverriddenFailover)
{
throw new IllegalArgumentException("Single step transformers (such as " + transformerName(name) +
") may not override a pipeline or failover transform as there is no T-Engine to perform" +
" work. Read from " + readFrom);
}
// We need to set the baseUrl of the original transform in the one overriding,
// so we can talk to its T-Engine
String overriddenBaseUrl = overriddenTransformAndItsOrigin.getBaseUrl();
Transformer overriddenTransformTransform = transformAndItsOrigin.get();
Origin<Transformer> overridingTransform =
new Origin<>(overriddenTransformTransform, overriddenBaseUrl, readFrom);
combinedTransformers.set(i, overridingTransform);
}
indexToRemove = j;
}
else if (isOneStepTransform && baseUrl == null)
{
throw new IllegalArgumentException("Single step transformers (such as " + transformerName(name) +
") must be defined in a T-Engine rather than in a pipeline file, unless they are overriding " +
"an existing single step definition. Read from " + readFrom);
}
return indexToRemove;
}
protected boolean isPassThroughTransformName(String name)
{
return false; // There is no pass through transformer in ATS but there is in the Repo.
}
private static int lastIndexOf(String name, List<Origin<Transformer>> combinedTransformers, int toIndex)
{
// Lists are short (< 100) entries and this is not a frequent or time critical step, so walking the list
// should be okay.
for (int j = toIndex-1; j >=0; j--)
{
Origin<Transformer> transformAndItsOrigin = combinedTransformers.get(j);
Transformer transformer = transformAndItsOrigin.get();
String transformerName = transformer.getTransformerName();
if (name.equals(transformerName))
{
return j;
}
}
return -1;
}
protected static String transformerName(String name)
{
return name == null ? " without a name" : "\"" + name + "\"";
}
// Copied from EqualsHelper
private static boolean nullSafeEquals(Object left, Object right)
{
return (left == right) || (left != null && left.equals(right));
}
/**
* Sort transformers so there are no forward references, if that is possible.
* Logs warning message for those that have missing step transformers and removes them.
* @param registry used to log messages
*/
private void sortTransformers(AbstractTransformRegistry registry)
{
List<Origin<Transformer>> transformers = new ArrayList<>(combinedTransformers.size());
List<Origin<Transformer>> todo = new ArrayList<>(combinedTransformers.size());
Set<String> transformerNames = new HashSet<>();
boolean added;
do
{
added = false;
for (Origin<Transformer> transformAndItsOrigin : combinedTransformers)
{
Transformer transformer = transformAndItsOrigin.get();
String name = transformer.getTransformerName();
Set<String> referencedTransformerNames = getReferencedTransformerNames(transformer);
boolean addEntry = true;
for (String referencedTransformerName : referencedTransformerNames)
{
if (!transformerNames.contains(referencedTransformerName))
{
todo.add(transformAndItsOrigin);
addEntry = false;
break;
}
}
if (addEntry)
{
transformers.add(transformAndItsOrigin);
added = true;
if (name != null)
{
transformerNames.add(name);
}
}
}
combinedTransformers.clear();
combinedTransformers.addAll(todo);
todo.clear();
}
while (added && !combinedTransformers.isEmpty());
transformers.addAll(todo);
for (Origin<Transformer> transformAndItsOrigin : combinedTransformers)
{
Transformer transformer = transformAndItsOrigin.get();
String name = transformer.getTransformerName();
registry.logWarn("Transformer " + transformerName(name) +
" ignored as step transforms " + getUnknownReferencedTransformerNames(transformer, transformerNames) +
" do not exist. Read from " + transformAndItsOrigin.getReadFrom());
}
this.combinedTransformers = transformers;
}
private Set<String> getReferencedTransformerNames(Transformer transformer)
{
Set<String> referencedTransformerNames = new HashSet<>();
List<TransformStep> pipeline = transformer.getTransformerPipeline();
if (pipeline != null)
{
for (TransformStep step : pipeline)
{
String stepName = step.getTransformerName();
referencedTransformerNames.add(stepName);
}
}
List<String> failover = transformer.getTransformerFailover();
if (failover != null)
{
referencedTransformerNames.addAll(failover);
}
return referencedTransformerNames;
}
private String getUnknownReferencedTransformerNames(Transformer transformer, Set<String> transformerNames)
{
StringJoiner sj = new StringJoiner(", ", "(", ")");
Set<String> referencedTransformerNames = getReferencedTransformerNames(transformer);
for (String referencedTransformerName : referencedTransformerNames)
{
if (!transformerNames.contains(referencedTransformerName))
{
sj.add(transformerName(referencedTransformerName));
}
}
return sj.toString();
}
/**
* Applies priority and size defaults. Must be called before {@link #addWildcardSupportedSourceAndTarget(AbstractTransformRegistry)}
* as it uses the priority value.
*/
private void applyDefaults()
{
combinedTransformers.stream()
.map(Origin::get)
.forEach(transformer ->
{
transformer.setSupportedSourceAndTargetList(
transformer.getSupportedSourceAndTargetList().stream().map(supportedSourceAndTarget ->
{
Integer priority = supportedSourceAndTarget.getPriority();
Long maxSourceSizeBytes = supportedSourceAndTarget.getMaxSourceSizeBytes();
if (defaults.valuesUnset(priority, maxSourceSizeBytes))
{
String transformerName = transformer.getTransformerName();
String sourceMediaType = supportedSourceAndTarget.getSourceMediaType();
supportedSourceAndTarget.setPriority(defaults.getPriority(transformerName, sourceMediaType, priority));
supportedSourceAndTarget.setMaxSourceSizeBytes(defaults.getMaxSourceSizeBytes(transformerName, sourceMediaType, maxSourceSizeBytes));
}
return supportedSourceAndTarget;
}).collect(toSet()));
});
defaults.clear();
}
/**
* When no supported source and target mimetypes have been defined in a failover or pipeline transformer
* this method adds all possible values that make sense.
* <lu>
* <li>Failover - all the supported values from the step transformers</li>
* <li>Pipeline - builds up supported source and target values. The list of source types and max sizes will come
* from the initial step transformer that have a target mimetype that matches the first
* intermediate mimetype. We then step through all intermediate transformers checking the next
* intermediate type is supported. When we get to the last step transformer, it provides all the
* target mimetypes based on the previous intermediate mimetype. Any combinations supported by
* the first transformer are excluded.</li>
* </lu>
* @param registry used to log messages
*/
private void addWildcardSupportedSourceAndTarget(AbstractTransformRegistry registry)
{
Map<String, Transformer> transformers = new HashMap<>();
combinedTransformers.forEach(ct -> transformers.put(ct.get().getTransformerName(), ct.get()));
combinedTransformers.forEach(transformAndItsOrigin ->
{
Transformer transformer = transformAndItsOrigin.get();
// If there are no SupportedSourceAndTarget, then work out all the wildcard combinations.
if (transformer.getSupportedSourceAndTargetList().isEmpty())
{
List<TransformStep> pipeline = transformer.getTransformerPipeline();
List<String> failover = transformer.getTransformerFailover();
boolean isPipeline = pipeline != null && !pipeline.isEmpty();
boolean isFailover = failover != null && !failover.isEmpty();
String errorReason = null;
if (isFailover)
{
Set<SupportedSourceAndTarget> supportedSourceAndTargets = failover.stream().flatMap(
name -> transformers.get(name).getSupportedSourceAndTargetList().stream()).
collect(toSet());
// The failover transform might not be picked if the priority is the same as the step transforms
// so reduce it here by 1. In future we might want to specify the priority in the json, but for now
// it should be okay.
supportedSourceAndTargets.forEach(s->s.setPriority(s.getPriority()-1));
transformer.setSupportedSourceAndTargetList(supportedSourceAndTargets);
errorReason = "the step transforms don't support any"; // only used if there are none
}
else if (isPipeline)
{
String sourceMediaType = null;
Set<SupportedSourceAndTarget> sourceMediaTypesAndMaxSizes = null;
Set<String> firstTransformOptions = null;
String firstTransformStepName = null;
int numberOfSteps = pipeline.size();
for (int stepIndex=0; stepIndex<numberOfSteps; stepIndex++)
{
TransformStep step = pipeline.get(stepIndex);
String name = step.getTransformerName();
Transformer stepTransformer = transformers.get(name);
if (stepTransformer == null) // should not happen as previous checks avoid this
{
errorReason = "one of the step transformers is missing";
break;
}
String stepTrg = step.getTargetMediaType();
if (stepIndex == 0)
{
sourceMediaTypesAndMaxSizes = stepTransformer.getSupportedSourceAndTargetList().stream().
filter(s -> stepTrg.equals(s.getTargetMediaType())).
collect(toSet());
sourceMediaType = stepTrg;
firstTransformOptions = stepTransformer.getTransformOptions();
firstTransformStepName = name;
if (sourceMediaTypesAndMaxSizes.isEmpty())
{
errorReason = "the first step transformer " + transformerName(name) +
" does not support to \"" + stepTrg + "\"";
break;
}
}
else
{
final String src = sourceMediaType;
if (stepIndex+1 == numberOfSteps) // if final step
{
if (stepTrg != null)
{
errorReason = THE_FINAL_STEP_SHOULD_NOT_HAVE_A_TARGET_MIMETYPE;
break;
}
// Create a cartesian product of sourceMediaType,MaxSourceSize and TargetMediaType where
// the source matches the last intermediate.
Set<SupportedSourceAndTarget> supportedSourceAndTargets = sourceMediaTypesAndMaxSizes.stream().
flatMap(s -> stepTransformer.getSupportedSourceAndTargetList().stream().
filter(st ->
{
String targetMimetype = st.getTargetMediaType();
return st.getSourceMediaType().equals(src) &&
!(MIMETYPE_METADATA_EXTRACT.equals(targetMimetype) ||
MIMETYPE_METADATA_EMBED.equals(targetMimetype));
}).
map(Types::getTargetMediaType).
map(trg -> SupportedSourceAndTarget.builder().
withSourceMediaType(s.getSourceMediaType()).
withMaxSourceSizeBytes(s.getMaxSourceSizeBytes()).
withPriority(s.getPriority()).
withTargetMediaType(trg).build())).
collect(toSet());
if (supportedSourceAndTargets.isEmpty())
{
errorReason = "the final step transformer " + transformerName(name) +
" does not support from \"" + src + "\"";
}
else
{
// Exclude duplicates with the first transformer, if it has the same options.
// There is no point doing more work.
Set<String> transformOptions = transformer.getTransformOptions();
if (sameOptions(transformOptions, firstTransformOptions))
{
supportedSourceAndTargets.removeAll(sourceMediaTypesAndMaxSizes);
}
if (supportedSourceAndTargets.isEmpty())
{
errorReason = "the first transformer " + transformerName(firstTransformStepName) +
" in the pipeline already supported all source and target mimetypes" +
" that would have been added as wildcards";
}
}
transformer.setSupportedSourceAndTargetList(supportedSourceAndTargets);
}
else // if intermediate step
{
if (stepTrg == null)
{
errorReason = INTERMEDIATE_STEPS_SHOULD_HAVE_A_TARGET_MIMETYPE;
break;
}
// Check source to target is supported (it normally is)
if (stepTransformer.getSupportedSourceAndTargetList().stream().
noneMatch(st -> st.getSourceMediaType().equals(src) &&
st.getTargetMediaType().equals(stepTrg)))
{
errorReason = "the step transformer " +
transformerName(stepTransformer.getTransformerName()) + " does not support \"" +
src + "\" to \"" + stepTrg + "\"";
break;
}
sourceMediaType = stepTrg;
}
}
}
}
if (transformer.getSupportedSourceAndTargetList().isEmpty() && (isFailover || isPipeline))
{
registry.logError("No supported source and target mimetypes could be added to the" +
" transformer " + transformerName(transformer.getTransformerName()) + " as " + errorReason +
". Read from " + transformAndItsOrigin.getReadFrom());
}
}
});
}
private boolean sameOptions(Set<String> transformOptionNames1, Set<String> transformOptionNames2)
{
// They have the same names
if (transformOptionNames1.equals(transformOptionNames2))
{
return true;
}
// Check the actual options.
Set<TransformOption> transformOptions1 = getTransformOptions(transformOptionNames1);
Set<TransformOption> transformOptions2 = getTransformOptions(transformOptionNames2);
return transformOptions1.equals(transformOptions2);
}
private Set<TransformOption> getTransformOptions(Set<String> transformOptionNames)
{
Set<TransformOption> transformOptions = new HashSet<>();
transformOptionNames.forEach(name->transformOptions.addAll(combinedTransformOptions.get(name)));
return transformOptions;
}
private void setCoreVersionOnCombinedMultiStepTransformers()
{
setCoreVersionOnMultiStepTransformers(combinedTransformOptions, combinedTransformers.stream()
.map(Origin::get)
.collect(Collectors.toList()));
}
protected int transformerCount()
{
return combinedTransformers.size();
}
}

View File

@ -0,0 +1,148 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import org.alfresco.transform.client.model.config.SupportedDefaults;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toSet;
/**
* Maintains a list of defaults of {@code maxSourceSizeBytes} and {@code priority} keyed on
* {@code transformerName} and {@code sourceMediaType} so that it can provide a lookup when we
* join everything together in {@link CombinedTransformConfig#combineTransformerConfig(AbstractTransformRegistry)}.
*
* @see SupportedDefaults
*/
class Defaults
{
private static final Integer DEFAULT_PRIORITY = 50;
private static final Long DEFAULT_MAX_SOURCE_SIZE_BYTES = -1L;
private static final TransformerAndSourceType SYSTEM_WIDE_KEY = new TransformerAndSourceType(null, null);
private final Map<TransformerAndSourceType, Integer> priorityDefaults = new HashMap<>();
private final Map<TransformerAndSourceType, Long> maxSourceSizeBytesDefaults = new HashMap<>();
public void add(SupportedDefaults supportedDefault)
{
TransformerAndSourceType key =
new TransformerAndSourceType(supportedDefault.getTransformerName(), supportedDefault.getSourceMediaType());
Long maxSourceSizeBytes = supportedDefault.getMaxSourceSizeBytes();
if (maxSourceSizeBytes != null)
{
maxSourceSizeBytesDefaults.put(key, maxSourceSizeBytes);
}
Integer priority = supportedDefault.getPriority();
if (priority != null)
{
priorityDefaults.put(key, priority);
}
}
Defaults()
{
clear();
}
public boolean valuesUnset(Integer supportedSourceAndTargetPriority, Long supportedSourceAndTargetMaxSourceSizeBytes)
{
return supportedSourceAndTargetPriority == null || supportedSourceAndTargetMaxSourceSizeBytes == null;
}
public int getPriority(String transformerName, String sourceMediaType, Integer supportedSourceAndTargetPriority)
{
return getDefault(transformerName, sourceMediaType, supportedSourceAndTargetPriority, priorityDefaults);
}
public long getMaxSourceSizeBytes(String transformerName, String sourceMediaType, Long supportedSourceAndTargetMaxSourceSizeBytes)
{
return getDefault(transformerName, sourceMediaType, supportedSourceAndTargetMaxSourceSizeBytes, maxSourceSizeBytesDefaults);
}
private <T> T getDefault(String transformerName, String sourceMediaType, T supportedSourceAndTargetValue,
Map<TransformerAndSourceType, T> map)
{
if (supportedSourceAndTargetValue != null)
{
return supportedSourceAndTargetValue;
}
// 0: transformer and source media type default
// 1: transformer default
// 2: source media type default
// 3: system wide default
TransformerAndSourceType key = new TransformerAndSourceType(transformerName, sourceMediaType);
for (int i=0; ; i++)
{
T value = map.get(key);
if (value != null)
{
return value;
}
switch (i)
{
case 0:
case 2: key.setSourceMediaType(null); break;
case 1: key.setSourceMediaType(sourceMediaType); key.setTransformerName(null); break;
default: throw new IllegalStateException("Should have found an entry with a null, null lookup");
}
}
}
public Set<SupportedDefaults> getSupportedDefaults()
{
return Stream.concat(maxSourceSizeBytesDefaults.keySet().stream(), priorityDefaults.keySet().stream())
.filter(key ->
{
// Discard the entry added by clear()
return !SYSTEM_WIDE_KEY.equals(key) ||
!DEFAULT_MAX_SOURCE_SIZE_BYTES.equals(maxSourceSizeBytesDefaults.get(key)) ||
!DEFAULT_PRIORITY.equals(priorityDefaults.get(key));
})
.map(key ->
{
Long maxSourceSizeBytes = maxSourceSizeBytesDefaults.get(key);
Integer priority = priorityDefaults.get(key);
return SupportedDefaults.builder()
.withTransformerName(key.getTransformerName())
.withSourceMediaType(key.getSourceMediaType())
.withPriority(priority)
.withMaxSourceSizeBytes(maxSourceSizeBytes)
.build();
}).collect(toSet());
}
public void clear()
{
priorityDefaults.clear();
maxSourceSizeBytesDefaults.clear();
priorityDefaults.put(SYSTEM_WIDE_KEY, DEFAULT_PRIORITY);
maxSourceSizeBytesDefaults.put(SYSTEM_WIDE_KEY, DEFAULT_MAX_SOURCE_SIZE_BYTES);
}
}

View File

@ -0,0 +1,82 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* Wraps an object so that we know where it was read from. The equals() and hashcode() are that of the wrapped object
* so it is still possible do set operations.
*/
public class Origin<T>
{
final T t;
final String baseUrl;
final String readFrom;
public Origin(T t, String baseUrl, String readFrom)
{
this.t = t;
this.baseUrl = baseUrl;
this.readFrom = readFrom;
}
public T get()
{
return t;
}
public String getBaseUrl()
{
return baseUrl;
}
public String getReadFrom()
{
return readFrom;
}
public static <T> Set<T> setOf(Collection<Origin<T>> originCollection)
{
Set<T> tSet = new HashSet<>(originCollection.size());
originCollection.forEach(element -> tSet.add(element.get()));
return tSet;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Origin<?> origin = (Origin<?>)o;
return t.equals(origin.t);
}
@Override
public int hashCode()
{
return Objects.hash(t);
}
}

View File

@ -0,0 +1,92 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import java.util.Objects;
import java.util.Set;
import org.alfresco.transform.client.model.config.TransformOption;
import org.alfresco.transform.client.model.config.TransformOptionGroup;
public class SupportedTransform
{
private final TransformOptionGroup transformOptions;
private final long maxSourceSizeBytes;
private final String name;
private final int priority;
SupportedTransform(String name, Set<TransformOption> transformOptions,
long maxSourceSizeBytes, int priority)
{
// Logically the top level TransformOptionGroup is required, so that child options are optional or required
// based on their own setting.
this.transformOptions = new TransformOptionGroup(true, transformOptions);
this.maxSourceSizeBytes = maxSourceSizeBytes;
this.name = name;
this.priority = priority;
}
public TransformOptionGroup getTransformOptions()
{
return transformOptions;
}
public long getMaxSourceSizeBytes()
{
return maxSourceSizeBytes;
}
public String getName()
{
return name;
}
public int getPriority()
{
return priority;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SupportedTransform that = (SupportedTransform) o;
return maxSourceSizeBytes == that.maxSourceSizeBytes &&
priority == that.priority &&
Objects.equals(transformOptions, that.transformOptions) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode()
{
return Objects.hash(transformOptions, maxSourceSizeBytes, name, priority);
}
@Override
public String toString()
{
return name + ':' + maxSourceSizeBytes + ':' + priority;
}
}

View File

@ -0,0 +1,107 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import static java.util.Collections.emptyMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class TransformCache
{
// Looks up supported transform routes given source to target media types.
private final Map<String, Map<String, List<SupportedTransform>>> transforms =
new ConcurrentHashMap<>();
// Looks up coreVersion given the transformer name
private final Map<String, String> coreVersions = new ConcurrentHashMap<>();
// Caches results in the ACS repository implementations which repeatedly make the same request.
// Looks up a sorted list of transform routes, for a rendition (if the name is supplied) and the source
// media type. Unlike a lookup on the transforms map above, processing of the transform options and priorities
// will have already been done if cached.
private final Map<String, Map<String, List<SupportedTransform>>> cachedSupportedTransformList =
new ConcurrentHashMap<>();
protected int transformerCount = 0;
protected int transformCount = 0;
public void incrementTransformerCount()
{
transformerCount++;
}
public void appendTransform(final String sourceMimetype,
final String targetMimetype, final SupportedTransform transform,
String transformerName, String coreVersion)
{
transforms
.computeIfAbsent(sourceMimetype, k -> new ConcurrentHashMap<>())
.computeIfAbsent(targetMimetype, k -> new ArrayList<>())
.add(transform);
coreVersions.put(transformerName, coreVersion == null ? "" : coreVersion);
transformCount++;
}
public Map<String, List<SupportedTransform>> retrieveTransforms(final String sourceMimetype)
{
return transforms.getOrDefault(sourceMimetype, emptyMap());
}
public Map<String, Map<String, List<SupportedTransform>>> getTransforms()
{
return transforms;
}
public void cache(final String transformerName, final String sourceMimetype,
final List<SupportedTransform> transformListBySize)
{
cachedSupportedTransformList
.get(transformerName)
.put(sourceMimetype, transformListBySize);
}
public List<SupportedTransform> retrieveCached(final String transformerName,
final String sourceMimetype)
{
return cachedSupportedTransformList
.computeIfAbsent(transformerName, k -> new ConcurrentHashMap<>())
.get(sourceMimetype);
}
@Override
public String toString()
{
return transformerCount == 0 && transformCount == 0
? ""
: "(transformers: " + transformerCount + " transforms: " + transformCount + ")";
}
public String getCoreVersion(String transformerName)
{
String coreVersion = coreVersions.get(transformerName);
return coreVersion.isBlank() ? null : coreVersion;
}
}

View File

@ -0,0 +1,370 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Map.Entry;
import static java.util.stream.Collectors.toMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.alfresco.transform.client.model.config.TransformOption;
import org.alfresco.transform.client.model.config.TransformOptionGroup;
import org.alfresco.transform.client.model.config.TransformOptionValue;
import org.alfresco.transform.exceptions.TransformException;
class TransformRegistryHelper
{
private static final String TIMEOUT = "timeout";
static Set<TransformOption> lookupTransformOptions(final Set<String> transformOptionNames,
final Map<String, Set<TransformOption>> transformOptions, final String readFrom,
final Consumer<String> logError)
{
if (transformOptionNames == null)
{
return emptySet();
}
final Set<TransformOption> options = new HashSet<>();
for (String name : transformOptionNames)
{
final Set<TransformOption> oneSetOfTransformOptions = transformOptions.get(name);
if (oneSetOfTransformOptions == null)
{
logError.accept("transformOptions in " + readFrom + " with the name " + name +
" does not exist. Ignored");
continue;
}
options.add(new TransformOptionGroup(false, oneSetOfTransformOptions));
}
return options.size() == 1 ?
((TransformOptionGroup) options.iterator().next()).getTransformOptions() :
options;
}
// Returns transformers in increasing supported size order, where lower priority transformers for the same size have
// been discarded.
static List<SupportedTransform> retrieveTransformListBySize(final TransformCache data,
final String sourceMimetype, final String targetMimetype,
Map<String, String> actualOptions, String transformerName)
{
if (actualOptions == null)
{
actualOptions = emptyMap();
}
if (transformerName != null && transformerName.trim().isEmpty())
{
transformerName = null;
}
final List<SupportedTransform> cachedTransformList =
transformerName == null ? null :
data.retrieveCached(transformerName, sourceMimetype);
if (cachedTransformList != null)
{
return cachedTransformList;
}
final List<SupportedTransform> builtTransformList = buildTransformList(data,
sourceMimetype,
targetMimetype,
filterTimeout(actualOptions));
if (transformerName != null)
{
data.cache(transformerName, sourceMimetype, builtTransformList);
}
return builtTransformList;
}
private static List<SupportedTransform> buildTransformList(
final TransformCache data, final String sourceMimetype, final String targetMimetype,
final Map<String, String> actualOptions)
{
if(sourceMimetype == null)
{
throw new TransformException(400, "Null value provided for sourceMimetype, please provide a value");
}
if(targetMimetype == null)
{
throw new TransformException(400, "Null value provided for tragetMimetype, please provide a value");
}
final Map<String, List<SupportedTransform>> targetMap = data.retrieveTransforms(
sourceMimetype);
final List<SupportedTransform> supportedTransformList = targetMap.getOrDefault(
targetMimetype, emptyList());
final List<SupportedTransform> transformListBySize = new ArrayList<>();
for (SupportedTransform supportedTransform : supportedTransformList)
{
final Map<String, Boolean> possibleTransformOptions = gatherPossibleTransformOptions(
supportedTransform.getTransformOptions(), actualOptions);
if (optionsMatch(possibleTransformOptions, actualOptions))
{
addToSupportedTransformList(transformListBySize, supportedTransform);
}
}
return transformListBySize;
}
// Add newTransform to the transformListBySize in increasing size order and discards
// lower priority (numerically higher) transforms with a smaller or equal size.
private static void addToSupportedTransformList(
final List<SupportedTransform> transformListBySize,
final SupportedTransform newTransform)
{
if (transformListBySize.isEmpty())
{
transformListBySize.add(newTransform);
}
else
{
for (int i = 0; i < transformListBySize.size(); i++)
{
final SupportedTransform existingTransform = transformListBySize.get(i);
final int compareMaxSize = compareMaxSize(newTransform.getMaxSourceSizeBytes(),
existingTransform.getMaxSourceSizeBytes());
final int comparePriority = existingTransform.getPriority() - newTransform.getPriority();
if (compareMaxSize == 0)
{
if (comparePriority == 0)
{
// If same priority and size limit, replace with the newer transform.
// It is possibly a replacement in an extension.
transformListBySize.set(i, newTransform);
break;
}
else if (comparePriority > 0)
{
// Replace as newer one is higher priority and try to discard some existing ones.
transformListBySize.set(i, newTransform);
discardFromSupportedTransformList(transformListBySize, i);
break;
}
else
{
// Ignore as lower priority
break;
}
}
else if (compareMaxSize < 0)
{
if (comparePriority > 0)
{
// If higher priority insert and try to discard some existing ones.
transformListBySize.add(i, newTransform);
discardFromSupportedTransformList(transformListBySize, i);
break;
}
else
{
// Ignore the newer one as its priority is lower or the same as one that has a higher size limit
break;
}
}
else // if (compareMaxSize > 0)
{
if (comparePriority < 0)
{
if (i+1 < transformListBySize.size())
{
// Look at the next element as size is higher but the priority is lower.
continue;
}
else
{
// Append to the list as the size is higher but the priority is lower.
transformListBySize.add(newTransform);
break;
}
}
// Else same or better priority and higher size limit, so replace with the newer transform and try
// to discard some existing ones.
transformListBySize.set(i, newTransform);
discardFromSupportedTransformList(transformListBySize, i);
break;
}
}
}
}
// Starting at i+1, try to remove transforms that will not be used.
private static void discardFromSupportedTransformList(List<SupportedTransform> transformListBySize, int i)
{
SupportedTransform newTransform = transformListBySize.get(i++);
while (i < transformListBySize.size())
{
final SupportedTransform existingTransform = transformListBySize.get(i);
final int compareMaxSize = compareMaxSize(newTransform.getMaxSourceSizeBytes(),
existingTransform.getMaxSourceSizeBytes());
final int comparePriority = existingTransform.getPriority() - newTransform.getPriority();
// Discard those with
// 1) the same priority but support a smaller size
// 2) those with a lower priority and a smaller size
if ((comparePriority == 0 && compareMaxSize >= 0) ||
(comparePriority > 0 && compareMaxSize >= 0))
{
transformListBySize.remove(i);
}
else
{
break;
}
}
}
private static Map<String, Boolean> gatherPossibleTransformOptions(
final TransformOptionGroup transformOptionGroup, final Map<String, String> actualOptions)
{
final Map<String, Boolean> possibleTransformOptions = new HashMap<>();
addToPossibleTransformOptions(possibleTransformOptions, transformOptionGroup, true,
actualOptions);
return possibleTransformOptions;
}
/**
* Flatten out the transform options by adding them to the supplied possibleTransformOptions.</p>
*
* If possible discards options in the supplied transformOptionGroup if the group is optional and the actualOptions
* don't provide any of the options in the group. Or to put it another way:<p/>
*
* It adds individual transform options from the transformOptionGroup to possibleTransformOptions if the group is
* required or if the actualOptions include individual options from the group. As a result it is possible that none
* of the group are added if it is optional. It is also possible to add individual transform options that are
* themselves required but not in the actualOptions. In this the optionsMatch method will return false.
*
* @return true if any options were added. Used by nested call parents to determine if an option was added from a
* nested sub group.
*/
static boolean addToPossibleTransformOptions(
final Map<String, Boolean> possibleTransformOptions,
final TransformOptionGroup transformOptionGroup, final Boolean parentGroupRequired,
final Map<String, String> actualOptions)
{
boolean added = false;
boolean required = false;
final Set<TransformOption> optionList = transformOptionGroup.getTransformOptions();
if (optionList != null && !optionList.isEmpty())
{
// We need to avoid adding options from a group that is required but its parents are not.
boolean transformOptionGroupRequired = transformOptionGroup.isRequired() && parentGroupRequired;
// Check if the group contains options in actualOptions. This will add any options from sub groups.
for (final TransformOption transformOption : optionList)
{
if (transformOption instanceof TransformOptionGroup)
{
added = addToPossibleTransformOptions(possibleTransformOptions,
(TransformOptionGroup) transformOption, transformOptionGroupRequired,
actualOptions);
required |= added;
}
else
{
final String name = ((TransformOptionValue) transformOption).getName();
if (actualOptions.containsKey(name))
{
required = true;
}
}
}
if (required || transformOptionGroupRequired)
{
for (TransformOption transformOption : optionList)
{
if (transformOption instanceof TransformOptionValue)
{
added = true;
final TransformOptionValue option = (TransformOptionValue) transformOption;
possibleTransformOptions.put(option.getName(), option.isRequired());
}
}
}
}
return added;
}
// compare where -1 is unlimited.
private static int compareMaxSize(final long a, final long b)
{
return a == -1 ? b == -1 ? 0 : 1 : b == -1 ? -1 : Long.compare(a, b);
}
static boolean optionsMatch(final Map<String, Boolean> transformOptions,
final Map<String, String> actualOptions)
{
// Check all required transformOptions are supplied
final boolean supported = transformOptions
.entrySet()
.stream()
.filter(Entry::getValue)// filter by the required status
.map(Entry::getKey)// map to the option name
.allMatch(actualOptions::containsKey);
if (!supported)
{
return false;
}
// Check there are no extra unused actualOptions
return actualOptions
.keySet()
.stream()
.allMatch(transformOptions::containsKey);
}
private static Map<String, String> filterTimeout(final Map<String, String> options)
{
// Remove the "timeout" property from the actualOptions as it is not used to select a transformer.
if (!options.containsKey(TIMEOUT))
{
return options;
}
return options
.entrySet()
.stream()
.filter(e -> !TIMEOUT.equals(e.getKey()))
.collect(toMap(Entry::getKey, Entry::getValue));
}
}

View File

@ -0,0 +1,103 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import org.alfresco.transform.client.model.config.CoreFunction;
import java.util.Map;
/**
* Used by clients work out if a transformation is supported by a Transform Service.
*/
public interface TransformServiceRegistry
{
/**
* Works out if the Transform Server should be able to transform content of a given source mimetype and size into a
* target mimetype given a list of actual transform option names and values (Strings) plus the data contained in the
* Transformer objects registered with this class.
*
* @param sourceMimetype the mimetype of the source content
* @param sourceSizeInBytes the size in bytes of the source content. Ignored if negative.
* @param targetMimetype the mimetype of the target
* @param actualOptions the actual name value pairs available that could be passed to the Transform Service.
* @param transformName (optional) name for the set of options and target mimetype. If supplied is used to cache
* results to avoid having to work out if a given transformation is supported a second time.
* The sourceMimetype and sourceSizeInBytes may still change. In the case of ACS this is the
* rendition name.
* @return {@code}true{@code} if it is supported.
*/
default boolean isSupported(final String sourceMimetype, final long sourceSizeInBytes,
final String targetMimetype, final Map<String, String> actualOptions,
final String transformName)
{
long maxSize = findMaxSize(sourceMimetype, targetMimetype, actualOptions, transformName);
return maxSize != 0 && (maxSize == -1L || maxSize >= sourceSizeInBytes);
}
/**
* Returns the maximun size (in bytes) of the source content that can be transformed.
*
* @param sourceMimetype the mimetype of the source content
* @param targetMimetype the mimetype of the target
* @param actualOptions the actual name value pairs available that could be passed to the Transform Service.
* @param transformName (optional) name for the set of options and target mimetype. If supplied is used to cache
* results to avoid having to work out if a given transformation is supported a second time.
* The sourceMimetype and sourceSizeInBytes may still change. In the case of ACS this is the
* rendition name.
* @return the maximum size (in bytes) of the source content that can be transformed. If {@code -1} there is no
* limit, but if {@code 0} the transform is not supported.
*/
long findMaxSize(String sourceMimetype, String targetMimetype, Map<String, String> actualOptions,
String transformName);
/**
* Works out the name of the transformer (might not map to an actual transformer) that will be used to transform
* content of a given source mimetype and size into a target mimetype given a list of actual transform option names
* and values (Strings) plus the data contained in the Transformer objects registered with this class.
*
* @param sourceMimetype the mimetype of the source content
* @param sourceSizeInBytes the size in bytes of the source content. Ignored if negative.
* @param targetMimetype the mimetype of the target
* @param actualOptions the actual name value pairs available that could be passed to the Transform Service.
* @param renditionName (optional) name for the set of options and target mimetype. If supplied is used to cache
* results to avoid having to work out if a given transformation is supported a second time.
* The sourceMimetype and sourceSizeInBytes may still change. In the case of ACS this is the
* rendition name.
* @return the name of the transformer or {@code}null{@code} if not set or there is no supported transformer.
*/
String findTransformerName(String sourceMimetype, long sourceSizeInBytes, String targetMimetype,
Map<String, String> actualOptions, String renditionName);
/**
* Returns {@code true} if the {@code function} is supported by the named transformer. Not all transformers are
* able to support all functionality, as newer features may have been introduced into the core t-engine code since
* it was released. Normally used in conjunction with {@link #findTransformerName(String, long, String, Map, String)}
* rather than {@link #isSupported(String, long, String, Map, String)}.
* @param function to be checked.
* @param transformerName name of the transformer.
* @return {@code true} is supported, {@code false} otherwise.
*/
default boolean isSupported(CoreFunction function, String transformerName)
{
return false;
}
}

View File

@ -0,0 +1,84 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import org.alfresco.transform.client.model.config.OverrideSupported;
import java.util.Objects;
/**
* Key based using {@code transformerName} and {@code sourceMediaType} used as a key to lookup default values held in
* {@link OverrideSupported} objects.
*/
class TransformerAndSourceType
{
private String transformerName;
private String sourceMediaType;
public TransformerAndSourceType(String transformerName, String sourceMediaType)
{
this.transformerName = transformerName;
this.sourceMediaType = sourceMediaType;
}
public void setTransformerName(String transformerName)
{
this.transformerName = transformerName;
}
public void setSourceMediaType(String sourceMediaType)
{
this.sourceMediaType = sourceMediaType;
}
public String getTransformerName()
{
return transformerName;
}
public String getSourceMediaType()
{
return sourceMediaType;
}
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
TransformerAndSourceType that = (TransformerAndSourceType)o;
return Objects.equals(transformerName, that.transformerName) &&
Objects.equals(sourceMediaType, that.sourceMediaType);
}
@Override
public int hashCode()
{
return Objects.hash(transformerName, sourceMediaType);
}
}

View File

@ -0,0 +1,77 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.util;
import org.alfresco.transform.client.model.config.CoreVersionDecorator;
/**
* Request parameters and transform options used in the core transformers.
*/
public interface RequestParamMap
{
// Transform options used in the core transformers.
String SOURCE_ENCODING = "sourceEncoding";
String TARGET_ENCODING = "targetEncoding";
String PAGE_REQUEST_PARAM = "page";
String WIDTH_REQUEST_PARAM = "width";
String HEIGHT_REQUEST_PARAM = "height";
String ALLOW_PDF_ENLARGEMENT = "allowPdfEnlargement";
String MAINTAIN_PDF_ASPECT_RATIO = "maintainPdfAspectRatio";
String START_PAGE = "startPage";
String END_PAGE = "endPage";
String ALPHA_REMOVE = "alphaRemove";
String AUTO_ORIENT = "autoOrient";
String CROP_GRAVITY = "cropGravity";
String CROP_WIDTH = "cropWidth";
String CROP_HEIGHT = "cropHeight";
String CROP_PERCENTAGE = "cropPercentage";
String CROP_X_OFFSET = "cropXOffset";
String CROP_Y_OFFSET = "cropYOffset";
String THUMBNAIL = "thumbnail";
String RESIZE_WIDTH = "resizeWidth";
String RESIZE_HEIGHT = "resizeHeight";
String RESIZE_PERCENTAGE = "resizePercentage";
String ALLOW_ENLARGEMENT = "allowEnlargement";
String MAINTAIN_ASPECT_RATIO = "maintainAspectRatio";
String COMMAND_OPTIONS = "commandOptions";
String TIMEOUT = "timeout";
String INCLUDE_CONTENTS = "includeContents";
String NOT_EXTRACT_BOOKMARKS_TEXT = "notExtractBookmarksText";
String PAGE_LIMIT = "pageLimit";
// Parameters interpreted by the AbstractTransformerController
String DIRECT_ACCESS_URL = "directAccessUrl";
// An optional parameter (defaults to 1) to be included in the request to the t-engine {@code /transform/config}
// endpoint to specify what version (of the schema) to return. Provides the flexibility to introduce changes
// without getting deserialization issues when we have components at different versions.
String CONFIG_VERSION = "configVersion";
String CONFIG_VERSION_DEFAULT = "1";
int CONFIG_VERSION_LATEST = CoreVersionDecorator.CONFIG_VERSION_INCLUDES_CORE_VERSION;
// Endpoints
String ENDPOINT_TRANSFORM = "/transform";
String ENDPOINT_TRANSFORM_CONFIG = "/transform/config";
String ENDPOINT_TRANSFORM_CONFIG_LATEST = ENDPOINT_TRANSFORM_CONFIG + "?" + CONFIG_VERSION + "=" + CONFIG_VERSION_LATEST;
String ENDPOINT_TRANSFORM_LOG = "/log";
String ENDPOINT_TRANSFORM_TEST = "/";
}

View File

@ -0,0 +1,44 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2018 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.exceptions;
public class TransformException extends RuntimeException
{
private final int statusCode;
public TransformException(int statusCode, String message)
{
super(message);
this.statusCode = statusCode;
}
public TransformException(int statusCode, String message, Throwable cause)
{
super(message, cause);
this.statusCode = statusCode;
}
public int getStatusCode()
{
return statusCode;
}
}

View File

@ -0,0 +1,155 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.router;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_DITA;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_EXCEL;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_IMAGE_SVG;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_GRAPHICS;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_PRESENTATION;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_SPREADSHEET;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_TEXT;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENOFFICE1_CALC;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENOFFICE1_IMPRESS;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENOFFICE1_WRITER;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_ADDIN;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_SLIDE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_SLIDE_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_TEMPLATE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_PRESENTATION_TEMPLATE_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_SPREADSHEET;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_BINARY_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_WORDPROCESSING;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_WORDPROCESSING_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_WORD_TEMPLATE;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OPENXML_WORD_TEMPLATE_MACRO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_OUTLOOK_MSG;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_PPT;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_VISIO;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_VISIO_2013;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_WORD;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_WORDPERFECT;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_TEXT_PLAIN;
import static org.alfresco.transform.router.TransformerDebug.MIMETYPE_METADATA_EMBED;
import static org.alfresco.transform.router.TransformerDebug.MIMETYPE_METADATA_EXTRACT;
import java.util.Map;
/**
* Provides mapping between mimtypes and file extensions, static and not configurable.
* The correct extension is required for a small subset of transforms in pipelines which go through the
* libreoffice transformer
*/
public class ExtensionService
{
private final static String MIMETYPE_TAB_SEPARATED_VALUES="text/tab-separated-values";
private final static String MIMETYPE_CALC_TEMPLATE="application/vnd.sun.xml.calc.template";
private final static String MIMETYPE_IMPRESS_TEMPLATE="application/vnd.sun.xml.impress.template";
private final static String MIMETYPE_WRITER_TEMPLATE="application/vnd.sun.xml.writer.template";
private static final Map<String,String> mimetpeExtensions = Map.ofEntries(
Map.entry(MIMETYPE_WORD, "doc"),
Map.entry(MIMETYPE_OPENXML_WORDPROCESSING_MACRO, "docm"),
Map.entry(MIMETYPE_OPENXML_WORDPROCESSING, "docx"),
Map.entry(MIMETYPE_OPENXML_WORD_TEMPLATE_MACRO, "dotm"),
Map.entry(MIMETYPE_OPENXML_WORD_TEMPLATE, "dotx"),
Map.entry(MIMETYPE_OPENDOCUMENT_GRAPHICS, "odg"),
Map.entry(MIMETYPE_OPENDOCUMENT_PRESENTATION, "odp"),
Map.entry(MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE, "otp"),
Map.entry(MIMETYPE_OPENDOCUMENT_SPREADSHEET, "ods"),
Map.entry(MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE, "ots"),
Map.entry(MIMETYPE_OPENDOCUMENT_TEXT, "odt"),
Map.entry(MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE, "ott"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_TEMPLATE_MACRO, "potm"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_TEMPLATE, "potx"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_ADDIN, "ppam"),
Map.entry(MIMETYPE_PPT, "ppt"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_MACRO, "pptm"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION, "pptx"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_SLIDE_MACRO, "sldm"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_SLIDE, "sldx"),
Map.entry(MIMETYPE_CALC_TEMPLATE, "stc"),
Map.entry(MIMETYPE_IMPRESS_TEMPLATE, "sti"),
Map.entry(MIMETYPE_WRITER_TEMPLATE, "stw"),
Map.entry(MIMETYPE_TAB_SEPARATED_VALUES, "tsv"),
Map.entry(MIMETYPE_OPENOFFICE1_CALC, "sxc"),
Map.entry(MIMETYPE_OPENOFFICE1_IMPRESS, "sxi"),
Map.entry(MIMETYPE_OPENOFFICE1_WRITER, "sxw"),
Map.entry(MIMETYPE_VISIO, "vsd"),
Map.entry(MIMETYPE_VISIO_2013, "vsdx"),
Map.entry(MIMETYPE_WORDPERFECT, "wp"),
Map.entry(MIMETYPE_EXCEL, "xls"),
Map.entry(MIMETYPE_OPENXML_SPREADSHEET_BINARY_MACRO, "xlsb"),
Map.entry(MIMETYPE_OPENXML_SPREADSHEET_MACRO, "xlsm"),
Map.entry(MIMETYPE_OPENXML_SPREADSHEET, "xlsx"),
Map.entry(MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE_MACRO, "xltm"),
Map.entry(MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW, "ppsx"),
Map.entry(MIMETYPE_OUTLOOK_MSG, "msg"),
Map.entry(MIMETYPE_DITA, "dita"),
Map.entry(MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE, "xltx"),
Map.entry(MIMETYPE_IMAGE_SVG, "svg"),
Map.entry(MIMETYPE_TEXT_PLAIN, "txt")
);
public static String getExtensionForTargetMimetype(String targetMimetype, String sourceMimetype)
{
if (targetMimetype == null)
{
return null;
}
return getExtensionForMimetype(MIMETYPE_METADATA_EMBED.equals(targetMimetype) ? sourceMimetype : targetMimetype);
}
public static String getExtensionForMimetype(String mimetype)
{
if (mimetype == null)
{
return null;
}
if (mimetpeExtensions.containsKey(mimetype))
{
return mimetpeExtensions.get(mimetype);
}
if (MIMETYPE_METADATA_EXTRACT.equals(mimetype))
{
return "json";
}
// else fall back to the original implementation
return splitMimetype(mimetype);
}
// Moved from Dispatcher. This fails to work in many cases, but we need to be backward compatible.
private static String splitMimetype(final String mimetype)
{
final String[] parts = mimetype.split("[.\\-_|/]");
return parts[parts.length - 1];
}
}

View File

@ -0,0 +1,97 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.router;
import java.util.StringJoiner;
import java.util.stream.Stream;
/**
* The client data supplied and echoed back to content repository (the client). May be modified to include
* TransformerDebug.
*/
class RepositoryClientData
{
private static final String CLIENT_DATA_SEPARATOR = "\u23D0";
public static final String DEBUG_SEPARATOR = "\u23D1";
private static final String REPO_ID = "Repo";
private static final String DEBUG = "debug:";
private final String[] split;
public RepositoryClientData(String clientData)
{
split = clientData == null ? null : clientData.split(CLIENT_DATA_SEPARATOR);
}
private boolean isRepositoryClientData()
{
return split != null && split.length == 10 && split[0].startsWith(REPO_ID);
}
public String getAcsVersion()
{
return isRepositoryClientData() ? split[0].substring(REPO_ID.length()) : "";
}
public int getRequestId()
{
try
{
return isRepositoryClientData() ? Integer.parseInt(split[6]) : -1;
}
catch (NumberFormatException e)
{
return -1;
}
}
public String getRenditionName()
{
return isRepositoryClientData() ? split[2] : "";
}
public void appendDebug(String message)
{
if (isDebugRequested())
{
split[9] += DEBUG_SEPARATOR+ message;
}
}
public boolean isDebugRequested()
{
return isRepositoryClientData() && split[9].startsWith(DEBUG);
}
@Override
public String toString()
{
if (split == null)
{
return null;
}
StringJoiner sj = new StringJoiner(CLIENT_DATA_SEPARATOR);
Stream.of(split).forEach(element -> sj.add(element));
return sj.toString();
}
}

View File

@ -0,0 +1,546 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.router;
import org.alfresco.transform.client.model.InternalContext;
import org.alfresco.transform.client.model.TransformReply;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
/**
* Represents the current state of a top level transform request in terms of its current nested call stack, which is
* the current transform step being performed and what other transform steps are still to be executed in the current
* level. This information is encoded in the {@link org.alfresco.transform.client.model.MultiStep} structure of the
* internal context passed between T-Router and T-Engines. Ideally we would have changed the structure,
* but for backward compatibility we are using the existing structure, which allows T-Engines that were developed
* previously to be used unchanged.<p><br/>
*
* Originally the T-Router only allowed pipeline and single step transforms, so it was possible to represent them as a
* flat list. However the original design was of a nested structure. Pipelines are just one example, with failover
* transforms being the other. To bring the T-Router up to the same level of functionality as the Repository, it too
* needs to support nested transforms. <p><br/>
*
* <li>@{code transformsToBeDone[0]} holds the original Transformer Request Options as a list of key values pairs.
* Needed so that we don't lose values as we walk down the individual transform steps</li>
* <li>@{code transformsToBeDone[1]} holds the original source reference so that we don't delete the original source
* until the whole transform is known to be successful, so that a queue entry may be retried on failure.</li>
* <li>@{code transformsToBeDone[2]} holds information about the top level transform</li>
* <li>@{code transformsToBeDone[size()-1]} holds information about the most nested transform being processed</li>
* <li>Each level contains a list of step transforms. Just one for a singe step transform or a list for pipeline and
* failover transforms</li>
* <li>When a step is processed it will result in the creation of another level if it is a pipeline or failover
* transform</li>
* <li>As steps are completed, they are removed</li>
* <li>When there are no steps left in a level the level is removed</li>
*
* Each level is represented by a String with a pipeline or failover flag @{code 'P'|'F'} followed by a step counter
* and start time used in debug, a retry count and a sequence of transform steps. Each step is made up of three parts:
* @{code<transformerName>|<sourceMimetype>|<targetMimetype> . All fields are separated by a @code{'\u23D0'} character.
* The last step in the sequence is the current transform being performed. The top level transform is a pipeline of
* one step. Although the source and target mimetypes are always the same for failover transforms, they use the same
* structure.
*/
public class TransformStack
{
public static final String PIPELINE_FLAG = "P";
public static final String FAILOVER_FLAG = "F";
static final String SEPARATOR = "\u23D0";
private static final String SEPARATOR_REGEX = "\u23D0";
static final int OPTIONS_LEVEL = 0;
static final int SOURCE_REFERENCE_LEVEL = 1;
static final int TOP_STACK_LEVEL = 2;
private static final int FLAG_INDEX = 0;
private static final int REFERENCE_INDEX = 1;
private static final int START_INDEX = 2;
private static final int RETRY_INDEX = 3;
private static final int FIELDS_IN_HEADER = 4; // flag | counter | retry
private static final int FIELDS_PER_STEP = 3; // name | source | target
public static LevelBuilder levelBuilder(String flag)
{
return new LevelBuilder(flag);
}
public static class LevelBuilder
{
private final ArrayList<String> reverseOrderStepElements = new ArrayList<>();
private final String flag;
private LevelBuilder(String flag)
{
this.flag = flag;
}
public LevelBuilder withStep(String transformerName, String sourceMediaType, String targetMediaType)
{
reverseOrderStepElements.add(targetMediaType);
reverseOrderStepElements.add(sourceMediaType);
reverseOrderStepElements.add(transformerName);
return this;
}
public String build()
{
StringJoiner stringJoiner = new StringJoiner(SEPARATOR);
stringJoiner.add(flag);
stringJoiner.add("1");
stringJoiner.add("0");
stringJoiner.add("0");
for (int i=reverseOrderStepElements.size()-1; i>=0; i--)
{
stringJoiner.add(reverseOrderStepElements.get(i));
}
return stringJoiner.toString();
}
}
public static class Step
{
private final String[] step;
private Step(String stepWithSeparatedFields)
{
this.step = stepWithSeparatedFields.split(SEPARATOR_REGEX);
}
public String getTransformerName()
{
return step[0];
}
public String getSourceMediaType()
{
return step[1];
}
public String getTargetMediaType()
{
return step[2];
}
}
public static void setInitialTransformRequestOptions(InternalContext internalContext,
Map<String, String> transformRequestOptions)
{
init(internalContext);
StringJoiner sj = new StringJoiner(SEPARATOR);
transformRequestOptions.forEach((key,value)-> sj.add(key).add(value));
levels(internalContext).set(OPTIONS_LEVEL, sj.toString());
}
public static void setInitialSourceReference(InternalContext internalContext, String sourceReference)
{
init(internalContext);
levels(internalContext).set(SOURCE_REFERENCE_LEVEL, sourceReference);
}
public static Map<String, String> getInitialTransformRequestOptions(InternalContext internalContext)
{
Map<String, String> transformRequestOptions = new HashMap<>();
// To avoid the case where split() discards the last value, when it is a zero length string, we add and remove
// a space. None of the keys or value may be null.
String[] split = (level(internalContext, OPTIONS_LEVEL)+' ').split(SEPARATOR_REGEX);
String lastValue = split[split.length - 1];
split[split.length-1] = lastValue.substring(0, lastValue.length()-1);
for (int i = split.length-2; i >= 0; i-=2)
{
transformRequestOptions.put(split[i], split[i+1]);
}
return transformRequestOptions;
}
public static String getInitialSourceReference(InternalContext internalContext)
{
return level(internalContext, SOURCE_REFERENCE_LEVEL);
}
private static List<String> levels(InternalContext internalContext)
{
return internalContext.getMultiStep().getTransformsToBeDone();
}
private static String level(InternalContext internalContext, int i)
{
return levels(internalContext).get(i);
}
private static void init(InternalContext internalContext)
{
while(levels(internalContext).size() < TOP_STACK_LEVEL)
{
levels(internalContext).add(null);
}
}
private static String currentLevel(InternalContext internalContext)
{
return parentLevel(internalContext, 0);
}
private static String parentLevel(InternalContext internalContext, int parentLevels)
{
List<String> levels = levels(internalContext);
int i = levels.size() - 1 - parentLevels;
return i >= TOP_STACK_LEVEL ? levels.get(i) : null;
}
public static boolean isFinished(InternalContext internalContext)
{
int levelsLeft = levels(internalContext).size() - TOP_STACK_LEVEL;
return levelsLeft <= 0 || // there has been an error, so we have lost the stack
levelsLeft == 1 && // on top level wrapper level
isTransformLevelFinished(internalContext); // the one step has been processed (removed)
}
public static void addTransformLevel(InternalContext internalContext, LevelBuilder levelBuilder)
{
levels(internalContext).add(levelBuilder.build());
}
public static void setReference(InternalContext internalContext, int requestCountOrClientRequestId)
{
setHeaderField(internalContext, REFERENCE_INDEX, requestCountOrClientRequestId);
}
public static void incrementReference(InternalContext internalContext)
{
setHeaderField(internalContext, REFERENCE_INDEX, getReferenceCounter(internalContext)+1);
}
public static void resetAttemptedRetries(InternalContext internalContext)
{
setHeaderField(internalContext, RETRY_INDEX, 0);
}
public static void setStartTime(InternalContext internalContext)
{
setHeaderField(internalContext, START_INDEX, System.currentTimeMillis());
}
public static void incrementAttemptedRetries(InternalContext internalContext)
{
setHeaderField(internalContext, RETRY_INDEX,getAttemptedRetries(internalContext)+1);
}
private static void setHeaderField(InternalContext internalContext, int index, long value)
{
List<String> levels = levels(internalContext);
int size = levels.size();
String level = levels.get(size-1);
int j = indexOfField(level, index);
int k = level.indexOf(SEPARATOR, j+1);
levels.set(size-1, level.substring(0, j) + value + level.substring(k));
}
public static String getReference(InternalContext internalContext)
{
StringJoiner ref = new StringJoiner(".");
List<String> levels = levels(internalContext);
for (int i=TOP_STACK_LEVEL; i<levels.size(); i++)
{
ref.add(getHeaderField(levels.get(i), REFERENCE_INDEX).toString());
}
return ref.toString();
}
public static void setReferenceInADummyTopLevelIfUnset(InternalContext internalContext, int reference)
{
if (reference != -1 && getReference(internalContext).isBlank() ) // When top transform level not set
{
init(internalContext);
addTransformLevel(internalContext, levelBuilder(PIPELINE_FLAG));
setReference(internalContext, reference);
}
}
public static long getElapsedTime(InternalContext internalContext)
{
return System.currentTimeMillis() - getHeaderField(internalContext, START_INDEX).longValue();
}
private static int getReferenceCounter(InternalContext internalContext)
{
return getHeaderField(internalContext, REFERENCE_INDEX).intValue();
}
public static int getAttemptedRetries(InternalContext internalContext)
{
return getHeaderField(internalContext, RETRY_INDEX).intValue();
}
private static Long getHeaderField(InternalContext internalContext, int index)
{
return getHeaderField(currentLevel(internalContext), index);
}
private static Long getHeaderField(String level, int index)
{
return Long.valueOf(level.split(SEPARATOR_REGEX)[index]);
}
public static void removeTransformLevel(InternalContext internalContext)
{
List<String> levels = levels(internalContext);
levels.remove(levels.size()-1);
}
public static void removeRemainingTransformLevels(TransformReply reply, TransformerDebug transformerDebug)
{
List<String> levels = levels(reply.getInternalContext());
if (levels != null)
{
while (!TransformStack.isFinished(reply.getInternalContext()))
{
removeFailedStep(reply, transformerDebug);
}
}
}
public static boolean isParentAFailover(InternalContext internalContext)
{
String level = currentLevel(internalContext);
return level != null && level.startsWith(FAILOVER_FLAG);
}
public static String getParentName(InternalContext internalContext)
{
String level = parentLevel(internalContext, 1);
return level == null ? null : new Step(level.substring(indexOfNextStep(level))).getTransformerName();
}
public static Step currentStep(InternalContext internalContext)
{
String level = currentLevel(internalContext);
return new Step(level.substring(indexOfNextStep(level)));
}
public static boolean isLastStepInTransformLevel(InternalContext internalContext)
{
return getStepCount(internalContext) == 1;
}
private static boolean isTransformLevelFinished(InternalContext internalContext)
{
return getStepCount(internalContext) == 0;
}
private static int getStepCount(InternalContext internalContext)
{
return (StringUtils.countMatches(currentLevel(internalContext), SEPARATOR)+1-FIELDS_IN_HEADER)/FIELDS_PER_STEP;
}
public static void removeSuccessfulStep(TransformReply reply, TransformerDebug transformerDebug)
{
removeFinishedSteps(reply, true, transformerDebug);
}
public static void removeFailedStep(TransformReply reply, TransformerDebug transformerDebug)
{
removeFinishedSteps(reply, false, transformerDebug);
}
private static void removeFinishedSteps(TransformReply reply, boolean successful,
TransformerDebug transformerDebug)
{
TransformStack.removeStep(reply, successful, transformerDebug);
InternalContext internalContext = reply.getInternalContext();
while (!TransformStack.isFinished(internalContext) &&
TransformStack.isTransformLevelFinished(internalContext))
{
TransformStack.removeTransformLevel(internalContext);
// We want to exit if removing steps for a failure and we have a parent that is a failover transform
if (successful || !TransformStack.isParentAFailover(internalContext))
{
TransformStack.removeStep(reply, successful, transformerDebug);
}
}
}
private static void removeStep(TransformReply reply, boolean successful, TransformerDebug transformerDebug)
{
InternalContext internalContext = reply.getInternalContext();
boolean parentAFailover = isParentAFailover(internalContext);
boolean successfulFailoverStep = successful && parentAFailover;
boolean unsuccessfulPipelineStep = !successful && !parentAFailover;
transformerDebug.popTransform(reply);
// For a successful failover step, or an unsuccessful pipeline step remove all sibling steps, otherwise just
// remove one step as it was a successful pipeline step or an unsuccessful failover step
List<String> levels = levels(internalContext);
int size = levels.size();
String level = levels.get(size-1);
levels.set(size-1, level.substring(0,
(successfulFailoverStep || unsuccessfulPipelineStep ? indexOfLastStep(level) : indexOfNextStep(level)) - 1));
if (!isTransformLevelFinished(internalContext))
{
TransformStack.incrementReference(internalContext);
}
}
private static int indexOfNextStep(String level)
{
int j = level.length()-1;
for (int i = FIELDS_PER_STEP; i >= 1 && j > 0; i--)
{
j = level.lastIndexOf(SEPARATOR, j-1);
}
return j+1;
}
private static int indexOfLastStep(String level)
{
return indexOfField(level, FIELDS_IN_HEADER);
}
private static int indexOfField(String level, int n)
{
int j = 0;
for (int i = n; i >= 1; i--)
{
j = level.indexOf(SEPARATOR, j+1);
}
return j+1;
}
public static String checkStructure(InternalContext internalContext, String type)
{
// A null value will have been replaced with an empty array, so no need to check for that.
String errorMessage = levels(internalContext).size() < (TOP_STACK_LEVEL + 1)
? type+" InternalContext did not have the Stack set"
: !validTransformOptions(internalContext)
? type+" InternalContext did not have the TransformOptions set correctly"
: levels(internalContext).size() == 1
? type+" InternalContext levels were not set"
: !validLevels(levels(internalContext))
? type+" InternalContext did not have levels set correctly"
: null;
return errorMessage;
}
private static boolean validTransformOptions(InternalContext internalContext)
{
String keysAndValues = level(internalContext, OPTIONS_LEVEL);
if (keysAndValues == null)
{
return false;
}
if ("".equals(keysAndValues))
{
return true;
}
String[] split = keysAndValues.split(SEPARATOR_REGEX);
if (split.length%2 != 0)
{
return false;
}
for (int i = split.length-2; i >= 0; i-=2)
{
if (split[i].isEmpty())
{
return false;
}
}
return true;
}
private static boolean validLevels(List<String> levels)
{
for (int i=levels.size()-1; i >=TOP_STACK_LEVEL; i--)
{
String level = levels.get(i);
if (!validLevel(level))
{
return false;
}
}
return true;
}
private static boolean validLevel(String level)
{
if (level == null)
{
return false;
}
String[] split = level.split(SEPARATOR_REGEX);
if (split.length < FIELDS_IN_HEADER+FIELDS_PER_STEP || // must be at least 1 step
(split.length-FIELDS_IN_HEADER)%FIELDS_PER_STEP != 0 ||
(!PIPELINE_FLAG.equals(split[FLAG_INDEX]) &&
!FAILOVER_FLAG.equals(split[FLAG_INDEX])) ||
!aPositiveInt(split[REFERENCE_INDEX]) ||
!aPositiveLong(split[START_INDEX]) ||
!aPositiveInt(split[RETRY_INDEX]))
{
return false;
}
for (int i=split.length-1; i>=FIELDS_IN_HEADER; i--)
{
if (split[i].isBlank())
{
return false;
}
}
return true;
}
private static boolean aPositiveInt(String string)
{
try
{
return Integer.valueOf(string).toString().equals(string) && Integer.valueOf(string) >= 0;
}
catch (NumberFormatException e)
{
return false;
}
}
private static boolean aPositiveLong(String string)
{
try
{
return Long.valueOf(string).toString().equals(string) && Long.valueOf(string) >= 0;
}
catch (NumberFormatException e)
{
return false;
}
}
}

View File

@ -0,0 +1,345 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.router;
import org.alfresco.transform.client.model.InternalContext;
import org.alfresco.transform.client.model.TransformReply;
import org.alfresco.transform.client.model.TransformRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Map;
public class TransformerDebug
{
public static final Logger logger = LoggerFactory.getLogger(TransformerDebug.class);
private static final int REFERENCE_SIZE = 15;
private static final String TRANSFORM_NAMESPACE = "transform:";
public static final String MIMETYPE_METADATA_EXTRACT = "alfresco-metadata-extract";
public static final String MIMETYPE_METADATA_EMBED = "alfresco-metadata-embed";
private static final String TIMEOUT = "timeout";
// For truncating long option values
private static int MAX_OPTION_VALUE = 60;
private static int MAX_OPTION_END_CHARS = 5;
private static String MAX_OPTION_DOTS = "...";
private boolean isTEngine = false;
public void pushTransform(TransformRequest request)
{
RepositoryClientData repositoryClientData = new RepositoryClientData(request.getClientData());
if (isEnabled(repositoryClientData))
{
TransformStack.Step step = TransformStack.currentStep(request.getInternalContext());
String reference = TransformStack.getReference(request.getInternalContext());
boolean isTopLevel = isTopLevel(reference);
String message = getPaddedReference(reference) +
getMimetypeExt(step.getSourceMediaType()) +
getTargetMimetypeExt(step.getTargetMediaType(), step.getSourceMediaType()) + ' ' +
(isTopLevel || isTEngine()
? fileSize(request.getSourceSize()) + ' ' +
getRenditionName(new RepositoryClientData(request.getClientData()).getRenditionName())
: "") +
step.getTransformerName();
if (isDebugToBeReturned(repositoryClientData))
{
repositoryClientData.appendDebug(message);
request.setClientData(repositoryClientData.toString());
}
logger.debug(message);
}
}
public void pushTransform(String reference, String sourceMimetype, String targetMimetype, File sourceFile, String transformerName)
{
if (logger.isDebugEnabled())
{
final long sourceSizeInBytes = sourceFile.length();
String message = getPaddedReference(reference) +
getMimetypeExt(sourceMimetype) +
getTargetMimetypeExt(targetMimetype, sourceMimetype) + ' ' +
fileSize(sourceSizeInBytes) + ' ' +
transformerName;
logger.debug(message);
}
}
public void popTransform(TransformReply reply)
{
if (logger.isDebugEnabled())
{
InternalContext internalContext = reply.getInternalContext();
String reference = TransformStack.getReference(internalContext);
long elapsedTime = TransformStack.getElapsedTime(internalContext);
String message = getPaddedReference(reference) + "Finished in " + ms(elapsedTime);
if (isTopLevel(reference) || isTEngine())
{
logger.debug(message);
}
else
{
logger.trace(message);
}
// We don't append the Finished message to ClientData as that would be too much
}
}
public void popTransform(String reference, long elapsedTime)
{
if (logger.isDebugEnabled())
{
String message = getPaddedReference(reference) + "Finished in " + ms(elapsedTime);
logger.debug(message);
}
}
public void logOptions(TransformRequest request)
{
RepositoryClientData repositoryClientData = new RepositoryClientData(request.getClientData());
if (isEnabled(repositoryClientData))
{
Map<String, String> options = request.getTransformRequestOptions();
if (options != null && !options.isEmpty())
{
String reference = TransformStack.getReference(request.getInternalContext());
for (Map.Entry<String, String> option : options.entrySet())
{
String key = option.getKey();
if (!TIMEOUT.equals(key))
{
String value = option.getValue();
String message = getOptionAndValue(reference, key, value);
logger.debug(message);
if (isDebugToBeReturned(repositoryClientData))
{
repositoryClientData.appendDebug(message);
}
}
}
request.setClientData(repositoryClientData.toString());
}
}
}
public void logOptions(String reference, Map<String, String> options)
{
if (logger.isDebugEnabled() && options != null && !options.isEmpty())
{
for (Map.Entry<String, String> option : options.entrySet())
{
String key = option.getKey();
if (!TIMEOUT.equals(key))
{
String value = option.getValue();
String message = getOptionAndValue(reference, key, value);
logger.debug(message);
}
}
}
}
String getOptionAndValue(String reference, String key, String value)
{
// Truncate the value if it is long or needs to be protected, like Direct Access Urls
int len = value.length();
if (len > MAX_OPTION_VALUE)
{
value = value.substring(0, MAX_OPTION_VALUE-MAX_OPTION_DOTS.length()-MAX_OPTION_END_CHARS) +
MAX_OPTION_DOTS +value.substring(len-MAX_OPTION_END_CHARS);
}
return getPaddedReference(reference) +
" " + key + "=\"" + value.replaceAll("\"", "\\\"") + "\"";
}
public void logFailure(TransformReply reply)
{
RepositoryClientData repositoryClientData = new RepositoryClientData(reply.getClientData());
if (isEnabled(repositoryClientData))
{
String reference = TransformStack.getReference(reply.getInternalContext());
String message = getPaddedReference(reference) + reply.getErrorDetails();
logger.debug(message);
if (isDebugToBeReturned(repositoryClientData))
{
repositoryClientData.appendDebug(message);
reply.setClientData(repositoryClientData.toString());
}
}
}
public void logFailure(String reference, String message)
{
if (logger.isDebugEnabled())
{
logger.debug(getPaddedReference(reference) + message);
}
}
// T-Engines call this method, as the T-Router will appended the same debug messages
public TransformerDebug setIsTEngine(boolean isTEngine)
{
this.isTEngine = isTEngine;
return this;
}
private boolean isEnabled(RepositoryClientData repositoryClientData)
{
return logger.isDebugEnabled() || isDebugToBeReturned(repositoryClientData);
}
private boolean isDebugToBeReturned(RepositoryClientData repositoryClientData)
{
return !isTEngine() && repositoryClientData.isDebugRequested();
}
private boolean isTEngine()
{
return isTEngine;
}
private boolean isTopLevel(String reference)
{
return !reference.contains(".");
}
private String getPaddedReference(String reference)
{
return reference + spaces(REFERENCE_SIZE + 3 - reference.length()); // 3 for "a) " ordered list
}
private String getMimetypeExt(String mimetype)
{
return padExt(ExtensionService.getExtensionForMimetype(mimetype), mimetype);
}
public String getTargetMimetypeExt(String targetMimetype, String sourceMimetype)
{
return padExt(ExtensionService.getExtensionForTargetMimetype(targetMimetype, sourceMimetype), targetMimetype);
}
private String padExt(String mimetypeExt, String mimetype)
{
StringBuilder sb = new StringBuilder("");
if (mimetypeExt == null)
{
sb.append(mimetype);
}
else
{
sb.append(mimetypeExt);
sb.append(spaces(4 - mimetypeExt.length())); // Pad to normal max ext (4)
}
sb.append(' ');
return sb.toString();
}
private String getRenditionName(String renditionName)
{
return !renditionName.isEmpty()
? "-- "+ replaceWithMetadataRenditionNameIfEmbedOrExtract(renditionName)+" -- "
: "";
}
private static String replaceWithMetadataRenditionNameIfEmbedOrExtract(String renditionName)
{
String transformName = getTransformName(renditionName);
return transformName.startsWith(MIMETYPE_METADATA_EXTRACT)
? "metadataExtract"
: transformName.startsWith(MIMETYPE_METADATA_EMBED)
? "metadataEmbed"
: renditionName;
}
private static String getTransformName(String renditionName)
{
return !renditionName.startsWith(TRANSFORM_NAMESPACE)
? ""
: renditionName.substring(TRANSFORM_NAMESPACE.length());
}
private String spaces(int i)
{
StringBuilder sb = new StringBuilder("");
while (--i >= 0)
{
sb.append(' ');
}
return sb.toString();
}
private String ms(long time)
{
return String.format("%,d ms", time);
}
private String fileSize(long size)
{
if (size < 0)
{
return "unlimited";
}
if (size == 1)
{
return "1 byte";
}
final String[] units = new String[] { "bytes", "KB", "MB", "GB", "TB" };
long divider = 1;
for(int i = 0; i < units.length-1; i++)
{
long nextDivider = divider * 1024;
if (size < nextDivider)
{
return fileSizeFormat(size, divider, units[i]);
}
divider = nextDivider;
}
return fileSizeFormat(size, divider, units[units.length-1]);
}
private String fileSizeFormat(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();
}
}

View File

@ -0,0 +1,198 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_IMAGE_PNG;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_PDF;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.UUID;
import org.junit.Test;
import org.springframework.validation.DirectFieldBindingResult;
import org.springframework.validation.Errors;
/**
* TransformRequestValidatorTest
* <p/>
* Unit test that checks the Transform request validation.
*/
public class TransformRequestValidatorTest
{
private TransformRequestValidator validator = new TransformRequestValidator();
@Test
public void testSupports()
{
assertTrue(validator.supports(TransformRequest.class));
}
@Test
public void testNullRequest()
{
Errors errors = new DirectFieldBindingResult(null, "request");
validator.validate(null, errors);
assertEquals(1, errors.getAllErrors().size());
assertEquals("request cannot be null",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingId()
{
TransformRequest request = new TransformRequest();
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("requestId cannot be null or empty",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingSourceSize()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("sourceSize cannot be null or have its value smaller than 0",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingSourceMediaType()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
request.setSourceSize(32L);
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("sourceMediaType cannot be null or empty",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingTargetMediaType()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
request.setSourceSize(32L);
request.setSourceMediaType(MIMETYPE_PDF);
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("targetMediaType cannot be null or empty",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingTargetExtension()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
request.setSourceSize(32L);
request.setSourceMediaType(MIMETYPE_PDF);
request.setTargetMediaType(MIMETYPE_IMAGE_PNG);
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("targetExtension cannot be null or empty",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingClientData()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
request.setSourceSize(32L);
request.setSourceMediaType(MIMETYPE_PDF);
request.setTargetMediaType(MIMETYPE_IMAGE_PNG);
request.setTargetExtension("png");
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("clientData cannot be null or empty",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testMissingSchema()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
request.setSourceSize(32L);
request.setSourceMediaType(MIMETYPE_PDF);
request.setTargetMediaType(MIMETYPE_IMAGE_PNG);
request.setTargetExtension("png");
request.setClientData("ACS");
request.setSchema(-1);
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertFalse(errors.getAllErrors().isEmpty());
assertEquals("schema cannot be less than 0",
errors.getAllErrors().iterator().next().getDefaultMessage());
}
@Test
public void testCompleteTransformRequest()
{
TransformRequest request = new TransformRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setSourceReference(UUID.randomUUID().toString());
request.setSourceSize(32L);
request.setSourceMediaType(MIMETYPE_PDF);
request.setTargetMediaType(MIMETYPE_IMAGE_PNG);
request.setTargetExtension("png");
request.setClientData("ACS");
Errors errors = new DirectFieldBindingResult(request, "request");
validator.validate(request, errors);
assertTrue(errors.getAllErrors().isEmpty());
}
}

View File

@ -0,0 +1,50 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CoreFunctionTest
{
@Test
void isSupported()
{
assertTrue(CoreFunction.HTTP.isSupported("0.1"));
assertTrue(CoreFunction.HTTP.isSupported("2.5.6"));
assertFalse(CoreFunction.HTTP.isSupported("100000"));
assertFalse(CoreFunction.ACTIVE_MQ.isSupported("0.1"));
assertTrue(CoreFunction.ACTIVE_MQ.isSupported("2.5"));
assertFalse(CoreFunction.DIRECT_ACCESS_URL.isSupported(null));
assertFalse(CoreFunction.DIRECT_ACCESS_URL.isSupported(""));
assertFalse(CoreFunction.DIRECT_ACCESS_URL.isSupported("2.5"));
assertFalse(CoreFunction.DIRECT_ACCESS_URL.isSupported("2.5.6"));
assertTrue(CoreFunction.DIRECT_ACCESS_URL.isSupported("2.5.7-SNAPSHOT"));
assertTrue(CoreFunction.DIRECT_ACCESS_URL.isSupported("2.5.7-A4-SNAPSHOT"));
assertTrue(CoreFunction.DIRECT_ACCESS_URL.isSupported("2.5.7"));
assertTrue(CoreFunction.DIRECT_ACCESS_URL.isSupported("2.6"));
assertTrue(CoreFunction.DIRECT_ACCESS_URL.isSupported("999999"));
}
}

View File

@ -0,0 +1,216 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.model.config;
import com.google.common.collect.ImmutableList;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.alfresco.transform.client.model.config.CoreVersionDecorator.CONFIG_VERSION_INCLUDES_CORE_VERSION;
import static org.alfresco.transform.client.model.config.CoreVersionDecorator.setCoreVersionOnMultiStepTransformers;
import static org.alfresco.transform.client.model.config.CoreVersionDecorator.setCoreVersionOnSingleStepTransformers;
import static org.alfresco.transform.client.model.config.CoreVersionDecorator.setOrClearCoreVersion;
import static org.alfresco.transform.client.util.RequestParamMap.CONFIG_VERSION_DEFAULT;
import static org.alfresco.transform.client.util.RequestParamMap.DIRECT_ACCESS_URL;
import static org.junit.jupiter.api.Assertions.*;
class CoreVersionDecoratorTest
{
private static final int CONFIG_VERSION_ORIGINAL = Integer.valueOf(CONFIG_VERSION_DEFAULT);
private static final String SOME_NAME = "optionName";
public static final Set<TransformOption> SOME_OPTIONS = Set.of(new TransformOptionValue(false, "someOption"));
private static final Set<TransformOption> DIRECT_ACCESS_URL_OPTION = Set.of(new TransformOptionValue(false, DIRECT_ACCESS_URL));
private final Map<String, Set<TransformOption>> TRANSFORM_OPTIONS_WITHOUT_DIRECT_ACCESS_URL = new HashMap<>();
private final Map<String, Set<TransformOption>> TRANSFORM_OPTIONS_WITH_DIRECT_ACCESS_URL = new HashMap<>();
{
TRANSFORM_OPTIONS_WITHOUT_DIRECT_ACCESS_URL.put(SOME_NAME, SOME_OPTIONS);
TRANSFORM_OPTIONS_WITH_DIRECT_ACCESS_URL.put(SOME_NAME, SOME_OPTIONS);
TRANSFORM_OPTIONS_WITH_DIRECT_ACCESS_URL.put(DIRECT_ACCESS_URL, DIRECT_ACCESS_URL_OPTION);
}
private TransformConfig newTransformConfig(String version1, String version2, String version3, String version4, String version5,
boolean hasDirectAccessUrls, boolean multiStepHaveDirectAccessUrls)
{
HashSet<String> transformOptions1 = new HashSet<>();
HashSet<String> transformOptions2 = new HashSet<>(transformOptions1);
transformOptions2.add(SOME_NAME);
HashSet<String> transformOptions3 = new HashSet<>(transformOptions1);
HashSet<String> transformOptions4 = new HashSet<>(transformOptions1);
transformOptions4.addAll(transformOptions2);
HashSet<String> transformOptions5 = new HashSet<>(transformOptions1);
transformOptions5.addAll(transformOptions2);
transformOptions5.addAll(transformOptions3);
if (hasDirectAccessUrls)
{
transformOptions1.add(DIRECT_ACCESS_URL);
transformOptions2.add(DIRECT_ACCESS_URL);
transformOptions3.add(DIRECT_ACCESS_URL);
}
if (multiStepHaveDirectAccessUrls)
{
transformOptions4.add(DIRECT_ACCESS_URL);
transformOptions5.add(DIRECT_ACCESS_URL);
}
return TransformConfig.builder()
.withTransformOptions(hasDirectAccessUrls ? TRANSFORM_OPTIONS_WITH_DIRECT_ACCESS_URL : TRANSFORM_OPTIONS_WITHOUT_DIRECT_ACCESS_URL)
.withTransformers(ImmutableList.of(
Transformer.builder()
.withTransformerName("transformer1")
.withCoreVersion(version1)
.withTransformOptions(transformOptions1)
.build(),
Transformer.builder()
.withTransformerName("transformer2")
.withCoreVersion(version2)
.withTransformOptions(transformOptions2)
.build(),
Transformer.builder()
.withTransformerName("transformer3")
.withCoreVersion(version3)
.withTransformOptions(transformOptions3)
.build(),
Transformer.builder()
.withTransformerName("pipeline4")
.withCoreVersion(version4)
.withTransformerPipeline(List.of(
new TransformStep("transformer1", "mimetype/c"),
new TransformStep("transformer2", null)))
.withTransformOptions(transformOptions4)
.build(),
Transformer.builder()
.withTransformerName("failover5")
.withCoreVersion(version5)
.withTransformerFailover(List.of("transformer1", "transformer2", "transformer3"))
.withTransformOptions(transformOptions5)
.build()))
.build();
}
@Test
void setCoreVersionOnSingleStepTransformersTest()
{
TransformConfig transformConfigReadFormTEngineJson = newTransformConfig(
null, null, null, null, null,
false, false);
setCoreVersionOnSingleStepTransformers(transformConfigReadFormTEngineJson, "2.3.1");
assertEquals(newTransformConfig("2.3.1", "2.3.1", "2.3.1", null, null,
false, false), transformConfigReadFormTEngineJson);
// Now with Direct Access URLs
transformConfigReadFormTEngineJson = newTransformConfig(
null, null, null, null, null,
false, false);
setCoreVersionOnSingleStepTransformers(transformConfigReadFormTEngineJson, "2.5.7");
assertEquals(newTransformConfig("2.5.7", "2.5.7", "2.5.7", null, null,
true, false), transformConfigReadFormTEngineJson);
}
@Test
void setCoreVersionOnMultiStepTransformersTest()
{
// All source T-Engines provide a coreVersion and have had the coreVersion added
TransformConfig decoratedSingleStepTransformConfig = newTransformConfig(
"2.1", "2.2", "1.2.3", null, null,
false, false);
setCoreVersionOnMultiStepTransformers(decoratedSingleStepTransformConfig.getTransformOptions(),
decoratedSingleStepTransformConfig.getTransformers());
assertEquals(newTransformConfig("2.1", "2.2", "1.2.3", "2.1", "1.2.3",
false, false), decoratedSingleStepTransformConfig);
// Some source T-Engines are pre coreVersion
decoratedSingleStepTransformConfig = newTransformConfig(
"2.1", null, null, null, null,
false, false);
setCoreVersionOnMultiStepTransformers(decoratedSingleStepTransformConfig.getTransformOptions(),
decoratedSingleStepTransformConfig.getTransformers());
assertEquals(newTransformConfig("2.1", null, null, null, null,
false, false), decoratedSingleStepTransformConfig);
// Now with Direct Access URLs
decoratedSingleStepTransformConfig = newTransformConfig("2.5.7", "2.5.7", "2.5.7", null, null,
true, false);
setCoreVersionOnMultiStepTransformers(decoratedSingleStepTransformConfig.getTransformOptions(),
decoratedSingleStepTransformConfig.getTransformers());
assertEquals(newTransformConfig("2.5.7", "2.5.7", "2.5.7", "2.5.7", "2.5.7",
true, true), decoratedSingleStepTransformConfig);
}
@Test
void setOrClearCoreVersionTest()
{
// All source T-Engines provide a coreVersion
TransformConfig transformConfigWithCoreVersion = newTransformConfig(
"2.1", "2.2", "1.2.3", "2.1", "1.2.3",
false, false);
assertEquals(newTransformConfig(null, null, null, null, null,
false, false),
setOrClearCoreVersion(transformConfigWithCoreVersion, CONFIG_VERSION_ORIGINAL));
assertEquals(newTransformConfig("2.1", "2.2", "1.2.3", "2.1", "1.2.3",
false, false),
setOrClearCoreVersion(transformConfigWithCoreVersion, CONFIG_VERSION_INCLUDES_CORE_VERSION));
assertEquals(newTransformConfig("2.1", "2.2", "1.2.3", "2.1", "1.2.3",
false, false),
setOrClearCoreVersion(transformConfigWithCoreVersion, CONFIG_VERSION_INCLUDES_CORE_VERSION+100));
// Some source T-Engines are pre coreVersion
TransformConfig transformConfigWithoutCoreVersion = newTransformConfig(
null, null, null, null, null,
false, false);
assertEquals(newTransformConfig(null, null, null, null, null,
false, false),
setOrClearCoreVersion(transformConfigWithoutCoreVersion, CONFIG_VERSION_ORIGINAL));
assertEquals(newTransformConfig(null, null, null, null, null,
false, false),
setOrClearCoreVersion(transformConfigWithoutCoreVersion, CONFIG_VERSION_INCLUDES_CORE_VERSION));
// Now with Direct Access URLs
transformConfigWithCoreVersion = newTransformConfig(
"2.5.7", "2.5.7", "2.5.7", "2.5.7", "2.5.7",
true, true);
assertEquals(newTransformConfig(null, null, null, null, null,
false, false),
setOrClearCoreVersion(transformConfigWithCoreVersion, CONFIG_VERSION_ORIGINAL));
assertEquals(newTransformConfig("2.5.7", "2.5.7", "2.5.7", "2.5.7", "2.5.7",
true, true),
setOrClearCoreVersion(transformConfigWithCoreVersion, CONFIG_VERSION_INCLUDES_CORE_VERSION));
}
}

View File

@ -0,0 +1,896 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2021 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.alfresco.transform.client.model.config.SupportedSourceAndTarget;
import org.alfresco.transform.client.model.config.TransformConfig;
import org.alfresco.transform.client.model.config.TransformStep;
import org.alfresco.transform.client.model.config.Transformer;
import org.junit.Test;
import java.util.List;
import java.util.Set;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
* Test the CombinedTransformConfig, extended by both T-Router and ACS repository.
*
* @author adavis
*/
public class CombinedTransformConfigTest
{
private static final Transformer TRANSFORMER1_A2B_A2E = Transformer.builder().withTransformerName("1")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/e")
.build()))
.build();
private static final Transformer TRANSFORMER1_A2B = Transformer.builder().withTransformerName("1")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.build()))
.build();
private static final Transformer TRANSFORMER1_A2E = Transformer.builder().withTransformerName("1")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/e")
.build()))
.build();
private static final Transformer TRANSFORMER1_A2D = Transformer.builder().withTransformerName("1")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.build()))
.build();
// Not static as pipelines gets modified.
private static final Transformer PIPELINE1_2C3 = Transformer.builder().withTransformerName("1")
.withTransformerPipeline(List.of(
new TransformStep("2", "mimetype/c"),
new TransformStep("3", null)))
.build();
// Not static as pipelines gets modified.
private static final Transformer PIPELINE1_2C3_COPY = Transformer.builder().withTransformerName("1")
.withTransformerPipeline(List.of(
new TransformStep("2", "mimetype/c"),
new TransformStep("3", null)))
.build();
private static final Transformer TRANSFORMER2_B2C = Transformer.builder().withTransformerName("2")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/b")
.withTargetMediaType("mimetype/c")
.build()))
.build();
private static final Transformer TRANSFORMER2_A2C = Transformer.builder().withTransformerName("2")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/c")
.build()))
.build();
private static final Transformer TRANSFORMER2_B2X = Transformer.builder().withTransformerName("2")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/b")
.withTargetMediaType("mimetype/X")
.build()))
.build();
private static final Transformer TRANSFORMER3_C2D = Transformer.builder().withTransformerName("3")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/c")
.withTargetMediaType("mimetype/d")
.build()))
.build();
private static final Transformer TRANSFORMER3_C2D_C2E = Transformer.builder().withTransformerName("3")
.withSupportedSourceAndTargetList(Set.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/c")
.withTargetMediaType("mimetype/d")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/e")
.build()))
.build();
private static final Transformer TRANSFORMER1 = Transformer.builder().withTransformerName("1").build();
private static final Transformer TRANSFORMER2 = Transformer.builder().withTransformerName("2").build();
private static final Transformer TRANSFORMER3 = Transformer.builder().withTransformerName("3").build();
public static final Transformer TRANSFORMER_WITH_NO_NAME = Transformer.builder().withTransformerName("").build();
public static final String PASS_THROUGH_NAME = "PassThrough";
private static final Transformer PASS_THROUGH = Transformer.builder().withTransformerName(PASS_THROUGH_NAME).build();
private static final Transformer PASS_THROUGH_COPY = Transformer.builder().withTransformerName(PASS_THROUGH_NAME).build();
// Not static as pipelines get modified.
private final Transformer PIPELINE4_1B2C3 = Transformer.builder().withTransformerName("4")
.withTransformerPipeline(List.of(
new TransformStep("1", "mimetype/b"),
new TransformStep("2", "mimetype/c"),
new TransformStep("3", null)))
.build();
// Not static as failover transforms get modified.
private final Transformer FAILOVER1_23 = Transformer.builder().withTransformerName("1")
.withTransformerFailover(List.of("2", "3"))
.build();
// Not static as failover transforms get modified.
private final Transformer FAILOVER4_123 = Transformer.builder().withTransformerName("4")
.withTransformerFailover(List.of("1", "2", "3"))
.build();
// Not static as the nested pipeline gets modified.
private final TransformConfig ONE_TRANSFORM = TransformConfig
.builder()
.withTransformers(ImmutableList.of(PIPELINE1_2C3))
.build();
private static final TransformConfig TWO_TRANSFORMS = TransformConfig
.builder()
.withTransformOptions(ImmutableMap.of(
"options/1", emptySet(),
"options/2", emptySet()))
.withTransformers(ImmutableList.of(TRANSFORMER2_B2C, TRANSFORMER3_C2D))
.build();
private static final String READ_FROM_A = "readFromA";
private static final String READ_FROM_B = "readFromB";
private static final String ROUTER_CONFIG_HAS_NO_BASE_URL = null;
private static final String BASE_URL_B = "baseUrlB";
private final CombinedTransformConfig config = new CombinedTransformConfig()
{
@Override
protected boolean isPassThroughTransformName(String name)
{
return PASS_THROUGH_NAME.equals(name);
}
};
private final TestTransformRegistry registry = new TestTransformRegistry();
private String expectedWildcardError(String errorReason)
{
return "No supported source and target mimetypes could be added to the transformer \"4\" as " +
errorReason + ". Read from " + READ_FROM_B;
}
// Assumes a t-engine transformer named "1" (normally TRANSFORMER1_A2B_A2E) is being overridden by an A2D transform.
// The overriding transform should be the last element in the tRouterTransformers (or if empty the last element in
// tEngineTransformers). The override is expected to good unless an error message is provided.
// A check is made at the end that A2D is possible and that A2B is not possible.
private void assertOverride(List<Transformer> tEngineTransformers,
List<Transformer> tRouterTransformers, String expectedError)
{
Transformer expectedTransformer = tRouterTransformers.isEmpty()
? tEngineTransformers.get(tEngineTransformers.size() - 1)
: tRouterTransformers.get(tRouterTransformers.size() - 1);
final TransformConfig tEngineTransformConfig = TransformConfig.builder()
.withTransformers(tEngineTransformers)
.build();
final TransformConfig tRouterTransformConfig = TransformConfig.builder()
.withTransformers(tRouterTransformers)
.build();
config.addTransformConfig(tEngineTransformConfig, READ_FROM_B, BASE_URL_B, registry);
config.addTransformConfig(tRouterTransformConfig, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
config.combineTransformerConfig(registry);
config.registerCombinedTransformers(registry);
TransformConfig transformConfig = config.buildTransformConfig();
if (expectedError == null)
{
assertEquals(0, registry.errorMessages.size());
int numberOfTEngineTransformers = tEngineTransformers.size();
assertEquals(numberOfTEngineTransformers, config.buildTransformConfig().getTransformers().size());
// Check we are using the overriding transformer from the t-router, or if none the last one in the t-engine
assertEquals(numberOfTEngineTransformers, transformConfig.getTransformers().size());
Transformer actualTransformer = transformConfig.getTransformers().get(numberOfTEngineTransformers - 1);
assertEquals(expectedTransformer, actualTransformer);
assertSame("It should even == the expected transform", expectedTransformer, actualTransformer);
// Check the baseUrl is that of the original t-engine that will do the work, if the overriding transform
// is a single step transform.
List<TransformStep> pipeline = actualTransformer.getTransformerPipeline();
List<String> failover = actualTransformer.getTransformerFailover();
boolean isPipeline = pipeline != null && !pipeline.isEmpty();
boolean isFailover = failover != null && !failover.isEmpty();
if (!isPipeline && !isFailover)
{
assertEquals(BASE_URL_B, registry.transformerBaseUrls.get(actualTransformer));
}
// Double check by finding the overriding transformer for A2D but not the overridden transformer for A2B in
// the registry.
assertEquals("1", registry.findTransformerName("mimetype/a", -1,
"mimetype/d", emptyMap(), null));
// If the assumption about the overridden transform being A2D was wrong, the following would pass anyway
assertNull(registry.findTransformerName("mimetype/a", -1,
"mimetype/b", emptyMap(), null));
}
else
{
assertEquals(1, registry.errorMessages.size());
assertEquals(expectedError, registry.errorMessages.get(0));
}
}
// No specific tests for addTransformConfig or buildTransformConfig as they are used in most
// other test methods which will fail if they are not working.
@Test
public void testClear()
{
config.addTransformConfig(TWO_TRANSFORMS, READ_FROM_B, BASE_URL_B, registry);
assertEquals(2, config.buildTransformConfig().getTransformers().size());
assertEquals(2, config.buildTransformConfig().getTransformOptions().size());
config.clear();
assertEquals(0, config.buildTransformConfig().getTransformers().size());
assertEquals(0, config.buildTransformConfig().getTransformOptions().size());
}
@Test
public void testCombineTransformerConfigNoOp()
{
config.addTransformConfig(TWO_TRANSFORMS, READ_FROM_B, BASE_URL_B, registry);
config.addTransformConfig(ONE_TRANSFORM, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
assertEquals(3, config.buildTransformConfig().getTransformers().size());
assertEquals(2, config.buildTransformConfig().getTransformOptions().size());
config.combineTransformerConfig(registry);
assertEquals(3, config.buildTransformConfig().getTransformers().size());
assertEquals(2, config.buildTransformConfig().getTransformOptions().size());
}
@Test
public void testTransformersAreRegistered()
{
config.addTransformConfig(TWO_TRANSFORMS, READ_FROM_B, BASE_URL_B, registry);
config.addTransformConfig(ONE_TRANSFORM, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
assertEquals(3, config.buildTransformConfig().getTransformers().size());
assertEquals(2, config.buildTransformConfig().getTransformOptions().size());
config.registerCombinedTransformers(registry);
assertEquals(3, registry.registeredCount);
assertEquals(1, registry.readFromACount);
assertEquals(2, registry.baseUrlBCount);
}
@Test
public void testInvalidBothPipelineAndFailover()
{
final Transformer invalidTransformer = Transformer.builder().withTransformerName("2")
.withTransformerPipeline(List.of(
new TransformStep("1", "mimetype/b"),
new TransformStep("2", "mimetype/c"),
new TransformStep("3", null)))
.withTransformerFailover(List.of("1", "2", "3"))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
invalidTransformer))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "Transformer \"2\" cannot have pipeline and failover sections. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testInvalidNoName()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(TRANSFORMER_WITH_NO_NAME))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "Transformer names may not be null. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testInvalidTransformOptionsUnknown()
{
final Transformer transformer = Transformer.builder().withTransformerName("1")
.withTransformOptions(ImmutableSet.of("unknown"))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(transformer))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "Transformer \"1\" references \"unknown\" which do not exist. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testInvalidDuplicateTEngineSingleStepTransform()
{
final Transformer transformer = Transformer.builder().withTransformerName("1")
.build();
final Transformer identicalTransformer = Transformer.builder().withTransformerName("1")
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
transformer,
identicalTransformer))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "Transformer \"1\" must be a unique name. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testOverrideSingleStepWithSingleStepInTEngine()
{
final Transformer transformerWithSameNameButDifferentDefinition = Transformer.builder().withTransformerName("1")
.withTransformOptions(ImmutableSet.of("options/1"))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1,
transformerWithSameNameButDifferentDefinition))
.withTransformOptions(ImmutableMap.of("options/1", emptySet()))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
// Expected: This is the same error as in testInvalidDuplicateTEngineSingleStepTransform
String expected = "Transformer \"1\" must be a unique name. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testInvalidSingleStepTransformInRouter()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(TRANSFORMER1))
.build();
config.addTransformConfig(transformConfig, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
config.combineTransformerConfig(registry);
String expected = "Single step transformers (such as \"1\") must be defined in a T-Engine rather than in a " +
"pipeline file, unless they are overriding an existing single step definition. Read from readFromA";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testOverrideSingleStepWithSingleStepInTRouter() // i.e. t-router redefines a t-engine single step transform
{
assertOverride(
ImmutableList.of(TRANSFORMER1_A2B_A2E),
ImmutableList.of(TRANSFORMER1_A2D),
null);
}
@Test
public void testOverrideMultipleTimesSingleStepWithSingleStepInTRouter() // need to make sure the url is remembered
{
assertOverride(
ImmutableList.of(TRANSFORMER1_A2B_A2E),
ImmutableList.of(TRANSFORMER1_A2B, TRANSFORMER1_A2E, TRANSFORMER1_A2D),
null);
}
@Test
public void testOverrideSingleStepWithPipelineInTRouter()
{
assertOverride(
ImmutableList.of(TRANSFORMER1_A2B_A2E, TRANSFORMER2_A2C, TRANSFORMER3_C2D),
ImmutableList.of(PIPELINE1_2C3),
null);
}
@Test
public void testOverridePipelineWithPipelineInTRouter()
{
assertOverride(
ImmutableList.of(PIPELINE1_2C3, TRANSFORMER2_A2C, TRANSFORMER3_C2D),
ImmutableList.of(PIPELINE1_2C3_COPY),
null);
}
@Test
public void testInvalidOverridePipelineWithSingleStepInTRouter()
{
assertOverride(
ImmutableList.of(PIPELINE1_2C3, TRANSFORMER2_A2C, TRANSFORMER3_C2D),
ImmutableList.of(TRANSFORMER1_A2D),
"Single step transformers (such as \"1\") may not override a pipeline or failover " +
"transform as there is no T-Engine to perform work. Read from readFromA");
}
@Test
public void testInvalidOverrideFailoverWithSingleStepInTRouter()
{
assertOverride(
ImmutableList.of(FAILOVER1_23, TRANSFORMER2_A2C, TRANSFORMER3_C2D),
ImmutableList.of(TRANSFORMER1_A2D),
"Single step transformers (such as \"1\") may not override a pipeline or failover " +
"transform as there is no T-Engine to perform work. Read from readFromA");
}
@Test
public void testSinglePassThroughInTRouter()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(PASS_THROUGH))
.build();
config.addTransformConfig(transformConfig, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
config.combineTransformerConfig(registry);
assertEquals(0, registry.errorMessages.size());
}
@Test
public void testMultiplePassThroughInTRouter()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(PASS_THROUGH, PASS_THROUGH_COPY))
.build();
config.addTransformConfig(transformConfig, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
config.combineTransformerConfig(registry);
String expected = "Pipeline files should not use \"PassThrough\" as a transform name. Read from readFromA";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testPassThroughInTEngine()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(PASS_THROUGH))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "T-Engines should not use \"PassThrough\" as a transform name. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testInvalidOverrideSingleStepWithPipelineInTEngine()
{
assertOverride(
ImmutableList.of(TRANSFORMER1_A2B_A2E, TRANSFORMER2_A2C, TRANSFORMER3_C2D, PIPELINE1_2C3),
emptyList(),
"Transformer \"1\" must be a unique name. Read from readFromB");
}
@Test
public void testInvalidOverridePipelineWithSingleStepInTEngine()
{
assertOverride(
ImmutableList.of(TRANSFORMER2_A2C, TRANSFORMER3_C2D, PIPELINE1_2C3, TRANSFORMER1_A2B_A2E),
emptyList(),
"Transformer \"1\" must be a unique name. Read from readFromB");
}
@Test
public void testInvalidIndexToRemoveBeforeCurrent()
{
// indexToRemove is is before the current i value, so we are removing an overridden transform
// Code throws an IllegalArgumentException and i is simply decremented to ignore the current value
final TransformConfig tEngineTransformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D))
.build();
final TransformConfig tRouterTransformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2D)) // this one triggers the removal of 0
.build();
config.addTransformConfig(tEngineTransformConfig, READ_FROM_B, BASE_URL_B, registry);
config.addTransformConfig(tRouterTransformConfig, READ_FROM_A, ROUTER_CONFIG_HAS_NO_BASE_URL, registry);
config.combineTransformerConfig(registry);
assertEquals(3, config.buildTransformConfig().getTransformers().size());
}
@Test
public void testInvalidIndexToRemoveIsCurrent()
{
// Code throws an IllegalArgumentException and i is simply decremented to ignore the current value
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2D,
TRANSFORMER2_B2C,
TRANSFORMER_WITH_NO_NAME, // discarded
TRANSFORMER3_C2D))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
assertEquals(3, config.buildTransformConfig().getTransformers().size());
}
@Test
public void testSortSoNoForwardRefs()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
PIPELINE1_2C3,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
// Check order includes forward references after setup
List<Transformer> transformers = config.buildTransformConfig().getTransformers();
assertEquals(3, transformers.size());
assertEquals("1", transformers.get(0).getTransformerName());
assertEquals("2", transformers.get(1).getTransformerName());
assertEquals("3", transformers.get(2).getTransformerName());
config.combineTransformerConfig(registry);
// Check order changed so there are no forward references after combined
transformers = config.buildTransformConfig().getTransformers();
assertEquals(3, transformers.size());
assertEquals("2", transformers.get(0).getTransformerName());
assertEquals("3", transformers.get(1).getTransformerName());
assertEquals("1", transformers.get(2).getTransformerName());
assertEquals(0, registry.warnMessages.size());
assertEquals(0, registry.errorMessages.size());
}
@Test
public void testInvalidTransformStepNullIntermediateMimetype()
{
final Transformer pipeline = Transformer.builder().withTransformerName("5")
.withTransformerPipeline(List.of(
new TransformStep("1", "mimetype/b"),
new TransformStep("2", null), // should not be null
new TransformStep("3", null)))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
pipeline))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "No supported source and target mimetypes could be added to the transformer \"5\" as " +
"intermediate steps should have a target mimetype. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testInvalidTransformStepNonNullFinalMimetype()
{
final Transformer pipeline = Transformer.builder().withTransformerName("5")
.withTransformerPipeline(List.of(
new TransformStep("1", "mimetype/b"),
new TransformStep("2", "mimetype/c"),
new TransformStep("3", "mimetype/d"))) // the last step's mimetype should be null
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
pipeline))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "No supported source and target mimetypes could be added to the transformer \"5\" as " +
"the final step should not have a target mimetype. Read from readFromB";
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
@Test
public void testIgnorePipelineWithMissingStep()
{
final Transformer pipeline = Transformer.builder().withTransformerName("1")
.withTransformerPipeline(List.of(
new TransformStep("2", "mimetype/c"),
new TransformStep("3", "mimetype/d"),
new TransformStep("4", null)))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
pipeline))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "Transformer \"1\" ignored as step transforms (\"4\") do not exist. Read from readFromB";
assertEquals(1, registry.warnMessages.size());
assertEquals(expected, registry.warnMessages.get(0));
}
@Test
public void testIgnorePipelineWithMissingSteps()
{
final Transformer pipeline = Transformer.builder().withTransformerName("1")
.withTransformerPipeline(List.of(
new TransformStep("3", "mimetype/d"),
new TransformStep("4", "mimetype/e"),
new TransformStep("5", "mimetype/f"),
new TransformStep("2", "mimetype/c"),
new TransformStep("6", null)))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
pipeline))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = "Transformer \"1\" ignored as step transforms (\"4\", \"5\", \"6\") do not exist. Read from readFromB";
assertEquals(1, registry.warnMessages.size());
assertEquals(expected, registry.warnMessages.get(0));
}
@Test
public void testInvalidCircularTransformStep()
{
final Transformer pipeline1 = Transformer.builder().withTransformerName("1")
.withTransformerPipeline(List.of(
new TransformStep("2", "mimetype/c"),
new TransformStep("4", null)))
.build();
final Transformer pipeline4 = Transformer.builder().withTransformerName("4")
.withTransformerPipeline(List.of(
new TransformStep("3", "mimetype/d"),
new TransformStep("5", "mimetype/f"),
new TransformStep("1", null)))
.build();
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
pipeline1,
pipeline4))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
assertEquals(2, registry.warnMessages.size());
assertEquals("Transformer \"1\" ignored as step transforms (\"4\") do not exist. Read from readFromB",
registry.warnMessages.get(0));
assertEquals("Transformer \"4\" ignored as step transforms (\"1\", \"5\") do not exist. Read from readFromB",
registry.warnMessages.get(1));
}
@Test
public void testWildcardNoOp()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
config.registerCombinedTransformers(registry);
assertEquals(0, registry.errorMessages.size());
assertEquals(3, config.buildTransformConfig().getTransformers().size());
assertEquals("1", registry.findTransformerName("mimetype/a", -1,
"mimetype/b", emptyMap(), null));
assertEquals("2", registry.findTransformerName("mimetype/b", -1,
"mimetype/c", emptyMap(), null));
assertEquals("3", registry.findTransformerName("mimetype/c", -1,
"mimetype/d", emptyMap(), null));
}
// It is not possible to test for expectedWildcardError("one of the step transformers is missing") as
// the sortTransformers() method will have already issued another error.
@Test
public void testWildcardPipeline()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
PIPELINE4_1B2C3))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
config.registerCombinedTransformers(registry);
assertEquals(0, registry.errorMessages.size());
assertEquals(4, config.buildTransformConfig().getTransformers().size());
assertEquals("4", registry.findTransformerName("mimetype/a", -1,
"mimetype/d", emptyMap(), null));
assertEquals("1", registry.findTransformerName("mimetype/a", -1,
"mimetype/b", emptyMap(), null));
}
@Test
public void testWildcardPipelineExcludeFirstTransformsSupportedFromCartesianProduct() // Exclude more complex path
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D_C2E,
PIPELINE4_1B2C3))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
config.registerCombinedTransformers(registry);
assertEquals(0, registry.errorMessages.size());
assertEquals(4, config.buildTransformConfig().getTransformers().size());
assertEquals("4", registry.findTransformerName("mimetype/a", -1,
"mimetype/d", emptyMap(), null));
// The pipeline could do A2D and A2E, but as A2E is also supported by the first transformer it is not included.
assertEquals("1", registry.findTransformerName("mimetype/a", -1,
"mimetype/e", emptyMap(), null));
}
@Test
public void testPipelineUnsupportedIntermediate()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2X,
TRANSFORMER3_C2D,
PIPELINE4_1B2C3))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = expectedWildcardError("the step transformer \"2\" does not support \"mimetype/b\" to \"mimetype/c\"");
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
// 4: the pipeline is not removed, but will not be used as it has no supported transforms.
assertEquals(4, config.buildTransformConfig().getTransformers().size());
}
@Test
public void testWildcardFailover()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1_A2B_A2E,
TRANSFORMER2_B2C,
TRANSFORMER3_C2D,
FAILOVER4_123))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
config.registerCombinedTransformers(registry);
assertEquals(0, registry.errorMessages.size());
assertEquals(4, config.buildTransformConfig().getTransformers().size());
assertEquals("4", registry.findTransformerName("mimetype/a", -1,
"mimetype/b", emptyMap(), null));
}
@Test
public void testWildcardFailoverNoneSupported()
{
final TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
TRANSFORMER1,
TRANSFORMER2,
TRANSFORMER3,
FAILOVER4_123))
.build();
config.addTransformConfig(transformConfig, READ_FROM_B, BASE_URL_B, registry);
config.combineTransformerConfig(registry);
String expected = expectedWildcardError("the step transforms don't support any");
assertEquals(1, registry.errorMessages.size());
assertEquals(expected, registry.errorMessages.get(0));
}
}

View File

@ -0,0 +1,460 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.alfresco.transform.client.model.config.AddSupported;
import org.alfresco.transform.client.model.config.SupportedDefaults;
import org.alfresco.transform.client.model.config.OverrideSupported;
import org.alfresco.transform.client.model.config.RemoveSupported;
import org.alfresco.transform.client.model.config.SupportedSourceAndTarget;
import org.alfresco.transform.client.model.config.TransformConfig;
import org.alfresco.transform.client.model.config.Transformer;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static junit.framework.TestCase.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* Tests the json elements: {@code removeTransformers}, {@code addSupported}, {@code removeSupported},
* {@code overrideSupported} and {@code supportedDefaults}.
*/
public class OverrideTransformConfigTests
{
private static final String READ_FROM_A = "readFromA";
private static final String READ_FROM_B = "readFromB";
private static final String BASE_URL_A = "baseUrlA";
private static final String BASE_URL_B = "baseUrlB";
private final SupportedSourceAndTarget supported_A2B = SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.build();
private final SupportedSourceAndTarget supported_A2B__40 = SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.withPriority(40)
.build();
private final SupportedSourceAndTarget supported_C2D = SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/c")
.withTargetMediaType("mimetype/d")
.build();
private final SupportedSourceAndTarget supported_A2D_1234_44 = SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.withMaxSourceSizeBytes(1234L)
.withPriority(44)
.build();
private final SupportedSourceAndTarget supported_X2Y_100_23 = SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/x")
.withTargetMediaType("mimetype/y")
.withMaxSourceSizeBytes(100L)
.withPriority(23)
.build();
private final SupportedSourceAndTarget supported_X2Y_200 = SupportedSourceAndTarget.builder()
.withSourceMediaType("mimetype/x")
.withTargetMediaType("mimetype/y")
.withMaxSourceSizeBytes(200L)
.build();
private final TransformConfig transformConfig_A2B_X2Y_100_23 = TransformConfig.builder()
.withTransformers(ImmutableList.of(
Transformer.builder().withTransformerName("1")
.withSupportedSourceAndTargetList(new HashSet<>(Set.of(
supported_A2B,
supported_X2Y_100_23)))
.build()))
.build();
private final CombinedTransformConfig config = new CombinedTransformConfig();
private final TestTransformRegistry registry = new TestTransformRegistry();
@Test
public void testRemoveTransformers()
{
final Transformer transformer1 = Transformer.builder().withTransformerName("1").build();
final Transformer transformer2 = Transformer.builder().withTransformerName("2").build();
final Transformer transformer3 = Transformer.builder().withTransformerName("3").build();
final Transformer transformer4 = Transformer.builder().withTransformerName("4").build();
final TransformConfig firstConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
transformer1,
transformer2,
transformer3,
transformer4))
.build();
final TransformConfig secondConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(
transformer2)) // Puts transform 2 back again
.withRemoveTransformers(ImmutableSet.of("2", "7", "3", "2", "5"))
.build();
config.addTransformConfig(firstConfig, READ_FROM_A, BASE_URL_A, registry);
TransformConfig resultConfig = config.buildTransformConfig();
assertEquals(4, resultConfig.getTransformers().size());
config.addTransformConfig(secondConfig, READ_FROM_B, BASE_URL_B, registry);
resultConfig = config.buildTransformConfig();
assertEquals(3, resultConfig.getTransformers().size());
String expected = "Unable to process \"removeTransformers\": [\"7\", \"5\"]. Read from readFromB";
assertEquals(1, registry.warnMessages.size());
assertEquals(expected, registry.warnMessages.get(0));
}
@Test
public void testSupportedDefaultsSet()
{
SupportedDefaults default_1A_100 = SupportedDefaults.builder()
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.withMaxSourceSizeBytes(100L)
.build();
SupportedDefaults default_1A_200 = SupportedDefaults.builder()
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.withMaxSourceSizeBytes(200L)
.build();
SupportedDefaults default_2A__45 = SupportedDefaults.builder()
.withTransformerName("2")
.withSourceMediaType("mimetype/a")
.withPriority(45)
.build();
SupportedDefaults default_3_400 = SupportedDefaults.builder()
.withTransformerName("3")
.withMaxSourceSizeBytes(400L)
.build();
SupportedDefaults default_B_400 = SupportedDefaults.builder()
.withSourceMediaType("mimetype/b")
.withMaxSourceSizeBytes(400L)
.build();
SupportedDefaults default_B_500 = SupportedDefaults.builder()
.withSourceMediaType("mimetype/b")
.withMaxSourceSizeBytes(500L)
.build();
SupportedDefaults default__600 = SupportedDefaults.builder()
.withMaxSourceSizeBytes(600L)
.build();
SupportedDefaults default___45 = SupportedDefaults.builder()
.withPriority(45)
.build();
SupportedDefaults default___50 = SupportedDefaults.builder()
.withPriority(50)
.build();
SupportedDefaults default__500_50 = SupportedDefaults.builder()
.withMaxSourceSizeBytes(500L)
.withPriority(50)
.build();
SupportedDefaults default__600_45 = SupportedDefaults.builder()
.withMaxSourceSizeBytes(600L)
.withPriority(45)
.build();
final TransformConfig firstConfig = TransformConfig.builder()
.build();
final TransformConfig secondConfig = TransformConfig.builder()
.withSupportedDefaults(ImmutableSet.of(
default_1A_100)) // 0: transformer and source media type default
.build();
final TransformConfig thirdConfig = TransformConfig.builder()
.withSupportedDefaults(ImmutableSet.of(
default_1A_200, // 0: transformer and source media type default
default_2A__45, // 0: transformer and source media type default
default_3_400, // 1: transformer default
default_B_400, // 2: source media type default
default_B_500, // 2: source media type default - overrides the previous value 400 defined in the same config
default__500_50, // 3: system wide default - totally overridden by the next lines.
default__600, // 3: system wide default
default___50, // 3: system wide default (combined with the other system default)
default___45)) // 3: system wide default - overrides the value 45 defined in the same config
.build();
final TransformConfig fourthConfig = TransformConfig.builder()
.withSupportedDefaults(ImmutableSet.of(
SupportedDefaults.builder() // 3: system wide default
.withMaxSourceSizeBytes(-1L)
.withPriority(45)
.build()))
.build();
final TransformConfig fifthConfig = TransformConfig.builder()
.withSupportedDefaults(ImmutableSet.of(
SupportedDefaults.builder() // 3: system wide default (reset to the default, so removed)
.withPriority(50)
.build(),
SupportedDefaults.builder() // Invalid as neither priority nor maxSourceSizeBytes are set
.withTransformerName("9")
.withSourceMediaType("mimetype/z")
.build()))
.build();
config.addTransformConfig(firstConfig, READ_FROM_A, BASE_URL_A, registry);
TransformConfig resultConfig = config.buildTransformConfig();
assertEquals(0, resultConfig.getSupportedDefaults().size());
config.addTransformConfig(secondConfig, READ_FROM_B, BASE_URL_B, registry);
resultConfig = config.buildTransformConfig();
assertEquals(ImmutableSet.of(
default_1A_100),
resultConfig.getSupportedDefaults());
config.addTransformConfig(thirdConfig, READ_FROM_B, BASE_URL_B, registry);
resultConfig = config.buildTransformConfig();
assertEquals(ImmutableSet.of(
default_1A_200, // overrides default_1A_100
default_2A__45,
default_3_400,
default_B_500,
default__600_45), // default__600 + default___45
resultConfig.getSupportedDefaults());
config.addTransformConfig(fourthConfig, READ_FROM_B, BASE_URL_B, registry);
resultConfig = config.buildTransformConfig();
assertEquals(5, resultConfig.getSupportedDefaults().size());
config.addTransformConfig(fifthConfig, READ_FROM_A, BASE_URL_A, registry);
resultConfig = config.buildTransformConfig();
assertEquals(4, resultConfig.getSupportedDefaults().size());
assertEquals(ImmutableSet.of(
default_1A_200, // overrides default_1A_100
default_2A__45,
default_3_400,
default_B_500), // default__600_45 removed as the system defaults have been reset to the defaults -1 and 50
resultConfig.getSupportedDefaults());
String expected = "Unable to process \"supportedDefaults\": [" +
"{\"transformerName\": \"9\", \"sourceMediaType\": \"mimetype/z\"}]. Read from readFromA";
assertEquals(1, registry.warnMessages.size());
assertEquals(expected, registry.warnMessages.get(0));
}
@Test
public void testRemoveSupported()
{
addTransformConfig_A2B_X2Y_100_23();
final TransformConfig secondConfig = TransformConfig.builder()
.withRemoveSupported(ImmutableSet.of(
RemoveSupported.builder()
.withTransformerName("1") // c -> d does not exist
.withSourceMediaType("mimetype/c")
.withTargetMediaType("mimetype/d")
.build(),
RemoveSupported.builder()
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.build(),
RemoveSupported.builder() // transformer does not exist
.withTransformerName("bad")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.build(),
RemoveSupported.builder() // transform name not set
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.build(),
RemoveSupported.builder() // source type not set
.withTransformerName("1")
.withTargetMediaType("mimetype/d")
.build(),
RemoveSupported.builder() // target type not set
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.build()))
.build();
String expectedWarnMessage = "Unable to process \"removeSupported\": [" +
"{\"transformerName\": \"bad\", \"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/d\"}, " +
"{\"transformerName\": \"1\", \"sourceMediaType\": \"mimetype/a\"}, " +
"{\"transformerName\": \"1\", \"sourceMediaType\": \"mimetype/c\", \"targetMediaType\": \"mimetype/d\"}, " +
"{\"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/d\"}, " +
"{\"transformerName\": \"1\", \"targetMediaType\": \"mimetype/d\"}]. " +
"Read from readFromB";
ImmutableSet<SupportedSourceAndTarget> expectedSupported = ImmutableSet.of(supported_X2Y_100_23);
String expectedToString = "[" +
"{\"sourceMediaType\": \"mimetype/x\", \"targetMediaType\": \"mimetype/y\", \"maxSourceSizeBytes\": \"100\", \"priority\": \"23\"}" +
"]";
addTransformConfig(secondConfig, expectedWarnMessage, expectedSupported, expectedToString);
}
@Test
public void testAddSupported()
{
addTransformConfig_A2B_X2Y_100_23();
final TransformConfig secondConfig = TransformConfig.builder()
.withAddSupported(ImmutableSet.of(
AddSupported.builder()
.withTransformerName("1")
.withSourceMediaType("mimetype/c")
.withTargetMediaType("mimetype/d")
.build(),
AddSupported.builder() // duplicates original
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.withPriority(40)
.build(),
AddSupported.builder()
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.withPriority(44)
.withMaxSourceSizeBytes(1234)
.build(),
AddSupported.builder() // transformer does not exist
.withTransformerName("bad")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.build(),
AddSupported.builder() // transform name not set
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.build(),
AddSupported.builder() // source type not set
.withTransformerName("1")
.withTargetMediaType("mimetype/d")
.build(),
AddSupported.builder() // target type not set
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.build()))
.build();
String expectedWarnMessage = "Unable to process \"addSupported\": [" +
"{\"transformerName\": \"1\", \"sourceMediaType\": \"mimetype/a\"}, " +
"{\"transformerName\": \"1\", \"targetMediaType\": \"mimetype/d\"}, " +
"{\"transformerName\": \"1\", \"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/b\", \"priority\": \"40\"}, " +
"{\"transformerName\": \"bad\", \"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/d\"}, " +
"{\"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/d\"}]. " +
"Read from readFromB";
ImmutableSet<SupportedSourceAndTarget> expectedSupported = ImmutableSet.of(
supported_A2B,
supported_C2D,
supported_X2Y_100_23,
supported_A2D_1234_44);
String expectedToString = "[" +
"{\"sourceMediaType\": \"mimetype/x\", \"targetMediaType\": \"mimetype/y\", \"maxSourceSizeBytes\": \"100\", \"priority\": \"23\"}, " +
"{\"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/d\", \"maxSourceSizeBytes\": \"1234\", \"priority\": \"44\"}, " +
"{\"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/b\"}, " + // priority and size should be missing (i.e. use defaults)
"{\"sourceMediaType\": \"mimetype/c\", \"targetMediaType\": \"mimetype/d\"}" +
"]";
addTransformConfig(secondConfig, expectedWarnMessage, expectedSupported, expectedToString);
}
@Test
public void testOverrideSupported()
{
addTransformConfig_A2B_X2Y_100_23();
final TransformConfig secondConfig = TransformConfig.builder()
.withOverrideSupported(ImmutableSet.of(
OverrideSupported.builder() // does not exist
.withTransformerName("1")
.withSourceMediaType("mimetype/c")
.withTargetMediaType("mimetype/d")
.build(),
OverrideSupported.builder() // size default -> 200 and priority default -> 100
.withTransformerName("1")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/b")
.withPriority(40)
.build(),
OverrideSupported.builder() // size 100 -> 200 and change priority to default
.withTransformerName("1")
.withSourceMediaType("mimetype/x")
.withTargetMediaType("mimetype/y")
.withMaxSourceSizeBytes(200)
.build(),
OverrideSupported.builder() // transformer does not exist
.withTransformerName("bad")
.withSourceMediaType("mimetype/a")
.withTargetMediaType("mimetype/d")
.build()))
// OverrideSupported values with missing fields are defaults, so no test values here
.build();
String expectedWarnMessage = "Unable to process \"overrideSupported\": [" +
"{\"transformerName\": \"1\", \"sourceMediaType\": \"mimetype/c\", \"targetMediaType\": \"mimetype/d\"}, " +
"{\"transformerName\": \"bad\", \"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/d\"}]. " +
"Read from readFromB";
ImmutableSet<SupportedSourceAndTarget> expectedSupported = ImmutableSet.of(
supported_X2Y_200,
supported_A2B__40);
String expectedToString = "[" +
"{\"sourceMediaType\": \"mimetype/a\", \"targetMediaType\": \"mimetype/b\", \"priority\": \"40\"}, " +
"{\"sourceMediaType\": \"mimetype/x\", \"targetMediaType\": \"mimetype/y\", \"maxSourceSizeBytes\": \"200\"}" +
"]";
addTransformConfig(secondConfig, expectedWarnMessage, expectedSupported, expectedToString);
}
private void addTransformConfig_A2B_X2Y_100_23()
{
config.addTransformConfig(transformConfig_A2B_X2Y_100_23, READ_FROM_A, BASE_URL_A, registry);
TransformConfig resultConfig = config.buildTransformConfig();
assertEquals(1, resultConfig.getTransformers().size());
assertEquals(2, resultConfig.getTransformers().get(0).getSupportedSourceAndTargetList().size());
}
private void addTransformConfig(TransformConfig secondConfig, String expectedWarnMessage,
Set<SupportedSourceAndTarget> expectedSupported, String expectedToString)
{
config.addTransformConfig(secondConfig, READ_FROM_B, BASE_URL_B, registry);
TransformConfig resultConfig = config.buildTransformConfig();
assertEquals(1, registry.warnMessages.size());
assertEquals(expectedWarnMessage, registry.warnMessages.get(0));
Set<SupportedSourceAndTarget> supportedSourceAndTargetList = resultConfig.getTransformers().get(0).getSupportedSourceAndTargetList();
assertTrue(supportedSourceAndTargetList.equals(expectedSupported));
assertEquals("toString() difference", expectedToString, supportedSourceAndTargetList.toString());
}
}

View File

@ -0,0 +1,88 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import org.alfresco.transform.client.model.config.TransformOption;
import org.alfresco.transform.client.model.config.Transformer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Helper class for testing an {@link AbstractTransformRegistry}.
*/
public class TestTransformRegistry extends AbstractTransformRegistry
{
private static final String READ_FROM_A = "readFromA";
private static final String BASE_URL_B = "baseUrlB";
private final TransformCache data = new TransformCache();
List<String> errorMessages = new ArrayList<>();
List<String> warnMessages = new ArrayList<>();
int registeredCount = 0;
int readFromACount = 0;
int baseUrlBCount = 0;
Map<Transformer, String> transformerBaseUrls = new HashMap<>();
@Override
protected void logError(String msg)
{
System.out.println(msg);
errorMessages.add(msg);
}
@Override
protected void logWarn(String msg)
{
System.out.println(msg);
warnMessages.add(msg);
}
@Override
public TransformCache getData()
{
return data;
}
@Override
protected void register(final Transformer transformer,
final Map<String, Set<TransformOption>> transformOptions, final String baseUrl,
final String readFrom)
{
super.register(transformer, transformOptions, baseUrl, readFrom);
registeredCount++;
if (READ_FROM_A.equals(readFrom))
{
readFromACount++;
}
if (BASE_URL_B.equals(baseUrl))
{
baseUrlBCount++;
}
transformerBaseUrls.put(transformer, baseUrl);
}
}

View File

@ -0,0 +1,181 @@
/*
* #%L
* Alfresco Transform Core
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transform.client.registry;
import org.alfresco.transform.exceptions.TransformException;
import org.junit.Test;
import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static org.alfresco.transform.client.registry.TransformRegistryHelper.retrieveTransformListBySize;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class TransformRegistryHelperTest
{
@Test
public void testListBySize()
{
// This test was inspired by a failure to pick libreoffice over textToPdf despite the fact libreoffice has a
// higher priority.
SupportedTransform libreoffice = new SupportedTransform("libreoffice", emptySet(), -1, 50);
SupportedTransform textToPdf = new SupportedTransform("textToPdf", emptySet(), 100,55);
assertOrder(asList(libreoffice, textToPdf), asList(libreoffice));
assertOrder(asList(textToPdf, libreoffice), asList(libreoffice));
// * If multiple transforms with the same priority can support the same size, the one with the highest size
// limit (or no limit) is used.
// * Transforms with a higher priority (lower numerically) are used up to their size limit in preference to
// lower priority transforms. These lower priority transforms will be used above that limit.
// * If there are multiple transforms with the same priority and size limit, the last one defined is used to
// allow extensions to override standard transforms.
// * In each of the above cases, it is possible for supplied transforms not to be returned from
// retrieveTransformListBySize as they will never be used. However this method is currently only used
// by (1) AbstractTransformRegistry.findTransformerName which filters out transformers that cannot support a
// given size and then uses the lowest element and (2) AbstractTransformRegistry.findMaxSize and gets the last
// element without filtering and returns its size limit. So there are opportunities to change the code so that
// it does not actually have to remove transformers that will not be used.
// Test transforms
SupportedTransform p45 = new SupportedTransform( "p45", emptySet(), -1, 45);
SupportedTransform p50 = new SupportedTransform( "p50", emptySet(), -1, 50);
SupportedTransform p55 = new SupportedTransform( "p55", emptySet(), -1, 55);
SupportedTransform s100p45 = new SupportedTransform("s100p45", emptySet(), 100, 45);
SupportedTransform s100p50 = new SupportedTransform("s100p50", emptySet(), 100, 50);
SupportedTransform s100p55 = new SupportedTransform("s100p55", emptySet(), 100, 55);
SupportedTransform s200p50 = new SupportedTransform("s200p50", emptySet(), 200, 50);
SupportedTransform s200p50b = new SupportedTransform("s200p50b", emptySet(), 200, 50);
SupportedTransform s200p55 = new SupportedTransform("s200p55", emptySet(), 200, 55);
SupportedTransform s300p45 = new SupportedTransform("s300p45", emptySet(), 300, 45);
SupportedTransform s300p50 = new SupportedTransform("s300p50", emptySet(), 300, 50);
SupportedTransform s300p55 = new SupportedTransform("s300p55", emptySet(), 300, 55);
// Just considers the priority
assertOrder(asList(p50), asList(p50));
assertOrder(asList(p45, p50), asList(p45));
assertOrder(asList(p50, p55), asList(p50));
assertOrder(asList(p50, p45), asList(p45));
assertOrder(asList(p45, p50, p55), asList(p45));
assertOrder(asList(p50, p55, p45), asList(p45));
assertOrder(asList(p50, p45, p55), asList(p45));
// Just considers the priority as the size limit is the same
assertOrder(asList(s100p45, s100p50, s100p55), asList(s100p45));
assertOrder(asList(s100p50, s100p45, s100p55), asList(s100p45));
// Just considers size as the priority is the same
assertOrder(asList(s100p50), asList(s100p50));
assertOrder(asList(s100p50, s200p50), asList(s200p50));
assertOrder(asList(s200p50, s100p50), asList(s200p50));
assertOrder(asList(s100p50, s200p50, s300p50), asList(s300p50));
assertOrder(asList(s200p50, s100p50, s300p50), asList(s300p50));
assertOrder(asList(s300p50, s200p50, s100p50), asList(s300p50));
// Just considers the order in which they were defined as the priority and size limit are the same.
assertOrder(asList(s200p50, s200p50b), asList(s200p50b));
assertOrder(asList(s200p50b, s200p50), asList(s200p50));
// Combinations of priority and a size limit (always set)
assertOrder(asList(s100p45, s100p50, s200p50, s200p55, s300p45, s300p50, s300p55), asList(s300p45));
assertOrder(asList(s200p50, s300p55, s300p45, s100p45, s100p50, s300p50, s200p55), asList(s300p45));
assertOrder(asList(s100p45, s200p50, s300p55), asList(s100p45, s200p50, s300p55));
assertOrder(asList(s200p50, s100p45, s300p55), asList(s100p45, s200p50, s300p55));
// Combinations of priority and a size limit or no size limit
assertOrder(asList(p45, s100p50, s200p50, s300p55), asList(p45));
assertOrder(asList(s100p50, s200p50, s300p55, p45), asList(p45));
assertOrder(asList(p55, s100p50, s200p50, s300p55), asList(s200p50, p55));
assertOrder(asList(p50, s100p50, s200p50, s300p55), asList(p50));
assertOrder(asList(s100p50, s200p50, s300p55, p50), asList(p50));
}
private void assertOrder(List<SupportedTransform> transformsInLoadOrder, List<SupportedTransform> expectedList)
{
TransformRegistryHelper helper = new TransformRegistryHelper();
TransformCache data = new TransformCache();
transformsInLoadOrder.forEach(t->data.appendTransform("text/plain", "application/pdf", t,
null, null));
List<SupportedTransform> supportedTransforms = retrieveTransformListBySize(data,
"text/plain", "application/pdf", null, null);
// Check the values used.
String transformerName = findTransformerName(supportedTransforms, 1);
long maxSize = findMaxSize(supportedTransforms);
String expectedTransformerName = expectedList.get(0).getName();
long expectedMaxSourceSizeBytes = findMaxSize(expectedList);
assertEquals(expectedList, supportedTransforms);
assertEquals("Transform name", expectedTransformerName, transformerName);
assertEquals("MaxSize", expectedMaxSourceSizeBytes, maxSize);
// If the above two pass, we don't really need the following one, but if it is wrong it might indicate
// something is wrong, where the sourceSizeInBytes is not just 1.
assertEquals(expectedList, supportedTransforms);
}
// Similar to the method in AbstractTransformRegistry
private String findTransformerName(List<SupportedTransform> supportedTransforms, final long sourceSizeInBytes)
{
return supportedTransforms
.stream()
.filter(t -> t.getMaxSourceSizeBytes() == -1 ||
t.getMaxSourceSizeBytes() >= sourceSizeInBytes)
.findFirst()
.map(SupportedTransform::getName)
.orElse(null);
}
// Similar to the method in AbstractTransformRegistry
private long findMaxSize(List<SupportedTransform> supportedTransforms)
{
return supportedTransforms.isEmpty() ? 0 :
supportedTransforms.get(supportedTransforms.size() - 1).getMaxSourceSizeBytes();
}
@Test(expected = TransformException.class)
public void buildTransformListSourceMimeTypeNullErrorTest()
{
TransformCache data = new TransformCache();
retrieveTransformListBySize(data, null, "application/pdf", null, null);
fail("No exception raised");
}
@Test(expected = TransformException.class)
public void buildTransformListTargetMimeTypeNullErrorTest()
{
TransformCache data = new TransformCache();
retrieveTransformListBySize(data, "text/plain", null, null, null);
fail("No exception raised");
}
}

View File

@ -0,0 +1,622 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.client.registry;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.alfresco.transform.client.registry.TransformRegistryHelper.addToPossibleTransformOptions;
import static org.alfresco.transform.client.registry.TransformRegistryHelper.optionsMatch;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.alfresco.transform.client.model.config.SupportedSourceAndTarget;
import org.alfresco.transform.client.model.config.TransformConfig;
import org.alfresco.transform.client.model.config.TransformOption;
import org.alfresco.transform.client.model.config.TransformOptionGroup;
import org.alfresco.transform.client.model.config.TransformOptionValue;
import org.alfresco.transform.client.model.config.Transformer;
import org.junit.Before;
import org.junit.Test;
import com.google.common.collect.ImmutableSet;
/**
* Test the AbstractTransformRegistry, extended by both T-Engines and ACS repository, which need to
* read JSON config to understand what is supported.
*
* @author adavis
*/
public class TransformRegistryTest
{
protected static final String GIF = "image/gif";
protected static final String JPEG = "image/jpeg";
protected static final String PDF = "application/pdf";
protected static final String DOC = "application/msword";
protected static final String XLS = "application/vnd.ms-excel";
protected static final String PPT = "application/vnd.ms-powerpoint";
protected static final String MSG = "application/vnd.ms-outlook";
protected static final String TXT = "text/plain";
protected AbstractTransformRegistry registry;
protected Map<String, Set<TransformOption>> mapOfTransformOptions;
@Before
public void setUp() throws Exception
{
registry = buildTransformServiceRegistryImpl();
mapOfTransformOptions = new HashMap<>();
}
protected AbstractTransformRegistry buildTransformServiceRegistryImpl() throws Exception
{
return new AbstractTransformRegistry()
{
private TransformCache data = new TransformCache();
@Override
protected void logError(String msg)
{
System.out.println(msg);
}
@Override
protected void logWarn(String msg)
{
System.out.println(msg);
}
@Override
public TransformCache getData()
{
return data;
}
};
}
private void assertAddToPossibleOptions(final TransformOptionGroup transformOptionGroup,
final Set<String> actualOptionNames, final Set<String> expectedNameSet,
final Set<String> expectedRequiredSet)
{
final Map<String, Boolean> possibleTransformOptions = new HashMap<>();
addToPossibleTransformOptions(possibleTransformOptions, transformOptionGroup, true,
buildActualOptions(actualOptionNames));
assertEquals("The expected options don't match", expectedNameSet,
possibleTransformOptions.keySet());
possibleTransformOptions.forEach((name, required) -> {
if (required)
{
assertTrue(name + " should be REQUIRED", expectedRequiredSet.contains(name));
}
else
{
assertFalse(name + " should be OPTIONAL", expectedRequiredSet.contains(name));
}
});
}
// transformOptionNames are upper case if required.
private void assertIsSupported(final Set<String> actualOptionNames,
final Set<String> transformOptionNames, final String unsupportedMsg)
{
final Map<String, Boolean> transformOptions = transformOptionNames
.stream()
.collect(toMap(identity(), name -> name.toUpperCase().equals(name)));
boolean supported = optionsMatch(transformOptions, buildActualOptions(actualOptionNames));
if (isBlank(unsupportedMsg))
{
assertTrue("Expected these options to be SUPPORTED", supported);
}
else
{
assertFalse("Expected these options NOT to be supported, because " + unsupportedMsg,
supported);
}
}
private void assertTransformOptions(Set<TransformOption> setOfTransformOptions) throws Exception
{
final Transformer transformer = new Transformer("name", singleton("testOptions"), set(
SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(TXT)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(XLS)
.withTargetMediaType(TXT)
.withMaxSourceSizeBytes(1024000L)
.build()));
final TransformConfig transformConfig = TransformConfig
.builder()
.withTransformers(singletonList(transformer))
.withTransformOptions(singletonMap("testOptions", setOfTransformOptions))
.build();
registry = buildTransformServiceRegistryImpl();
CombinedTransformConfig.combineAndRegister(transformConfig, getClass().getName(), getBaseUrl(transformer), registry);
assertTrue(registry.isSupported(XLS, 1024, TXT, emptyMap(), null));
assertTrue(registry.isSupported(XLS, 1024000, TXT, null, null));
assertFalse(registry.isSupported(XLS, 1024001, TXT, emptyMap(), null));
assertTrue(registry.isSupported(DOC, 1024001, TXT, null, null));
}
protected String getBaseUrl(Transformer transformer)
{
return "xxx";
}
private void assertTransformerName(String sourceMimetype, long sourceSizeInBytes,
String targetMimetype, Map<String, String> actualOptions, String expectedTransformerName,
Transformer... transformers) throws Exception
{
buildAndPopulateRegistry(transformers);
String transformerName = registry.findTransformerName(sourceMimetype, sourceSizeInBytes,
targetMimetype, actualOptions, null);
assertEquals(
sourceMimetype + " to " + targetMimetype + " should have returned " + expectedTransformerName,
expectedTransformerName, transformerName);
}
private void assertSupported(final Transformer transformer, final String sourceMimetype,
final long sourceSizeInBytes, final String targetMimetype,
final Map<String, String> actualOptions, final String unsupportedMsg) throws Exception
{
assertSupported(sourceMimetype, sourceSizeInBytes, targetMimetype, actualOptions,
unsupportedMsg, transformer);
}
private void assertSupported(String sourceMimetype, long sourceSizeInBytes,
String targetMimetype, Map<String, String> actualOptions, String unsupportedMsg,
Transformer... transformers) throws Exception
{
buildAndPopulateRegistry(transformers);
assertSupported(sourceMimetype, sourceSizeInBytes, targetMimetype, actualOptions, null,
unsupportedMsg);
}
private void buildAndPopulateRegistry(Transformer[] transformers) throws Exception
{
registry = buildTransformServiceRegistryImpl();
TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(Arrays.asList(transformers))
.withTransformOptions(mapOfTransformOptions)
.build();
CombinedTransformConfig.combineAndRegister(transformConfig, getClass().getName(), "---", registry);
}
protected void assertSupported(String sourceMimetype, long sourceSizeInBytes,
String targetMimetype, Map<String, String> actualOptions, String renditionName,
String unsupportedMsg)
{
boolean supported = registry.isSupported(sourceMimetype, sourceSizeInBytes, targetMimetype,
actualOptions, renditionName);
if (unsupportedMsg == null || unsupportedMsg.isEmpty())
{
assertTrue(sourceMimetype + " to " + targetMimetype + " should be SUPPORTED",
supported);
}
else
{
assertFalse(sourceMimetype + " to " + targetMimetype + " should NOT be supported",
supported);
}
}
private static Map<String, String> buildActualOptions(final Set<String> optionNames)
{
return optionNames
.stream()
.collect(toMap(identity(), name -> "value for " + name));
}
@Test
public void testOptionalGroups()
{
final TransformOptionGroup transformOptionGroup =
new TransformOptionGroup(true, set(
new TransformOptionValue(false, "1"),
new TransformOptionValue(true, "2"),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "3.1"),
new TransformOptionValue(false, "3.2"),
new TransformOptionValue(false, "3.3"))),
new TransformOptionGroup(false, set( // OPTIONAL
new TransformOptionValue(false, "4.1"),
new TransformOptionValue(true, "4.2"),
new TransformOptionValue(false, "4.3")))));
assertAddToPossibleOptions(transformOptionGroup, emptySet(),
set("1", "2"), set("2"));
assertAddToPossibleOptions(transformOptionGroup, set("1"),
set("1", "2"), set("2"));
assertAddToPossibleOptions(transformOptionGroup, set("2"),
set("1", "2"), set("2"));
assertAddToPossibleOptions(transformOptionGroup, set("2", "3.2"),
set("1", "2", "3.1", "3.2", "3.3"), set("2"));
assertAddToPossibleOptions(transformOptionGroup, set("2", "4.1"),
set("1", "2", "4.1", "4.2", "4.3"), set("2", "4.2"));
assertAddToPossibleOptions(transformOptionGroup, set("2", "4.2"),
set("1", "2", "4.1", "4.2", "4.3"), set("2", "4.2"));
}
@Test
public void testRequiredGroup()
{
TransformOptionGroup transformOptionGroup =
new TransformOptionGroup(true, set(
new TransformOptionValue(false, "1"),
new TransformOptionValue(true, "2"),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "3.1"),
new TransformOptionValue(false, "3.2"),
new TransformOptionValue(false, "3.3"))),
new TransformOptionGroup(true, set(
new TransformOptionValue(false, "4.1"),
new TransformOptionValue(true, "4.2"),
new TransformOptionValue(false, "4.3")))));
assertAddToPossibleOptions(transformOptionGroup, emptySet(),
set("1", "2", "4.1", "4.2", "4.3"), set("2", "4.2"));
assertAddToPossibleOptions(transformOptionGroup, set("1"),
set("1", "2", "4.1", "4.2", "4.3"), set("2", "4.2"));
assertAddToPossibleOptions(transformOptionGroup, set("2", "3.2"),
set("1", "2", "3.1", "3.2", "3.3", "4.1", "4.2", "4.3"), set("2", "4.2"));
assertAddToPossibleOptions(transformOptionGroup, set("2", "4.1"),
set("1", "2", "4.1", "4.2", "4.3"), set("2", "4.2"));
assertAddToPossibleOptions(transformOptionGroup, set("2", "4.2"),
set("1", "2", "4.1", "4.2", "4.3"), set("2", "4.2"));
}
@Test
public void testNestedGroups()
{
TransformOptionGroup transformOptionGroup =
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "1"),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "1.2"),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "1.2.3"))))))),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "2"),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "2.2"),
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "2.2.1.2"))))))))),
new TransformOptionGroup(false, set(
new TransformOptionValue(true, "3"),
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "3.1.1.2"))))))))),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "4"),
new TransformOptionGroup(true, set(
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "4.1.1.2"))))))))),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "5"),
new TransformOptionGroup(false, set(
new TransformOptionGroup(true, set(
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "5.1.1.2"))))))))),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "6"),
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionGroup(true, set(
new TransformOptionValue(false, "6.1.1.2"))))))))),
new TransformOptionGroup(false, set(
new TransformOptionValue(false, "7"),
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionGroup(false, set(
new TransformOptionValue(true, "7.1.1.2")))))))))
));
assertAddToPossibleOptions(transformOptionGroup, emptySet(),
emptySet(), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1"),
set("1"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "7"),
set("1", "7"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "7.1.1.2"),
set("1", "7", "7.1.1.2"), set("7.1.1.2"));
assertAddToPossibleOptions(transformOptionGroup, set("1", "6"),
set("1", "6"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "6.1.1.2"),
set("1", "6", "6.1.1.2"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "5"),
set("1", "5"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "5.1.1.2"),
set("1", "5", "5.1.1.2"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "4"),
set("1", "4"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "4.1.1.2"),
set("1", "4", "4.1.1.2"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("1", "3"),
set("1", "3"), set("3"));
assertAddToPossibleOptions(transformOptionGroup, set("1", "3.1.1.2"),
set("1", "3", "3.1.1.2"), set("3"));
assertAddToPossibleOptions(transformOptionGroup, set("2"),
set("2"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("2", "2.2"),
set("2", "2.2"), emptySet());
assertAddToPossibleOptions(transformOptionGroup, set("3"),
set("3"), set("3"));
assertAddToPossibleOptions(transformOptionGroup, set("3.1.1.2"),
set("3", "3.1.1.2"), set("3"));
}
@Test
public void testRegistryIsSupportedMethod()
{
assertIsSupported(set("a"), set("a", "B", "c"), "required option B is missing");
assertIsSupported(emptySet(), set("a", "B", "c"), "required option B is missing");
assertIsSupported(set("B"), set("a", "B", "c"), null);
assertIsSupported(set("B", "c"), set("a", "B", "c"), null);
assertIsSupported(set("B", "a", "c"), set("a", "B", "c"), null);
assertIsSupported(set("B", "d"), set("a", "B", "c"), "there is an extra option d");
assertIsSupported(set("B", "c", "d"), set("a", "B", "c"), "there is an extra option d");
assertIsSupported(set("d"), set("a", "B", "c"),
"required option B is missing and there is an extra option d");
assertIsSupported(set("a"), set("a", "b", "c"), null);
assertIsSupported(emptySet(), set("a", "b", "c"), null);
assertIsSupported(set("a", "b", "c"), set("a", "b", "c"), null);
}
@Test
public void testNoActualOptions() throws Exception
{
assertTransformOptions(set(
new TransformOptionValue(false, "option1"),
new TransformOptionValue(false, "option2")));
}
@Test
public void testNoTransformOptions() throws Exception
{
assertTransformOptions(emptySet());
assertTransformOptions(null);
}
@Test
public void testSupported() throws Exception
{
mapOfTransformOptions.put("options1", set(
new TransformOptionValue(false, "page"),
new TransformOptionValue(false, "width"),
new TransformOptionValue(false, "height")));
final Transformer transformer = new Transformer("name", singleton("options1"), set(
SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(GIF)
.withMaxSourceSizeBytes(102400L)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(JPEG)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(MSG)
.withTargetMediaType(GIF)
.build()));
assertSupported(transformer, DOC, 1024, GIF, emptyMap(), null);
assertSupported(transformer, DOC, 102400, GIF, emptyMap(), null);
assertSupported(transformer, DOC, 102401, GIF, emptyMap(), "source is too large");
assertSupported(transformer, DOC, 1024, JPEG, emptyMap(), null);
assertSupported(transformer, GIF, 1024, DOC, emptyMap(),
GIF + " is not a source of this transformer");
assertSupported(transformer, MSG, 1024, GIF, emptyMap(), null);
assertSupported(transformer, MSG, 1024, JPEG, emptyMap(),
MSG + " to " + JPEG + " is not supported by this transformer");
assertSupported(transformer, DOC, 1024, GIF, buildActualOptions(set("page", "width")),
null);
assertSupported(transformer, DOC, 1024, GIF,
buildActualOptions(set("page", "width", "startPage")), "startPage is not an option");
}
@Test
// renditionName used as the cache key, is an alias for a set of actualOptions and the target mimetype.
// The source mimetype may change.
public void testCache()
{
mapOfTransformOptions.put("options1", set(
new TransformOptionValue(false, "page"),
new TransformOptionValue(false, "width"),
new TransformOptionValue(false, "height")));
final Transformer transformer = new Transformer("name", singleton("options1"), set(
SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(GIF)
.withMaxSourceSizeBytes(102400L)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(MSG)
.withTargetMediaType(GIF)
.build()));
TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(Collections.singletonList(transformer))
.withTransformOptions(mapOfTransformOptions)
.build();
CombinedTransformConfig.combineAndRegister(transformConfig, getClass().getName(), getBaseUrl(transformer), registry);
assertSupported(DOC, 1024, GIF, emptyMap(), "doclib", "");
assertSupported(MSG, 1024, GIF, emptyMap(), "doclib", "");
assertEquals(102400L, registry.findMaxSize(DOC, GIF, emptyMap(), "doclib"));
assertEquals(-1L, registry.findMaxSize(MSG, GIF, emptyMap(), "doclib"));
// check we are now using the cached value.
final SupportedTransform cachedSupportedTransform = new SupportedTransform("name1",
emptySet(), 999999L, 0);
registry.getData()
.retrieveCached("doclib", DOC)
.add(cachedSupportedTransform);
assertEquals(999999L, registry.findMaxSize(DOC, GIF, emptyMap(), "doclib"));
}
@Test
public void testGetTransformerName() throws Exception
{
Transformer t1 = newTransformer("transformer1", MSG, GIF, 100, 50);
Transformer t2 = newTransformer("transformer2", MSG, GIF, 200, 60);
Transformer t3 = newTransformer("transformer3", MSG, GIF, 200, 40);
Transformer t4 = newTransformer("transformer4", MSG, GIF, -1, 100);
Transformer t5 = newTransformer("transformer5", MSG, GIF, -1, 80);
// Select on size - priority is ignored
assertTransformerName(MSG, 100, GIF, emptyMap(), "transformer1", t1, t2);
assertTransformerName(MSG, 150, GIF, emptyMap(), "transformer2", t1, t2);
assertTransformerName(MSG, 250, GIF, emptyMap(), null, t1, t2);
// Select on priority - t1, t2 and t4 are discarded.
// t3 is a higher priority and has a larger size than t1 and t2.
// Similar story fo t4 with t5.
assertTransformerName(MSG, 100, GIF, emptyMap(), "transformer3", t1, t2, t3, t4, t5);
assertTransformerName(MSG, 200, GIF, emptyMap(), "transformer3", t1, t2, t3, t4, t5);
// Select on size and priority, t1 and t2 discarded
assertTransformerName(MSG, 200, GIF, emptyMap(), "transformer3", t1, t2, t3, t4);
assertTransformerName(MSG, 300, GIF, emptyMap(), "transformer4", t1, t2, t3, t4);
assertTransformerName(MSG, 300, GIF, emptyMap(), "transformer5", t1, t2, t3, t4, t5);
}
private Transformer newTransformer(String transformerName, String sourceMediaType, String targetMediaType,
long maxSourceSizeBytes, int priority)
{
return Transformer.builder().withTransformerName(transformerName)
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType(sourceMediaType)
.withTargetMediaType(targetMediaType)
.withMaxSourceSizeBytes(maxSourceSizeBytes)
.withPriority(priority)
.build()))
.build();
}
@Test
public void testMultipleTransformers() throws Exception
{
mapOfTransformOptions.put("options1", set(
new TransformOptionValue(false, "page"),
new TransformOptionValue(false, "width"),
new TransformOptionValue(false, "height")));
mapOfTransformOptions.put("options2", set(
new TransformOptionValue(false, "opt1"),
new TransformOptionValue(false, "opt2")));
mapOfTransformOptions.put("options3", new HashSet<>(singletonList(
new TransformOptionValue(false, "opt1"))));
Transformer transformer1 = new Transformer("transformer1", singleton("options1"), set(
SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(GIF)
.withMaxSourceSizeBytes(102400L)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(JPEG)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(MSG)
.withTargetMediaType(GIF)
.build()));
Transformer transformer2 = new Transformer("transformer2", singleton("options2"), set(
SupportedSourceAndTarget.builder()
.withSourceMediaType(PDF)
.withTargetMediaType(GIF)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(PPT)
.withTargetMediaType(JPEG)
.build()));
Transformer transformer3 = new Transformer("transformer3", singleton("options3"),
new HashSet(singletonList(SupportedSourceAndTarget.builder()
.withSourceMediaType(DOC)
.withTargetMediaType(GIF)
.build())));
assertSupported(DOC, 1024, GIF, emptyMap(), null, transformer1);
assertSupported(DOC, 1024, GIF, emptyMap(), null, transformer1, transformer2);
assertSupported(DOC, 1024, GIF, emptyMap(), null, transformer1, transformer2,
transformer3);
assertSupported(DOC, 102401, GIF, emptyMap(), "source is too large", transformer1);
assertSupported(DOC, 102401, GIF, emptyMap(), null, transformer1, transformer3);
assertSupported(PDF, 1024, GIF, emptyMap(), "Only transformer2 supports these mimetypes",
transformer1);
assertSupported(PDF, 1024, GIF, emptyMap(), null, transformer1, transformer2);
assertSupported(PDF, 1024, GIF, emptyMap(), null, transformer1, transformer2,
transformer3);
final Map<String, String> actualOptions = buildActualOptions(set("opt1"));
assertSupported(PDF, 1024, GIF, actualOptions, "Only transformer2/4 supports these options",
transformer1);
assertSupported(PDF, 1024, GIF, actualOptions, null, transformer1, transformer2);
assertSupported(PDF, 1024, GIF, actualOptions, null, transformer1, transformer2,
transformer3);
assertSupported(PDF, 1024, GIF, actualOptions,
"transformer4 supports opt1 but not the source mimetype ", transformer1, transformer3);
}
@SafeVarargs
private static <T> Set<T> set(T... elements)
{
if (elements == null || elements.length == 0)
{
return emptySet();
}
return ImmutableSet.copyOf(elements);
}
}

View File

@ -0,0 +1,537 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.router;
import com.google.common.collect.ImmutableMap;
import org.alfresco.transform.client.model.InternalContext;
import org.alfresco.transform.client.model.MultiStep;
import org.alfresco.transform.client.model.TransformReply;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import static org.alfresco.transform.router.TransformStack.OPTIONS_LEVEL;
import static org.alfresco.transform.router.TransformStack.SEPARATOR;
import static org.alfresco.transform.router.TransformStack.TOP_STACK_LEVEL;
import static org.alfresco.transform.router.TransformStack.getInitialSourceReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.doReturn;
class TransformStackTest
{
private static long START = System.currentTimeMillis();
private static String STEP = SEPARATOR + "name" + SEPARATOR + "source" + SEPARATOR + "target";
@Mock
private TransformerDebug transformerDebug;
@Mock
private TransformReply reply;
// Note: If you change this also change AbstractRouterTest.testNestedTransform as they match
public static final ImmutableMap<String, TransformStack.LevelBuilder> TEST_LEVELS = ImmutableMap.of(
"top", TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("pipeline 1-N", "type1", "typeN"),
"pipeline 1-N", TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("transform1-2", "type1", "type2")
.withStep("pipeline 2-3", "type2", "type3")
.withStep("failover 3-N", "type3", "typeN"),
"pipeline 2-3", TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("transform2-4", "type2", "type4")
.withStep("transform4-3", "type4", "type3"),
"failover 3-N", TransformStack.levelBuilder(TransformStack.FAILOVER_FLAG)
.withStep("transform3-Na", "type3", "typeN")
.withStep("transform3-Nb", "type3", "typeN")
.withStep("pipeline 3-Nc", "type3", "typeN"),
"pipeline 3-Nc", TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("transform3-5", "type3", "type5")
.withStep("pipeline 5-N", "type5", "typeN"),
"pipeline 5-N", TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("transform5-6", "type5", "type6")
.withStep("transform6-N", "type6", "typeN"));
private final InternalContext internalContext = new InternalContext();
private final Map<String, String> options = ImmutableMap.of("key1", "value1", "key2", "", "key3", "value3");
private final String sourceReference = UUID.randomUUID().toString();
@BeforeEach
void setUp()
{
MockitoAnnotations.openMocks(this);
// Repeat what is done by Router.initialiseContext
internalContext.setMultiStep(new MultiStep());
internalContext.getMultiStep().setTransformsToBeDone(new ArrayList<>());
TransformStack.setInitialTransformRequestOptions(internalContext, options);
TransformStack.setInitialSourceReference(internalContext, sourceReference);
doReturn(internalContext).when(reply).getInternalContext();
}
@Test
public void testOptions()
{
assertEquals(options, TransformStack.getInitialTransformRequestOptions(internalContext));
}
@Test
public void testOptionsEmpty()
{
ImmutableMap<String, String> options = ImmutableMap.of();
TransformStack.setInitialTransformRequestOptions(internalContext, options);
assertEquals(options, TransformStack.getInitialTransformRequestOptions(internalContext));
}
@Test
public void testOptionsEmptyLastValue()
{
ImmutableMap<String, String> options = ImmutableMap.of("key1", "value1", "key2", "");
TransformStack.setInitialTransformRequestOptions(internalContext, options);
assertEquals(options, TransformStack.getInitialTransformRequestOptions(internalContext));
}
@Test
public void testSourceReference()
{
assertEquals(sourceReference, getInitialSourceReference(internalContext));
}
@Test
public void testSourceReferenceNull()
{
TransformStack.setInitialSourceReference(internalContext, null);
assertEquals(null, getInitialSourceReference(internalContext));
}
@Test
public void testSourceReferenceBlank()
{
TransformStack.setInitialSourceReference(internalContext, "");
assertEquals("", getInitialSourceReference(internalContext));
}
@Test
public void testLevelBuilder()
{
assertEquals("P⏐1⏐0⏐0⏐pipeline 1-N⏐type1⏐typeN", TEST_LEVELS.get("top").build());
assertEquals("P⏐1⏐0⏐0⏐failover 3-N⏐type3⏐typeN⏐pipeline 2-3⏐type2⏐type3⏐transform1-2⏐type1⏐type2", TEST_LEVELS.get("pipeline 1-N").build());
assertEquals("F⏐1⏐0⏐0⏐pipeline 3-Nc⏐type3⏐typeN⏐transform3-Nb⏐type3⏐typeN⏐transform3-Na⏐type3⏐typeN", TEST_LEVELS.get("failover 3-N").build());
}
@Test
public void testAttemptedRetries()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
assertEquals(0, TransformStack.getAttemptedRetries(internalContext));
TransformStack.incrementAttemptedRetries(internalContext);
assertEquals(1, TransformStack.getAttemptedRetries(internalContext));
TransformStack.incrementAttemptedRetries(internalContext);
assertEquals(2, TransformStack.getAttemptedRetries(internalContext));
TransformStack.resetAttemptedRetries(internalContext);
assertEquals(0, TransformStack.getAttemptedRetries(internalContext));
}
@Test
public void testReference()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
assertEquals("1", TransformStack.getReference(internalContext));
TransformStack.setReference(internalContext, 123);
assertEquals("123", TransformStack.getReference(internalContext));
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("pipeline 1-N"));
assertEquals("123.1", TransformStack.getReference(internalContext));
TransformStack.incrementReference(internalContext);
assertEquals("123.2", TransformStack.getReference(internalContext));
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("pipeline 2-3"));
assertEquals("123.2.1", TransformStack.getReference(internalContext));
TransformStack.removeTransformLevel(internalContext);
TransformStack.incrementReference(internalContext);
assertEquals("123.3", TransformStack.getReference(internalContext));
TransformStack.removeTransformLevel(internalContext);
assertEquals("123", TransformStack.getReference(internalContext));
}
@Test
public void testSetReferenceInADummyTopLevelIfUnset()
{
// Used when creating a TransformRouterException prior to setting the reference
// Undo setup()
internalContext.getMultiStep().setTransformsToBeDone(new ArrayList<>());
TransformStack.setReferenceInADummyTopLevelIfUnset(internalContext, 23);
assertEquals("23", TransformStack.getReference(internalContext));
}
@Test
public void testReplicateWorkflowWithSuccess()
{
replicateWorkflowStepsPriorToFailureOrSuccess();
// Assume a successful transform, so successful there should be no more steps or levels after this
TransformStack.removeSuccessfulStep(reply, transformerDebug);
assertTrue(TransformStack.isFinished(internalContext));
}
@Test
// Tests the failure on a transform indirectly (it is a step in a pipeline) under a failover transformer.
public void testReplicateWorkflowWithFailure()
{
replicateWorkflowStepsPriorToFailureOrSuccess();
// Assume a transform failure. While loop should remove the 2 indirect pipeline levels
int removedLevels = 0;
while (!TransformStack.isParentAFailover(internalContext))
{
removedLevels++;
TransformStack.removeTransformLevel(internalContext);
}
assertEquals(2, removedLevels);
assertTrue(TransformStack.isLastStepInTransformLevel(internalContext));
TransformStack.removeFailedStep(reply, transformerDebug); // Should remove the rest as failure was last step in failover
assertTrue(TransformStack.isFinished(internalContext));
}
// Replicates the sequence of TransformStack method calls for a workflow. Steps through each transform, with
// some failures in a failover transformer, before returning to allow the calling test method to either fail the
// next step or not.
private void replicateWorkflowStepsPriorToFailureOrSuccess()
{
// Initial transform request, so add a level
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
assertFalse(TransformStack.isFinished(internalContext));
TransformStack.Step step = TransformStack.currentStep(internalContext);
assertEquals("pipeline 1-N", step.getTransformerName());
assertEquals("type1", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals(null, TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Because it is a pipeline, add a level
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("pipeline 1-N"));
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform1-2", step.getTransformerName());
assertEquals("type1", step.getSourceMediaType());
assertEquals("type2", step.getTargetMediaType());
assertEquals("pipeline 1-N", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Assume a successful transform, so move on to next step add a level
TransformStack.removeSuccessfulStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("pipeline 2-3", step.getTransformerName());
assertEquals("type2", step.getSourceMediaType());
assertEquals("type3", step.getTargetMediaType());
assertEquals("pipeline 1-N", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Because it is a pipeline, add a level
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("pipeline 2-3"));
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform2-4", step.getTransformerName());
assertEquals("type2", step.getSourceMediaType());
assertEquals("type4", step.getTargetMediaType());
assertEquals("pipeline 2-3", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Assume a successful transform, so move on to next step add a level
TransformStack.removeSuccessfulStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform4-3", step.getTransformerName());
assertEquals("type4", step.getSourceMediaType());
assertEquals("type3", step.getTargetMediaType());
assertEquals("pipeline 2-3", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Assume a successful transform, so move on to next step add a level
TransformStack.removeSuccessfulStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("failover 3-N", step.getTransformerName());
assertEquals("type3", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals("pipeline 1-N", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Because it is a failover, add a level
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("failover 3-N"));
step = TransformStack.currentStep(internalContext);
assertEquals("transform3-Na", step.getTransformerName());
assertEquals("type3", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals("failover 3-N", TransformStack.getParentName(internalContext));
assertTrue(TransformStack.isParentAFailover(internalContext));
// Assume 1st failover step fails
while (!TransformStack.isParentAFailover(internalContext))
{
TransformStack.removeTransformLevel(internalContext);
}
assertFalse(TransformStack.isLastStepInTransformLevel(internalContext));
TransformStack.removeFailedStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform3-Nb", step.getTransformerName());
assertEquals("type3", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals("failover 3-N", TransformStack.getParentName(internalContext));
assertTrue(TransformStack.isParentAFailover(internalContext));
// Assume 2nd failover step fails
while (!TransformStack.isParentAFailover(internalContext))
{
TransformStack.removeTransformLevel(internalContext);
}
assertFalse(TransformStack.isLastStepInTransformLevel(internalContext));
TransformStack.removeFailedStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("pipeline 3-Nc", step.getTransformerName());
assertEquals("type3", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals("failover 3-N", TransformStack.getParentName(internalContext));
assertTrue(TransformStack.isParentAFailover(internalContext));
// Because it is a pipeline, add a level
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("pipeline 3-Nc"));
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform3-5", step.getTransformerName());
assertEquals("type3", step.getSourceMediaType());
assertEquals("type5", step.getTargetMediaType());
assertEquals("pipeline 3-Nc", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Assume a successful transform, so move on to next step add a level
TransformStack.removeSuccessfulStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("pipeline 5-N", step.getTransformerName());
assertEquals("type5", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals("pipeline 3-Nc", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Because it is a pipeline, add a level
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("pipeline 5-N"));
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform5-6", step.getTransformerName());
assertEquals("type5", step.getSourceMediaType());
assertEquals("type6", step.getTargetMediaType());
assertEquals("pipeline 5-N", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
// Assume a successful transform, so move on to next step add a level
TransformStack.removeSuccessfulStep(reply, transformerDebug);
assertFalse(TransformStack.isFinished(internalContext));
step = TransformStack.currentStep(internalContext);
assertEquals("transform6-N", step.getTransformerName());
assertEquals("type6", step.getSourceMediaType());
assertEquals("typeN", step.getTargetMediaType());
assertEquals("pipeline 5-N", TransformStack.getParentName(internalContext));
assertFalse(TransformStack.isParentAFailover(internalContext));
}
@Test
// Tests a workflow where all transforms are successful, using a loop.
public void testWorkflowWithLoop()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
int transformStepCount = 0;
do
{
transformStepCount++;
TransformStack.Step step = TransformStack.currentStep(internalContext);
String transformerName = step.getTransformerName();
int depth = internalContext.getMultiStep().getTransformsToBeDone().size() - 2;
System.out.println(transformStepCount + " ".substring(0, depth*2+1) + transformerName);
TransformStack.LevelBuilder nextLevel = TEST_LEVELS.get(transformerName);
if (nextLevel == null)
{
TransformStack.removeSuccessfulStep(reply, transformerDebug);
}
else
{
TransformStack.addTransformLevel(internalContext, nextLevel);
}
if (transformStepCount >= 25)
{
fail("Appear to be in an infinite loop");
}
} while (!TransformStack.isFinished(internalContext));
assertEquals(7, transformStepCount);
}
@Test
public void testCheckStructureNoOptions()
{
internalContext.getMultiStep().setTransformsToBeDone(new ArrayList<>());
assertEquals("T-Reply InternalContext did not have the Stack set",
TransformStack.checkStructure(internalContext, "T-Reply"));
}
@Test
public void testCheckStructureNoSourceRef()
{
internalContext.getMultiStep().setTransformsToBeDone(new ArrayList<>());
TransformStack.setInitialTransformRequestOptions(internalContext, options);
assertEquals("T-Request InternalContext did not have the Stack set",
TransformStack.checkStructure(internalContext, "T-Request"));
}
@Test
public void testCheckStructureNoStack()
{
internalContext.getMultiStep().setTransformsToBeDone(new ArrayList<>());
TransformStack.setInitialTransformRequestOptions(internalContext, options);
TransformStack.setInitialSourceReference(internalContext, sourceReference);
assertEquals("T-something InternalContext did not have the Stack set",
TransformStack.checkStructure(internalContext, "T-something"));
}
@Test
public void testCheckStructureOptionsOk()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
for (String value : Arrays.asList(
"",
"key1" + SEPARATOR + "value1",
"key1" + SEPARATOR + "value1" + SEPARATOR + "key2" + SEPARATOR + "value2"))
{
System.out.println("TransformOptions value: " + value);
internalContext.getMultiStep().getTransformsToBeDone().set(OPTIONS_LEVEL, value);
assertNull(TransformStack.checkStructure(internalContext, "T-Reply"));
// call the getter just in case we have missed something
TransformStack.getInitialTransformRequestOptions(internalContext);
}
}
@Test
public void testCheckStructureOptionsBad()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
for (String value : Arrays.asList(
null,
"noValue",
SEPARATOR + "noKey",
"key" + SEPARATOR + "value" + SEPARATOR + "noKey"))
{
System.out.println("TransformOptions value: " + value);
internalContext.getMultiStep().getTransformsToBeDone().set(OPTIONS_LEVEL, value);
assertEquals("T-Reply InternalContext did not have the TransformOptions set correctly",
TransformStack.checkStructure(internalContext, "T-Reply"));
}
}
@Test
public void testCheckStructureStackLevelsOk()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
for (String value : Arrays.asList(
"P" + SEPARATOR + "20" + SEPARATOR + START + SEPARATOR + "1" + STEP,
"P" + SEPARATOR + "4" + SEPARATOR + "123" + SEPARATOR + "12" + STEP + STEP))
{
System.out.println("TransformLevel value: " + value);
internalContext.getMultiStep().getTransformsToBeDone().set(TOP_STACK_LEVEL, value);
assertNull(TransformStack.checkStructure(internalContext, "T-Reply"));
// call a getter just in case we have missed something
TransformStack.currentStep(internalContext);
};
}
@Test
public void testCheckStructureStackLevelsBad()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
String MAX_INT_PLUS_1 = BigInteger.valueOf(Integer.MAX_VALUE + 1).toString();
String MAX_LONG_PLUS_1 = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).toString();
for (String value : Arrays.asList(
null,
"",
"F" + SEPARATOR + "12" + SEPARATOR + START + SEPARATOR + "2",
"F" + SEPARATOR + "-1" + SEPARATOR + START + SEPARATOR + "2" + STEP,
"F" + SEPARATOR + "1" + SEPARATOR + "-3" + SEPARATOR + "2" + STEP,
"F" + SEPARATOR + "1" + SEPARATOR + START + SEPARATOR + "-2" + STEP,
"F" + SEPARATOR + "a" + SEPARATOR + START + SEPARATOR + "-2" + STEP,
"F" + SEPARATOR + "1" + SEPARATOR + START + SEPARATOR + "b" + STEP,
"P" + SEPARATOR + "0" + SEPARATOR + START + SEPARATOR + "12" + SEPARATOR + "name",
"P" + SEPARATOR + "0" + SEPARATOR + START + SEPARATOR + "12" + SEPARATOR + "name" + SEPARATOR + "source",
"P" + SEPARATOR + "0" + SEPARATOR + START + SEPARATOR + "12" + SEPARATOR + "name" + SEPARATOR + "source" + SEPARATOR + "",
"P" + SEPARATOR + "0" + SEPARATOR + START + SEPARATOR + "12" + SEPARATOR + "name" + SEPARATOR + "" + SEPARATOR + "target",
"F" + SEPARATOR + MAX_INT_PLUS_1 + SEPARATOR + START + SEPARATOR + "2" + STEP,
"F" + SEPARATOR + "1" + SEPARATOR + MAX_LONG_PLUS_1 + SEPARATOR + "2" + STEP,
"F" + SEPARATOR + "1" + SEPARATOR + START + SEPARATOR + MAX_INT_PLUS_1 + STEP
))
{
System.out.println("TransformLevel value: " + value);
internalContext.getMultiStep().getTransformsToBeDone().set(TOP_STACK_LEVEL, value);
assertEquals("T-Reply InternalContext did not have levels set correctly",
TransformStack.checkStructure(internalContext, "T-Reply"));
};
}
}

View File

@ -0,0 +1,218 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* %%
* This program 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.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package org.alfresco.transform.router;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import com.google.common.collect.ImmutableMap;
import org.alfresco.transform.client.model.InternalContext;
import org.alfresco.transform.client.model.TransformReply;
import org.alfresco.transform.client.model.TransformRequest;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.StringJoiner;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_TEXT_PLAIN;
import static org.alfresco.transform.client.model.Mimetype.MIMETYPE_WORD;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests TransformerDebug. AbstractRouterTest in the t-router contains more complete end to end tests. The tests in this
* class are a smoke test at the moment.
*/
class TransformerDebugTest
{
private static String MIMETYPE_PDF = "application/pdf";
TransformerDebug transformerDebug = new TransformerDebug();
private StringJoiner transformerDebugOutput = new StringJoiner("\n");
public String getTransformerDebugOutput()
{
return transformerDebugOutput.toString()
.replaceAll(" [\\d,]+ ms", " -- ms");
}
private void twoStepTransform(boolean isTEngine, boolean fail, Level logLevel)
{
transformerDebug.setIsTEngine(isTEngine);
monitorLogs(logLevel);
TransformRequest request = TransformRequest.builder()
.withSourceSize(1234L)
.withInternalContext(InternalContext.initialise(null))
.build();
TransformStack.setInitialSourceReference(request.getInternalContext(), "fileRef");
TransformReply reply = TransformReply.builder()
.withInternalContext(request.getInternalContext())
.build();
TransformStack.addTransformLevel(request.getInternalContext(),
TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("wrapper", MIMETYPE_TEXT_PLAIN, MIMETYPE_PDF));
transformerDebug.pushTransform(request);
TransformStack.addTransformLevel(request.getInternalContext(),
TransformStack.levelBuilder(TransformStack.PIPELINE_FLAG)
.withStep("transformer1", MIMETYPE_TEXT_PLAIN, MIMETYPE_WORD)
.withStep("transformer2", MIMETYPE_WORD, MIMETYPE_PDF));
transformerDebug.pushTransform(request);
transformerDebug.logOptions(request);
TransformStack.removeSuccessfulStep(reply, transformerDebug);
request.setTransformRequestOptions(ImmutableMap.of("k1", "v1", "k2","v2"));
transformerDebug.pushTransform(request);
transformerDebug.logOptions(request);
if (fail)
{
reply.setErrorDetails("Dummy error");
transformerDebug.logFailure(reply);
}
else
{
TransformStack.removeSuccessfulStep(reply, transformerDebug);
TransformStack.removeTransformLevel(reply.getInternalContext());
}
}
private void monitorLogs(Level logLevel)
{
Logger logger = (Logger)LoggerFactory.getLogger(TransformerDebug.class);
AppenderBase<ILoggingEvent> logAppender = new AppenderBase<>()
{
@Override
protected void append(ILoggingEvent iLoggingEvent)
{
transformerDebugOutput.add(iLoggingEvent.getMessage());
}
};
logAppender.setContext((LoggerContext)LoggerFactory.getILoggerFactory());
logger.setLevel(logLevel);
logger.addAppender(logAppender);
logAppender.start();
}
@Test
void testRouterTwoStepTransform()
{
twoStepTransform(false, false, Level.DEBUG);
assertEquals("" +
"1 txt pdf 1.2 KB wrapper\n" +
"1.1 txt doc transformer1\n" +
"1.2 doc pdf transformer2\n" +
"1.2 k1=\"v1\"\n" +
"1.2 k2=\"v2\"\n" +
"1 Finished in -- ms",
getTransformerDebugOutput());
}
@Test
void testRouterTwoStepTransformWithTrace()
{
twoStepTransform(false, false, Level.TRACE);
// With trace there are "Finished" lines for nested transforms, like a T-Engine's debug but still without
// the size and rendition name
assertEquals("" +
"1 txt pdf 1.2 KB wrapper\n" +
"1.1 txt doc transformer1\n" +
"1.1 Finished in -- ms\n" +
"1.2 doc pdf transformer2\n" +
"1.2 k1=\"v1\"\n" +
"1.2 k2=\"v2\"\n" +
"1.2 Finished in -- ms\n" +
"1 Finished in -- ms",
getTransformerDebugOutput());
}
@Test
void testEngineTwoStepTransform()
{
twoStepTransform(true, false, Level.DEBUG);
// Note the first and last lines would only ever be logged on the router, but the expected data includes
// the extra "Finished" lines, sizes and renditions (if set in client data).
assertEquals("" +
"1 txt pdf 1.2 KB wrapper\n" +
"1.1 txt doc 1.2 KB transformer1\n" +
"1.1 Finished in -- ms\n" +
"1.2 doc pdf 1.2 KB transformer2\n" +
"1.2 k1=\"v1\"\n" +
"1.2 k2=\"v2\"\n" +
"1.2 Finished in -- ms\n" +
"1 Finished in -- ms",
getTransformerDebugOutput());
}
@Test
void testRouterTwoStepTransformWithFailure()
{
twoStepTransform(false, true, Level.DEBUG);
assertEquals("" +
"1 txt pdf 1.2 KB wrapper\n" +
"1.1 txt doc transformer1\n" +
"1.2 doc pdf transformer2\n" +
"1.2 k1=\"v1\"\n" +
"1.2 k2=\"v2\"\n" +
"1.2 Dummy error",
getTransformerDebugOutput());
}
@Test
void testLogFailure()
{
monitorLogs(Level.TRACE);
TransformReply reply = TransformReply.builder()
.withInternalContext(InternalContext.initialise(null))
.withErrorDetails("T-Request was null - a major error")
.build();
transformerDebug.logFailure(reply);
assertEquals(" T-Request was null - a major error", getTransformerDebugOutput());
}
@Test
void tesGetOptionAndValue()
{
String sixtyChars = "12345678 10 345678 20 345678 30 345678 40 345678 50 abcdefgh";
String sixtyOneChars = "12345678 10 345678 20 345678 30 345678 40 345678 50 abcd12345";
String expected = "12345678 10 345678 20 345678 30 345678 40 345678 50 ...12345";
assertEquals("ref key=\"value\"",
transformerDebug.getOptionAndValue("ref", "key", "value"));
assertEquals("ref key=\""+sixtyChars+"\"",
transformerDebug.getOptionAndValue("ref", "key", sixtyChars));
assertEquals("ref key=\""+expected+"\"",
transformerDebug.getOptionAndValue("ref", "key", sixtyOneChars));
}
}

View File

@ -51,6 +51,7 @@
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-model</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -24,7 +24,7 @@ Travis CI builds differ by branch:
include the _Release_ stage;
- PR builds where the latest commit contains the `[trigger release]` tag will execute dry runs
of the release jobs (no artifacts will be published until the PR is actually merged).
* `ATS-*` branches:
* `ATS-*` / `ACS-*` branches:
- regular builds which include only the _Build_ and _Tests_ stages;
All other branches are ignored.

14
pom.xml
View File

@ -22,7 +22,6 @@
<dependency.pdfbox.version>2.0.25</dependency.pdfbox.version>
<dependency.alfresco-jodconverter-core.version>3.0.1.12</dependency.alfresco-jodconverter-core.version>
<env.project_version>${project.version}</env.project_version>
<dependency.alfresco-transform-model.version>1.4.16</dependency.alfresco-transform-model.version>
<dependency.activemq.version>5.16.4</dependency.activemq.version>
<dependency.jackson.version>2.13.2</dependency.jackson.version>
<dependency.jackson-databind.version>2.13.2.2</dependency.jackson-databind.version>
@ -42,6 +41,7 @@
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-imagemagick/alfresco-transform-imagemagick</module>
<module>alfresco-transform-imagemagick/alfresco-transform-imagemagick-boot</module>
@ -60,12 +60,14 @@
<profile>
<id>base</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
</modules>
</profile>
<profile>
<id>imagemagick</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-imagemagick/alfresco-transform-imagemagick</module>
<module>alfresco-transform-imagemagick/alfresco-transform-imagemagick-boot</module>
@ -74,6 +76,7 @@
<profile>
<id>libreoffice</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-libreoffice/alfresco-transform-libreoffice</module>
<module>alfresco-transform-libreoffice/alfresco-transform-libreoffice-boot</module>
@ -82,6 +85,7 @@
<profile>
<id>misc</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-misc/alfresco-transform-misc</module>
<module>alfresco-transform-misc/alfresco-transform-misc-boot</module>
@ -90,6 +94,7 @@
<profile>
<id>pdf-renderer</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-pdf-renderer/alfresco-transform-pdf-renderer</module>
<module>alfresco-transform-pdf-renderer/alfresco-transform-pdf-renderer-boot</module>
@ -98,6 +103,7 @@
<profile>
<id>tika</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-tika/alfresco-transform-tika</module>
<module>alfresco-transform-tika/alfresco-transform-tika-boot</module>
@ -107,6 +113,7 @@
<!-- Should only be run after all other snapshot images have been build -->
<id>aio-test</id>
<modules>
<module>alfresco-transform-model</module>
<module>alfresco-transformer-base</module>
<module>alfresco-transform-core-aio/alfresco-transform-core-aio</module>
<module>alfresco-transform-core-aio/alfresco-transform-core-aio-boot</module>
@ -143,11 +150,6 @@
<artifactId>pdfbox-tools</artifactId>
<version>${dependency.pdfbox.version}</version>
</dependency>
<dependency>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-transform-model</artifactId>
<version>${dependency.alfresco-transform-model.version}</version>
</dependency>
<!-- via Data Model / OpenCMIS Client -->
<dependency>
<groupId>org.apache.cxf</groupId>