REPO-4791 If possible, use Local transforms where Legacy transforms are called.

The objective of this PR is to replace all the ContentService calls related to transforms with calls to the SynchronousTransformClient that switches between Local and Legacy transforms. This will allow us to remove the Legacy transforms at some point and also take advantage of the T-Engines which can be scaled.

- Introduced a ContentTransformService interface to hold all the ContentService calls related to transforms and an implementation class ContentTransformServiceAdaptor as a super class of ContentServiceImpl. The Adaptor provides code to allow the SynchronousTransformClient to be called. As a result any external custom code that uses these methods will be able to take advantage of Local or Legacy transforms.
- The transform code originally in ContentServiceImpl has been moved to ContentTransformServiceImpl, which is a super class of the LegacySynchronousTransformClient.
- All calls in the repository have been replaced with calls to SynchronousTransformClient. There are still a few calls to these methods in ContentServiceImplTest and ArchiveContentTransformerTest that have not been changes as they test Legacy transform code.
- The asynchronous LocalTransformClient now uses the SynchronousTransformClient.
- Bug fix to the TransformationOptionsConverter as it was found that the page number being used for images was wrong. Legacy transformers start at page 1, where as Local and ImageMagick itself start at 0.
- Added a conversion to the new transform option (Map<String,String) in TransformationOptionsConverter, as some of the ContentTransformServiceAdaptor methods need to wrap a Local transformer in a Legacy transformer, so that it may be returned.
- A number of existing tests were passing because the wrong exception was being thrown. When the Local  transformer was used this became more obvious as the tests would fail.
- To avoid other test failing the Local Transform registry is loaded on start up rather than waiting for it to be scheduled, as a few milliseconds needed to read the config was till too large for the test to run and fail. A similar approach had been taken before with tests.
This commit is contained in:
alandavis
2019-11-29 22:13:43 +00:00
committed by GitHub
parent da62a5de31
commit 2af1a96211
66 changed files with 1734 additions and 1388 deletions

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2019 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -26,22 +26,11 @@
package org.alfresco.repo.thumbnail;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel;
import org.alfresco.repo.action.evaluator.NoConditionEvaluator;
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.AbstractContentTransformer2;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.content.transform.magick.ImageResizeOptions;
@@ -51,6 +40,8 @@ import org.alfresco.repo.domain.dialect.Oracle9Dialect;
import org.alfresco.repo.domain.dialect.SQLServerDialect;
import org.alfresco.repo.jscript.ClasspathScriptLocation;
import org.alfresco.repo.model.Repository;
import org.alfresco.repo.rendition2.SynchronousTransformClient;
import org.alfresco.repo.rendition2.TransformationOptionsConverter;
import org.alfresco.repo.thumbnail.script.ScriptThumbnailService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -64,7 +55,6 @@ import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentServiceTransientException;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -88,8 +78,10 @@ import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.transform.client.registry.TransformServiceRegistry;
import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -105,6 +97,16 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.transaction.TestTransaction;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Thumbnail service implementation unit test
*
@@ -135,6 +137,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
private PermissionService permissionService;
private LockService lockService;
private CopyService copyService;
private SynchronousTransformClient synchronousTransformClient;
private TransformationOptionsConverter converter;
private NodeRef folder;
private static final String TEST_FAILING_MIME_TYPE = "application/vnd.alfresco.test.transientfailure";
@@ -161,6 +165,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
this.permissionService = (PermissionService) applicationContext.getBean("PermissionService");
this.lockService = (LockService) applicationContext.getBean("lockService");
this.copyService = (CopyService) applicationContext.getBean("CopyService");
synchronousTransformClient = (SynchronousTransformClient) applicationContext.getBean("synchronousTransformClient");
converter = (TransformationOptionsConverter) applicationContext.getBean("transformOptionsConverter");
// Create a folder and some content
Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>(1);
@@ -172,13 +178,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
private void checkTransformer()
{
ContentTransformer transformer = this.contentService.getImageTransformer();
assertNotNull("No transformer returned for 'getImageTransformer'", transformer);
// Check that it is working
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
if (!transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, -1, MimetypeMap.MIMETYPE_IMAGE_JPEG,
imageTransformationOptions))
if (!synchronousTransformClient.isSupported(MimetypeMap.MIMETYPE_IMAGE_JPEG, -1, null,
MimetypeMap.MIMETYPE_IMAGE_JPEG, Collections.emptyMap(), null, null))
{
fail("Image transformer is not working. Please check your image conversion command setup.");
}
@@ -551,10 +552,17 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
final NodeRef testNode = this.secureNodeService.createNode(folder, ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "transientThumbnail.transientThumbnail"),
ContentModel.TYPE_CONTENT, props).getChildRef();
// Modified test to add content. Having no content was failing to find a transformer with Legacy, but now
// does not with both Legacy and Local transforms. As a result the test was passing for the wrong reason.
secureNodeService.setProperty(testNode, ContentModel.PROP_CONTENT,
new ContentData(null, TEST_FAILING_MIME_TYPE, 0L, null));
// We don't need to write any content into this node, as our test transformer will fail immediately.
File testFile = AbstractContentTransformerTest.loadNamedQuickTestFile("quick.pdf");
assertNotNull("Failed to load required test file.", testFile);
ContentWriter writer = contentService.getWriter(testNode, ContentModel.PROP_CONTENT, true);
writer.setMimetype(TEST_FAILING_MIME_TYPE);
writer.setEncoding("UTF-8");
writer.putContent(testFile);
logger.debug("Running failing thumbnail on " + testNode);
@@ -1118,10 +1126,12 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
{
NodeRef nodeRef = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_HTML);
ThumbnailDefinition def = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("medium");
ContentTransformer transformer = this.contentService.getTransformer(null, MimetypeMap.MIMETYPE_HTML, -1, def
.getMimetype(), def.getTransformationOptions());
if (transformer != null)
TransformationOptions transformationOptions = def.getTransformationOptions();
Map<String, String> options = converter.getOptions(transformationOptions);
String targetMimetype = def.getMimetype();
boolean supported = synchronousTransformClient.isSupported(MimetypeMap.MIMETYPE_HTML, -1, null,
targetMimetype, options, null, null);
if (supported)
{
NodeRef thumb = this.thumbnailService.createThumbnail(nodeRef, ContentModel.PROP_CONTENT,
def.getMimetype(), def.getTransformationOptions(), def.getName());
@@ -1133,7 +1143,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
}
def = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("webpreview");
if (transformer != null)
if (supported)
{
NodeRef thumb = this.thumbnailService.createThumbnail(nodeRef, ContentModel.PROP_CONTENT,
def.getMimetype(), def.getTransformationOptions(), def.getName());
@@ -1183,27 +1193,8 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
checkRendition("smallPng", thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "png", "smallPng - 64x64, marked as thumbnail");
// Create thumbnail - different content property
// TODO
// Create thumbnail - different command options
// We'll pass illegal command options to ImageMagick in order to trigger an exception
Exception x = null;
try
{
imageTransformationOptions.setCommandOptions("-noSuchOption");
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallCO");
} catch (ContentIOException ciox)
{
x = ciox;
ciox.printStackTrace();
}
assertNotNull("Expected exception from ImageMagick due to invalid option", x);
// Reset the command options
imageTransformationOptions.setCommandOptions("");
// Removd code: We now automatically discard all extra command options for security reasons.
// Create thumbnail - different target assoc details
ThumbnailParentAssociationDetails tpad
= new ThumbnailParentAssociationDetails(otherFolder,
@@ -1224,7 +1215,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, null);
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig,
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, null, 5)));
Collections.singletonList(new ExpectedAssoc(RegexQNamePattern.MATCH_ALL, null, 4)));
checkRendition(null, thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "png", "'null' - 64x64, marked as thumbnail");
}
@@ -1304,11 +1295,6 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
try
{
// Reset our transformer count for each test
LongRunningTransformer transformer = (LongRunningTransformer) contentService
.getTransformer(TEST_LONG_RUNNING_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG);
transformer.setTransformCount(0);
Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
props.put(ContentModel.PROP_NAME, "original.test");
final NodeRef source = secureNodeService.createNode(folder, ContentModel.ASSOC_CONTAINS,
@@ -1500,68 +1486,90 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
performLongRunningThumbnailTest(expectedThumbnails, expectedAssocs, new EmptyLongRunningConcurrentWork(), 1, 1);
}
/**
* Test transformer.
*
* @since 4.0.1
*/
private static class TransientFailTransformer extends AbstractContentTransformer2
public static class TestTransformServiceRegistry implements TransformServiceRegistry
{
public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options)
private TransformServiceRegistry delegate;
public TestTransformServiceRegistry(TransformServiceRegistry delegate)
{
return sourceMimetype.equals(MimetypeMap.MIMETYPE_PDF) && targetMimetype.equals(TEST_FAILING_MIME_TYPE);
this.delegate = delegate;
}
protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception
@Override
public boolean isSupported(String sourceMimetype, long sourceSizeInBytes, String targetMimetype,
Map<String, String> actualOptions, String transformName)
{
// fail every time.
throw new ContentServiceTransientException("Transformation intentionally failed for test purposes.");
return sourceMimetype.equals(TEST_FAILING_MIME_TYPE) || sourceMimetype.equals(TEST_LONG_RUNNING_MIME_TYPE)
? true
: delegate.isSupported(sourceMimetype, sourceSizeInBytes, targetMimetype, actualOptions, transformName);
}
@Override
public long findMaxSize(String sourceMimetype, String targetMimetype, Map<String, String> actualOptions, String transformName)
{
return sourceMimetype.equals(TEST_FAILING_MIME_TYPE) || sourceMimetype.equals(TEST_LONG_RUNNING_MIME_TYPE)
? -1
: delegate.findMaxSize(sourceMimetype, targetMimetype, actualOptions, transformName);
}
@Override
public String findTransformerName(String sourceMimetype, long sourceSizeInBytes,
String targetMimetype, Map<String, String> actualOptions, String renditionName)
{
throw new UnsupportedOperationException("not implemented");
}
}
/**
* Bogus transformer that simulates a somewhat longer running transformation
*/
private static class LongRunningTransformer extends AbstractContentTransformer2
public static class TestSynchronousTransformClient<T> implements SynchronousTransformClient
{
private int transformCount = 0;
private SynchronousTransformClient delegate;
@Override
public void register()
public TestSynchronousTransformClient(SynchronousTransformClient delegate)
{
super.register();
setStrictMimeTypeCheck(false);
}
public void setTransformCount(int transformCount)
{
this.transformCount = transformCount;
}
public int getTransformCount()
{
return transformCount;
}
@Override
public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options)
{
return sourceMimetype.equals(TEST_LONG_RUNNING_MIME_TYPE) &&
(targetMimetype.equals(MimetypeMap.MIMETYPE_IMAGE_JPEG) ||
targetMimetype.equals(MimetypeMap.MIMETYPE_IMAGE_PNG));
this.delegate = delegate;
}
@Override
protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options)
throws Exception
public boolean isSupported(String sourceMimetype, long sourceSizeInBytes, String contentUrl, String targetMimetype,
Map<String, String> actualOptions, String transformName, NodeRef sourceNodeRef)
{
Thread.sleep(TEST_LONG_RUNNING_TRANSFORM_TIME);
writer.putContent("SUCCESS");
transformCount++;
boolean supported = true;
if (!sourceMimetype.equals(TEST_FAILING_MIME_TYPE) && !sourceMimetype.equals(TEST_LONG_RUNNING_MIME_TYPE))
{
supported = delegate.isSupported(sourceMimetype, sourceSizeInBytes, contentUrl, targetMimetype, actualOptions,
transformName, sourceNodeRef);
}
return supported;
}
@Override
public void transform(ContentReader reader, ContentWriter writer, Map<String, String> actualOptions, String transformName, NodeRef sourceNodeRef)
{
String sourceMimetype = reader.getMimetype();
if (sourceMimetype.equals(TEST_FAILING_MIME_TYPE))
{
throw new ContentServiceTransientException("Transformation intentionally failed for test purposes.");
}
else if (sourceMimetype.equals(TEST_LONG_RUNNING_MIME_TYPE))
{
try
{
Thread.sleep(TEST_LONG_RUNNING_TRANSFORM_TIME);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
writer.putContent("SUCCESS");
}
else
{
delegate.transform(reader, writer, actualOptions, transformName, sourceNodeRef);
}
}
}
/**
* Defines the work to be done while long running transformations are being performed
* and the means to verify that work completed successfully.