alfresco-community-repo/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java
Dave Ward c3a622e3c4 Merged V4.0-BUG-FIX to HEAD
33836: Fix for ALF-10651 Fix patches that trigger reindexing and ALF-10656 SOLR: Patches execute search during bootstrap causing deadlock
   33842: Fixes ALF-12797: i18n strings in activiti-admin login-screen escaped properly
   33844: Fix for ALF-10651 Fix patches that trigger reindexing and ALF-10656 SOLR: Patches execute search during bootstrap causing deadlock
   - batch touch to limit the in clause size generated
   33845: Manually added extra core Share extensions needed for the V4.0 Records Management module from the development branch.
   - Refactored JSON property decorators for the Document Library data webscripts
   - Document List banners (e.g. working copy) moved into metadata template config
   - Ability to override default document/folder title within Document Library (<title> element in metadata template - unused in core code)
   - Additional extension point in surf-doclist to override remote data URL
   - Better handling for missing content property
   33852: ALF-12725: Merged V3.4-BUG-FIX (3.4.9) to V4.0-BUG-FIX (4.0.1)
      33849: Merged V3 (3.4.8) to V3.4-BUG-FIX (3.4.9)
         33848: ALF-10976 (relates to ALF-10412)
            Fixed bug to do with preview being stuck as always being 'Content cannot be previewed.
            Do you wish to download?' or a 'blank preview after a transformer is not found' for all
            content with the same mimetype. Cache in ThumbnailRegistory.getThumbnailDefinitions()
            now understands that transformers may have an upper content size limit. The choice between
            the two options was based on the size of the first file previewed of each mimetype.
            Needed to add getMaxSourceSizeBytes() to support this (see below).
            - refactored (previous refactor was incomplete) ContentTransformer so that
              the two halfs of isTransformable is now split into sub methods
              isTransformableMimetypes and isTransformableSize.
              This is why there are so many files changed.
            - Moved getMaxSourceSizeBytes() from AbstractContentTransformerLimits to ContentTransformer as
              there were becomming too many places in the code that needed needed to check if the ContentTransformer
              was an instanceof AbstractContentTransformerLimits before calling this method.
            - TransformerDebug now uses KB MB GB values in output to make it simpler to read.
            - TransformerDebug now uses thousand separaters in millisecond values to make it simpler to read.
            - TransformerDebug now reports the 'parent' transformer name rather than the sub-transformer name 
              when an unavailable transformer is found. Makes it simpler to tie up with the 'available transformer'
              list with the new pushIsTransformableSize() calls.
            - TransformerDebug now uses trace logging for calls from ThumbnailRegistory.isThumbnailDefinitionAvailable()
              as it is normally followed by a ContentService.transform() which is logged at debug level anyway.
            - TransformerDebug now turns logging level to trace if the file size is 0 bytes. Request from Jan.
              Not sure how one uploads such a file!
            - Modified ComplexContentTransformer.isTransformable() so that it checks the mimetypes before the sizes
              so that TransformerDebug does not report 'unavailable transformers' that don't support the
              mimetype conversion.
            - Modified ComplexContentTransformer.getLimits and ComplexContentTransformer.isPageLimitSupported()
              to include the limits from the first sub transformer.
              Was not an issue until ContentTransformer.getMaxSourceSizeBytes() was introduced.
            - Added logger to RhinoScriptProcessor to debug requests run javascript on the server.
            - Dropped the sourceUrl parameter from ThumbnailRegistry.getThumbnailDefinitions() which was
              introduced with limits as it is logicall not needed.
   33853: DiskInterface.renameFile() can now throw PermissionDeniedException to return a different status to the client. Part of ALF-12717.
   33856: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      33835: ALF-12546: Remove references to retired RegPaths.exe from installed apply_amps.bat script
      33843: Fix for ALF-12775
      33855: Merged V3.4 to V3.4-BUG-FIX
         33851: ALF-12588: Documents Intermittently Do Not Appear in Share
            - Fix by Alex Busel for regression I accidentally caused in 3.4.6
            - Simple typo in mergeDeletions() caused path deletions to sometimes not get applied or get processed twice
            - Yikes!


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@33857 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-02-13 12:24:24 +00:00

805 lines
40 KiB
Java

/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.repo.thumbnail;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
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.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;
import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
import org.alfresco.repo.jscript.ClasspathScriptLocation;
import org.alfresco.repo.thumbnail.script.ScriptThumbnailService;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
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;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.ScriptLocation;
import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.service.cmr.thumbnail.FailedThumbnailInfo;
import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails;
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.TempFileProvider;
/**
* Thumbnail service implementation unit test
*
* @author Roy Wetherall
* @author Neil McErlean
*/
public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest
{
private RenditionService renditionService;
private ThumbnailService thumbnailService;
private ScriptThumbnailService scriptThumbnailService;
private ScriptService scriptService;
private MimetypeMap mimetypeMap;
private RetryingTransactionHelper transactionHelper;
private ServiceRegistry services;
private NodeRef folder;
private static final String TEST_FAILING_MIME_TYPE = "application/vnd.alfresco.test.transientfailure";
/**
* Called during the transaction setup
*/
@SuppressWarnings("deprecation")
@Override
protected void onSetUpInTransaction() throws Exception
{
super.onSetUpInTransaction();
// Get the required services
this.renditionService = (RenditionService) this.applicationContext.getBean("RenditionService");
this.thumbnailService = (ThumbnailService) this.applicationContext.getBean("ThumbnailService");
this.scriptThumbnailService = (ScriptThumbnailService) this.applicationContext.getBean("thumbnailServiceScript");
this.mimetypeMap = (MimetypeMap) this.applicationContext.getBean("mimetypeService");
this.scriptService = (ScriptService) this.applicationContext.getBean("ScriptService");
this.services = (ServiceRegistry) this.applicationContext.getBean("ServiceRegistry");
this.transactionHelper = (RetryingTransactionHelper) this.applicationContext.getBean("retryingTransactionHelper");
// Create a folder and some content
Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>(1);
folderProps.put(ContentModel.PROP_NAME, "testFolder");
this.folder = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), ContentModel.TYPE_FOLDER)
.getChildRef();
}
@Override protected String[] getConfigLocations()
{
List<String> configLocations = new ArrayList<String>();
for (String config : ApplicationContextHelper.CONFIG_LOCATIONS)
{
configLocations.add(config);
}
configLocations.add("classpath*:org.alfresco.repo.thumbnail.test-thumbnail-context.xml");
return configLocations.toArray(new String[0]);
}
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))
{
fail("Image transformer is not working. Please check your image conversion command setup.");
}
}
public void testCreateRenditionThumbnailFromImage() throws Exception
{
QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition(
qname.getLocalName());
assertEquals("doclib", details.getName());
assertEquals("image/png", details.getMimetype());
assertEquals("alfresco/thumbnail/thumbnail_placeholder_doclib.png", details.getPlaceHolderResourcePath());
checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib");
assertNotNull(thumbnail0);
checkRenditioned(jpgOrig, "doclib");
checkRendition("doclib", thumbnail0);
outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test");
}
public void testCreateRenditionThumbnailFromPdf() throws Exception
{
QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition(
qname.getLocalName());
assertEquals("doclib", details.getName());
assertEquals("image/png", details.getMimetype());
assertEquals("alfresco/thumbnail/thumbnail_placeholder_doclib.png", details.getPlaceHolderResourcePath());
checkTransformer();
NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF);
NodeRef thumbnail0 = this.thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib");
assertNotNull(thumbnail0);
checkRenditioned(pdfOrig, "doclib");
checkRendition("doclib", thumbnail0);
outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test");
}
public void testCreateThumbnailFromImage() throws Exception
{
checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
NodeRef gifOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF);
// ===== small: 64x64, marked as thumbnail ====
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
// ThumbnailDetails createOptions = new ThumbnailDetails();
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, "small");
checkRendition("small", thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "jpg", "small - 64x64, marked as thumbnail");
// ===== small2: 64x64, aspect not maintained ====
ImageResizeOptions imageResizeOptions2 = new ImageResizeOptions();
imageResizeOptions2.setWidth(64);
imageResizeOptions2.setHeight(64);
imageResizeOptions2.setMaintainAspectRatio(false);
ImageTransformationOptions imageTransformationOptions2 = new ImageTransformationOptions();
imageTransformationOptions2.setResizeOptions(imageResizeOptions2);
// ThumbnailDetails createOptions2 = new ThumbnailDetails();
NodeRef thumbnail2 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions2, "small2");
checkRenditioned(jpgOrig, "small2");
checkRendition("small2", thumbnail2);
outputThumbnailTempContentLocation(thumbnail2, "jpg", "small2 - 64x64, aspect not maintained");
// ===== half: 50%x50 =====
ImageResizeOptions imageResizeOptions3 = new ImageResizeOptions();
imageResizeOptions3.setWidth(50);
imageResizeOptions3.setHeight(50);
imageResizeOptions3.setPercentResize(true);
ImageTransformationOptions imageTransformationOptions3 = new ImageTransformationOptions();
imageTransformationOptions3.setResizeOptions(imageResizeOptions3);
// ThumbnailDetails createOptions3 = new ThumbnailDetails();
NodeRef thumbnail3 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions3, "half");
checkRenditioned(jpgOrig, "half");
checkRendition("half", thumbnail3);
outputThumbnailTempContentLocation(thumbnail3, "jpg", "half - 50%x50%");
// ===== half2: 50%x50 from gif =====
ImageResizeOptions imageResizeOptions4 = new ImageResizeOptions();
imageResizeOptions4.setWidth(50);
imageResizeOptions4.setHeight(50);
imageResizeOptions4.setPercentResize(true);
ImageTransformationOptions imageTransformationOptions4 = new ImageTransformationOptions();
imageTransformationOptions4.setResizeOptions(imageResizeOptions4);
// ThumbnailDetails createOptions4 = new ThumbnailDetails();
NodeRef thumbnail4 = this.thumbnailService.createThumbnail(gifOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions4, "half2");
checkRenditioned(gifOrig, "half2");
checkRendition("half2", thumbnail4);
outputThumbnailTempContentLocation(thumbnail4, "jpg", "half2 - 50%x50%, from gif");
}
public void testDuplicationNames() throws Exception
{
checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
// ThumbnailDetails createOptions = new ThumbnailDetails();
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, "small");
checkRendition("small", thumbnail1);
// the origional thumbnail is returned if we are attempting to create a duplicate
NodeRef duplicate = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG,
imageTransformationOptions, "small");
assertNotNull(duplicate);
assertEquals(duplicate, thumbnail1);
}
/**
* @since 3.5.0
*/
public void testCreateFailingThumbnail() throws Exception
{
final NodeRef corruptNode = this.createCorruptedContent(folder);
logger.debug("Running failing thumbnail on " + corruptNode);
// Make sure the source node is correctly set up before we start
// It should not be renditioned and should not be marked as having any failed thumbnails.
assertFalse(nodeService.hasAspect(corruptNode, RenditionModel.ASPECT_RENDITIONED));
assertFalse(nodeService.hasAspect(corruptNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE));
setComplete();
endTransaction();
// Attempt to perform a thumbnail that we know will fail.
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry().getThumbnailDefinition("doclib");
Action createThumbnailAction = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, services);
actionService.executeAction(createThumbnailAction, corruptNode, true, true);
return null;
}
});
// The thumbnail attempt has now failed. But a compensating action should have been scheduled that will mark the
// source node with a failure aspect. As that is an asynchronous action, we need to wait for that to complete.
Thread.sleep(3000); // This should be long enough for the compensating action to run.
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
assertFalse("corrupt node should not have renditioned aspect", nodeService.hasAspect(corruptNode, RenditionModel.ASPECT_RENDITIONED));
assertTrue("corrupt node should have failed thumbnails aspect", nodeService.hasAspect(corruptNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE));
Map<String, FailedThumbnailInfo> failedThumbnails = thumbnailService.getFailedThumbnails(corruptNode);
assertEquals("Wrong number of failed thumbnails", 1, failedThumbnails.size());
assertTrue("Missing QName for failed thumbnail", failedThumbnails.containsKey("doclib"));
final FailedThumbnailInfo doclibFailureInfo = failedThumbnails.get("doclib");
assertNotNull("Failure info was null", doclibFailureInfo);
assertEquals("Failure count was wrong.", 1, doclibFailureInfo.getFailureCount());
assertEquals("thumbnail name was wrong.", "doclib", doclibFailureInfo.getThumbnailDefinitionName());
return null;
}
});
// If you uncomment this line and set the timeout to a value greater than ${system.thumbnail.minimum.retry.period} * 1000.
// Then the retry period will have passed, the below re-thumbnail attempt will be made and the test will fail with a
// failureCount == 2.
//
// Thread.sleep(150 * 1000);
// Run the thumbnail again. It should not run because the action condition should prevent it.
// We can check that it does not run by ensuring the failureCount does not change.
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry().getThumbnailDefinition("doclib");
Action createThumbnailAction = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, services);
actionService.executeAction(createThumbnailAction, corruptNode, true, true);
return null;
}
});
// Pause to let the async action be considered for running (but not run).
Thread.sleep(3000);
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
Map<String, FailedThumbnailInfo> failedThumbnails = thumbnailService.getFailedThumbnails(corruptNode);
assertEquals("Wrong number of failed thumbnails", 1, failedThumbnails.size());
assertTrue("Missing QName for failed thumbnail", failedThumbnails.containsKey("doclib"));
final FailedThumbnailInfo doclibFailureInfo = failedThumbnails.get("doclib");
assertNotNull("Failure info was null", doclibFailureInfo);
assertEquals("Failure count was wrong.", 1, doclibFailureInfo.getFailureCount());
assertEquals("thumbnail name was wrong.", "doclib", doclibFailureInfo.getThumbnailDefinitionName());
return null;
}
});
}
/**
* From 4.0.1 we support 'transient' thumbnail failure. This occurs when the {@link ContentTransformer}
* cannot attempt to perform the transformation for some reason (e.g. process/service unavailable) and wishes
* to decline the request. Such 'failures' should not lead to the addition of the {@link ContentModel#ASPECT_FAILED_THUMBNAIL_SOURCE}
* aspect.
*
* @since 4.0.1
*/
public void testCreateTransientlyFailingThumbnail() throws Exception
{
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(ContentModel.PROP_NAME, "transientThumbnail.transientThumbnail");
final NodeRef testNode = this.nodeService.createNode(folder, ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "transientThumbnail.transientThumbnail"),
ContentModel.TYPE_CONTENT, props).getChildRef();
nodeService.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.
logger.debug("Running failing thumbnail on " + testNode);
// Make sure the source node is correctly set up before we start
// It should not be renditioned and should not be marked as having any failed thumbnails.
assertFalse(nodeService.hasAspect(testNode, RenditionModel.ASPECT_RENDITIONED));
assertFalse(nodeService.hasAspect(testNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE));
setComplete();
endTransaction();
// Attempt to perform a thumbnail that we know will fail.
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
ThumbnailDefinition thumbnailDef = thumbnailService.getThumbnailRegistry().getThumbnailDefinition("doclib");
Action createThumbnailAction = ThumbnailHelper.createCreateThumbnailAction(thumbnailDef, services);
actionService.executeAction(createThumbnailAction, testNode, true, true);
return null;
}
});
// The thumbnail attempt has now failed. But in this case the compensating action should NOT have been scheduled.
// We'll wait briefly in case it has erroneously been scheduled.
Thread.sleep(3000); // This should be long enough for the compensating action to run - if it has been scheduled, which it shouldn't.
transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Void>()
{
public Void execute() throws Throwable
{
assertFalse("Node should not have renditioned aspect", nodeService.hasAspect(testNode, RenditionModel.ASPECT_RENDITIONED));
assertFalse("Node should not have failed thumbnails aspect", nodeService.hasAspect(testNode, ContentModel.ASPECT_FAILED_THUMBNAIL_SOURCE));
return null;
}
});
}
public void testThumbnailUpdate() throws Exception
{
checkTransformer();
// First create a thumbnail
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small");
// Thumbnails should always be of type cm:thumbnail.
assertEquals(ContentModel.TYPE_THUMBNAIL, nodeService.getType(thumbnail1));
// Update the thumbnail
this.thumbnailService.updateThumbnail(thumbnail1, imageTransformationOptions);
// ALF-2047. Thumbnails were changing to type cm:content after update.
assertEquals(ContentModel.TYPE_THUMBNAIL, nodeService.getType(thumbnail1));
}
public void testGetThumbnailByName() throws Exception
{
checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
// Check for missing thumbnail
NodeRef result1 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "small");
assertNull("The thumbnail 'small' should have been missing", result1);
// Create the thumbnail
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG,
imageTransformationOptions, "small");
// Try and retrieve the thumbnail
NodeRef result2 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "small");
assertNotNull(result2);
checkRendition("small", result2);
// Check for an other thumbnail that doesn't exist
NodeRef result3 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "anotherone");
assertNull("The thumbnail 'anotherone' should have been missing", result3);
}
private void checkRenditioned(NodeRef thumbnailed, String assocName)
{
assertTrue("Renditioned aspect should have been applied", this.nodeService.hasAspect(thumbnailed,
RenditionModel.ASPECT_RENDITIONED));
if (assocName != null)
{
List<ChildAssociationRef> assocs = this.nodeService.getChildAssocs(thumbnailed, RegexQNamePattern.MATCH_ALL,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, assocName));
assertNotNull(assocs);
assertEquals(1, assocs.size());
}
}
private void checkRendition(String thumbnailName, NodeRef thumbnail)
{
// Check the thumbnail is of the correct type
assertTrue("Thumbnail should have been a rendition",
renditionService.isRendition(thumbnail));
// Check the name
if (thumbnailName != null)
{
assertEquals(thumbnailName, this.nodeService.getProperty(thumbnail, ContentModel.PROP_NAME));
}
// Check the content property value
assertEquals(ContentModel.PROP_CONTENT, this.nodeService.getProperty(thumbnail,
ContentModel.PROP_CONTENT_PROPERTY_NAME));
// Check the thumbnail is of type cm:thumbnail.
assertEquals("The thumbnail node should be of type cm:thumbnail!",
ContentModel.TYPE_THUMBNAIL, nodeService.getType(thumbnail));
// Check the thumbnail name property is correctly set on thumbnail.
assertEquals( thumbnailName, nodeService.getProperty(thumbnail, ContentModel.PROP_THUMBNAIL_NAME));
}
private void outputThumbnailTempContentLocation(NodeRef thumbnail, String ext, String message) throws IOException
{
File tempFile = TempFileProvider.createTempFile("thumbnailServiceImplTest", "." + ext);
ContentReader reader = this.contentService.getReader(thumbnail, ContentModel.PROP_CONTENT);
reader.getContent(tempFile);
System.out.println(message + ": " + tempFile.getPath());
}
/**
* This method creates a node under the specified folder whose content is
* taken from the quick file corresponding to the specified MIME type.
*
* @param parentFolder
* @param mimetype
* @return
* @throws IOException
*/
private NodeRef createOriginalContent(NodeRef parentFolder, String mimetype) throws IOException
{
String ext = this.mimetypeMap.getExtension(mimetype);
File origFile = AbstractContentTransformerTest.loadQuickTestFile(ext);
Map<QName, Serializable> props = new HashMap<QName, Serializable>(1);
props.put(ContentModel.PROP_NAME, "origional." + ext);
NodeRef node = this.nodeService.createNode(parentFolder, ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "original." + ext),
ContentModel.TYPE_CONTENT, props).getChildRef();
ContentWriter writer = this.contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
writer.setMimetype(mimetype);
writer.setEncoding("UTF-8");
writer.putContent(origFile);
return node;
}
private NodeRef createCorruptedContent(NodeRef parentFolder) throws IOException
{
// The below pdf file has been truncated such that it is identifiable as a PDF but otherwise corrupt.
File corruptPdfFile = AbstractContentTransformerTest.loadNamedQuickTestFile("quickCorrupt.pdf");
assertNotNull("Failed to load required test file.", corruptPdfFile);
Map<QName, Serializable> props = new HashMap<QName, Serializable>();
props.put(ContentModel.PROP_NAME, "corrupt.pdf");
NodeRef node = this.nodeService.createNode(parentFolder, ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "quickCorrupt.pdf"),
ContentModel.TYPE_CONTENT, props).getChildRef();
nodeService.setProperty(node, ContentModel.PROP_CONTENT, new ContentData(null,
MimetypeMap.MIMETYPE_PDF, 0L, null));
ContentWriter writer = contentService.getWriter(node, ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_PDF);
writer.setEncoding("UTF-8");
writer.putContent(corruptPdfFile);
return node;
}
@SuppressWarnings("deprecation")
public void testAutoUpdate() throws Exception
{
checkTransformer();
final NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
ThumbnailDefinition details = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("medium");
@SuppressWarnings("unused")
final NodeRef thumbnail = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, details
.getMimetype(), details.getTransformationOptions(), details.getName());
setComplete();
endTransaction();
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
String ext = ThumbnailServiceImplTest.this.mimetypeMap.getExtension(MimetypeMap.MIMETYPE_IMAGE_JPEG);
File origFile = AbstractContentTransformerTest.loadQuickTestFile(ext);
ContentWriter writer = ThumbnailServiceImplTest.this.contentService.getWriter(jpgOrig,
ContentModel.PROP_CONTENT, true);
writer.putContent(origFile);
return null;
}
});
// TODO
// this test should wait for the async action to run .. will need to
// commit transaction for that thou!
// Thread.sleep(1000);
}
public void testHTMLToImageAndSWF() throws Exception
{
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)
{
NodeRef thumb = this.thumbnailService.createThumbnail(nodeRef, ContentModel.PROP_CONTENT,
def.getMimetype(), def.getTransformationOptions(), def.getName());
assertNotNull(thumb);
ContentReader reader = this.contentService.getReader(thumb, ContentModel.PROP_CONTENT);
assertNotNull(reader);
assertEquals(def.getMimetype(), reader.getMimetype());
assertTrue(reader.getSize() != 0);
}
def = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("webpreview");
if (transformer != null)
{
NodeRef thumb = this.thumbnailService.createThumbnail(nodeRef, ContentModel.PROP_CONTENT,
def.getMimetype(), def.getTransformationOptions(), def.getName());
assertNotNull(thumb);
ContentReader reader = this.contentService.getReader(thumb, ContentModel.PROP_CONTENT);
assertNotNull(reader);
assertEquals(def.getMimetype(), reader.getMimetype());
assertTrue(reader.getSize() != 0);
}
}
public void testThumbnailServiceCreateApi() throws Exception
{
// Create a second folder
Map<QName, Serializable> folderProps = new HashMap<QName, Serializable>();
folderProps.put(ContentModel.PROP_NAME, "otherTestFolder");
NodeRef otherFolder = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN,
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "otherTestFolder"), ContentModel.TYPE_FOLDER)
.getChildRef();
checkTransformer();
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
ImageResizeOptions imageResizeOptions = new ImageResizeOptions();
imageResizeOptions.setWidth(64);
imageResizeOptions.setHeight(64);
imageResizeOptions.setResizeToThumbnail(true);
ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
imageTransformationOptions.setResizeOptions(imageResizeOptions);
// Create thumbnail - same MIME type
NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "smallJpeg");
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, "smallJpeg");
checkRendition("smallJpeg", thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "jpg", "smallJpeg - 64x64, marked as thumbnail");
// Create thumbnail - different MIME type
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallPng");
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, "smallPng");
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("");
// Create thumbnail - different target assoc details
ThumbnailParentAssociationDetails tpad
= new ThumbnailParentAssociationDetails(otherFolder,
QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "foo"),
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bar"));
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "targetDetails", tpad);
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, "targetDetails");
checkRendition("targetDetails", thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "png", "targetDetails - 64x64, marked as thumbnail");
// Create thumbnail - null thumbnail name
thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT,
MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, null);
assertNotNull(thumbnail1);
checkRenditioned(jpgOrig, null);
checkRendition(null, thumbnail1);
outputThumbnailTempContentLocation(thumbnail1, "png", "'null' - 64x64, marked as thumbnail");
}
public void testRegistry()
{
ThumbnailRegistry thumbnailRegistry = this.thumbnailService.getThumbnailRegistry();
List<ThumbnailDefinition> defs = thumbnailRegistry.getThumbnailDefinitions(MimetypeMap.MIMETYPE_HTML, -1);
System.out.println("Definitions ...");
for (ThumbnailDefinition def : defs)
{
System.out.println("Thumbnail Available: " + def.getName());
}
}
// == Test the JavaScript API ==
public void testJSAPI() throws Exception
{
NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG);
NodeRef gifOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF);
NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF);
NodeRef docOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_WORD);
Map<String, Object> model = new HashMap<String, Object>(2);
model.put("jpgOrig", jpgOrig);
model.put("gifOrig", gifOrig);
model.put("pdfOrig", pdfOrig);
model.put("docOrig", docOrig);
ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/thumbnail/script/test_thumbnailAPI.js");
this.scriptService.executeScript(location, model);
}
/**
* This test method tests the thumbnail placeholders which are handled in the {@link ScriptThumbnailService}.
* See ALF-6566.
*/
public void testPlaceHoldersByMimeType() throws Exception
{
// Retrieve the classpath paths for all the standard icon resources for doclib.
final String standardDoclibIcon = "alfresco/thumbnail/thumbnail_placeholder_doclib.png";
// This used to be the cogs, but as of ALF-6566 is a generic document icon.
String doclibIcon = scriptThumbnailService.getPlaceHolderResourcePath("doclib");
assertEquals(standardDoclibIcon, doclibIcon);
// The same but with explicit null mimetype.
doclibIcon = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath("doclib", null);
assertEquals(standardDoclibIcon, doclibIcon);
// The icon for a .doc mime type - a sample, recognised mime type.
String docxDoclibIcon = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath("doclib", MimetypeMap.MIMETYPE_WORD);
assertEquals("alfresco/thumbnail/thumbnail_placeholder_doclib_doc.png", docxDoclibIcon);
// The icon for an unrecognised mime type.
String fallbackDoclibIcon = scriptThumbnailService.getMimeAwarePlaceHolderResourcePath("doclib", "application/wibble");
assertEquals(standardDoclibIcon, fallbackDoclibIcon);
// And one from the 'medium' set.
String mediumIcon = scriptThumbnailService.getPlaceHolderResourcePath("medium");
final String standardMediumIcon = "alfresco/thumbnail/thumbnail_placeholder_medium.jpg"; // This one jpg, not png
assertEquals(standardMediumIcon, mediumIcon);
}
/**
* Test transformer.
*
* @since 4.0.1
*/
private static class TransientFailTransformer extends AbstractContentTransformer2
{
public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options)
{
return sourceMimetype.equals(MimetypeMap.MIMETYPE_PDF) && targetMimetype.equals(TEST_FAILING_MIME_TYPE);
}
protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception
{
// fail every time.
throw new ContentServiceTransientException("Transformation intentionally failed for test purposes.");
}
}
}