HXENG-64 refactor ATS (#657)

Refactor to clean up packages in the t-model and to introduce a simpler to implement t-engine base.

The new t-engines (tika, imagemagick, libreoffice, pdfrenderer, misc, aio, aspose) and t-router may be used in combination with older components as the API between the content Repo and between components has not changed. As far as possible the same artifacts are created (the -boot projects no longer exist). They may be used with older ACS repo versions.

The main changes to look for are:
* The introduction of TransformEngine and CustomTransformer interfaces to be implemented.
* The removal in t-engines and t-router of the Controller, Application, test template page, Controller tests and application config, as this is all now done by the t-engine base package.
* The t-router now extends the t-engine base, which also reduced the amount of duplicate code.
* The t-engine base provides the test page, which includes drop downs of known transform options. The t-router is able to use pipeline and failover transformers. This was not possible to do previously as the router had no test UI.
* Resources including licenses are automatically included in the all-in-one t-engine, from the individual t-engines. They just need to be added as dependencies in the pom. 
* The ugly code in the all-in-one t-engine and misc t-engine to pick transformers has gone, as they are now just selected by the transformRegistry.
* The way t-engines respond to http or message queue transform requests has been combined (eliminates the similar but different code that existed before).
* The t-engine base now uses InputStream and OutputStream rather than Files by default. As a result it will be simpler to avoid writing content to a temporary location.
* A number of the Tika and Misc CustomTransforms no longer use Files.
* The original t-engine base still exists so customers can continue to create custom t-engines the way they have done previously. the project has just been moved into a folder called deprecated.
* The folder structure has changed. The long "alfresco-transform-..." names have given way to shorter easier to read and type names.
* The t-engine project structure now has a single project rather than two. 
* The previous config values still exist, but there are now a new set for config values for in files with names that don't misleadingly imply they only contain pipeline of routing information. 
* The concept of 'routing' has much less emphasis in class names as the code just uses the transformRegistry. 
* TransformerConfig may now be read as json or yaml. The restrictions about what could be specified in yaml has gone.
* T-engines and t-router may use transform config from files. Previously it was just the t-router.
* The POC code to do with graphs of possible routes has been removed.
* All master branch changes have been merged in.
* The concept of a single transform request which results in multiple responses (e.g. images from a video) has been added to the core processing of requests in the t-engine base.
* Many SonarCloud linter fixes.
This commit is contained in:
Alan Davis
2022-09-14 13:40:19 +01:00
committed by GitHub
parent ea83ef9ebc
commit babe26b0ba
652 changed files with 19479 additions and 18195 deletions

View File

@@ -0,0 +1,176 @@
/*
* #%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.messages.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
*/
// This class is in the package org.alfresco.transform.messages in HxP because that is more readable, but in
// org.alfresco.transform.client.model in Alfresco for backward compatibility.
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,30 @@
/*
* #%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;
/**
* @deprecated will be removed in a future release when the deprecated alfresco-transform-model is removed.
*/
@Deprecated
public interface Mimetype extends org.alfresco.transform.common.Mimetype
{
}

View File

@@ -0,0 +1,84 @@
/*
* #%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 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
*/
// This class is in the package org.alfresco.transform.messages in HxP because that is more readable, but in
// org.alfresco.transform.client.model in Alfresco for backward compatibility.
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,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.client.model;
import java.io.Serializable;
import java.util.Objects;
// This class is in the package org.alfresco.transform.messages in HxP because that is more readable, but in
// org.alfresco.transform.client.model in Alfresco for backward compatibility.
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,309 @@
/*
* #%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.common.ExtensionService;
import org.alfresco.transform.messages.TransformStack;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static org.alfresco.transform.messages.TransformStack.PIPELINE_FLAG;
import static org.alfresco.transform.messages.TransformStack.levelBuilder;
import static org.alfresco.transform.messages.TransformStack.setInitialTransformRequestOptions;
// This class is in the package org.alfresco.transform.messages in HxP because that is more readable, but in
// org.alfresco.transform.client.model in Alfresco for backward compatibility.
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 +
'}';
}
/**
* Sets up the internal context structure when a client request is initially received by the router,
* so that we don't have to keep checking if bits of it are initialised. Prior to making this call,
* the id, sourceMimetypes, targetMimetype, transformRequestOptions and sourceReference should have
* been set, if they are to be set.
*/
public TransformRequest initialiseContextWhenReceivedByRouter()
{
setInternalContext(InternalContext.initialise(getInternalContext()));
setTargetExtension(ExtensionService.getExtensionForTargetMimetype(getTargetMediaType(),
getSourceMediaType()));
getInternalContext().getMultiStep().setInitialRequestId(getRequestId());
getInternalContext().getMultiStep().setInitialSourceMediaType(getSourceMediaType());
getInternalContext().setTransformRequestOptions(getTransformRequestOptions());
setInitialTransformRequestOptions(getInternalContext(), getTransformRequestOptions());
TransformStack.setInitialSourceReference(getInternalContext(), getSourceReference());
return this;
}
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 Builder withInternalContextForTransformEngineTests()
{
request.initialiseContextWhenReceivedByRouter();
TransformStack.addTransformLevel(request.internalContext, levelBuilder(PIPELINE_FLAG)
.withStep("dummyTransformerName", request.sourceMediaType, request.targetMediaType));
return this;
}
public TransformRequest build()
{
return request;
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* #%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;
/**
* @deprecated will be removed in a future release when the deprecated alfresco-transform-model is removed.
*/
@Deprecated
public interface RequestParamMap extends org.alfresco.transform.common.RequestParamMap
{
}

View File

@@ -0,0 +1,163 @@
/*
* #%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.common;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_DITA;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_EXCEL;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_SVG;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_XWD;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_GRAPHICS;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_PRESENTATION;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_SPREADSHEET;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_TEXT;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENOFFICE1_CALC;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENOFFICE1_IMPRESS;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENOFFICE1_WRITER;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_ADDIN;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_SLIDE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_SLIDE_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_TEMPLATE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_PRESENTATION_TEMPLATE_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_SPREADSHEET;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_BINARY_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_WORDPROCESSING;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_WORDPROCESSING_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_WORD_TEMPLATE;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OPENXML_WORD_TEMPLATE_MACRO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_OUTLOOK_MSG;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_PPT;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_VISIO;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_VISIO_2013;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_WORD;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_WORDPERFECT;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_TEXT_PLAIN;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_XHTML;
import static org.alfresco.transform.common.TransformerDebug.MIMETYPE_METADATA_EMBED;
import static org.alfresco.transform.common.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"),
Map.entry(MIMETYPE_XHTML, "xhtml"),
Map.entry(MIMETYPE_IMAGE_XWD, "xwd")
);
protected ExtensionService()
{
}
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,183 @@
/*
* #%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.common;
/**
* Mimetype
* <p>
* Values from in Alfresco repo org.alfresco.repo.content.MimetypeMap
*/
public interface Mimetype
{
String MIMETYPE_ACP = "application/acp";
String MIMETYPE_APPLEFILE = "application/applefile";
String MIMETYPE_APPLICATION_EPS = "application/eps";
String MIMETYPE_APPLICATION_FLA = "application/x-fla";
String MIMETYPE_APPLICATION_ILLUSTRATOR = "application/illustrator";
String MIMETYPE_APPLICATION_PHOTOSHOP = "image/vnd.adobe.photoshop";
String MIMETYPE_APPLICATION_PS = "application/postscript";
String MIMETYPE_APP_DWG = "application/dwg";
String MIMETYPE_ATOM = "application/atom+xml";
String MIMETYPE_AUDIO_MP4 = "audio/mp4";
String MIMETYPE_BINARY = "application/octet-stream";
String MIMETYPE_DITA = "application/dita+xml";
String MIMETYPE_ENCRYPTED_OFFICE = "application/x-tika-ooxml-protected";
String MIMETYPE_EXCEL = "application/vnd.ms-excel";
String MIMETYPE_FLAC = "audio/x-flac";
String MIMETYPE_FLASH = "application/x-shockwave-flash";
String MIMETYPE_HTML = "text/html";
String MIMETYPE_IMAGE_BMP = "image/bmp";
String MIMETYPE_IMAGE_CGM = "image/cgm";
String MIMETYPE_IMAGE_DWT = "image/x-dwt";
String MIMETYPE_IMAGE_GIF = "image/gif";
String MIMETYPE_IMAGE_IEF = "image/ief";
String MIMETYPE_IMAGE_JP2 = "image/jp2";
String MIMETYPE_IMAGE_JPEG = "image/jpeg";
String MIMETYPE_IMAGE_PBM = "image/x-portable-bitmap";
String MIMETYPE_IMAGE_PGM = "image/x-portable-graymap";
String MIMETYPE_IMAGE_PNG = "image/png";
String MIMETYPE_IMAGE_PNM = "image/x-portable-anymap";
String MIMETYPE_IMAGE_PPJ = "image/vnd.adobe.premiere";
String MIMETYPE_IMAGE_PPM = "image/x-portable-pixmap";
String MIMETYPE_IMAGE_PSD = "image/vnd.adobe.photoshop";
String MIMETYPE_IMAGE_RAS = "image/x-cmu-raster";
String MIMETYPE_IMAGE_RAW_3FR = "image/x-raw-hasselblad";
String MIMETYPE_IMAGE_RAW_ARW = "image/x-raw-sony";
String MIMETYPE_IMAGE_RAW_CR2 = "image/x-raw-canon";
String MIMETYPE_IMAGE_RAW_DNG = "image/x-raw-adobe";
String MIMETYPE_IMAGE_RAW_K25 = "image/x-raw-kodak";
String MIMETYPE_IMAGE_RAW_MRW = "image/x-raw-minolta";
String MIMETYPE_IMAGE_RAW_NEF = "image/x-raw-nikon";
String MIMETYPE_IMAGE_RAW_ORF = "image/x-raw-olympus";
String MIMETYPE_IMAGE_RAW_PEF = "image/x-raw-pentax";
String MIMETYPE_IMAGE_RAW_R3D = "image/x-raw-red";
String MIMETYPE_IMAGE_RAW_RAF = "image/x-raw-fuji";
String MIMETYPE_IMAGE_RAW_RW2 = "image/x-raw-panasonic";
String MIMETYPE_IMAGE_RAW_RWL = "image/x-raw-leica";
String MIMETYPE_IMAGE_RAW_X3F = "image/x-raw-sigma";
String MIMETYPE_IMAGE_RGB = "image/x-rgb";
String MIMETYPE_IMAGE_SVG = "image/svg+xml";
String MIMETYPE_IMAGE_TIFF = "image/tiff";
String MIMETYPE_IMAGE_XBM = "image/x-xbitmap";
String MIMETYPE_IMAGE_XPM = "image/x-xpixmap";
String MIMETYPE_IMAGE_XWD = "image/x-xwindowdump";
String MIMETYPE_IMG_DWG = "image/vnd.dwg";
String MIMETYPE_IWORK_KEYNOTE = "application/vnd.apple.keynote";
String MIMETYPE_IWORK_NUMBERS = "application/vnd.apple.numbers";
String MIMETYPE_IWORK_PAGES = "application/vnd.apple.pages";
String MIMETYPE_JAVASCRIPT = "application/x-javascript";
String MIMETYPE_JSON = "application/json";
String MIMETYPE_METADATA_EMBED = "alfresco-metadata-embed";
String MIMETYPE_METADATA_EXTRACT = "alfresco-metadata-extract";
String MIMETYPE_MP3 = "audio/mpeg";
String MIMETYPE_MULTIPART_ALTERNATIVE = "multipart/alternative";
String MIMETYPE_OGG = "application/ogg";
String MIMETYPE_OPENDOCUMENT_CHART = "application/vnd.oasis.opendocument.chart";
String MIMETYPE_OPENDOCUMENT_CHART_TEMPLATE = "applicationvnd.oasis.opendocument.chart-template";
String MIMETYPE_OPENDOCUMENT_DATABASE = "application/vnd.oasis.opendocument.database";
String MIMETYPE_OPENDOCUMENT_FORMULA = "application/vnd.oasis.opendocument.formula";
String MIMETYPE_OPENDOCUMENT_FORMULA_TEMPLATE = "applicationvnd.oasis.opendocument.formula-template";
String MIMETYPE_OPENDOCUMENT_GRAPHICS = "application/vnd.oasis.opendocument.graphics";
String MIMETYPE_OPENDOCUMENT_GRAPHICS_TEMPLATE = "application/vnd.oasis.opendocument.graphics-template";
String MIMETYPE_OPENDOCUMENT_IMAGE = "application/vnd.oasis.opendocument.image";
String MIMETYPE_OPENDOCUMENT_IMAGE_TEMPLATE = "applicationvnd.oasis.opendocument.image-template";
String MIMETYPE_OPENDOCUMENT_PRESENTATION = "application/vnd.oasis.opendocument.presentation";
String MIMETYPE_OPENDOCUMENT_PRESENTATION_TEMPLATE = "application/vnd.oasis.opendocument.presentation-template";
String MIMETYPE_OPENDOCUMENT_SPREADSHEET = "application/vnd.oasis.opendocument.spreadsheet";
String MIMETYPE_OPENDOCUMENT_SPREADSHEET_TEMPLATE = "application/vnd.oasis.opendocument.spreadsheet-template";
String MIMETYPE_OPENDOCUMENT_TEXT = "application/vnd.oasis.opendocument.text";
String MIMETYPE_OPENDOCUMENT_TEXT_MASTER = "application/vnd.oasis.opendocument.text-master";
String MIMETYPE_OPENDOCUMENT_TEXT_TEMPLATE = "application/vnd.oasis.opendocument.text-template";
String MIMETYPE_OPENDOCUMENT_TEXT_WEB = "application/vnd.oasis.opendocument.text-web";
String MIMETYPE_OPENOFFICE1_CALC = "application/vnd.sun.xml.calc";
String MIMETYPE_OPENOFFICE1_DRAW = "application/vnd.sun.xml.draw";
String MIMETYPE_OPENOFFICE1_IMPRESS = "application/vnd.sun.xml.impress";
String MIMETYPE_OPENOFFICE1_WRITER = "application/vnd.sun.xml.writer";
String MIMETYPE_OPENSEARCH_DESCRIPTION = "application/opensearchdescription+xml";
String MIMETYPE_OPENXML_PRESENTATION = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
String MIMETYPE_OPENXML_PRESENTATION_ADDIN = "application/vnd.ms-powerpoint.addin.macroenabled.12";
String MIMETYPE_OPENXML_PRESENTATION_MACRO = "application/vnd.ms-powerpoint.presentation.macroenabled.12";
String MIMETYPE_OPENXML_PRESENTATION_SLIDE = "application/vnd.openxmlformats-officedocument.presentationml.slide";
String MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW = "application/vnd.openxmlformats-officedocument.presentationml.slideshow";
String MIMETYPE_OPENXML_PRESENTATION_SLIDESHOW_MACRO = "application/vnd.ms-powerpoint.slideshow.macroenabled.12";
String MIMETYPE_OPENXML_PRESENTATION_SLIDE_MACRO = "application/vnd.ms-powerpoint.slide.macroenabled.12";
String MIMETYPE_OPENXML_PRESENTATION_TEMPLATE = "application/vnd.openxmlformats-officedocument.presentationml.template";
String MIMETYPE_OPENXML_PRESENTATION_TEMPLATE_MACRO = "application/vnd.ms-powerpoint.template.macroenabled.12";
String MIMETYPE_OPENXML_SPREADSHEET = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
String MIMETYPE_OPENXML_SPREADSHEET_ADDIN_MACRO = "application/vnd.ms-excel.addin.macroenabled.12";
String MIMETYPE_OPENXML_SPREADSHEET_BINARY_MACRO = "application/vnd.ms-excel.sheet.binary.macroenabled.12";
String MIMETYPE_OPENXML_SPREADSHEET_MACRO = "application/vnd.ms-excel.sheet.macroenabled.12";
String MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE = "application/vnd.openxmlformats-officedocument.spreadsheetml.template";
String MIMETYPE_OPENXML_SPREADSHEET_TEMPLATE_MACRO = "application/vnd.ms-excel.template.macroenabled.12";
String MIMETYPE_OPENXML_WORDPROCESSING = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
String MIMETYPE_OPENXML_WORDPROCESSING_MACRO = "application/vnd.ms-word.document.macroenabled.12";
String MIMETYPE_OPENXML_WORD_TEMPLATE = "application/vnd.openxmlformats-officedocument.wordprocessingml.template";
String MIMETYPE_OPENXML_WORD_TEMPLATE_MACRO = "application/vnd.ms-word.template.macroenabled.12";
String MIMETYPE_OUTLOOK_MSG = "application/vnd.ms-outlook";
String MIMETYPE_PBM = "image/x-portable-bitmap";
String MIMETYPE_PDF = "application/pdf";
String MIMETYPE_PNM = "image/x-portable-anymap";
String MIMETYPE_PPM = "image/x-portable-pixmap";
String MIMETYPE_PPT = "application/vnd.ms-powerpoint";
String MIMETYPE_RFC822 = "message/rfc822";
String MIMETYPE_RSS = "application/rss+xml";
String MIMETYPE_RTF = "application/rtf";
String MIMETYPE_STAROFFICE5_CALC = "application/vnd.stardivision.calc";
String MIMETYPE_STAROFFICE5_CHART = "application/vnd.stardivision.chart";
String MIMETYPE_STAROFFICE5_DRAW = "application/vnd.stardivision.draw";
String MIMETYPE_STAROFFICE5_IMPRESS = "application/vnd.stardivision.impress";
String MIMETYPE_STAROFFICE5_IMPRESS_PACKED = "application/vnd.stardivision.impress-packed";
String MIMETYPE_STAROFFICE5_MATH = "application/vnd.stardivision.math";
String MIMETYPE_STAROFFICE5_WRITER = "application/vnd.stardivision.writer";
String MIMETYPE_STAROFFICE5_WRITER_GLOBAL = "application/vnd.stardivision.writer-global";
String MIMETYPE_STC = "application/vnd.sun.xml.calc.template";
String MIMETYPE_STI = "application/vnd.sun.xml.impress.template";
String MIMETYPE_STW = "application/vnd.sun.xml.writer.template";
String MIMETYPE_SXC = "application/vnd.sun.xml.calc";
String MIMETYPE_SXI = "application/vnd.sun.xml.impress";
String MIMETYPE_TAR = "application/x-tar";
String MIMETYPE_TEXT_CSS = "text/css";
String MIMETYPE_TEXT_CSV = "text/csv";
String MIMETYPE_TEXT_JAVASCRIPT = "text/javascript";
String MIMETYPE_TEXT_MEDIAWIKI = "text/mediawiki";
String MIMETYPE_TEXT_PLAIN = "text/plain";
String MIMETYPE_TSV = "text/tab-separated-values";
String MIMETYPE_VIDEO_3GP = "video/3gpp";
String MIMETYPE_VIDEO_3GP2 = "video/3gpp2";
String MIMETYPE_VIDEO_AVI = "video/x-msvideo";
String MIMETYPE_VIDEO_FLV = "video/x-flv";
String MIMETYPE_VIDEO_MP4 = "video/mp4";
String MIMETYPE_VIDEO_MPG = "video/mpeg";
String MIMETYPE_VIDEO_QUICKTIME = "video/quicktime";
String MIMETYPE_VIDEO_WMV = "video/x-ms-wmv";
String MIMETYPE_VISIO = "application/vnd.visio";
String MIMETYPE_VISIO_2013 = "application/vnd.visio2013";
String MIMETYPE_VORBIS = "audio/vorbis";
String MIMETYPE_WORD = "application/msword";
String MIMETYPE_WORDPERFECT = "application/wordperfect";
String MIMETYPE_XBM = "image/x-xbitmap";
String MIMETYPE_XHTML = "application/xhtml+xml";
String MIMETYPE_XML = "text/xml";
String MIMETYPE_XPM = "image/x-xpixmap";
String MIMETYPE_Z = "application/x-compress";
String MIMETYPE_ZIP = "application/zip";
}

View File

@@ -0,0 +1,152 @@
/*
* #%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.common;
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.
*/
public class RepositoryClientData {
public static final String CLIENT_DATA_SEPARATOR = "\u23D0";
public static final String DEBUG_SEPARATOR = "\u23D1";
public static final String REPO_ID = "Repo";
public static final String DEBUG = "debug:";
private static final String NO_DEBUG = "nodebug:";
private static final int REPO_INDEX = 0;
private static final int RENDITION_INDEX = 2;
private static final int REQUEST_ID_INDEX = 6;
private static final int DEBUG_INDEX = 9;
private static final int EXPECTED_ELEMENTS = 10;
private final String origClientData;
private final String[] split;
public RepositoryClientData(String clientData)
{
origClientData = clientData;
split = clientData == null ? null : clientData.split(CLIENT_DATA_SEPARATOR);
}
private boolean isRepositoryClientData()
{
return split != null && split.length == EXPECTED_ELEMENTS && split[REPO_INDEX].startsWith(REPO_ID);
}
public String getAcsVersion()
{
return isRepositoryClientData() ? split[REPO_INDEX].substring(REPO_ID.length()) : "";
}
public String getRequestId()
{
return isRepositoryClientData() ? split[REQUEST_ID_INDEX] : "";
}
public String getRenditionName() {
return isRepositoryClientData() ? split[RENDITION_INDEX] : "";
}
public void appendDebug(String message) {
if (isDebugRequested())
{
split[DEBUG_INDEX] += DEBUG_SEPARATOR + message;
}
}
public boolean isDebugRequested() {
return isRepositoryClientData() && split[9].startsWith(DEBUG);
}
@Override public String toString() {
if (split == null) {
return origClientData;
}
StringJoiner sj = new StringJoiner(CLIENT_DATA_SEPARATOR);
Stream.of(split).forEach(sj::add);
return sj.toString();
}
public static Builder builder() {
return new Builder();
}
public static class Builder
{
private final RepositoryClientData clientData = new RepositoryClientData(emptyClientData());
private Builder()
{
}
private static String emptyClientData()
{
StringJoiner sj = new StringJoiner(CLIENT_DATA_SEPARATOR);
sj.add(REPO_ID+"ACS1234");
for (int i=0; i<EXPECTED_ELEMENTS-2; i++)
{
sj.add("");
}
sj.add(NO_DEBUG);
return sj.toString();
}
public Builder withRepoId(final String version)
{
clientData.split[REPO_INDEX] = REPO_ID+version;
return this;
}
public Builder withRequestId(final String requestId)
{
clientData.split[REQUEST_ID_INDEX] = requestId;
return this;
}
public Builder withRenditionName(final String renditionName)
{
clientData.split[RENDITION_INDEX] = renditionName;
return this;
}
public Builder withDebug()
{
clientData.split[DEBUG_INDEX]=DEBUG;
return this;
}
public Builder withDebugMessage(final String message)
{
clientData.split[DEBUG_INDEX]=DEBUG+DEBUG_SEPARATOR+message;
return this;
}
public RepositoryClientData build()
{
return clientData;
}
}
}

View File

@@ -0,0 +1,89 @@
/*
* #%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.common;
import org.alfresco.transform.config.CoreVersionDecorator;
/**
* Request parameters and transform options used in the core transformers.
*/
public interface RequestParamMap
{
// html parameter names
String FILE = "file";
String SOURCE_EXTENSION = "sourceExtension";
String TARGET_EXTENSION = "targetExtension";
String SOURCE_MIMETYPE = "sourceMimetype";
String TARGET_MIMETYPE = "targetMimetype";
// 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 TransformController
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_TEST = "/test";
String ENDPOINT_TRANSFORM_CONFIG = "/transform/config";
String ENDPOINT_TRANSFORM_CONFIG_LATEST = ENDPOINT_TRANSFORM_CONFIG + "?" + CONFIG_VERSION + "=" + CONFIG_VERSION_LATEST;
String ENDPOINT_VERSION = "/version";
String ENDPOINT_READY = "/ready";
String ENDPOINT_LIVE = "/live";
String ENDPOINT_ERROR = "/error";
String ENDPOINT_LOG = "/log";
String ENDPOINT_ROOT = "/";
}

View File

@@ -0,0 +1,366 @@
/*
* #%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.common;
import org.alfresco.transform.client.model.InternalContext;
import org.alfresco.transform.client.model.TransformReply;
import org.alfresco.transform.client.model.TransformRequest;
import org.alfresco.transform.messages.TransformStack;
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 final int MAX_OPTION_VALUE = 60;
private static final int MAX_OPTION_END_CHARS = 5;
private static final String MAX_OPTION_DOTS = "...";
private boolean isTRouter = 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 || !isTRouter()
? fileSize(request.getSourceSize()) + ' ' +
getRenditionName(new RepositoryClientData(request.getClientData()).getRenditionName())
: "") +
step.getTransformerName();
if (isDebugToBeReturned(repositoryClientData))
{
repositoryClientData.appendDebug(message);
request.setClientData(repositoryClientData.toString());
}
logger.debug(message);
}
}
/**
* @deprecated Only called from the deprecated transformer base
*/
@Deprecated
public void pushTransform(String reference, String sourceMimetype, String targetMimetype, File sourceFile, String transformerName)
{
final long sourceSizeInBytes = sourceFile.length();
pushTransform(reference, sourceMimetype, targetMimetype, sourceSizeInBytes, transformerName);
}
public void pushTransform(String reference, String sourceMimetype, String targetMimetype, long sourceSizeInBytes, String transformerName)
{
if (logger.isDebugEnabled() && !isTRouter)
{
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);
popTransformWithDebugForEnginesOrAtTopForRouter(reference, elapsedTime);
}
}
public void popTransform(String reference, long elapsedTime)
{
if (logger.isDebugEnabled() && !isTRouter)
{
popTransformWithDebugForEnginesOrAtTopForRouter(reference, elapsedTime);
}
}
private void popTransformWithDebugForEnginesOrAtTopForRouter(String reference, long elapsedTime)
{
String message = getPaddedReference(reference) + "Finished in " + ms(elapsedTime);
if (isTopLevel(reference) || !isTRouter())
{
logger.debug(message);
}
else
{
logger.trace(message);
}
// We don't append the Finished message to ClientData as that would be too much
}
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() && !isTRouter && 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 logFragment(String reference, int index, long size)
{
if (logger.isDebugEnabled())
{
logger.debug("{} fragment[{}]{}", getPaddedReference(reference), index, fileSize(size));
}
}
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 setIsTRouter(boolean isTRouter)
{
this.isTRouter = isTRouter;
return this;
}
private boolean isEnabled(RepositoryClientData repositoryClientData)
{
return logger.isDebugEnabled() || isDebugToBeReturned(repositoryClientData);
}
private boolean isDebugToBeReturned(RepositoryClientData repositoryClientData)
{
return isTRouter() && repositoryClientData.isDebugRequested();
}
private boolean isTRouter()
{
return isTRouter;
}
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,76 @@
/*
* #%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.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 - 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.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,86 @@
/*
* #%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.config;
import org.apache.maven.artifact.versioning.ComparableVersion;
import static org.alfresco.transform.config.CoreFunction.Constants.NO_UPPER_VERSION;
import static org.alfresco.transform.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;
}
public static String standardizeCoreVersion(String version)
{
return newComparableVersion(version, NO_VERSION).toString();
}
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,194 @@
/*
* #%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.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.config.CoreFunction.Constants.NO_VERSION;
import static org.alfresco.transform.config.CoreFunction.newComparableVersion;
import static org.alfresco.transform.common.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 TransformController} 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 new {@link TransformConfig} that includes or excludes the {@code coreVersion} field and
* associated elements like directAccessUrl.
*/
public static TransformConfig setOrClearCoreVersion(TransformConfig transformConfig, int configVersion)
{
boolean includeCoreVersion = configVersion >= 2;
Map<String, Set<TransformOption>> transformOptions = new HashMap<>(transformConfig.getTransformOptions());
transformConfig = 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,
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();
addOrRemoveDirectAccessUrlOption(transformConfig.getTransformOptions(), transformConfig.getTransformers());
return transformConfig;
}
public static void setCoreVersionOnSingleStepTransformers(TransformConfig transformConfig, String coreVersion)
{
List<Transformer> transformers = transformConfig.getTransformers();
transformers.stream()
.filter(CoreVersionDecorator::isSingleStep)
.forEach(transformer -> {
transformer.setCoreVersion(coreVersion);
transformer.setTransformOptions(setOrClearCoreTransformOptions(coreVersion, transformer.getTransformOptions()));
});
addOrRemoveDirectAccessUrlOption(transformConfig.getTransformOptions(), transformers);
}
/**
* 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(),
transformer.getTransformOptions()));
});
addOrRemoveDirectAccessUrlOption(transformOptions, transformers);
}
private static Set<String> setOrClearCoreTransformOptions(String coreVersion, Set<String> transformerTransformOptions)
{
// If we have more options being added in the 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))
{
// Add DIRECT_ACCESS_URL to a copy of this Transformer's transform options.
transformerTransformOptions.add(DIRECT_ACCESS_URL);
}
else
{
transformerTransformOptions.remove(DIRECT_ACCESS_URL);
}
return transformerTransformOptions;
}
private static void addOrRemoveDirectAccessUrlOption(Map<String, Set<TransformOption>> transformOptions,
List<Transformer> transformers)
{
if (transformers.stream()
.anyMatch(transformer -> CoreFunction.DIRECT_ACCESS_URL.isSupported(transformer.getCoreVersion())))
{
transformOptions.put(DIRECT_ACCESS_URL, DIRECT_ACCESS_URL_TRANSFORM_OPTIONS);
}
else
{
transformOptions.remove(DIRECT_ACCESS_URL);
}
}
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 - 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.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 - 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.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 - 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.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 - 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.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,211 @@
/*
* #%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.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 == null ? new HashMap<>() : new HashMap<>(transformOptions);
}
public void setRemoveTransformers(Set<String> removeTransformers)
{
this.removeTransformers = removeTransformers == null ? new HashSet<>() : removeTransformers;
}
public void setAddSupported(Set<AddSupported> addSupported)
{
this.addSupported = addSupported == null ? new HashSet<>() : addSupported;
}
public void setRemoveSupported(Set<RemoveSupported> removeSupported)
{
this.removeSupported = removeSupported == null ? new HashSet<>() : removeSupported;
}
public void setOverrideSupported(Set<OverrideSupported> overrideSupported)
{
this.overrideSupported = overrideSupported == null ? new HashSet<>() : overrideSupported;
}
public void setSupportedDefaults(Set<SupportedDefaults> supportedDefaults)
{
this.supportedDefaults = supportedDefaults == null ? new HashSet<>() : supportedDefaults;
}
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 == null ? new ArrayList<>() : 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.setTransformOptions(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 - 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.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 - 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.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 == null ? new HashSet<>() : 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 - 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.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 - 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.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.config;
import org.alfresco.transform.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 == null ? new ArrayList<>() : transformerPipeline;
}
public List<String> getTransformerFailover()
{
return transformerFailover;
}
public void setTransformerFailover(List<String> transformerFailover)
{
this.transformerFailover = transformerFailover == null ? new ArrayList<>() : transformerFailover;
}
public Set<String> getTransformOptions()
{
return transformOptions;
}
public void setTransformOptions(Set<String> transformOptions)
{
this.transformOptions = transformOptions == null ? new HashSet<>() : transformOptions;
}
public Set<SupportedSourceAndTarget> getSupportedSourceAndTargetList()
{
return supportedSourceAndTargetList;
}
public void setSupportedSourceAndTargetList(
Set<SupportedSourceAndTarget> supportedSourceAndTargetList)
{
this.supportedSourceAndTargetList = supportedSourceAndTargetList == null ? new HashSet<>() : 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.setCoreVersion(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 - 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.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 - 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.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 - 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.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,31 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.config.reader;
import org.alfresco.transform.config.TransformConfig;
import java.io.IOException;
public interface TransformConfigReader
{
TransformConfig load() throws IOException;
}

View File

@@ -0,0 +1,57 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.config.reader;
import static java.util.Objects.isNull;
import org.springframework.core.io.Resource;
public class TransformConfigReaderFactory
{
private TransformConfigReaderFactory()
{
}
public static TransformConfigReader create(final Resource resource)
{
final String fileName = resource.getFilename();
if (isNull(fileName) || !fileName.contains("."))
{
throw new RuntimeException("Invalid configuration file: " + fileName);
}
final String extension = fileName.substring(fileName.lastIndexOf('.') + 1);
switch (extension)
{
case "properties":
throw new UnsupportedOperationException(".properties configuration files are no longer " +
"supported: " + fileName);
case "yaml":
case "yml":
return new TransformConfigReaderYaml(resource);
case "json":
return new TransformConfigReaderJson(resource);
default:
throw new RuntimeException("Unknown configuration file type: " + fileName);
}
}
}

View File

@@ -0,0 +1,47 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.config.reader;
import java.io.IOException;
import org.alfresco.transform.config.TransformConfig;
import org.springframework.core.io.Resource;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TransformConfigReaderJson implements TransformConfigReader
{
private static final ObjectMapper MAPPER= new ObjectMapper();
private final Resource resource;
TransformConfigReaderJson(final Resource resource)
{
this.resource = resource;
}
@Override
public TransformConfig load() throws IOException
{
return MAPPER.readValue(resource.getInputStream(), TransformConfig.class);
}
}

View File

@@ -0,0 +1,48 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.config.reader;
import java.io.IOException;
import org.alfresco.transform.config.TransformConfig;
import org.springframework.core.io.Resource;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
public class TransformConfigReaderYaml implements TransformConfigReader
{
private static final ObjectMapper MAPPER = new ObjectMapper(new YAMLFactory());
private final Resource resource;
TransformConfigReaderYaml(final Resource resource)
{
this.resource = resource;
}
@Override
public TransformConfig load() throws IOException
{
return MAPPER.readValue(resource.getInputStream(), TransformConfig.class);
}
}

View File

@@ -0,0 +1,67 @@
/*
* #%L
* Alfresco Transform Core
* %%
* Copyright (C) 2022 - 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.config.reader;
import org.alfresco.transform.exceptions.TransformException;
import org.alfresco.transform.config.TransformConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import java.io.IOException;
import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
/**
* Reads {@link TransformConfig} from json or yaml files. Typically used by {@code TransformEngine.getTransformConfig()}.
* <pre>
* transformConfigResourceReader.read("classpath:pdfrenderer_engine_config.json");
* </pre>
*/
@Component
public class TransformConfigResourceReader
{
@Autowired ResourceLoader resourceLoader;
public TransformConfig read(String resourcePath)
{
return read(resourceLoader.getResource(resourcePath));
}
public TransformConfig read(Resource resource)
{
try
{
return TransformConfigReaderFactory.create(resource).load();
}
catch (IOException e)
{
throw new TransformException(INTERNAL_SERVER_ERROR, "Could not read " + resource.getFilename(), e);
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* #%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.exceptions;
import org.springframework.http.HttpStatus;
public class TransformException extends RuntimeException
{
private final HttpStatus status;
public TransformException(HttpStatus statusCode, String message)
{
super(message);
this.status = statusCode;
}
public TransformException(HttpStatus status, String message, Throwable cause)
{
super(message, cause);
this.status = status;
}
@Deprecated
public TransformException(int statusCode, String message)
{
this(HttpStatus.valueOf(statusCode), message);
}
@Deprecated
public TransformException(int statusCode, String message, Throwable cause)
{
this(HttpStatus.valueOf(statusCode), message, cause);
}
@Deprecated
public int getStatusCode()
{
return status.value();
}
public HttpStatus getStatus()
{
return status;
}
}

View File

@@ -0,0 +1,94 @@
/*
* #%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.messages;
import org.alfresco.transform.client.model.TransformRequest;
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,564 @@
/*
* #%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.messages;
import org.alfresco.transform.client.model.InternalContext;
import org.alfresco.transform.client.model.MultiStep;
import org.alfresco.transform.client.model.TransformReply;
import org.alfresco.transform.common.TransformerDebug;
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 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";
public 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, String requestCountOrClientRequestId)
{
setHeaderField(internalContext, REFERENCE_INDEX, requestCountOrClientRequestId);
}
public static void incrementReference(InternalContext internalContext)
{
setHeaderField(internalContext, REFERENCE_INDEX, Integer.toString(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)
{
setHeaderField(internalContext, index, Long.toString(value));
}
private static void setHeaderField(InternalContext internalContext, int index, String 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(getHeaderFieldString(levels.get(i), REFERENCE_INDEX));
}
return ref.toString();
}
public static void setReferenceInADummyTopLevelIfUnset(InternalContext internalContext, String reference)
{
if (!reference.isBlank() && 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(getHeaderFieldString(level, index));
}
private static String getHeaderFieldString(String level, int index)
{
return 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])) ||
!aValidReference(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 aValidReference(String string)
{
string = string.startsWith("e") ? string.substring(1) : string;
return aPositiveInt(string);
}
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,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.registry;
import org.alfresco.transform.config.CoreFunction;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.Transformer;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.alfresco.transform.registry.TransformRegistryHelper.lookupTransformOptions;
import static org.alfresco.transform.registry.TransformRegistryHelper.retrieveTransformListBySize;
/**
* 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,910 @@
/*
* #%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.registry;
import org.alfresco.transform.config.AddSupported;
import org.alfresco.transform.config.OverrideSupported;
import org.alfresco.transform.config.RemoveSupported;
import org.alfresco.transform.config.SupportedDefaults;
import org.alfresco.transform.config.SupportedSourceAndTarget;
import org.alfresco.transform.config.TransformConfig;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.TransformStep;
import org.alfresco.transform.config.Transformer;
import org.alfresco.transform.config.TransformerAndTypes;
import org.alfresco.transform.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.TreeSet;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toSet;
import static org.alfresco.transform.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);
removePipelinesWithUnsupportedTransforms(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 both 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;
}
/**
* Removes pipeline transformers if the step transformers cannot be chained together via the intermediary types
* to produce the set of claimed source and target mimetypes.
* @param registry used to log messages
*/
private void removePipelinesWithUnsupportedTransforms(AbstractTransformRegistry registry)
{
Map<String, Transformer> transformersByName = combinedTransformers.stream()
.map(Origin::get)
.collect(Collectors.toMap(Transformer::getTransformerName, Function.identity()));
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();
boolean isPipeline = pipeline != null && !pipeline.isEmpty();
if (isPipeline)
{
if (transformer.getSupportedSourceAndTargetList().isEmpty())
{
throw new IllegalStateException("Transformer " + transformerName(name) +
" has no supported source and target mimetypes, so will be ignored. Read from " + readFrom);
}
List<String> unsupported = new ArrayList<>();
transformer.getSupportedSourceAndTargetList()
.forEach(supportedSourceAndTarget -> {
String nextSource = supportedSourceAndTarget.getSourceMediaType();
for (TransformStep step : pipeline)
{
String source = nextSource;
String target = step.getTargetMediaType() == null
? supportedSourceAndTarget.getTargetMediaType()
: step.getTargetMediaType();
if (transformersByName.get(step.getTransformerName()).getSupportedSourceAndTargetList().stream()
.noneMatch(stepsSupportedSourceAndTarget -> source.equals(stepsSupportedSourceAndTarget.getSourceMediaType())
&& target.equals(stepsSupportedSourceAndTarget.getTargetMediaType())))
{
unsupported.add(supportedSourceAndTarget.getSourceMediaType()+"->"+supportedSourceAndTarget.getTargetMediaType());
break;
}
nextSource = target;
}
});
if (!unsupported.isEmpty())
{
throw new IllegalStateException("Pipeline transformer " + transformerName(name) +
" steps do not support ("+
unsupported.stream()
.sorted()
.collect(Collectors.joining(", "))+
") so will be ignored");
}
}
}
catch (IllegalStateException e)
{
String msg = e.getMessage();
registry.logError(msg);
combinedTransformers.remove(i--);
}
}
}
private void setCoreVersionOnCombinedMultiStepTransformers()
{
setCoreVersionOnMultiStepTransformers(combinedTransformOptions, combinedTransformers.stream()
.map(Origin::get)
.collect(Collectors.toList()));
}
protected int transformerCount()
{
return combinedTransformers.size();
}
public Map<String,Origin<Transformer>> getTransformerByNameMap()
{
return combinedTransformers.stream().collect(Collectors.toMap(origin -> origin.get().getTransformerName(), origin -> origin));
}
}

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.registry;
import org.alfresco.transform.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,55 @@
/*
* #%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.registry;
/**
* 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;
}
}

View File

@@ -0,0 +1,92 @@
/*
* #%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.registry;
import java.util.Objects;
import java.util.Set;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.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,106 @@
/*
* #%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.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 renditionName, final String sourceMimetype,
final List<SupportedTransform> transformListBySize)
{
cachedSupportedTransformList
.get(renditionName)
.put(sourceMimetype, transformListBySize);
}
public List<SupportedTransform> retrieveCached(final String renditionName,
final String sourceMimetype)
{
return cachedSupportedTransformList
.computeIfAbsent(renditionName, 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,364 @@
/*
* #%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.registry;
import org.alfresco.transform.exceptions.TransformException;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.TransformOptionGroup;
import org.alfresco.transform.config.TransformOptionValue;
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 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 org.alfresco.transform.common.RequestParamMap.SOURCE_ENCODING;
import static org.alfresco.transform.common.RequestParamMap.TIMEOUT;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
class TransformRegistryHelper
{
private TransformRegistryHelper()
{
}
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 renditionName)
{
if (actualOptions == null)
{
actualOptions = emptyMap();
}
if (renditionName != null && renditionName.trim().isEmpty())
{
renditionName = null;
}
final List<SupportedTransform> cachedTransformList = renditionName == null
? null
: data.retrieveCached(renditionName, sourceMimetype);
if (cachedTransformList != null)
{
return cachedTransformList;
}
// The transformOptions sometimes contains sourceEncoding and timeout, even though they should not be used
// to select a transformer. Would like to change this, but cannot as we need to support all ACS repo versions.
if (actualOptions.containsKey(SOURCE_ENCODING) || actualOptions.containsKey(TIMEOUT))
{
actualOptions = new HashMap<>(actualOptions);
actualOptions.remove(SOURCE_ENCODING);
actualOptions.remove(TIMEOUT);
}
final List<SupportedTransform> builtTransformList = buildTransformList(data,
sourceMimetype,
targetMimetype,
actualOptions);
if (renditionName != null)
{
data.cache(renditionName, 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(BAD_REQUEST, "Null value provided for sourceMimetype, please provide a value");
}
if (targetMimetype == null)
{
throw new TransformException(BAD_REQUEST, "Null value provided for targetMimetype, 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);
}
}

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.registry;
import org.alfresco.transform.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 - 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.registry;
import org.alfresco.transform.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,55 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.registry;
import org.alfresco.transform.config.Transformer;
public enum TransformerType
{
ENGINE_TRANSFORMER,
PIPELINE_TRANSFORMER,
FAILOVER_TRANSFORMER,
UNSUPPORTED_TRANSFORMER;
public static TransformerType valueOf(Transformer transformer)
{
if (transformer == null)
{
return null;
}
if ((transformer.getTransformerFailover() == null || transformer.getTransformerFailover().isEmpty()) &&
(transformer.getTransformerPipeline() == null || transformer.getTransformerPipeline().isEmpty()))
{
return ENGINE_TRANSFORMER;
}
if (transformer.getTransformerPipeline() != null && !transformer.getTransformerPipeline().isEmpty())
{
return PIPELINE_TRANSFORMER;
}
if (transformer.getTransformerFailover() != null && !transformer.getTransformerFailover().isEmpty())
{
return FAILOVER_TRANSFORMER;
}
return UNSUPPORTED_TRANSFORMER;
}
}

View File

@@ -0,0 +1,33 @@
/*
* #%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;
/**
* @deprecated will be removed in a future release when the deprecated alfresco-transform-model is removed.
*/
@Deprecated
public class ExtensionService extends org.alfresco.transform.common.ExtensionService
{
private ExtensionService()
{
}
}

View File

@@ -0,0 +1,195 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2022 - 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.common;
import org.junit.jupiter.api.Test;
import java.util.StringJoiner;
import static org.alfresco.transform.common.RepositoryClientData.CLIENT_DATA_SEPARATOR;
import static org.alfresco.transform.common.RepositoryClientData.DEBUG;
import static org.alfresco.transform.common.RepositoryClientData.DEBUG_SEPARATOR;
import static org.alfresco.transform.common.RepositoryClientData.REPO_ID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RepositoryClientDataTest
{
RepositoryClientData repositoryClientData;
@Test
void AcsClientDataWithDebugTest()
{
repositoryClientData = RepositoryClientData.builder()
.withRepoId("ACS1234")
.withRenditionName("renditionName")
.withRequestId("e123")
.withDebug()
.build();
String clientData = repositoryClientData.toString();
assertEquals("ACS1234", repositoryClientData.getAcsVersion());
assertEquals("renditionName", repositoryClientData.getRenditionName());
assertEquals("e123", repositoryClientData.getRequestId());
assertTrue(repositoryClientData.isDebugRequested());
assertEquals(clientData, repositoryClientData.toString());
}
@Test
void AcsClientDataWithoutDebugTest()
{
String clientData = new StringJoiner(CLIENT_DATA_SEPARATOR)
.add(REPO_ID + "ACS1234")
.add("1")
.add("renditionName")
.add("3")
.add("4")
.add("5")
.add("54321")
.add("7")
.add("8")
.add("9")
.toString();
repositoryClientData = new RepositoryClientData(clientData);
assertEquals("ACS1234", repositoryClientData.getAcsVersion());
assertEquals("renditionName", repositoryClientData.getRenditionName());
assertEquals("54321", repositoryClientData.getRequestId());
assertFalse(repositoryClientData.isDebugRequested());
assertEquals(clientData, repositoryClientData.toString());
}
@Test
void noLeadingRepoTest()
{
String clientData = new StringJoiner(CLIENT_DATA_SEPARATOR)
.add("ACS1234")
.add("1")
.add("renditionName")
.add("3")
.add("4")
.add("5")
.add("54321")
.add("7")
.add("8")
.add("9")
.toString();
repositoryClientData = new RepositoryClientData(clientData);
assertEquals("", repositoryClientData.getAcsVersion());
assertEquals("", repositoryClientData.getRenditionName());
assertEquals("", repositoryClientData.getRequestId());
assertFalse(repositoryClientData.isDebugRequested());
assertEquals(clientData, repositoryClientData.toString());
}
@Test
void tooFewElementsTest()
{
String clientData = new StringJoiner(CLIENT_DATA_SEPARATOR)
.add(REPO_ID + "ACS1234")
.add("1")
.add("renditionName")
.add("3")
.add("4")
.add("5")
.add("54321")
.add("7")
.add("8")
.toString();
repositoryClientData = new RepositoryClientData(clientData);
assertEquals("", repositoryClientData.getAcsVersion());
assertEquals("", repositoryClientData.getRenditionName());
assertEquals("", repositoryClientData.getRequestId());
assertFalse(repositoryClientData.isDebugRequested());
assertEquals(clientData, repositoryClientData.toString());
}
@Test
void tooManyElementsTest()
{
String clientData = new StringJoiner(CLIENT_DATA_SEPARATOR)
.add(REPO_ID + "ACS1234")
.add("1")
.add("renditionName")
.add("3")
.add("4")
.add("5")
.add("54321")
.add("7")
.add("8")
.add(DEBUG)
.add("10")
.toString();
repositoryClientData = new RepositoryClientData(clientData);
assertEquals("", repositoryClientData.getAcsVersion());
assertEquals("", repositoryClientData.getRenditionName());
assertEquals("", repositoryClientData.getRequestId());
assertFalse(repositoryClientData.isDebugRequested());
assertEquals(clientData, repositoryClientData.toString());
}
@Test
void nullClientDataTest()
{
repositoryClientData = new RepositoryClientData(null);
assertEquals(null, repositoryClientData.toString());
}
@Test
void noElementsClientDataTest()
{
String clientData = "There are no CLIENT_DATA_SEPARATOR chars";
repositoryClientData = new RepositoryClientData(clientData);
assertEquals(clientData, repositoryClientData.toString());
}
@Test
void debugTest()
{
String clientData = new StringJoiner(CLIENT_DATA_SEPARATOR)
.add(REPO_ID + "ACS1234")
.add("1")
.add("2")
.add("3")
.add("4")
.add("5")
.add("6")
.add("7")
.add("8")
.add(DEBUG)
.toString();
repositoryClientData = new RepositoryClientData(clientData);
assertEquals(clientData, repositoryClientData.toString());
repositoryClientData.appendDebug("Some debug");
assertEquals(clientData+DEBUG_SEPARATOR+"Some debug",
repositoryClientData.toString());
repositoryClientData.appendDebug("Some other debug");
assertEquals(clientData+DEBUG_SEPARATOR+"Some debug"+DEBUG_SEPARATOR+"Some other debug",
repositoryClientData.toString());
}
}

View File

@@ -0,0 +1,323 @@
/*
* #%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.common;
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.alfresco.transform.messages.TransformStack;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import java.util.StringJoiner;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_TEXT_PLAIN;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_WORD;
import static org.alfresco.transform.common.RepositoryClientData.DEBUG_SEPARATOR;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* 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 isTRouter, boolean fail, Level logLevel, String renditionName,
long sourceSize)
{
transformerDebug.setIsTRouter(isTRouter);
monitorLogs(logLevel);
TransformRequest request = TransformRequest.builder()
.withSourceSize(sourceSize)
.withInternalContext(InternalContext.initialise(null))
.withClientData(RepositoryClientData.builder().withRenditionName(renditionName).build().toString())
.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(true, false, Level.DEBUG, "", 1234L);
Assertions.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(true, false, Level.TRACE, "", 1234L);
// With trace there are "Finished" lines for nested transforms, like a T-Engine's debug but still without
// the size and rendition name
Assertions.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(false, false, Level.DEBUG, "", 1234L);
// 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).
Assertions.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(true, true, Level.DEBUG, "", 1234L);
Assertions.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 testRenditionName()
{
twoStepTransform(true, false, Level.DEBUG, "renditionName", 1234L);
Assertions.assertEquals("" +
"1 txt pdf 1.2 KB -- renditionName -- 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 testMetadataExtract()
{
twoStepTransform(true, false, Level.DEBUG, "transform:alfresco-metadata-extract", 1234L);
Assertions.assertEquals("" +
"1 txt pdf 1.2 KB -- metadataExtract -- 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 testMetadataEmbed()
{
twoStepTransform(true, false, Level.DEBUG, "transform:alfresco-metadata-embed", 1234L);
Assertions.assertEquals("" +
"1 txt pdf 1.2 KB -- metadataEmbed -- 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 testSourceSize1Byte()
{
twoStepTransform(true, false, Level.DEBUG, "", 1);
Assertions.assertEquals("" +
"1 txt pdf 1 byte 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 testSourceSize23TB()
{
twoStepTransform(true, false, Level.DEBUG, "", 23L*1024*1024*1024*1024);
Assertions.assertEquals("" +
"1 txt pdf 23 TB 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 testLogFailureOnTEngine()
{
monitorLogs(Level.TRACE);
String origClientData = RepositoryClientData.builder().build().toString();
TransformReply reply = TransformReply.builder()
.withInternalContext(InternalContext.initialise(null))
.withErrorDetails("T-Request was null - a major error")
.withClientData(origClientData)
.build();
transformerDebug.setIsTRouter(false);
transformerDebug.logFailure(reply);
String expectedDebug = " T-Request was null - a major error";
Assertions.assertEquals(expectedDebug, getTransformerDebugOutput());
assertEquals(origClientData, reply.getClientData());
}
@Test
void testLogFailureOnTRouter()
{
monitorLogs(Level.TRACE);
String origClientData = RepositoryClientData.builder().withDebug().build().toString();
TransformReply reply = TransformReply.builder()
.withInternalContext(InternalContext.initialise(null))
.withErrorDetails("T-Request was null - a major error")
.withClientData(origClientData)
.build();
transformerDebug.setIsTRouter(true);
transformerDebug.logFailure(reply);
String expectedDebug = " T-Request was null - a major error";
String expectedClientData = origClientData+DEBUG_SEPARATOR+expectedDebug;
Assertions.assertEquals(expectedDebug, getTransformerDebugOutput());
assertEquals(expectedClientData, reply.getClientData());
}
@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

@@ -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.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,226 @@
/*
* #%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.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.common.RequestParamMap.CONFIG_VERSION_DEFAULT;
import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL;
import static org.alfresco.transform.config.CoreFunction.standardizeCoreVersion;
import static org.alfresco.transform.config.CoreVersionDecorator.CONFIG_VERSION_INCLUDES_CORE_VERSION;
import static org.alfresco.transform.config.CoreVersionDecorator.setCoreVersionOnMultiStepTransformers;
import static org.alfresco.transform.config.CoreVersionDecorator.setCoreVersionOnSingleStepTransformers;
import static org.alfresco.transform.config.CoreVersionDecorator.setOrClearCoreVersion;
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));
}
@Test
void standardizeCoreVersionTest()
{
assertEquals("2.5.7", standardizeCoreVersion("2.5.7"));
assertEquals("2.5.7", standardizeCoreVersion("2.5.7-SNAPSHOT"));
assertEquals("2", standardizeCoreVersion("2"));
assertEquals("2.5.7", standardizeCoreVersion("2.5.7-A-SNAPSHOT"));
}
}

View File

@@ -0,0 +1,300 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.config.reader;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
import org.alfresco.transform.config.TransformConfig;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.TransformOptionGroup;
import org.alfresco.transform.config.TransformOptionValue;
import org.alfresco.transform.config.Transformer;
import org.alfresco.transform.config.SupportedSourceAndTarget;
import org.alfresco.transform.config.TransformStep;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
public class TransformConfigReaderJsonTest
{
@Test
public void testEmptyRoutesFile() throws Exception
{
final Resource resource = new ClassPathResource("config/sample1.json");
final TransformConfigReader loader = TransformConfigReaderFactory.create(resource);
TransformConfig transformConfig = loader.load();
final List<Transformer> transformers = transformConfig.getTransformers();
assertNotNull(transformers);
assertEquals(Collections.emptyList(), transformers);
}
@Test
public void testMixedRoutesFile() throws Exception
{
final List<Transformer> expected = prepareSample2();
final Resource resource = new ClassPathResource("config/sample2.json");
final TransformConfigReader loader = TransformConfigReaderFactory.create(resource);
TransformConfig transformConfig = loader.load();
final List<Transformer> transformers = transformConfig.getTransformers();
assertNotNull(transformers);
assertEquals(expected.size(), transformers.size());
assertTrue(expected.containsAll(transformers));
}
private List<Transformer> prepareSample2()
{
return List.of(
Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("image/gif")
.withTargetMediaType("image/gif")
.build()
))
.withTransformOptions(ImmutableSet.of("imageMagickOptions"))
.build(),
Transformer.builder()
.withTransformerName("IMAGEMAGICK")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("image/gif")
.withTargetMediaType("image/gif")
.build()
))
.withTransformOptions(ImmutableSet.of("imageMagickOptions"))
.build(),
Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/msword")
.withTargetMediaType("application/pdf")
.withMaxSourceSizeBytes(18874368L)
.build()
))
.build(),
Transformer.builder()
.withTransformerName("PDF_RENDERER")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/vnd.ms-powerpoint")
.withTargetMediaType("application/pdf")
.withPriority(55)
.withMaxSourceSizeBytes(50331648L)
.build()
))
.build(),
Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/mediawiki")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/css")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/html")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/x-javascript")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/dita+xml")
.withTargetMediaType("text/plain")
.build()
))
.withTransformOptions(ImmutableSet.of("stringOptions"))
.build(),
Transformer.builder()
.withTransformerName("officeToImageViaPdf")
.withTransformerPipeline(ImmutableList.of(
new TransformStep("libreoffice", "application/pdf"),
new TransformStep("pdfToImageViaPng", null)
))
.withTransformOptions(ImmutableSet.of(
"pdfRendererOptions",
"imageMagickOptions"
))
.build(),
Transformer.builder()
.withTransformerName("textToImageViaPdf")
.withTransformerPipeline(ImmutableList.of(
new TransformStep("libreoffice", "application/pdf"),
new TransformStep("pdfToImageViaPng", null)
))
.withTransformOptions(ImmutableSet.of(
"pdfRendererOptions",
"imageMagickOptions"
))
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/gif")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/jpeg")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/tiff")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/png")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/gif")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/jpeg")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/tiff")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/png")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/gif")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/jpeg")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/tiff")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/png")
.build()
))
.withTransformOptions(ImmutableSet.of(
"pdfRendererOptions",
"imageMagickOptions"
))
.build()
);
}
@Test
public void testRouteFileWithTransformOptions() throws Exception
{
final List<Transformer> expected = prepareSample5Transformers();
Map<String, Set<TransformOption>> expectedOptions = prepareSample5Options();
final Resource resource = new ClassPathResource("config/sample5.json");
final TransformConfigReader loader = TransformConfigReaderFactory.create(resource);
TransformConfig transformConfig = loader.load();
final List<Transformer> transformers = transformConfig.getTransformers();
Map<String, Set<TransformOption>> transformOptions = transformConfig.getTransformOptions();
assertNotNull(transformers);
assertEquals(expected.size(), transformers.size());
assertTrue(expected.containsAll(transformers));
assertEquals(expectedOptions, transformOptions);
}
private List<Transformer> prepareSample5Transformers()
{
return List.of(
Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("image/gif")
.withTargetMediaType("image/gif")
.build()
))
.withTransformOptions(ImmutableSet.of("imageMagickOptions"))
.build()
);
}
public static Map<String, Set<TransformOption>> prepareSample5Options()
{
return ImmutableMap.of(
"imageMagickOptions", ImmutableSet.of(
new TransformOptionValue(false, "alphaRemove"),
new TransformOptionValue(false, "autoOrient"),
new TransformOptionValue(false, "startPage"),
new TransformOptionValue(false, "endPage"),
new TransformOptionGroup(false, ImmutableSet.of(
new TransformOptionValue(false, "cropGravity"),
new TransformOptionValue(false, "cropWidth"),
new TransformOptionValue(false, "cropHeight"),
new TransformOptionValue(false, "cropPercentage"),
new TransformOptionValue(false, "cropXOffset"),
new TransformOptionValue(false, "cropYOffset")
)),
new TransformOptionGroup(false, ImmutableSet.of(
new TransformOptionValue(false, "thumbnail"),
new TransformOptionValue(false, "resizeHeight"),
new TransformOptionValue(false, "resizeWidth"),
new TransformOptionValue(false, "resizePercentage"),
new TransformOptionValue(false, "allowEnlargement"),
new TransformOptionValue(false, "maintainAspectRatio")
)))
);
}
}

View File

@@ -0,0 +1,245 @@
/*
* #%L
* Alfresco Transform Model
* %%
* Copyright (C) 2015 - 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.config.reader;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.transform.config.TransformConfig;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.Transformer;
import org.alfresco.transform.config.SupportedSourceAndTarget;
import org.alfresco.transform.config.TransformStep;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
public class TransformConfigReaderYamlTest
{
@Test
public void testEmptyRoutesFile() throws Exception
{
final Resource resource = new ClassPathResource("config/sample3.yaml");
final TransformConfigReader loader = TransformConfigReaderFactory.create(resource);
TransformConfig transformConfig = loader.load();
final List<Transformer> transformers = transformConfig.getTransformers();
assertNotNull(transformers);
assertEquals(Collections.emptyList(), transformers);
}
@Test
public void testMixedRoutesFile() throws Exception
{
final List<Transformer> expected = prepareSample4();
Map<String, Set<TransformOption>> expectedOptions = TransformConfigReaderJsonTest.prepareSample5Options();
final Resource resource = new ClassPathResource("config/sample4.yaml");
final TransformConfigReader loader = TransformConfigReaderFactory.create(resource);
TransformConfig transformConfig = loader.load();
final List<Transformer> transformers = transformConfig.getTransformers();
Map<String, Set<TransformOption>> transformOptions = transformConfig.getTransformOptions();
assertNotNull(transformers);
assertEquals(expected.size(), transformers.size());
assertTrue(expected.containsAll(transformers));
assertEquals(expectedOptions, transformOptions);
}
private List<Transformer> prepareSample4()
{
var result = new ArrayList<Transformer>();
result.add(Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("image/gif")
.withTargetMediaType("image/gif")
.build()
))
.withTransformOptions(ImmutableSet.of("imageMagickOptions"))
.build());
result.add(Transformer.builder()
.withTransformerName("IMAGEMAGICK")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("image/gif")
.withTargetMediaType("image/gif")
.build()
))
.withTransformOptions(ImmutableSet.of("imageMagickOptions"))
.build());
result.add(Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/msword")
.withTargetMediaType("application/pdf")
.withMaxSourceSizeBytes(18874368L)
.build()
))
.build());
result.add(Transformer.builder()
.withTransformerName("PDF_RENDERER")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/vnd.ms-powerpoint")
.withTargetMediaType("application/pdf")
.withPriority(55)
.withMaxSourceSizeBytes(50331648L)
.build()
))
.build());
result.add(Transformer.builder()
.withTransformerName("CORE_AIO")
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/mediawiki")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/css")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/html")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/x-javascript")
.withTargetMediaType("text/plain")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("application/dita+xml")
.withTargetMediaType("text/plain")
.build()
))
.withTransformOptions(ImmutableSet.of("stringOptions"))
.build());
result.add(Transformer.builder()
.withTransformerName("officeToImageViaPdf")
.withTransformerPipeline(ImmutableList.of(
new TransformStep("libreoffice", "application/pdf"),
new TransformStep("pdfToImageViaPng", null)
))
.withTransformOptions(ImmutableSet.of(
"pdfRendererOptions",
"imageMagickOptions"
))
.build());
result.add(Transformer.builder()
.withTransformerName("textToImageViaPdf")
.withTransformerPipeline(ImmutableList.of(
new TransformStep("libreoffice", "application/pdf"),
new TransformStep("pdfToImageViaPng", null)
))
.withTransformOptions(ImmutableSet.of(
"pdfRendererOptions",
"imageMagickOptions"
))
.withSupportedSourceAndTargetList(ImmutableSet.of(
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/gif")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/jpeg")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/tiff")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/plain")
.withTargetMediaType("image/png")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/gif")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/jpeg")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/tiff")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/csv")
.withTargetMediaType("image/png")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/gif")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/jpeg")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/tiff")
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType("text/xml")
.withTargetMediaType("image/png")
.build()
))
.withTransformOptions(ImmutableSet.of(
"pdfRendererOptions",
"imageMagickOptions"
))
.build());
return result;
}
}

View File

@@ -0,0 +1,199 @@
/*
* #%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.messages;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_IMAGE_PNG;
import static org.alfresco.transform.common.Mimetype.MIMETYPE_PDF;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.UUID;
import org.alfresco.transform.client.model.TransformRequest;
import org.junit.jupiter.api.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,566 @@
/*
* #%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.messages;
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.alfresco.transform.common.TransformerDebug;
import org.junit.jupiter.api.Assertions;
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.messages.TransformStack.OPTIONS_LEVEL;
import static org.alfresco.transform.messages.TransformStack.SEPARATOR;
import static org.alfresco.transform.messages.TransformStack.TOP_STACK_LEVEL;
import static org.alfresco.transform.messages.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.assertTrue;
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.initialiseContextWhenReceivedByRouter
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);
}
Assertions.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)
{
Assertions.fail("Appear to be in an infinite loop");
}
} while (!TransformStack.isFinished(internalContext));
Assertions.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);
Assertions.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);
Assertions.assertNull(TransformStack.checkStructure(internalContext, "T-Reply"));
// call a getter just in case we have missed something
TransformStack.currentStep(internalContext);
};
}
@Test
public void testCheckStructureStackLevelsOkWithLeadingE()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
for (String value : Arrays.asList(
"P" + SEPARATOR + "e20" + SEPARATOR + START + SEPARATOR + "1" + STEP))
{
System.out.println("TransformLevel value: " + value);
internalContext.getMultiStep().getTransformsToBeDone().set(TOP_STACK_LEVEL, value);
Assertions.assertNull(TransformStack.checkStructure(internalContext, "T-Reply"));
};
}
@Test
public void testCheckStructureStackLevelsFailWithLeadingX()
{
TransformStack.addTransformLevel(internalContext, TEST_LEVELS.get("top"));
for (String value : Arrays.asList(
"P" + SEPARATOR + "x20" + SEPARATOR + START + SEPARATOR + "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"));
};
}
@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,88 @@
/*
* #%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.registry;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.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 FakeTransformRegistry 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,460 @@
/*
* #%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.registry;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.alfresco.transform.config.AddSupported;
import org.alfresco.transform.config.SupportedDefaults;
import org.alfresco.transform.config.OverrideSupported;
import org.alfresco.transform.config.RemoveSupported;
import org.alfresco.transform.config.SupportedSourceAndTarget;
import org.alfresco.transform.config.TransformConfig;
import org.alfresco.transform.config.Transformer;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.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 FakeTransformRegistry registry = new FakeTransformRegistry();
@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(expectedToString, supportedSourceAndTargetList.toString());
}
}

View File

@@ -0,0 +1,214 @@
/*
* #%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.registry;
import com.google.common.collect.ImmutableMap;
import org.alfresco.transform.exceptions.TransformException;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static org.alfresco.transform.common.RequestParamMap.SOURCE_ENCODING;
import static org.alfresco.transform.common.RequestParamMap.TIMEOUT;
import static org.alfresco.transform.registry.TransformRegistryHelper.retrieveTransformListBySize;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
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)
{
AtomicInteger transformerCount = new AtomicInteger(0);
TransformCache data = new TransformCache();
transformsInLoadOrder.forEach(t->data.appendTransform("text/plain", "application/pdf", t,
"transformer"+transformerCount.getAndIncrement(), 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(expectedTransformerName, transformerName);
assertEquals(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
public void buildTransformListSourceMimeTypeNullErrorTest()
{
TransformCache data = new TransformCache();
assertThrows(TransformException.class, () ->
{
retrieveTransformListBySize(data, null, "application/pdf", null, null);
});
}
@Test
public void buildTransformListTargetMimeTypeNullErrorTest()
{
TransformCache data = new TransformCache();
assertThrows(TransformException.class, () ->
{
retrieveTransformListBySize(data, "text/plain", null, null, null);
});
}
@Test
public void filterTimeoutTest()
{
// Almost identical to buildTransformListTargetMimeTypeNullErrorTest
TransformCache data = new TransformCache();
assertThrows(TransformException.class, () ->
{
retrieveTransformListBySize(data, "text/plain", null,
new HashMap<>(ImmutableMap.of(TIMEOUT, "1234")), null);
});
}
@Test
public void filterSourceEncodingTest()
{
// Almost identical to buildTransformListTargetMimeTypeNullErrorTest
TransformCache data = new TransformCache();
assertThrows(TransformException.class, () ->
{
retrieveTransformListBySize(data, "text/plain", null,
new HashMap<>(ImmutableMap.of(SOURCE_ENCODING, "UTF-8")), null);
});
}
}

View File

@@ -0,0 +1,667 @@
/*
* #%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.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.registry.TransformRegistryHelper.addToPossibleTransformOptions;
import static org.alfresco.transform.registry.TransformRegistryHelper.optionsMatch;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.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 com.google.common.collect.ImmutableList;
import org.alfresco.transform.config.CoreFunction;
import org.alfresco.transform.config.SupportedSourceAndTarget;
import org.alfresco.transform.config.TransformConfig;
import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.TransformOptionGroup;
import org.alfresco.transform.config.TransformOptionValue;
import org.alfresco.transform.config.Transformer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.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 TransformRegistryModelTest
{
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;
@BeforeEach
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(expectedNameSet, possibleTransformOptions.keySet());
possibleTransformOptions.forEach((name, required) -> {
if (required)
{
assertTrue(expectedRequiredSet.contains(name));
}
else
{
assertFalse(expectedRequiredSet.contains(name));
}
});
}
// transformOptionNames are upper case if required.
private void assertOptionsMatch(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(supported);
}
else
{
assertFalse(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(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(supported);
}
else
{
assertFalse(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 testRegistryOptionsMatchMethod()
{
assertOptionsMatch(set("a"), set("a", "B", "c"), "required option B is missing");
assertOptionsMatch(emptySet(), set("a", "B", "c"), "required option B is missing");
assertOptionsMatch(set("B"), set("a", "B", "c"), null);
assertOptionsMatch(set("B", "c"), set("a", "B", "c"), null);
assertOptionsMatch(set("B", "a", "c"), set("a", "B", "c"), null);
assertOptionsMatch(set("B", "d"), set("a", "B", "c"), "there is an extra option d");
assertOptionsMatch(set("B", "c", "d"), set("a", "B", "c"), "there is an extra option d");
assertOptionsMatch(set("d"), set("a", "B", "c"),
"required option B is missing and there is an extra option d");
assertOptionsMatch(set("a"), set("a", "b", "c"), null);
assertOptionsMatch(emptySet(), set("a", "b", "c"), null);
assertOptionsMatch(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 testTransformCacheGetTransforms() // Used in the Alfresco Repo tests
{
TransformConfig transformConfig = TransformConfig.builder()
.withTransformers(ImmutableList.of(new Transformer("transformer1", emptySet(), set(
SupportedSourceAndTarget.builder()
.withSourceMediaType(GIF)
.withTargetMediaType(PDF)
.build(),
SupportedSourceAndTarget.builder()
.withSourceMediaType(GIF)
.withTargetMediaType(JPEG)
.build()))))
.build();
assertEquals(0, registry.getData().getTransforms().size());
assertEquals("", registry.getData().toString());
CombinedTransformConfig.combineAndRegister(transformConfig, "readFrom", "baseUrl", registry);
assertEquals(1, registry.getData().getTransforms().size());
assertEquals(2, registry.getData().getTransforms().get(GIF).size());
assertEquals("(transformers: 1 transforms: 2)", registry.getData().toString());
}
@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);
}
@Test
public void testIsSupportedCoreFunction() 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);
t2.setCoreVersion("1.0");
t3.setCoreVersion("2.5.7");
buildAndPopulateRegistry(new Transformer[] {t1, t2, t3});
assertTrue(registry.isSupported(CoreFunction.HTTP, "transformer1"));
assertTrue(registry.isSupported(CoreFunction.HTTP, "transformer2"));
assertTrue(registry.isSupported(CoreFunction.HTTP, "transformer3"));
assertFalse(registry.isSupported(CoreFunction.ACTIVE_MQ, "transformer1"));
assertTrue(registry.isSupported(CoreFunction.ACTIVE_MQ, "transformer2"));
assertTrue(registry.isSupported(CoreFunction.ACTIVE_MQ, "transformer3"));
assertFalse(registry.isSupported(CoreFunction.DIRECT_ACCESS_URL, "transformer1"));
assertFalse(registry.isSupported(CoreFunction.DIRECT_ACCESS_URL, "transformer2"));
assertTrue(registry.isSupported(CoreFunction.DIRECT_ACCESS_URL, "transformer3"));
}
}

View File

@@ -0,0 +1,3 @@
{
"transformers":[]
}

View File

@@ -0,0 +1,92 @@
{
"transformers": [
{
"transformerName": "CORE_AIO",
"supportedSourceAndTargetList": [
{"sourceMediaType": "image/gif", "targetMediaType": "image/gif" }
],
"transformOptions": [
"imageMagickOptions"
]
},
{
"transformerName": "IMAGEMAGICK",
"supportedSourceAndTargetList": [
{"sourceMediaType": "image/gif", "targetMediaType": "image/gif" }
],
"transformOptions": [
"imageMagickOptions"
]
},
{
"transformerName": "CORE_AIO",
"supportedSourceAndTargetList": [
{"sourceMediaType": "application/msword", "maxSourceSizeBytes": 18874368, "targetMediaType": "application/pdf" }
]
},
{
"transformerName": "PDF_RENDERER",
"supportedSourceAndTargetList": [
{"sourceMediaType": "application/vnd.ms-powerpoint", "maxSourceSizeBytes": 50331648, "priority": 55, "targetMediaType": "application/pdf" }
],
"transformOptions": [
]
},
{
"transformerName": "CORE_AIO",
"supportedSourceAndTargetList": [
{"sourceMediaType": "text/plain", "targetMediaType": "text/plain" },
{"sourceMediaType": "text/mediawiki", "targetMediaType": "text/plain" },
{"sourceMediaType": "text/css", "targetMediaType": "text/plain" },
{"sourceMediaType": "text/csv", "targetMediaType": "text/plain" },
{"sourceMediaType": "text/xml", "targetMediaType": "text/plain" },
{"sourceMediaType": "text/html", "targetMediaType": "text/plain" },
{"sourceMediaType": "application/x-javascript", "targetMediaType": "text/plain" },
{"sourceMediaType": "application/dita+xml", "targetMediaType": "text/plain" }
],
"transformOptions": [
"stringOptions"
]
},
{
"transformerName": "officeToImageViaPdf",
"transformerPipeline" : [
{"transformerName": "libreoffice", "targetMediaType": "application/pdf"},
{"transformerName": "pdfToImageViaPng"}
],
"supportedSourceAndTargetList": [
],
"transformOptions": [
"pdfRendererOptions",
"imageMagickOptions"
]
},
{
"transformerName": "textToImageViaPdf",
"transformerPipeline" : [
{"transformerName": "libreoffice", "targetMediaType": "application/pdf"},
{"transformerName": "pdfToImageViaPng"}
],
"supportedSourceAndTargetList": [
{"sourceMediaType": "text/plain", "targetMediaType": "image/gif" },
{"sourceMediaType": "text/plain", "targetMediaType": "image/jpeg"},
{"sourceMediaType": "text/plain", "targetMediaType": "image/tiff"},
{"sourceMediaType": "text/plain", "targetMediaType": "image/png" },
{"sourceMediaType": "text/csv", "targetMediaType": "image/gif" },
{"sourceMediaType": "text/csv", "targetMediaType": "image/jpeg"},
{"sourceMediaType": "text/csv", "targetMediaType": "image/tiff"},
{"sourceMediaType": "text/csv", "targetMediaType": "image/png" },
{"sourceMediaType": "text/xml", "targetMediaType": "image/gif" },
{"sourceMediaType": "text/xml", "targetMediaType": "image/jpeg"},
{"sourceMediaType": "text/xml", "targetMediaType": "image/tiff"},
{"sourceMediaType": "text/xml", "targetMediaType": "image/png" }
],
"transformOptions": [
"pdfRendererOptions",
"imageMagickOptions"
]
}
]
}

View File

@@ -0,0 +1,2 @@
---
transformers:

View File

@@ -0,0 +1,130 @@
---
transformOptions:
imageMagickOptions:
- value:
name: alphaRemove
- value:
name: autoOrient
- value:
name: startPage
- value:
name: endPage
- group:
transformOptions:
- value:
name: cropGravity
- value:
name: cropWidth
- value:
name: cropHeight
- value:
name: cropPercentage
- value:
name: cropXOffset
- value:
name: cropYOffset
- group:
transformOptions:
- value:
name: thumbnail
- value:
name: resizeHeight
- value:
name: resizeWidth
- value:
name: resizePercentage
- value:
name: allowEnlargement
- value:
name: maintainAspectRatio
transformers:
- transformerName: CORE_AIO
supportedSourceAndTargetList:
- sourceMediaType: image/gif
targetMediaType: image/gif
transformOptions:
- imageMagickOptions
- transformerName: IMAGEMAGICK
supportedSourceAndTargetList:
- sourceMediaType: image/gif
targetMediaType: image/gif
transformOptions:
- imageMagickOptions
- transformerName: PDF_RENDERER
supportedSourceAndTargetList:
- sourceMediaType: application/vnd.ms-powerpoint
targetMediaType: application/pdf
priority: 55
maxSourceSizeBytes: 50331648
transformOptions: []
- transformerName: CORE_AIO
supportedSourceAndTargetList:
- sourceMediaType: application/msword
targetMediaType: application/pdf
maxSourceSizeBytes: 18874368
- transformerName: CORE_AIO
supportedSourceAndTargetList:
- sourceMediaType: text/plain
targetMediaType: text/plain
- sourceMediaType: text/mediawiki
targetMediaType: text/plain
- sourceMediaType: text/css
targetMediaType: text/plain
- sourceMediaType: text/csv
targetMediaType: text/plain
- sourceMediaType: text/xml
targetMediaType: text/plain
- sourceMediaType: text/html
targetMediaType: text/plain
- sourceMediaType: application/x-javascript
targetMediaType: text/plain
- sourceMediaType: application/dita+xml
targetMediaType: text/plain
transformOptions:
- stringOptions
- transformerName: officeToImageViaPdf
transformerPipeline:
- transformerName: libreoffice
targetMediaType: application/pdf
- transformerName: pdfToImageViaPng
transformOptions:
- pdfRendererOptions
- imageMagickOptions
- transformerName: textToImageViaPdf
transformerPipeline:
- transformerName: libreoffice
targetMediaType: application/pdf
- transformerName: pdfToImageViaPng
supportedSourceAndTargetList:
- sourceMediaType: text/plain
targetMediaType: image/gif
- sourceMediaType: text/plain
targetMediaType: image/jpeg
- sourceMediaType: text/plain
targetMediaType: image/tiff
- sourceMediaType: text/plain
targetMediaType: image/png
- sourceMediaType: text/csv
targetMediaType: image/gif
- sourceMediaType: text/csv
targetMediaType: image/jpeg
- sourceMediaType: text/csv
targetMediaType: image/tiff
- sourceMediaType: text/csv
targetMediaType: image/png
- sourceMediaType: text/xml
targetMediaType: image/gif
- sourceMediaType: text/xml
targetMediaType: image/jpeg
- sourceMediaType: text/xml
targetMediaType: image/tiff
- sourceMediaType: text/xml
targetMediaType: image/png
transformOptions:
- pdfRendererOptions
- imageMagickOptions

View File

@@ -0,0 +1,37 @@
{
"transformOptions": {
"imageMagickOptions": [
{"value": {"name": "alphaRemove"}},
{"value": {"name": "autoOrient"}},
{"value": {"name": "startPage"}},
{"value": {"name": "endPage"}},
{"group": {"transformOptions": [
{"value": {"name": "cropGravity"}},
{"value": {"name": "cropWidth"}},
{"value": {"name": "cropHeight"}},
{"value": {"name": "cropPercentage"}},
{"value": {"name": "cropXOffset"}},
{"value": {"name": "cropYOffset"}}
]}},
{"group": {"transformOptions": [
{"value": {"name": "thumbnail"}},
{"value": {"name": "resizeHeight"}},
{"value": {"name": "resizeWidth"}},
{"value": {"name": "resizePercentage"}},
{"value": {"name": "allowEnlargement"}},
{"value": {"name": "maintainAspectRatio"}}
]}}
]
},
"transformers": [
{
"transformerName": "CORE_AIO",
"supportedSourceAndTargetList": [
{"sourceMediaType": "image/gif", "targetMediaType": "image/gif" }
],
"transformOptions": [
"imageMagickOptions"
]
}
]
}

View File

@@ -0,0 +1,22 @@
{
"transformOptions": {
"engineXOptions": [
{"value": {"name": "page"}},
{"value": {"name": "width"}},
{"group": {"transformOptions": [
{"value": {"name": "cropGravity"}}
]}}
]
},
"transformers": [
{
"transformerName": "engineX",
"supportedSourceAndTargetList": [
{"sourceMediaType": "application/pdf", "targetMediaType": "image/png" }
],
"transformOptions": [
"engineXOptions"
]
}
]
}

View File

@@ -0,0 +1,10 @@
{
"transformOptions": {},
"transformers": [
{
"supportedSourceAndTargetList": [
{"sourceMediaType": "application/pdf", "targetMediaType": "image/png" }
]
}
]
}

View File

@@ -0,0 +1,10 @@
{
"transformers": [
{
"transformerName": "engineX",
"supportedSourceAndTargetList": [
{"sourceMediaType": "application/pdf", "targetMediaType": "image/png" }
]
}
]
}

View File

@@ -0,0 +1,26 @@
{
"transformOptions": {
"engineXOptions": [
{"value": {"name": "page"}},
{"value": {"name": "page"}},
{"value": {"name": "width"}},
{"group": {"transformOptions": [
{"value": {"name": "cropGravity"}}
]}}
]
},
"transformers": [
{
"transformerName": "engineX",
"supportedSourceAndTargetList": [
{"sourceMediaType": "application/pdf", "targetMediaType": "image/png" },
{"sourceMediaType": "application/pdf", "targetMediaType": "image/png" },
{"sourceMediaType": "application/pdf", "targetMediaType": "image/png" }
],
"transformOptions": [
"engineXOptions",
"engineXOptions"
]
}
]
}