diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 131a198fcf..f0a7701b2f 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -112,6 +112,9 @@ ${policy.content.update.ignoreEmpty} + + + @@ -253,6 +256,13 @@ + + + + + + + + + + + ${content.transformer.default.timeoutMs} + ${content.transformer.default.readLimitTimeMs} + ${content.transformer.default.maxSourceSizeKBytes} + ${content.transformer.default.readLimitKBytes} + ${content.transformer.default.pageLimit} + ${content.transformer.default.maxPages} - - - - + class="org.alfresco.repo.content.transform.PdfToImageContentTransformer" + parent="baseContentTransformer" /> + - - - - + class="org.alfresco.repo.content.transform.PdfBoxPdfToImageContentTransformer" + parent="baseContentTransformer" /> application/pdf + + ${content.transformer.PdfBox.TextToPdf.maxSourceSizeKBytes} text/plain - + + + + + + + + + 0 + + + + + + ${content.transformer.OpenOffice.mimeTypeLimits.txt.pdf.maxSourceSizeKBytes} + + + + + + + + + + ${content.transformer.OpenOffice.mimeTypeLimits.xlsx.pdf.maxSourceSizeKBytes} + + + + + + application/pdf + + + + + + + + 0 + + + + + + + + + + 0 + + + + + + pdf using PdfBox (txt=text/*) 10M takes about 12 seconds +content.transformer.PdfBox.TextToPdf.maxSourceSizeKBytes=10240 + +# pdf -> swf using Pdf2swf 1M takes about 30 seconds. +# Using a value of 1.25M (a bit larger that the txt or xlsx) used to create +# the pdf on the way to swf to avoid the second part of a transform failing +content.transformer.Pdf2swf.maxSourceSizeKBytes=1152 + +# txt -> pdf -> swf 1M (pdf is about the same size as the txt) +# Need this limit as transformer.PdfBox txt -> pdf is allowed up to 10M +# unlike transformer.OpenOffice and transformer.JodConverter which +# also only allow 1M text +content.transformer.complex.Text.Pdf2swf.maxSourceSizeKBytes=1024 + +# Limits for specific mimetype transformations (txt=text/plain only) 1M +# Has implication for Share preview which generally goes via pdf to get to swf +content.transformer.OpenOffice.mimeTypeLimits.txt.pdf.maxSourceSizeKBytes=1024 +content.transformer.OpenOffice.mimeTypeLimits.xlsx.pdf.maxSourceSizeKBytes=1024 + +content.transformer.JodConverter.mimeTypeLimits.txt.pdf.maxSourceSizeKBytes=1024 +content.transformer.JodConverter.mimeTypeLimits.xlsx.pdf.maxSourceSizeKBytes=1024 + # Property to enable upgrade from 2.1-A V2.1-A.fixes.to.schema=0 #V2.1-A.fixes.to.schema=82 diff --git a/config/alfresco/subsystems/Search/lucene/lucene-search-context.xml b/config/alfresco/subsystems/Search/lucene/lucene-search-context.xml index 9504149931..4606104698 100644 --- a/config/alfresco/subsystems/Search/lucene/lucene-search-context.xml +++ b/config/alfresco/subsystems/Search/lucene/lucene-search-context.xml @@ -282,9 +282,8 @@ - - - + + @@ -381,6 +380,9 @@ + + + diff --git a/config/alfresco/swf-transform-context.xml b/config/alfresco/swf-transform-context.xml index 33c567df3e..3e0e3fc2fa 100644 --- a/config/alfresco/swf-transform-context.xml +++ b/config/alfresco/swf-transform-context.xml @@ -23,6 +23,7 @@ + ${content.transformer.Pdf2swf.maxSourceSizeKBytes} application/pdf + application/pdf + + ${content.transformer.complex.Text.Pdf2swf.maxSourceSizeKBytes} diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index c8e4658b94..fe3c2044de 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -1,7 +1,10 @@ - - + @@ -93,6 +96,12 @@ + + + + + + @@ -114,27 +123,27 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -200,6 +209,13 @@ + + + ${system.thumbnail.generate} + + + + @@ -216,8 +232,48 @@ + + + ${system.thumbnail.generate} + + + + + + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.pdf} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.txt} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.docx} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.pptx} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.odt} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.ods} + + + ${system.thumbnail.mimetype.maxSourceSizeKBytes.odp} + + + diff --git a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java index ec01c1c60a..015db34dea 100644 --- a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java @@ -80,7 +80,7 @@ public class ImageTransformActionExecuter extends TransformActionExecuter imageOptions.setCommandOptions(convertCommand); // check if the transformer is going to work, i.e. is available - if (!this.imageMagickContentTransformer.isTransformable(contentReader.getMimetype(), contentWriter + if (!this.imageMagickContentTransformer.isTransformable(contentReader.getMimetype(), contentReader.getSize(), contentWriter .getMimetype(), imageOptions)) { throw new NoTransformerException(contentReader.getMimetype(), contentWriter.getMimetype()); diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java index f024661b41..5e5cf635ad 100644 --- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java @@ -172,7 +172,7 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase throw new RuleServiceException(CONTENT_READER_NOT_FOUND_MESSAGE); } - if (null == contentService.getTransformer(contentReader.getMimetype(), mimeType)) + if (null == contentService.getTransformer(contentReader.getContentUrl(), contentReader.getMimetype(), contentReader.getSize(), mimeType, new TransformationOptions())) { throw new RuleServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, contentReader.getMimetype(), mimeType)); } diff --git a/source/java/org/alfresco/repo/content/AbstractContentReader.java b/source/java/org/alfresco/repo/content/AbstractContentReader.java index 1c72302849..5e4ae7432e 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentReader.java +++ b/source/java/org/alfresco/repo/content/AbstractContentReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -32,13 +32,20 @@ import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicBoolean; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentStreamListener; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.alfresco.service.cmr.repository.TransformationOptionPair; +import org.alfresco.service.cmr.repository.TransformationOptionPair.Action; import org.alfresco.util.EqualsHelper; import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; @@ -46,6 +53,9 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aop.framework.ProxyFactory; import org.springframework.util.FileCopyUtils; +import sun.nio.ch.ChannelInputStream; + + /** * Implements all the convenience methods of the interface. The only methods * that need to be implemented, i.e. provide low-level content access are: @@ -59,10 +69,20 @@ import org.springframework.util.FileCopyUtils; public abstract class AbstractContentReader extends AbstractContentAccessor implements ContentReader { private static final Log logger = LogFactory.getLog(AbstractContentReader.class); + private static final Timer timer = new Timer(); private List listeners; private ReadableByteChannel channel; + // Optional limits on reading + private TransformationOptionLimits limits; + + // Only needed if limits are set + private TransformerDebug transformerDebug; + + // For testing: Allows buffering to be turned off + private boolean useBufferedInputStream = true; + /** * @param contentUrl the content URL - this should be relative to the root of the store * and not absolute: to enable moving of the stores @@ -73,7 +93,37 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl listeners = new ArrayList(2); } + + public void setLimits(TransformationOptionLimits limits) + { + this.limits = limits; + } + + public TransformationOptionLimits getLimits() + { + return limits; + } + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + + public TransformerDebug getTransformerDebug() + { + return transformerDebug; + } + + public void setUseBufferedInputStream(boolean useBufferedInputStream) + { + this.useBufferedInputStream = useBufferedInputStream; + } + + public boolean getUseBufferedInputStream() + { + return useBufferedInputStream; + } + /** * Adds the listener after checking that the output stream isn't already in * use. @@ -323,7 +373,26 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl try { ReadableByteChannel channel = getReadableChannel(); - InputStream is = new BufferedInputStream(Channels.newInputStream(channel)); + InputStream is = Channels.newInputStream(channel); + + // If we have a timeout or read limit, intercept the calls. + if (limits != null) + { + TransformationOptionPair time = limits.getTimePair(); + TransformationOptionPair kBytes = limits.getKBytesPair(); + long timeoutMs = time.getValue(); + long readLimitBytes = kBytes.getValue() * 1024; + + if (timeoutMs > 0 || readLimitBytes > 0) + { + Action timeoutAction = time.getAction(); + Action readLimitAction = kBytes.getAction(); + + is = new TimeSizeRestrictedInputStream(is, timeoutMs, timeoutAction, + readLimitBytes, readLimitAction, transformerDebug); + } + } + is = new BufferedInputStream(is); // done return is; } @@ -486,4 +555,105 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl " right: " + right); } } + + /** + * InputStream that wraps another InputStream to terminate early after a timeout + * or after reading a number of bytes. It terminates by either returning end of file + * (-1) or throwing an IOException. + + * @author Alan Davis + */ + private class TimeSizeRestrictedInputStream extends InputStream + { + private final AtomicBoolean timeoutFlag = new AtomicBoolean(false); + + private final InputStream is; + private final long timeoutMs; + private final long readLimitBytes; + private final Action timeoutAction; + private final Action readLimitAction; + private final TransformerDebug transformerDebug; + + private long readCount = 0; + + public TimeSizeRestrictedInputStream(InputStream is, + long timeoutMs, Action timeoutAction, + long readLimitBytes, Action readLimitAction, + TransformerDebug transformerDebug) + { + this.is = useBufferedInputStream ? new BufferedInputStream(is) : is; + this.timeoutMs = timeoutMs; + this.timeoutAction = timeoutAction; + this.readLimitBytes = readLimitBytes; + this.readLimitAction = readLimitAction; + this.transformerDebug = transformerDebug; + + if (timeoutMs > 0) + { + timer.schedule(new TimerTask() + { + @Override + public void run() + { + timeoutFlag.set(true); + } + + }, timeoutMs); + } + } + + @Override + public int read() throws IOException + { + // Throws exception or return true to indicate EOF + if (hitTimeout() || hitReadLimit()) + { + return -1; + } + + int n = is.read(); + if (n > 0) + { + readCount++; + } + return n; + } + + private boolean hitTimeout() throws IOException + { + if (timeoutMs > 0 && timeoutFlag.get()) + { + timeoutAction.throwIOExceptionIfRequired( + "Transformation has taken too long ("+ + (timeoutMs/1000)+" seconds)", transformerDebug); + return true; + } + return false; + } + + private boolean hitReadLimit() throws IOException + { + if (readLimitBytes > 0 && readCount >= readLimitBytes) + { + readLimitAction.throwIOExceptionIfRequired( + "Transformation has read too many bytes ("+ + (readLimitBytes/1024)+"K)", transformerDebug); + return true; + } + return false; + } + + @Override + public void close() throws IOException + { + try + { + is.close(); + } + finally + { + super.close(); + } + } + }; } diff --git a/source/java/org/alfresco/repo/content/AbstractContentReaderLimitTest.java b/source/java/org/alfresco/repo/content/AbstractContentReaderLimitTest.java new file mode 100644 index 0000000000..33a5cc21c1 --- /dev/null +++ b/source/java/org/alfresco/repo/content/AbstractContentReaderLimitTest.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.repo.content; + + +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; + +import org.alfresco.repo.content.transform.TransformerDebug; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +/** + * Test class for time and byte limits on an AbstractContentReader + * + * @author Alan Davis + */ +public class AbstractContentReaderLimitTest +{ + private static final int K = 1024; + private static final int M = K*K; + + // Normal test file size 5K + private static final long SIZE = 5*K; + + // Normal test delay between returning bytes, giving a run time of about 5 seconds if not interrupted. + private static final long MS_PER_BYTE = 1; + + // Large test file size 100 MB with no delay takes about a second to read. + private static final long LARGE_SIZE = 100*M; + + // Top speed to read 1 MB via the DummyAbstractContentReader + // Assume about 10ms normally per MB, so use half that and a high + // MARGIN_OF_ERROR_PERCENTAGE_FAST. + private static final long MS_PER_MB = 5; + + // Margins of error when using a DummyAbstractContentReader + // with or without a delay. Used to make sure different runs + // don't result in failing tests but at the same time that + // they will if there is a real problem. + private static final int MARGIN_OF_ERROR_PERCENTAGE_SLOW = 50; + private static final int MARGIN_OF_ERROR_PERCENTAGE_FAST = 900; + + private DummyAbstractContentReader reader; + private TransformationOptionLimits limits; + private TransformerDebug transformerDebug; + private long minTime; + private long maxTime; + private long minLength; + private long maxLength; + + @Before + public void setUp() throws Exception + { + ApplicationContext ctx = ContentMinimalContextTestSuite.getContext(); + transformerDebug = (TransformerDebug) ctx.getBean("transformerDebug"); + + limits = new TransformationOptionLimits(); + reader = new DummyAbstractContentReader(SIZE, MS_PER_BYTE); + reader.setLimits(limits); + reader.setTransformerDebug(transformerDebug); + + // Without the following, the bytes from the DummyAbstractContentReader are read in 4K blocks + // so the test to do with timeouts and read limits will not work, as they expect to read 1K per + // second. Not an issue in the real world as read rates are much higher, so a buffer makes no + // difference to limit checking. It does make a vast difference to performance when the InputStream + // is wrapped in a InputStreamReader as is done by a number of transformers. + reader.setUseBufferedInputStream(false); + } + + @Test + public void noLimitTest() throws Exception + { + readAndCheck(); + } + + @Test(expected=ContentIOException.class) + public void maxKBytesTest() throws Exception + { + limits.setMaxSourceSizeKBytes(1); + readAndCheck(); + } + + @Test(expected=ContentIOException.class) + public void maxTimeTest() throws Exception + { + limits.setTimeoutMs(1000); + readAndCheck(); + } + + @Test(expected=ContentIOException.class) + public void maxTimeAndKBytesTest() throws Exception + { + limits.setTimeoutMs(1000); + limits.setMaxSourceSizeKBytes(1); + readAndCheck(); + } + + @Test + public void limitKBytesTest() throws Exception + { + limits.setReadLimitKBytes(1); + readAndCheck(); + } + + @Test + public void limitTimeTest() throws Exception + { + limits.setReadLimitTimeMs(1000); + readAndCheck(); + } + + @Test + public void limitTimeAndKBytesTest() throws Exception + { + limits.setReadLimitTimeMs(1000); + limits.setReadLimitKBytes(1); + readAndCheck(); + } + + @Test + public void fullSpeedReader() throws Exception + { + // Check that we have not slowed down reading of large files. + reader = new DummyAbstractContentReader(LARGE_SIZE, 0); + reader.setLimits(limits); + reader.setTransformerDebug(transformerDebug); + reader.setUseBufferedInputStream(true); + + readAndCheck(); + } + + private void readAndCheck() throws Exception + { + Exception exception = null; + + long length = 0; + long time = System.currentTimeMillis(); + try + { + String content = reader.getContentString(); + length = content.length(); + } + catch(ContentIOException e) + { + exception = e; + } + time = System.currentTimeMillis() - time; + + calcMaxMinValues(); + + System.out.printf("Time %04d %04d..%04d length %04d %04d..%04d %s\n", + time, minTime, maxTime, + length, minLength, maxLength, + (exception == null ? "" : exception.getClass().getSimpleName())); + + assertTrue("Reader is too fast ("+time+"ms range is "+minTime+"..."+maxTime+"ms)", time >= minTime); + assertTrue("Reader is too slow ("+time+"ms range is "+minTime+"..."+maxTime+"ms)", time <= maxTime); + + if (exception != null) + throw exception; + + assertTrue("Content is too short ("+length+" bytes range is "+minLength+"..."+maxLength+")", length >= minLength); + assertTrue("Content is too long ("+length+" bytes range is "+minLength+"..."+maxLength+")", length <= maxLength); + } + + private void calcMaxMinValues() + { + long size = reader.size; + + long timeout = limits.getTimePair().getValue(); + assertTrue("The test time value ("+timeout+ + "ms) should be lowered given the file size ("+size+ + ") and the margin of error (of "+marginOfError(timeout)+"ms)", + timeout <= 0 || msToBytes(timeout+marginOfError(timeout)) <= size); + + long readKBytes = limits.getKBytesPair().getValue(); + long readBytes = readKBytes * K; + assertTrue("The test KByte value ("+readKBytes+ + "K) should be lowered given the file size ("+size+ + ") and the margin of error (of "+marginOfError(readBytes)+"bytes)", + readBytes <= 0 || readBytes+marginOfError(readBytes) <= size); + + long bytes = (readBytes > 0) ? readBytes : size; + long readTime = bytesToMs(bytes); + + minTime = (timeout > 0) ? Math.min(timeout, readTime) : readTime; + maxTime = minTime + marginOfError(minTime); + minLength = (timeout > 0) ? msToBytes(minTime-marginOfError(minTime)) : bytes; + maxLength = (timeout > 0) ? Math.min(msToBytes(maxTime), size) : bytes; + } + + private long msToBytes(long ms) + { + return (reader.msPerByte > 0) + ? ms / reader.msPerByte + : ms / MS_PER_MB * M; + } + + private long bytesToMs(long bytes) + { + return (reader.msPerByte > 0) + ? bytes * reader.msPerByte + : bytes * MS_PER_MB / M; + } + + private long marginOfError(long value) + { + return + value * + ((reader.msPerByte > 0) + ? MARGIN_OF_ERROR_PERCENTAGE_SLOW + : MARGIN_OF_ERROR_PERCENTAGE_FAST) / + 100; + } + + /** + * A dummy AbstractContentReader that returns a given number of bytes + * (all 'a') very slowly. There is a configurable delay returning each byte. + * Used to test timeouts and read limits. + */ + public static class DummyAbstractContentReader extends AbstractContentReader + { + final long size; + final long msPerByte; + + /** + * @param size of the dummy data + * @param msPerByte milliseconds between byte reads + */ + public DummyAbstractContentReader(long size, long msPerByte) + { + super("a"); + this.size = size; + this.msPerByte = msPerByte; + } + + /** + * @return Returns an instance of the this class + */ + @Override + protected ContentReader createReader() throws ContentIOException + { + return new DummyAbstractContentReader(size, msPerByte); + } + + @Override + protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException + { + InputStream is = new InputStream() + { + long read = 0; + long start = 0; + + @Override + public int read() throws IOException + { + if (read >= size) + return -1; + + read++; + + if (msPerByte > 0) + { + long elapse = System.currentTimeMillis() - start; + if (read == 1) + { + start = elapse; + } + else + { + // On Windows it is possible to just wait 1 ms per byte but this + // does not work on linux hence (end up with a full read taking + // 40 seconds rather than 5) the need to wait if elapse time + // is too fast. + long delay = (read * msPerByte) - elapse; + if (delay > 0) + { + try + { + Thread.sleep(delay); + } + catch (InterruptedException e) + { + // ignore + } + } + } + } + + return 'a'; + } + + // Just a way to tell AbstractContentReader not to wrap the ChannelInputStream + // in a BufferedInputStream + @Override + public boolean markSupported() + { + return true; + } + }; + return Channels.newChannel(is); + } + + public boolean exists() + { + return true; + } + + public long getLastModified() + { + return 0L; + } + + public long getSize() + { + return size; + } + }; +} diff --git a/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java b/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java index 56636688cb..0316f94177 100644 --- a/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java +++ b/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java @@ -29,6 +29,7 @@ import org.alfresco.repo.content.metadata.PdfBoxMetadataExtracterTest; import org.alfresco.repo.content.metadata.PoiMetadataExtracterTest; import org.alfresco.repo.content.metadata.RFC822MetadataExtracterTest; import org.alfresco.repo.content.metadata.TikaAutoMetadataExtracterTest; +import org.alfresco.repo.content.transform.AbstractContentTransformerLimitsTest; import org.alfresco.repo.content.transform.AppleIWorksContentTransformerTest; import org.alfresco.repo.content.transform.BinaryPassThroughContentTransformerTest; import org.alfresco.repo.content.transform.ComplexContentTransformerTest; @@ -47,9 +48,14 @@ import org.alfresco.repo.content.transform.TextMiningContentTransformerTest; import org.alfresco.repo.content.transform.TextToPdfContentTransformerTest; import org.alfresco.repo.content.transform.TikaAutoContentTransformerTest; import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformerTest; +import org.alfresco.service.cmr.repository.TransformationOptionLimitsTest; +import org.alfresco.service.cmr.repository.TransformationOptionPairTest; import org.alfresco.util.ApplicationContextHelper; import org.springframework.context.ApplicationContext; +import com.google.gdata.data.acl.AclRole; + +import junit.framework.JUnit4TestAdapter; import junit.framework.Test; import junit.framework.TestSuite; @@ -85,6 +91,12 @@ public class ContentMinimalContextTestSuite extends TestSuite // Off we go TestSuite suite = new TestSuite(); + // Limits + suite.addTest(new JUnit4TestAdapter(AbstractContentReaderLimitTest.class)); + suite.addTest(new JUnit4TestAdapter(AbstractContentTransformerLimitsTest.class)); + suite.addTest(new JUnit4TestAdapter(TransformationOptionLimitsTest.class)); + suite.addTest(new JUnit4TestAdapter(TransformationOptionPairTest.class)); + // Metadata tests suite.addTestSuite( DWGMetadataExtracterTest.class ); suite.addTestSuite( HtmlMetadataExtracterTest.class ); diff --git a/source/java/org/alfresco/repo/content/ContentServiceImpl.java b/source/java/org/alfresco/repo/content/ContentServiceImpl.java index b17caba098..1114435fbf 100644 --- a/source/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/source/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -35,6 +35,7 @@ import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; import org.alfresco.repo.content.filestore.FileContentStore; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.ContentTransformerRegistry; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; @@ -89,7 +90,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa private MimetypeService mimetypeService; private RetryingTransactionHelper transactionHelper; private ApplicationContext applicationContext; - + protected TransformerDebug transformerDebug; + /** a registry of all available content transformers */ private ContentTransformerRegistry transformerRegistry; /** The cleaner that will ensure that rollbacks clean up after themselves */ @@ -177,6 +179,15 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa this.applicationContext = applicationContext; } + /** + * Helper setter of the transformer debug. + * @param transformerDebug + */ + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + /** * Service initialise */ @@ -559,40 +570,74 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa { throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer); } - // look for a transformer - ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype, options); - if (transformer == null) + + try { - throw new NoTransformerException(sourceMimetype, targetMimetype); + // look for a transformer + transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype); + long sourceSize = reader.getSize(); + List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); + transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.transform(...)"); + + if (transformers.isEmpty()) + { + throw new NoTransformerException(sourceMimetype, targetMimetype); + } + + // we have a transformer, so do it + ContentTransformer transformer = transformers.size() == 0 ? null : transformers.get(0); + transformer.transform(reader, writer, options); + // done + } + finally + { + transformerDebug.popAvailable(); } - // we have a transformer, so do it - transformer.transform(reader, writer, options); - // done } - + /** * @see org.alfresco.repo.content.transform.ContentTransformerRegistry * @see org.alfresco.repo.content.transform.ContentTransformer */ public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) { - return getTransformer(sourceMimetype, targetMimetype, new TransformationOptions()); + return getTransformer(null, sourceMimetype, -1, targetMimetype, new TransformationOptions()); + } + + public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return getTransformer(null, sourceMimetype, -1, targetMimetype, options); } /** - * @see org.alfresco.service.cmr.repository.ContentService#getTransformer(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) + * @see org.alfresco.service.cmr.repository.ContentService#getTransformer(String, java.lang.String, long, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) */ - public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options) + public ContentTransformer getTransformer(String sourceUrl, String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { - return transformerRegistry.getTransformer(sourceMimetype, targetMimetype, options); + try + { + // look for a transformer + transformerDebug.pushAvailable(sourceUrl, sourceMimetype, targetMimetype); + List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); + transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.getTransformer(...)"); + return (transformers.isEmpty()) ? null : transformers.get(0); + } + finally + { + transformerDebug.popAvailable(); + } } public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) { - return transformerRegistry.getActiveTransformers(sourceMimetype, targetMimetype, options); + return getActiveTransformers(sourceMimetype, -1, targetMimetype, options); + } + + public List getActiveTransformers(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return transformerRegistry.getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); } - /** * @see org.alfresco.service.cmr.repository.ContentService#getImageTransformer() */ @@ -628,7 +673,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } // look for a transformer - ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, targetMimetype, options); + ContentTransformer transformer = transformerRegistry.getTransformer(sourceMimetype, reader.getSize(), targetMimetype, options); return (transformer != null); } diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java index 0a8c7f3e63..fbd6e4a0b8 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -198,9 +198,21 @@ public abstract class AbstractContentTransformer implements ContentTransformer } // it all checks out OK } + + /** + * {@inheritDoc}

+ * + * Implementation calls the deprecated overloaded method without the sourceSize parameter. + * Note: source size checked has not been added to this deprecated class. + */ + @Override + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return isTransformable(sourceMimetype, targetMimetype, options); + } /** - * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) + * @deprecated use version with extra sourceSize parameter. */ public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) { diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java index d5a65121d2..41e4226ff3 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -38,7 +38,7 @@ import org.apache.commons.logging.LogFactory; * @author Derek Hulley * @author Roy Wetherall */ -public abstract class AbstractContentTransformer2 extends ContentTransformerHelper implements ContentTransformer +public abstract class AbstractContentTransformer2 extends AbstractContentTransformerLimits { private static final Log logger = LogFactory.getLog(AbstractContentTransformer2.class); @@ -103,12 +103,14 @@ public abstract class AbstractContentTransformer2 extends ContentTransformerHelp { String sourceMimetype = getMimetype(reader); String targetMimetype = getMimetype(writer); - boolean transformable = isTransformable(sourceMimetype, targetMimetype, options); + long sourceSize = reader.getSize(); + boolean transformable = isTransformable(sourceMimetype, sourceSize, targetMimetype, options); if (transformable == false) { - throw new AlfrescoRuntimeException("Unsuported transformation attempted: \n" + + AlfrescoRuntimeException e = new AlfrescoRuntimeException("Unsuported transformation attempted: \n" + " reader: " + reader + "\n" + " writer: " + writer); + throw transformerDebug.setCause(e); } // it all checks out OK } @@ -154,8 +156,16 @@ public abstract class AbstractContentTransformer2 extends ContentTransformerHelp try { + if (transformerDebug.isEnabled()) + { + transformerDebug.pushTransform(this, reader.getContentUrl(), reader.getMimetype(), writer.getMimetype(), reader.getSize()); + } + // Check the transformability checkTransformable(reader, writer, options); + + // Pass on any limits to the reader + setReaderLimits(reader, writer, options); // Transform transformInternal(reader, writer, options); @@ -174,18 +184,22 @@ public abstract class AbstractContentTransformer2 extends ContentTransformerHelp // Report the error if(differentType == null) { - throw new ContentIOException("Content conversion failed: \n" + + transformerDebug.debug("Failed", e); + throw new ContentIOException("Content conversion failed: \n" + " reader: " + reader + "\n" + " writer: " + writer + "\n" + - " options: " + options, + " options: " + options.toString(false) + "\n" + + " limits: " + getLimits(reader, writer, options), e); } else { + transformerDebug.debug("Failed: Mime type was '"+differentType+"'", e); throw new ContentIOException("Content conversion failed: \n" + " reader: " + reader + "\n" + " writer: " + writer + "\n" + - " options: " + options + "\n" + + " options: " + options.toString(false) + "\n" + + " limits: " + getLimits(reader, writer, options) + "\n" + " claimed mime type: " + reader.getMimetype() + "\n" + " detected mime type: " + differentType, e); @@ -193,6 +207,8 @@ public abstract class AbstractContentTransformer2 extends ContentTransformerHelp } finally { + transformerDebug.popTransform(); + // check that the reader and writer are both closed if (reader.isChannelOpen()) { diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java new file mode 100644 index 0000000000..78af71c77e --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.repo.content.transform; + +import java.util.Map; + +import org.alfresco.repo.content.AbstractContentReader; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.springframework.beans.factory.BeanNameAware; + +/** + * Provides transformation limits for {@link org.alfresco.repo.content.transform.ContentTransformer} + * implementations. + *

+ * This class maintains the limits and provides methods that combine limits: + * a) for the transformer as a whole + * b) for specific combinations if source and target mimetypes + * c) for the {@link TransformationOptions} provided for a specific transform. + * + * @author Alan Davis + */ +public abstract class AbstractContentTransformerLimits extends ContentTransformerHelper implements ContentTransformer, BeanNameAware +{ + /** Transformer wide Time, KBytes and page limits */ + private TransformationOptionLimits limits = new TransformationOptionLimits(); + + /** + * Time, KBytes and page limits by source and target mimetype combination. + * The first map's key is the source mimetype. The second map's key is the + * target mimetype and the value are the limits. */ + private Map> mimetypeLimits; + + /** Indicates if 'page' limits are supported. */ + private boolean pageLimitsSupported; + + /** For debug **/ + protected TransformerDebug transformerDebug; + + /** The bean name. Used in debug only. */ + private String beanName; + + /** + * Indicates if 'page' limits are supported. + * @return false by default. + */ + protected boolean isPageLimitSupported() + { + return pageLimitsSupported; + } + + /** + * Indicates if 'page' limits are supported. + */ + public void setPageLimitsSuported(boolean pageLimitsSupported) + { + this.pageLimitsSupported = pageLimitsSupported; + } + + /** + * Helper setter of the transformer debug. + * @param transformerDebug + */ + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + + /** + * {@inheritDoc}

+ * + * Implementation calls the deprecated overloaded method without the sourceSize parameter + * and then {@link #isTransformableSize(String, long, String, TransformationOptions)}. + */ + @Override + @SuppressWarnings("deprecation") + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return + isTransformable(sourceMimetype, targetMimetype, options) && + isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + } + + /** + * Indicates if this transformer is able to transform the given {@code sourceSize}. + * The {@code maxSourceSizeKBytes} property may indicate that only small source files + * may be transformed. + * @param sourceSize size in bytes of the source. If negative, the source size is unknown. + * @return {@code true} if the source is transformable. + */ + protected boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + boolean sizeOkay = true; + if (sourceSize >= 0) + { + TransformationOptionLimits limits = getLimits(sourceMimetype, targetMimetype, options); + + // The maxSourceSizeKbytes value is ignored if this transformer is able to use + // page limits and the limits include a pageLimit. Normally used in the creation + // of icons. Note the readLimitKBytes value is not checked as the combined limits + // only have the max or limit kbytes value set (the smaller value is returned). + if (!isPageLimitSupported() || limits.getPageLimit() <= 0) + { + // if maxSourceSizeKBytes == 0 this implies the transformation is disabled + long maxSourceSizeKBytes = limits.getMaxSourceSizeKBytes(); + sizeOkay = maxSourceSizeKBytes < 0 || (maxSourceSizeKBytes > 0 && sourceSize <= maxSourceSizeKBytes*1024); + if (!sizeOkay && transformerDebug.isEnabled()) + { + transformerDebug.unavailableTransformer(this, maxSourceSizeKBytes); + } + } + } + return sizeOkay; + } + + /** + * Gets the timeout (ms) on the InputStream after which an IOExecption is thrown + * to terminate very slow transformations or a subprocess is terminated (killed). + * @return timeoutMs in milliseconds. If less than or equal to zero (the default) + * there is no timeout. + */ + protected long getTimeoutMs() + { + return limits.getTimeoutMs(); + } + + /** + * Sets a timeout (ms) on the InputStream after which an IOExecption is thrown + * to terminate very slow transformations or to terminate (kill) a subprocess. + * @param timeoutMs in milliseconds. If less than or equal to zero (the default) + * there is no timeout. + * If greater than zero the {@code readLimitTimeMs} must not be set. + */ + public void setTimeoutMs(long timeoutMs) + { + limits.setTimeoutMs(timeoutMs); + } + + /** + * Gets the limit in terms of the amount of data read (by time) to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @return readLimitBytes if less than or equal to zero (the default) there is no limit. + */ + protected long getReadLimitTimeMs() + { + return limits.getReadLimitTimeMs(); + } + + /** + * Sets a limit in terms of the amount of data read (by time) to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @param readLimitBytes if less than or equal to zero (the default) there is no limit. + * If greater than zero the {@code timeoutMs} must not be set. + */ + public void setReadLimitTimeMs(long readLimitTimeMs) + { + limits.setReadLimitTimeMs(readLimitTimeMs); + } + + /** + * Gets the maximum source content size, to skip transformations where + * the source is just too large to expect it to perform. If the source is larger + * the transformer indicates it is not available. + * @return maxSourceSizeKBytes if less than or equal to zero (the default) there is no limit. + */ + protected long getMaxSourceSizeKBytes() + { + return limits.getMaxSourceSizeKBytes(); + } + + /** + * Sets a maximum source content size, to skip transformations where + * the source is just too large to expect it to perform. If the source is larger + * the transformer indicates it is not available. + * @param maxSourceSizeKBytes if less than or equal to zero (the default) there is no limit. + * If greater than zero the {@code readLimitKBytes} must not be set. + */ + public void setMaxSourceSizeKBytes(long maxSourceSizeKBytes) + { + limits.setMaxSourceSizeKBytes(maxSourceSizeKBytes); + } + + /** + * Gets the limit in terms of the about of data read to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @return readLimitKBytes if less than or equal to zero (the default) no limit should be applied. + */ + protected long getReadLimitKBytes() + { + return limits.getReadLimitKBytes(); + } + + /** + * Sets a limit in terms of the about of data read to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @param readLimitKBytes if less than or equal to zero (the default) there is no limit. + * If greater than zero the {@code maxSourceSizeKBytes} must not be set. + */ + public void setReadLimitKBytes(long readLimitKBytes) + { + limits.setReadLimitKBytes(readLimitKBytes); + } + + /** + * Get the maximum number of pages read before an exception is thrown. + * @return If less than or equal to zero (the default) no limit should be applied. + */ + protected int getMaxPages() + { + return limits.getMaxPages(); + } + + /** + * Set the number of pages read from the source before an exception is thrown. + * + * @param maxPages the number of pages to be read from the source. If less than or equal to zero + * (the default) no limit is applied. + */ + public void setMaxPages(int maxPages) + { + limits.setMaxPages(maxPages); + } + + /** + * Get the page limit before returning EOF. + * @return If less than or equal to zero (the default) no limit should be applied. + */ + protected int getPageLimit() + { + return limits.getPageLimit(); + } + + /** + * Set the number of pages read from the source before returning EOF. + * + * @param pageLimit the number of pages to be read from the source. If less + * than or equal to zero (the default) no limit is applied. + */ + public void setPageLimit(int pageLimit) + { + limits.setPageLimit(pageLimit); + } + + /** + * Returns max and limit values for time, size and pages in a single operation. + */ + protected TransformationOptionLimits getLimits() + { + return limits; + } + + /** + * Sets max and limit values for time, size and pages in a single operation. + */ + public void setLimits(TransformationOptionLimits limits) + { + this.limits = limits; + } + + /** + * Gets the max and limit values for time, size and pages per source and target mimetype + * combination. + */ + protected Map> getMimetypeLimits() + { + return mimetypeLimits; + } + + /** + * Sets the max and limit values for time, size and pages per source and target mimetype + * combination. + */ + public void setMimetypeLimits(Map> mimetypeLimits) + { + this.mimetypeLimits = mimetypeLimits; + } + + /** + * Returns max and limit values for time, size and pages for a specified source and + * target mimetypes, combined with this Transformer's general limits and optionally + * the supplied transformation option's limits. + */ + protected TransformationOptionLimits getLimits(ContentReader reader, ContentWriter writer, + TransformationOptions options) + { + return (reader == null || writer == null) + ? limits.combine(options.getLimits()) + : getLimits(reader.getMimetype(), writer.getMimetype(), options); + } + + /** + * Returns max and limit values for time, size and pages for a specified source and + * target mimetypes, combined with this Transformer's general limits and optionally + * the supplied transformation option's limits. + */ + protected TransformationOptionLimits getLimits(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + // Get the limits for the source and target mimetypes + TransformationOptionLimits mimetypeLimits = null; + if (this.mimetypeLimits != null) + { + Map targetLimits = + this.mimetypeLimits.get(sourceMimetype); + if (targetLimits == null) + { + targetLimits = this.mimetypeLimits.get("*"); + } + if (targetLimits != null) + { + mimetypeLimits = targetLimits.get(targetMimetype); + if (mimetypeLimits == null) + { + mimetypeLimits = targetLimits.get("*"); + } + } + } + + TransformationOptionLimits combined = (mimetypeLimits == null) ? limits : limits.combine(mimetypeLimits); + return (options == null) ? combined : combined.combine(options.getLimits()); + } + + /** + * Sets the Spring bean name - only for use in debug. + */ + @Override + public void setBeanName(String beanName) + { + this.beanName = beanName; + } + + /** + * Returns the Spring bean name - only for use in debug. + */ + public String getBeanName() + { + return beanName; + } + + /** + * Pass on any limits to the reader. Will only do so if the reader is an + * {@link AbstractContentReader}. + * @param reader passed to {@link #transform(ContentReader, ContentWriter, TransformationOptions). + * @param writer passed to {@link #transform(ContentReader, ContentWriter, TransformationOptions). + * @param options passed to {@link #transform(ContentReader, ContentWriter, TransformationOptions). + */ + protected void setReaderLimits(ContentReader reader, ContentWriter writer, + TransformationOptions options) + { + if (reader instanceof AbstractContentReader) + { + AbstractContentReader abstractContentReader = (AbstractContentReader)reader; + TransformationOptionLimits limits = getLimits(reader, writer, options); + abstractContentReader.setLimits(limits); + abstractContentReader.setTransformerDebug(transformerDebug); + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java new file mode 100644 index 0000000000..0879af6dc0 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.content.AbstractContentReader; +import org.alfresco.repo.content.ContentMinimalContextTestSuite; +import org.alfresco.repo.content.AbstractContentReaderLimitTest.DummyAbstractContentReader; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +/** + * Test methods that control limits in {@link AbstractContentTransformerLimits} + */ +public class AbstractContentTransformerLimitsTest +{ + private static final String A = "a"; + private static final String B = "b"; + private static final String C = "c"; + + private AbstractContentTransformerLimits transformer; + private TransformationOptionLimits limits; + private Map> mimetypeLimits; + private TransformationOptions options; + + @Before + public void setUp() throws Exception + { + ApplicationContext ctx = ContentMinimalContextTestSuite.getContext(); + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + MimetypeService mimetypeService = serviceRegistry.getMimetypeService(); + TransformerDebug transformerDebug = (TransformerDebug) ctx.getBean("transformerDebug"); + + transformer = new AbstractContentTransformer2() + { + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } + + @Override + protected void transformInternal(ContentReader reader, ContentWriter writer, + TransformationOptions options) throws Exception + { + } + }; + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); + + limits = new TransformationOptionLimits(); + options = new TransformationOptions(); + } + + private void addMimetypeLimits(String source, String target, TransformationOptionLimits limits) + { + if (mimetypeLimits == null) + { + mimetypeLimits = new HashMap>(); + } + + Map targetLimits = mimetypeLimits.get(source); + if (targetLimits == null) + { + targetLimits = new HashMap(); + mimetypeLimits.put(source, targetLimits); + } + + targetLimits.put(target, limits); + } + + @Test + public void testTimeoutMs() throws Exception + { + long value = 1234; + transformer.setTimeoutMs(value); + long actual = transformer.getTimeoutMs(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testReadLimitTimeMs() throws Exception + { + long value = 1234; + transformer.setReadLimitTimeMs(value); + long actual = transformer.getReadLimitTimeMs(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testMaxSourceSizeKBytes() throws Exception + { + long value = 1234; + transformer.setMaxSourceSizeKBytes(value); + long actual = transformer.getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testReadLimitKBytes() throws Exception + { + long value = 1234; + transformer.setReadLimitKBytes(value); + long actual = transformer.getReadLimitKBytes(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testMaxPages() throws Exception + { + int value = 1234; + transformer.setMaxPages(value); + int actual = transformer.getMaxPages(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testPageLimit() throws Exception + { + int value = 1234; + transformer.setPageLimit(value); + int actual = transformer.getPageLimit(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testMimetypeLimit() throws Exception + { + long value = 1234; + limits.setMaxSourceSizeKBytes(value); + addMimetypeLimits(A, B, limits); + + transformer.setMimetypeLimits(mimetypeLimits); + long actual = transformer.getLimits(A, B, options).getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value, actual); + + actual = transformer.getLimits(A, C, options).getMaxSourceSizeKBytes(); + assertEquals("Other values should not be set", -1, actual); + } + + @Test + public void testMimetypeLimitTargetWildcard() throws Exception + { + long value = 1234; + limits.setMaxSourceSizeKBytes(value); + addMimetypeLimits(A, "*", limits); + + transformer.setMimetypeLimits(mimetypeLimits); + long actual = transformer.getLimits(A, B, options).getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value, actual); + + actual = transformer.getLimits(B, A, options).getMaxSourceSizeKBytes(); + assertEquals("Other values should not be set", -1, actual); + } + + @Test + public void testMimetypeLimitSourceWildcard() throws Exception + { + long value = 1234; + limits.setMaxSourceSizeKBytes(value); + addMimetypeLimits("*", B, limits); + + transformer.setMimetypeLimits(mimetypeLimits); + long actual = transformer.getLimits(A, B, options).getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value, actual); + + actual = transformer.getLimits(B, A, options).getMaxSourceSizeKBytes(); + assertEquals("Other values should not be set", -1, actual); + } + + @Test + public void testPassedInOptions() throws Exception + { + long value = 1234; + limits.setMaxSourceSizeKBytes(value+1); + addMimetypeLimits(A, B, limits); + + transformer.setMimetypeLimits(mimetypeLimits); + long actual = transformer.getLimits(A, B, options).getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value+1, actual); + + options.setMaxSourceSizeKBytes(value); + actual = transformer.getLimits(A, B, options).getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testIsTransformableSize() throws Exception + { + long kValue = 12; + long byteValue = kValue*1024; + + // Not set mimetype limits yet + assertTrue("No limits so should have been ok", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // Set limit for A to B mimetypes and test + limits.setMaxSourceSizeKBytes(kValue); + addMimetypeLimits(A, B, limits); + transformer.setMimetypeLimits(mimetypeLimits); + + assertTrue("Size is less than limit so should have been ok", + transformer.isTransformableSize(A, byteValue-1, B, options)); + assertTrue("Size is equal to limit so should have been ok", + transformer.isTransformableSize(A, byteValue, B, options)); + assertFalse("Size is greater than limit so should not have failed", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // With a mimetype that does not have any specific limits + assertTrue("No limits so should have been ok", + transformer.isTransformableSize(C, byteValue+1, B, options)); + assertTrue("No limits so should have been ok", + transformer.isTransformableSize(A, byteValue+1, C, options)); + assertTrue("No limits so should have been ok", + transformer.isTransformableSize(C, byteValue+1, C, options)); + + // Clear the mimetype limits and double check + limits.setMaxSourceSizeKBytes(-1); + + assertTrue("No limits so should have been ok", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // Check for combinations with transformer limits + + // a) Using just transformer limit to start with + transformer.setMaxSourceSizeKBytes(kValue); + assertTrue("Size is equal to limit so should have been ok", + transformer.isTransformableSize(A, byteValue, B, options)); + assertFalse("Size is greater than limit so should not have failed", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // b) combination where transformer limit is used + transformer.setMaxSourceSizeKBytes(kValue+1); + limits.setMaxSourceSizeKBytes(kValue); + assertTrue("Size is equal to limit so should have been ok", + transformer.isTransformableSize(A, byteValue, B, options)); + assertFalse("Size is greater than limit so should not have failed", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // c) combination where mimetype limit is used + transformer.setMaxSourceSizeKBytes(kValue); + limits.setMaxSourceSizeKBytes(kValue+1); + assertTrue("Size is equal to limit so should have been ok", + transformer.isTransformableSize(A, byteValue, B, options)); + assertFalse("Size is greater than limit so should not have failed", + transformer.isTransformableSize(A, byteValue+1, B, options)); + } + + @Test + public void testIsTransformableSizeWithPageLimit() throws Exception + { + long kValue = 12; + long byteValue = kValue*1024; + + transformer.setMaxSourceSizeKBytes(kValue); + transformer.setPageLimitsSuported(true); + + // Test works as normal before setting the pageLimit + assertTrue("Size is less than limit so should have been ok", + transformer.isTransformableSize(A, byteValue-1, B, options)); + assertTrue("Size is equal to limit so should have been ok", + transformer.isTransformableSize(A, byteValue, B, options)); + assertFalse("Size is greater than limit so should not have failed", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // test with pageLimit set + options.getLimits().setPageLimit(1); + assertTrue("Size is greater than limit BUT pageLimit is set so should have been ok", + transformer.isTransformableSize(A, byteValue+1, B, options)); + } + + @Test + public void testSetReaderLimits() throws Exception + { + AbstractContentReader reader = new DummyAbstractContentReader(0, 0); + + long value = 1234; + transformer.setTimeoutMs(value); + + assertEquals("Limit should not have been set in the reader", null, reader.getLimits()); + + transformer.setReaderLimits(reader, null, options); + assertEquals("Limit should have been set in the reader", value, reader.getLimits().getTimeoutMs()); + + options.setTimeoutMs(--value); + transformer.setReaderLimits(reader, null, options); + assertEquals("Limit should have been set in the reader", value, reader.getLimits().getTimeoutMs()); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java index 4c7349eb97..1e66141149 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerTest.java @@ -68,6 +68,7 @@ public abstract class AbstractContentTransformerTest extends TestCase protected ServiceRegistry serviceRegistry; protected MimetypeService mimetypeService; + protected TransformerDebug transformerDebug; /** * Fetches a transformer to test for a given transformation. The transformer @@ -94,6 +95,7 @@ public abstract class AbstractContentTransformerTest extends TestCase // Grab other useful beans serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); mimetypeService = serviceRegistry.getMimetypeService(); + transformerDebug = (TransformerDebug) ctx.getBean("transformerDebug"); // perform a little cleaning up long now = System.currentTimeMillis(); TempFileProvider.TempFileCleanerJob.removeFiles(now); @@ -212,7 +214,14 @@ public abstract class AbstractContentTransformerTest extends TestCase { // attempt to get a source file for each mimetype String[] quickFiles = getQuickFilenames(sourceMimetype); - sb.append(" Source Files: ").append(quickFiles).append("\n"); + sb.append(" Source Files: "); + for (String quickFile: quickFiles) + { + sb.append(quickFile); + sb.append(' '); + } + sb.append("\n"); + for (String quickFile : quickFiles) { @@ -232,7 +241,7 @@ public abstract class AbstractContentTransformerTest extends TestCase // must we test the transformation? ContentTransformer transformer = getTransformer(sourceMimetype, targetMimetype); - if (transformer == null || transformer.isTransformable(sourceMimetype, targetMimetype, null) == false) + if (transformer == null || transformer.isTransformable(sourceMimetype, -1, targetMimetype, null) == false) { // no transformer continue; diff --git a/source/java/org/alfresco/repo/content/transform/ArchiveContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/ArchiveContentTransformerTest.java index 9b6f6da54d..880f7febdf 100644 --- a/source/java/org/alfresco/repo/content/transform/ArchiveContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/ArchiveContentTransformerTest.java @@ -45,6 +45,8 @@ public class ArchiveContentTransformerTest extends AbstractContentTransformerTes super.setUp(); transformer = new ArchiveContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) @@ -54,9 +56,9 @@ public class ArchiveContentTransformerTest extends AbstractContentTransformerTes public void testIsTransformable() throws Exception { - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_ZIP, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable("application/x-tar", MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable("application/x-gtar", MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_ZIP, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable("application/x-tar", -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable("application/x-gtar", -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); } @Override diff --git a/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformerTest.java index 3c3dda2f20..565251833e 100644 --- a/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,7 +28,7 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public class BinaryPassThroughContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private BinaryPassThroughContentTransformer transformer; @Override public void setUp() throws Exception @@ -36,6 +36,8 @@ public class BinaryPassThroughContentTransformerTest extends AbstractContentTran super.setUp(); transformer = new BinaryPassThroughContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -51,13 +53,13 @@ public class BinaryPassThroughContentTransformerTest extends AbstractContentTran TransformationOptions options = new TransformationOptions(); boolean reliability = false; - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); assertFalse("Mimetype should not be supported", reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_XML, MimetypeMap.MIMETYPE_XML, options); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_XML, -1, MimetypeMap.MIMETYPE_XML, options); assertFalse("Mimetype should not be supported", reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_WORD, options); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_WORD, options); assertTrue("Mimetype should be supported", reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_EXCEL, options); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_EXCEL, options); assertTrue("Mimetype should be supported", reliability); } } diff --git a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java index a6caa6d52a..7063739cd6 100644 --- a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java @@ -130,7 +130,7 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple * * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { boolean result = true; String currentSourceMimetype = sourceMimetype; @@ -178,6 +178,7 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple } } + boolean first = true; Iterator transformerIterator = transformers.iterator(); Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); while (transformerIterator.hasNext()) @@ -195,8 +196,9 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple currentTargetMimetype = intermediateMimetypeIterator.next(); } - // check we can tranform the current stage - if (transformer.isTransformable(currentSourceMimetype, currentTargetMimetype, options) == false) + // check we can tranform the current stage (using -1 if not the first stage as we can't know the size) + long size = first ? sourceSize : -1; + if (transformer.isTransformable(currentSourceMimetype, size, currentTargetMimetype, options) == false) { result = false; break; @@ -204,6 +206,12 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple // move on currentSourceMimetype = currentTargetMimetype; + first = false; + } + + if (result && !isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options)) + { + result = false; } return result; @@ -255,4 +263,15 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple { return Collections.unmodifiableList(intermediateMimetypes); } + + /** + * @deprecated This method should no longer be called as the overloaded method + * that calls it has the overridden. + */ + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } } diff --git a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformerTest.java index ae4548a840..6316d101cd 100644 --- a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -45,12 +45,12 @@ public class ComplexContentTransformerTest extends AbstractContentTransformerTes ContentTransformer unoTransformer = (ContentTransformer) ctx.getBean("transformer.OpenOffice"); ContentTransformer pdfBoxTransformer = (ContentTransformer) ctx.getBean("transformer.PdfBox"); // make sure that they are working for this test - if (unoTransformer.isTransformable(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()) == false) + if (unoTransformer.isTransformable(MimetypeMap.MIMETYPE_PPT, -1, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()) == false) { isAvailable = false; return; } - else if (pdfBoxTransformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()) == false) + else if (pdfBoxTransformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()) == false) { isAvailable = false; return; @@ -62,6 +62,7 @@ public class ComplexContentTransformerTest extends AbstractContentTransformerTes transformer = new ComplexContentTransformer(); transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); // set the transformer list List transformers = new ArrayList(2); transformers.add(unoTransformer); @@ -86,9 +87,9 @@ public class ComplexContentTransformerTest extends AbstractContentTransformerTes { return; } - boolean reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()); + boolean reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, -1, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()); assertEquals("Mimetype should not be supported", false, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); } } diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ContentTransformer.java index f18b3cbde2..f644ba6aef 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -33,16 +33,22 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public interface ContentTransformer extends ContentWorker { + /** + * @deprecated use version with extra sourceSize parameter. + */ + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** * Indicates whether the provided source mimetype can be transformed into the target mimetype with * the options specified by this content transformer. * * @param sourceMimetype the source mimetype - * @param destinationMimetype the destination mimetype + * @param sourceSize the size (bytes) of the source. If negative it is unknown. + * @param targetMimetype the target mimetype * @param options the transformation options * @return boolean true if this content transformer can satify the mimetypes and options specified, false otherwise */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options); + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options); /** * Indicates whether given the provided transformation parmaters this transformer can prvide an explict diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java index d15a3364d4..c1206755db 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java @@ -71,13 +71,21 @@ public class ContentTransformerRegistry } /** - * Gets the best transformer possible. This is a combination of the most reliable - * and the most performant transformer. + * @deprecated use overloaded version with sourceSize parameter. */ public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return getTransformer(sourceMimetype, -1, targetMimetype, options); + } + + /** + * Gets the best transformer possible. This is a combination of the most reliable + * and the most performant transformer. + */ + public ContentTransformer getTransformer(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { // Get the sorted list of transformers - List transformers = getActiveTransformers(sourceMimetype, targetMimetype, options); + List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); // select the most performant transformer ContentTransformer bestTransformer = null; @@ -92,10 +100,10 @@ public class ContentTransformerRegistry /** * @since 3.5 */ - public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) + public List getActiveTransformers(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { // Get the list of transformers - List transformers = findTransformers(sourceMimetype, targetMimetype, options); + List transformers = findTransformers(sourceMimetype, sourceSize, targetMimetype, options); final Map activeTransformers = new HashMap(); @@ -103,7 +111,7 @@ public class ContentTransformerRegistry for (ContentTransformer transformer : transformers) { // Transformability can be dynamic, i.e. it may have become unusable - if (transformer.isTransformable(sourceMimetype, targetMimetype, options) == false) + if (transformer.isTransformable(sourceMimetype, sourceSize, targetMimetype, options) == false) { // It is unreliable now. continue; @@ -135,10 +143,10 @@ public class ContentTransformerRegistry * @return Returns best transformer for the translation - null if all * score 0.0 on reliability */ - private List findTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) + private List findTransformers(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { // search for a simple transformer that can do the job - List transformers = findDirectTransformers(sourceMimetype, targetMimetype, options); + List transformers = findDirectTransformers(sourceMimetype, sourceSize, targetMimetype, options); // get the complex transformers that can do the job List complexTransformers = findComplexTransformer(sourceMimetype, targetMimetype, options); transformers.addAll(complexTransformers); @@ -161,7 +169,7 @@ public class ContentTransformerRegistry * @return Returns the most reliable transformers for the translation - empty list if there * are none. */ - private List findDirectTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) + private List findDirectTransformers(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { //double maxReliability = 0.0; @@ -171,7 +179,7 @@ public class ContentTransformerRegistry // loop through transformers for (ContentTransformer transformer : this.transformers) { - if (transformer.isTransformable(sourceMimetype, targetMimetype, options) == true) + if (transformer.isTransformable(sourceMimetype, sourceSize, targetMimetype, options) == true) { if (transformer.isExplicitTransformation(sourceMimetype, targetMimetype, options) == true) { diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java index f989ef3c53..ea4fb31bf1 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -72,17 +72,17 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe // create the dummyRegistry dummyRegistry = new ContentTransformerRegistry(); // create some dummy transformers for reliability tests - new DummyTransformer(mimetypeService, dummyRegistry, A, B, 10L); - new DummyTransformer(mimetypeService, dummyRegistry, A, B, 10L); - new DummyTransformer(mimetypeService, dummyRegistry, A, C, 10L); - new DummyTransformer(mimetypeService, dummyRegistry, A, C, 10L); - new DummyTransformer(mimetypeService, dummyRegistry, B, C, 10L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, B, 10L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, B, 10L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, C, 10L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, C, 10L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, B, C, 10L); // create some dummy transformers for speed tests - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 20L); - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 30L); - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 10L); // the fast one - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 25L); - new DummyTransformer(mimetypeService, dummyRegistry, A, D, 25L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, D, 20L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, D, 30L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, D, 10L); // the fast one + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, D, 25L); + new DummyTransformer(mimetypeService, transformerDebug, dummyRegistry, A, D, 25L); } /** @@ -99,17 +99,17 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe */ protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options) { - return registry.getTransformer(sourceMimetype, targetMimetype, options); + return registry.getTransformer(sourceMimetype, -1, targetMimetype, options); } public void testNullRetrieval() throws Exception { ContentTransformer transformer = null; - transformer = dummyRegistry.getTransformer(C, B, OPTIONS); + transformer = dummyRegistry.getTransformer(C, -1, B, OPTIONS); assertNull("No transformer expected", transformer); - transformer = dummyRegistry.getTransformer(C, A, OPTIONS); + transformer = dummyRegistry.getTransformer(C, -1, A, OPTIONS); assertNull("No transformer expected", transformer); - transformer = dummyRegistry.getTransformer(B, A, OPTIONS); + transformer = dummyRegistry.getTransformer(B, -1, A, OPTIONS); assertNull("No transformer expected", transformer); } @@ -117,11 +117,11 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe { ContentTransformer transformer = null; // B -> C expect true - transformer = dummyRegistry.getTransformer(B, C, OPTIONS); + transformer = dummyRegistry.getTransformer(B, -1, C, OPTIONS); //transformer = dummyRegistry.getTransformer(B, C, OPTIONS); assertNotNull("No transformer found", transformer); - assertTrue("Incorrect reliability", transformer.isTransformable(B, C, OPTIONS)); - assertFalse("Incorrect reliability", transformer.isTransformable(C, B, OPTIONS)); + assertTrue("Incorrect reliability", transformer.isTransformable(B, -1, C, OPTIONS)); + assertFalse("Incorrect reliability", transformer.isTransformable(C, -1, B, OPTIONS)); } /** @@ -132,13 +132,13 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe public void testPerformanceRetrieval() throws Exception { // A -> D expect 1.0, 10ms - ContentTransformer transformer1 = dummyRegistry.getTransformer(A, D, OPTIONS); - assertTrue("Incorrect reliability", transformer1.isTransformable(A, D, OPTIONS)); - assertFalse("Incorrect reliability", transformer1.isTransformable(D, A, OPTIONS)); + ContentTransformer transformer1 = dummyRegistry.getTransformer(A, -1, D, OPTIONS); + assertTrue("Incorrect reliability", transformer1.isTransformable(A, -1, D, OPTIONS)); + assertFalse("Incorrect reliability", transformer1.isTransformable(D, -1, A, OPTIONS)); assertEquals("Incorrect transformation time", 10L, transformer1.getTransformationTime()); // A -> D has 10, 20, 25, 25, 30 - List activeTransformers = dummyRegistry.getActiveTransformers(A, D, OPTIONS); + List activeTransformers = dummyRegistry.getActiveTransformers(A, -1, D, OPTIONS); assertEquals("Not all found", 5, activeTransformers.size()); assertEquals("Incorrect order", 10L, activeTransformers.get(0).getTransformationTime()); assertEquals("Incorrect order", 20L, activeTransformers.get(1).getTransformationTime()); @@ -150,7 +150,7 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe ((DummyTransformer)activeTransformers.get(2)).disable(); ((DummyTransformer)activeTransformers.get(4)).disable(); - activeTransformers = dummyRegistry.getActiveTransformers(A, D, OPTIONS); + activeTransformers = dummyRegistry.getActiveTransformers(A, -1, D, OPTIONS); assertEquals("Not all found", 3, activeTransformers.size()); assertEquals("Incorrect order", 10L, activeTransformers.get(0).getTransformationTime()); assertEquals("Incorrect order", 20L, activeTransformers.get(1).getTransformationTime()); @@ -161,15 +161,15 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe { ContentTransformer transformer = null; // A -> B expect 0.6 - transformer = dummyRegistry.getTransformer(A, B, OPTIONS); + transformer = dummyRegistry.getTransformer(A, -1, B, OPTIONS); assertNotNull("No transformer found", transformer); - assertTrue("Incorrect reliability", transformer.isTransformable(A, B, OPTIONS)); - assertFalse("Incorrect reliability", transformer.isTransformable(B, A, OPTIONS)); + assertTrue("Incorrect reliability", transformer.isTransformable(A, -1, B, OPTIONS)); + assertFalse("Incorrect reliability", transformer.isTransformable(B, -1, A, OPTIONS)); // A -> C expect 1.0 - transformer = dummyRegistry.getTransformer(A, C, OPTIONS); + transformer = dummyRegistry.getTransformer(A, -1, C, OPTIONS); assertNotNull("No transformer found", transformer); - assertTrue("Incorrect reliability", transformer.isTransformable(A, C, OPTIONS)); - assertFalse("Incorrect reliability", transformer.isTransformable(C, A, OPTIONS)); + assertTrue("Incorrect reliability", transformer.isTransformable(A, -1, C, OPTIONS)); + assertFalse("Incorrect reliability", transformer.isTransformable(C, -1, A, OPTIONS)); } /** @@ -180,9 +180,9 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe { AbstractContentTransformer2 dummyTransformer = new DummyTransformer( mimetypeService, - dummyRegistry, - MimetypeMap.MIMETYPE_FLASH, MimetypeMap.MIMETYPE_EXCEL, - 12345); + transformerDebug, + dummyRegistry, MimetypeMap.MIMETYPE_FLASH, + MimetypeMap.MIMETYPE_EXCEL, 12345); // set an explicit transformation ExplictTransformationDetails key = new ExplictTransformationDetails( @@ -193,7 +193,7 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe dummyTransformer.register(); // get the appropriate transformer for the bizarre mapping - ContentTransformer checkTransformer = dummyRegistry.getTransformer(MimetypeMap.MIMETYPE_FLASH, MimetypeMap.MIMETYPE_EXCEL, OPTIONS); + ContentTransformer checkTransformer = dummyRegistry.getTransformer(MimetypeMap.MIMETYPE_FLASH, -1, MimetypeMap.MIMETYPE_EXCEL, OPTIONS); assertNotNull("No explicit transformer found", checkTransformer); assertTrue("Expected explicit transformer", dummyTransformer == checkTransformer); @@ -212,11 +212,12 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe public DummyTransformer( MimetypeService mimetypeService, - ContentTransformerRegistry registry, - String sourceMimetype, String targetMimetype, - long transformationTime) + TransformerDebug transformerDebug, + ContentTransformerRegistry registry, String sourceMimetype, + String targetMimetype, long transformationTime) { super.setMimetypeService(mimetypeService); + super.setTransformerDebug(transformerDebug); super.setRegistry(registry); this.sourceMimetype = sourceMimetype; this.targetMimetype = targetMimetype; diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java index 96fc5066aa..ac6ada5789 100644 --- a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java @@ -80,9 +80,9 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl /** * - * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) + * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, long sourceSize, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { // For this transformer to be considered operational, there must be at least one transformer // in the chain that can perform for us. @@ -90,15 +90,27 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl for (ContentTransformer ct : this.transformers) { - if (ct.isTransformable(sourceMimetype, targetMimetype, options)) + if (ct.isTransformable(sourceMimetype, sourceSize, targetMimetype, options)) { - result = true; + // There may be size limits on this transformer as well as those it contains. + result = isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); break; } } return result; } + + /** + * @deprecated This method should no longer be called as the overloaded method + * that calls it has the overridden. + */ + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } public boolean isExplicitTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options) { @@ -130,7 +142,7 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl // then move on to the next transformer. In the event that they all fail, we will throw // the final exception. Exception transformationException = null; - + for (int i = 0; i < transformers.size(); i++) { int oneBasedCount = i + 1; @@ -198,6 +210,7 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl // At this point we have tried all transformers in the sequence without apparent success. if (transformationException != null) { + transformerDebug.debug("No more transformations to failover to"); if (logger.isDebugEnabled()) { logger.debug("All transformations were unsuccessful. Throwing latest exception.", transformationException); diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java index ae61de91fe..f0faf92015 100644 --- a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -56,6 +56,7 @@ public class FailoverContentTransformerTest extends AbstractContentTransformerTe transformer = (FailoverContentTransformer) failoverAppContext.getBean("transformer.failover.Test-FailThenSucceed"); transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -70,7 +71,7 @@ public class FailoverContentTransformerTest extends AbstractContentTransformerTe { // The MIME types here are rather arbitrary - boolean reliability = transformer.isTransformable(sourceMimeType, targetMimeType, new TransformationOptions()); + boolean reliability = transformer.isTransformable(sourceMimeType, -1, targetMimeType, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); } } diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java index ff5b816cc3..feadb5e3fd 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformerTest.java @@ -28,13 +28,15 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public class HtmlParserContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private HtmlParserContentTransformer transformer; @Override public void setUp() throws Exception { super.setUp(); transformer = new HtmlParserContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) @@ -50,11 +52,11 @@ public class HtmlParserContentTransformerTest extends AbstractContentTransformer public void checkIsTransformable() throws Exception { // check reliability - boolean reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_HTML, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + boolean reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_HTML, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); assertTrue(reliability); // plain text to plain text is supported // check other way around - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_HTML, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions()); assertFalse(reliability); // plain text to plain text is not supported } } diff --git a/source/java/org/alfresco/repo/content/transform/MailContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/MailContentTransformerTest.java index e199e49c54..3b410590f1 100644 --- a/source/java/org/alfresco/repo/content/transform/MailContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/MailContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -35,7 +35,7 @@ import org.alfresco.util.TempFileProvider; */ public class MailContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private MailContentTransformer transformer; @Override public void setUp() throws Exception @@ -43,6 +43,8 @@ public class MailContentTransformerTest extends AbstractContentTransformerTest super.setUp(); transformer = new MailContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -55,8 +57,8 @@ public class MailContentTransformerTest extends AbstractContentTransformerTest public void testIsTransformable() throws Exception { - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_OUTLOOK_MSG, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_OUTLOOK_MSG, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); } /** diff --git a/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformerTest.java index 88ec12dace..b69b281d09 100644 --- a/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -39,7 +39,7 @@ import de.schlichtherle.io.FileOutputStream; */ public class MediaWikiContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private MediaWikiContentTransformer transformer; private static final String WIKI_TEXT = "== This is a title ==\n" + @@ -69,6 +69,8 @@ public class MediaWikiContentTransformerTest extends AbstractContentTransformerT { super.setUp(); transformer = new MediaWikiContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) @@ -84,8 +86,8 @@ public class MediaWikiContentTransformerTest extends AbstractContentTransformerT public void testIsTransformable() throws Exception { // check reliability - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_MEDIAWIKI, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_HTML, MimetypeMap.MIMETYPE_TEXT_MEDIAWIKI, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_MEDIAWIKI, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_HTML, -1, MimetypeMap.MIMETYPE_TEXT_MEDIAWIKI, new TransformationOptions())); } public void testMediaWikiToHTML() throws Exception diff --git a/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerTest.java index 29269a7efd..d3728cb03c 100644 --- a/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -39,7 +39,7 @@ public class OpenOfficeContentTransformerTest extends AbstractContentTransformer private static String MIMETYPE_RUBBISH = "text/rubbish"; private ContentTransformerWorker worker; - private ContentTransformer transformer; + private ProxyContentTransformer transformer; @Override public void setUp() throws Exception @@ -47,10 +47,10 @@ public class OpenOfficeContentTransformerTest extends AbstractContentTransformer super.setUp(); this.worker = (ContentTransformerWorker) ctx.getBean("transformer.worker.OpenOffice"); - ProxyContentTransformer transformer = new ProxyContentTransformer(); + transformer = new ProxyContentTransformer(); transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); transformer.setWorker(this.worker); - this.transformer = transformer; } /** @@ -74,15 +74,15 @@ public class OpenOfficeContentTransformerTest extends AbstractContentTransformer // no connection return; } - boolean reliability = transformer.isTransformable(MIMETYPE_RUBBISH, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + boolean reliability = transformer.isTransformable(MIMETYPE_RUBBISH, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); assertEquals("Mimetype should not be supported", false, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MIMETYPE_RUBBISH, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MIMETYPE_RUBBISH, new TransformationOptions()); assertEquals("Mimetype should not be supported", false, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_XHTML, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_XHTML, new TransformationOptions()); assertEquals("Mimetype should not be supported", false, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_WORD, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_WORD, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); } diff --git a/source/java/org/alfresco/repo/content/transform/PdfBoxContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/PdfBoxContentTransformerTest.java index bcceb505de..d83eae72b3 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfBoxContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/PdfBoxContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,7 +28,7 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public class PdfBoxContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private PdfBoxContentTransformer transformer; @Override public void setUp() throws Exception @@ -36,6 +36,8 @@ public class PdfBoxContentTransformerTest extends AbstractContentTransformerTest super.setUp(); transformer = new PdfBoxContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -48,9 +50,9 @@ public class PdfBoxContentTransformerTest extends AbstractContentTransformerTest public void testIsTransformable() throws Exception { - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); } } diff --git a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java index 2cb042393a..ea3f0c0a32 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformerTest.java @@ -36,7 +36,7 @@ import org.alfresco.util.TempFileProvider; */ public class PdfBoxPdfToImageContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private PdfBoxPdfToImageContentTransformer transformer; @Override public void setUp() throws Exception @@ -44,7 +44,8 @@ public class PdfBoxPdfToImageContentTransformerTest extends AbstractContentTrans super.setUp(); transformer = new PdfBoxPdfToImageContentTransformer(); - ((ContentTransformerHelper)transformer).setMimetypeService(mimetypeService); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -57,7 +58,7 @@ public class PdfBoxPdfToImageContentTransformerTest extends AbstractContentTrans public void testIsTransformable() throws Exception { - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_IMAGE_PNG, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_IMAGE_PNG, new TransformationOptions())); } /** diff --git a/source/java/org/alfresco/repo/content/transform/PoiContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/PoiContentTransformerTest.java index 28e49c261a..06386995c6 100644 --- a/source/java/org/alfresco/repo/content/transform/PoiContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/PoiContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,7 +28,7 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public class PoiContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private PoiContentTransformer transformer; @Override public void setUp() throws Exception @@ -36,6 +36,8 @@ public class PoiContentTransformerTest extends AbstractContentTransformerTest super.setUp(); transformer = new PoiContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -48,22 +50,22 @@ public class PoiContentTransformerTest extends AbstractContentTransformerTest public void testIsTransformable() throws Exception { - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_WORD, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_WORD, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_PPT, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_PPT, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PPT, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_OUTLOOK_MSG, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_OUTLOOK_MSG, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OUTLOOK_MSG, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); // Doesn't claim excel - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); } } diff --git a/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java index 02f6405aad..8963fd5343 100644 --- a/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java @@ -72,19 +72,30 @@ public class PoiHssfContentTransformer extends TikaPoweredContentTransformer * We support transforming to HTML, XML, Text or CSV */ @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { if(sourceMimeTypes.contains(sourceMimetype) && MimetypeMap.MIMETYPE_TEXT_CSV.equals(targetMimetype)) { - // Special case for CSV - return true; + // Special case for CSV + return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); } // Otherwise fall back on the default Tika rules - return super.isTransformable(sourceMimetype, targetMimetype, options); + return super.isTransformable(sourceMimetype, sourceSize, targetMimetype, options); } + /** + * @deprecated This method should no longer be called as the overloaded method + * that calls it has been overridden. + */ + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return isTransformable(sourceMimetype, -1, targetMimetype, options); + } + /** * Make sure we win over openoffice when it comes to producing * HTML diff --git a/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformerTest.java index 29ab1d8122..1a8e1ccbfc 100644 --- a/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -36,7 +36,7 @@ import org.alfresco.util.TempFileProvider; */ public class PoiHssfContentTransformerTest extends TikaPoweredContentTransformerTest { - private ContentTransformer transformer; + private PoiHssfContentTransformer transformer; @Override public void setUp() throws Exception @@ -44,6 +44,8 @@ public class PoiHssfContentTransformerTest extends TikaPoweredContentTransformer super.setUp(); transformer = new PoiHssfContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } @Override @@ -63,11 +65,11 @@ public class PoiHssfContentTransformerTest extends TikaPoweredContentTransformer public void testIsTransformable() throws Exception { - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_EXCEL, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_TEXT_CSV, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_EXCEL, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_TEXT_CSV, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); } public void testCsvOutput() throws Exception diff --git a/source/java/org/alfresco/repo/content/transform/PoiOOXMLContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/PoiOOXMLContentTransformerTest.java index aea7de61d2..6f29a9a974 100644 --- a/source/java/org/alfresco/repo/content/transform/PoiOOXMLContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/PoiOOXMLContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,7 +28,7 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public class PoiOOXMLContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private PoiOOXMLContentTransformer transformer; @Override public void setUp() throws Exception @@ -36,6 +36,8 @@ public class PoiOOXMLContentTransformerTest extends AbstractContentTransformerTe super.setUp(); transformer = new PoiOOXMLContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -48,19 +50,19 @@ public class PoiOOXMLContentTransformerTest extends AbstractContentTransformerTe public void testIsTransformable() throws Exception { - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_WORDPROCESSING, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_PRESENTATION, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENXML_SPREADSHEET, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); } } diff --git a/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java index 79a79159c4..071a8a6dc6 100644 --- a/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java @@ -22,6 +22,7 @@ import net.sf.jooreports.converter.DocumentFormatRegistry; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; import org.alfresco.service.cmr.repository.TransformationOptions; /** @@ -65,6 +66,18 @@ public class ProxyContentTransformer extends AbstractContentTransformer2 protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception { - this.worker.transform(reader, writer, options); - } + TransformationOptionLimits original = options.getLimits(); + try + { + // Combine the transformer's limit values into the options so they are available to the worker + options.setLimits(getLimits(reader, writer, options)); + + // Perform the transformation + this.worker.transform(reader, writer, options); + } + finally + { + options.setLimits(original); + } + } } diff --git a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerTest.java index 8fc5e7ad75..40fdbb8a62 100644 --- a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerTest.java @@ -73,8 +73,11 @@ public class RuntimeExecutableContentTransformerTest extends BaseAlfrescoTestCas // initialise so that it doesn't score 0 worker.afterPropertiesSet(); + TransformerDebug transformerDebug = (TransformerDebug) ctx.getBean("transformerDebug"); + ProxyContentTransformer transformer = new ProxyContentTransformer(); transformer.setMimetypeService(serviceRegistry.getMimetypeService()); + transformer.setTransformerDebug(transformerDebug); transformer.setWorker(worker); this.transformer = transformer; } diff --git a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java index 7198b72c9f..9537fed4b6 100644 --- a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java @@ -256,10 +256,11 @@ public class RuntimeExecutableContentTransformerWorker extends ContentTransforme reader.getContent(sourceFile); // execute the transformation command + long timeoutMs = options.getTimeoutMs(); ExecutionResult result = null; try { - result = transformCommand.execute(properties); + result = transformCommand.execute(properties, timeoutMs); } catch (Throwable e) { diff --git a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java index 2f95e45f44..742c36d21e 100644 --- a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java @@ -118,7 +118,7 @@ public class StringExtractingContentTransformer extends AbstractContentTransform charWriter = new OutputStreamWriter(writer.getContentOutputStream(), writer.getEncoding()); } // copy from the one to the other - char[] buffer = new char[1024]; + char[] buffer = new char[8192]; int readCount = 0; while (readCount > -1) { diff --git a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformerTest.java index 17c3551b3a..d00b5f0932 100644 --- a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformerTest.java @@ -56,7 +56,7 @@ public class StringExtractingContentTransformerTest extends AbstractContentTrans } } - private ContentTransformer transformer; + private StringExtractingContentTransformer transformer; /** the final destination of transformations */ private ContentWriter targetWriter; @@ -66,6 +66,8 @@ public class StringExtractingContentTransformerTest extends AbstractContentTrans super.setUp(); transformer = new StringExtractingContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); targetWriter = new FileContentWriter(getTempFile()); targetWriter.setMimetype("text/plain"); targetWriter.setEncoding("UTF-8"); @@ -112,7 +114,7 @@ public class StringExtractingContentTransformerTest extends AbstractContentTrans ContentReader reader = writeContent("text/plain", "MacDingbat"); // check transformability - assertTrue(transformer.isTransformable(reader.getMimetype(), targetWriter.getMimetype(), new TransformationOptions())); + assertTrue(transformer.isTransformable(reader.getMimetype(), -1, targetWriter.getMimetype(), new TransformationOptions())); // transform transformer.transform(reader, targetWriter); @@ -128,7 +130,7 @@ public class StringExtractingContentTransformerTest extends AbstractContentTrans ContentReader reader = writeContent("text/xml", "MacDingbat"); // check transformability - assertTrue(transformer.isTransformable(reader.getMimetype(), targetWriter.getMimetype(), new TransformationOptions())); + assertTrue(transformer.isTransformable(reader.getMimetype(), -1, targetWriter.getMimetype(), new TransformationOptions())); // transform transformer.transform(reader, targetWriter); diff --git a/source/java/org/alfresco/repo/content/transform/TextMiningContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/TextMiningContentTransformerTest.java index 5e0d677ba6..3005d57d43 100644 --- a/source/java/org/alfresco/repo/content/transform/TextMiningContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/TextMiningContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -36,7 +36,7 @@ import org.alfresco.util.TempFileProvider; */ public class TextMiningContentTransformerTest extends AbstractContentTransformerTest { - private ContentTransformer transformer; + private TextMiningContentTransformer transformer; @Override public void setUp() throws Exception @@ -44,6 +44,8 @@ public class TextMiningContentTransformerTest extends AbstractContentTransformer super.setUp(); transformer = new TextMiningContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -63,8 +65,8 @@ public class TextMiningContentTransformerTest extends AbstractContentTransformer public void testIsTransformable() throws Exception { - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_WORD, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_WORD, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); } /** diff --git a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java index 01da29f134..8c3269bdbc 100644 --- a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,24 +18,31 @@ */ package org.alfresco.repo.content.transform; +import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.Reader; import java.nio.charset.Charset; +import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.alfresco.service.cmr.repository.TransformationOptionPair; +import org.alfresco.service.cmr.repository.TransformationOptionPair.Action; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.pdfbox.TextToPDF; import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; import org.apache.pdfbox.pdmodel.font.PDTrueTypeFont; import org.apache.pdfbox.pdmodel.font.PDType1Font; -import org.apache.tika.io.IOUtils; - /** * Makes use of the {@link http://www.pdfbox.org/ PDFBox} library's TextToPDF utility. * @@ -46,11 +53,12 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 { private static final Log logger = LogFactory.getLog(TextToPdfContentTransformer.class); - private TextToPDF transformer; + private PagedTextToPDF transformer; public TextToPdfContentTransformer() { - transformer = new TextToPDF(); + setPageLimitsSuported(true); + transformer = new PagedTextToPDF(); } public void setStandardFont(String fontName) @@ -92,7 +100,7 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 /** * Only supports Text to PDF */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { if ( (!MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && !MimetypeMap.MIMETYPE_TEXT_CSV.equals(sourceMimetype) && @@ -104,10 +112,21 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 } else { - return true; + return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); } } + /** + * @deprecated This method should no longer be called as the overloaded method + * that calls it has the overridden. + */ + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } + @Override protected void transformInternal( ContentReader reader, @@ -122,8 +141,11 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 { is = reader.getContentInputStream(); ir = buildReader(is, reader.getEncoding(), reader.getContentUrl()); - - pdf = transformer.createPDFFromText(ir); + + TransformationOptionLimits limits = getLimits(reader, writer, options); + TransformationOptionPair pageLimits = limits.getPagesPair(); + pdf = transformer.createPDFFromText(ir, pageLimits, reader.getContentUrl()); + // dump it all to the writer os = writer.getContentOutputStream(); pdf.save(os); @@ -174,4 +196,124 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 logger.debug("Processing plain text using system default encoding"); return new InputStreamReader(is); } + + private class PagedTextToPDF extends TextToPDF + { + // The following code is based on the code in TextToPDF with the addition of + // checks for page limits + public PDDocument createPDFFromText(Reader text, TransformationOptionPair pageLimits, + String contentUrl) throws IOException + { + int pageLimit = (int)pageLimits.getValue(); + PDDocument doc = null; + int pageCount = 0; + try + { + final int margin = 40; + float height = getFont().getFontDescriptor().getFontBoundingBox().getHeight()/1000; + + //calculate font height and increase by 5 percent. + height = height*getFontSize()*1.05f; + doc = new PDDocument(); + BufferedReader data = new BufferedReader( text ); + String nextLine = null; + PDPage page = new PDPage(); + PDPageContentStream contentStream = null; + float y = -1; + float maxStringLength = page.getMediaBox().getWidth() - 2*margin; + + // There is a special case of creating a PDF document from an empty string. + boolean textIsEmpty = true; + + outer: + while( (nextLine = data.readLine()) != null ) + { + + // The input text is nonEmpty. New pages will be created and added + // to the PDF document as they are needed, depending on the length of + // the text. + textIsEmpty = false; + + String[] lineWords = nextLine.trim().split( " " ); + int lineIndex = 0; + while( lineIndex < lineWords.length ) + { + StringBuffer nextLineToDraw = new StringBuffer(); + float lengthIfUsingNextWord = 0; + do + { + nextLineToDraw.append( lineWords[lineIndex] ); + nextLineToDraw.append( " " ); + lineIndex++; + if( lineIndex < lineWords.length ) + { + String lineWithNextWord = nextLineToDraw.toString() + lineWords[lineIndex]; + lengthIfUsingNextWord = + (getFont().getStringWidth( lineWithNextWord )/1000) * getFontSize(); + } + } + while( lineIndex < lineWords.length && + lengthIfUsingNextWord < maxStringLength ); + if( y < margin ) + { + if (pageLimit > 0 && pageCount++ >= pageLimit) + { + pageLimits.getAction().throwIOExceptionIfRequired("Page limit ("+pageLimit+ + ") reached.", transformerDebug); + break outer; + } + + // We have crossed the end-of-page boundary and need to extend the + // document by another page. + page = new PDPage(); + doc.addPage( page ); + if( contentStream != null ) + { + contentStream.endText(); + contentStream.close(); + } + contentStream = new PDPageContentStream(doc, page); + contentStream.setFont(getFont(), getFontSize()); + contentStream.beginText(); + y = page.getMediaBox().getHeight() - margin + height; + contentStream.moveTextPositionByAmount( + margin, y ); + } + //System.out.println( "Drawing string at " + x + "," + y ); + + if( contentStream == null ) + { + throw new IOException( "Error:Expected non-null content stream." ); + } + contentStream.moveTextPositionByAmount( 0, -height); + y -= height; + contentStream.drawString( nextLineToDraw.toString() ); + } + } + + // If the input text was the empty string, then the above while loop will have short-circuited + // and we will not have added any PDPages to the document. + // So in order to make the resultant PDF document readable by Adobe Reader etc, we'll add an empty page. + if (textIsEmpty) + { + doc.addPage(page); + } + + if( contentStream != null ) + { + contentStream.endText(); + contentStream.close(); + } + } + catch( IOException io ) + { + if( doc != null ) + { + doc.close(); + } + throw io; + } + return doc; + } + } } diff --git a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java index 5992bf4425..dbc79e45ae 100644 --- a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,6 +19,7 @@ package org.alfresco.repo.content.transform; import java.io.File; +import java.io.IOException; import java.io.StringWriter; import java.nio.charset.Charset; @@ -47,8 +48,11 @@ public class TextToPdfContentTransformerTest extends AbstractContentTransformerT super.setUp(); transformer = new TextToPdfContentTransformer(); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); transformer.setStandardFont("Times-Roman"); transformer.setFontSize(20); + transformer.setPageLimit(-1); } /** @@ -62,11 +66,11 @@ public class TextToPdfContentTransformerTest extends AbstractContentTransformerT public void testReliability() throws Exception { - boolean reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + boolean reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); assertEquals("Mimetype should not be supported", false, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); - reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_XML, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()); + reliability = transformer.isTransformable(MimetypeMap.MIMETYPE_XML, -1, MimetypeMap.MIMETYPE_PDF, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); } @@ -82,44 +86,90 @@ public class TextToPdfContentTransformerTest extends AbstractContentTransformerT String european = "En français où les choses sont accentués\n" + "En español, así"; - ContentReader reader; for(String text : new String[] {allAscii, european}) { for(String encoding : new String[] {"ISO-8859-1", "UTF-8", "UTF-16"}) { - // Get a reader for the text - reader = buildContentReader(text, Charset.forName(encoding)); - - // And a temp writer - File out = TempFileProvider.createTempFile("AlfrescoTest_", ".pdf"); - ContentWriter writer = new FileContentWriter(out); - writer.setMimetype("application/pdf"); - - // Transform to PDF - transformer.transform(reader, writer); - - // Read back in the PDF and check it - PDDocument doc = PDDocument.load(out); - PDFTextStripper textStripper = new PDFTextStripper(); - StringWriter textWriter = new StringWriter(); - textStripper.writeText(doc, textWriter); - doc.close(); - // Newlines etc may be different, so zap them String checkText = clean(text); - String roundTrip = clean(textWriter.toString()); - - // Now check it -// System.err.println("== " + encoding + " =="); -// System.err.println(roundTrip); -// System.err.println("===="); - assertEquals( - "Incorrect text in PDF when starting from text in " + encoding, - checkText, roundTrip - ); + + transformTextAndCheck(text, encoding, checkText); } } } + + public void testUnlimitedPages() throws Exception + { + transformTextAndCheckPageLength(-1); + } + + public void testLimitedTo1Page() throws Exception + { + transformTextAndCheckPageLength(1); + } + + public void testLimitedTo2Pages() throws Exception + { + transformTextAndCheckPageLength(2); + } + + public void testLimitedTo50Pages() throws Exception + { + transformTextAndCheckPageLength(50); + } + + private void transformTextAndCheckPageLength(int pageLimit) throws IOException + { + transformer.setPageLimit(pageLimit); + + int pageLength = 32; + int lines = (pageLength+10) * ((pageLimit > 0) ? pageLimit : 1); + StringBuilder sb = new StringBuilder(); + String checkText = null; + int cutoff = pageLimit * pageLength; + for (int i=1; i<=lines; i++) + { + sb.append(i); + sb.append(" I must not talk in class or feed my homework to my cat.\n"); + if (i == cutoff) + checkText = sb.toString(); + } + sb.append("\nBart\n"); + String text = sb.toString(); + checkText = (checkText == null) ? clean(text) : clean(checkText); + + transformTextAndCheck(text, "UTF-8", checkText); + } + + private void transformTextAndCheck(String text, String encoding, String checkText) + throws IOException + { + // Get a reader for the text + ContentReader reader = buildContentReader(text, Charset.forName(encoding)); + + // And a temp writer + File out = TempFileProvider.createTempFile("AlfrescoTest_", ".pdf"); + ContentWriter writer = new FileContentWriter(out); + writer.setMimetype("application/pdf"); + + // Transform to PDF + transformer.transform(reader, writer); + + // Read back in the PDF and check it + PDDocument doc = PDDocument.load(out); + PDFTextStripper textStripper = new PDFTextStripper(); + StringWriter textWriter = new StringWriter(); + textStripper.writeText(doc, textWriter); + doc.close(); + + String roundTrip = clean(textWriter.toString()); + + assertEquals( + "Incorrect text in PDF when starting from text in " + encoding, + checkText, roundTrip + ); + } + private String clean(String text) { text = text.replaceAll("\\s+\\r", ""); diff --git a/source/java/org/alfresco/repo/content/transform/TikaAutoContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/TikaAutoContentTransformerTest.java index 076c6bb6e0..bb31e33e31 100644 --- a/source/java/org/alfresco/repo/content/transform/TikaAutoContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/TikaAutoContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -32,7 +32,7 @@ import org.apache.tika.config.TikaConfig; */ public class TikaAutoContentTransformerTest extends TikaPoweredContentTransformerTest { - private ContentTransformer transformer; + private TikaAutoContentTransformer transformer; @Override public void setUp() throws Exception @@ -41,6 +41,8 @@ public class TikaAutoContentTransformerTest extends TikaPoweredContentTransforme TikaConfig config = (TikaConfig)ctx.getBean("tikaConfig"); transformer = new TikaAutoContentTransformer( config ); + transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); } /** @@ -58,36 +60,36 @@ public class TikaAutoContentTransformerTest extends TikaPoweredContentTransforme public void testIsTransformable() throws Exception { // Excel (but this isn't normally used) - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_EXCEL, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_EXCEL, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); // Word - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_WORD, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_WORD, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_WORD, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); // PDF - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_PDF, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_PDF, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); // Open Office - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_TEXT_PLAIN, -1, MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertTrue(transformer.isTransformable(MimetypeMap.MIMETYPE_OPENDOCUMENT_PRESENTATION, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); // We don't do images - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); // Ditto music - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_MP3, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_MP3, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); - assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_MP3, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_MP3, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_MP3, -1, MimetypeMap.MIMETYPE_HTML, new TransformationOptions())); + assertFalse(transformer.isTransformable(MimetypeMap.MIMETYPE_MP3, -1, MimetypeMap.MIMETYPE_XML, new TransformationOptions())); } } diff --git a/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java b/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java index 0d848f9b30..d9d135975f 100644 --- a/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java @@ -87,7 +87,7 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans * Can we do the requested transformation via Tika? * We support transforming to HTML, XML or Text */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { if(! sourceMimeTypes.contains(sourceMimetype)) { @@ -101,7 +101,8 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans MimetypeMap.MIMETYPE_XML.equals(targetMimetype)) { // We can output to this - return true; + // But there may be size limits on this transformer. + return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); } else { @@ -109,6 +110,17 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans return false; } } + + /** + * @deprecated This method should no longer be called as the overloaded method + * that calls it has been overridden. + */ + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return isTransformable(sourceMimetype, -1, targetMimetype, options); + } /** * Returns an appropriate Tika ContentHandler for the diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java new file mode 100644 index 0000000000..80b2761014 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java @@ -0,0 +1,570 @@ +/* + * 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 . + */ +package org.alfresco.repo.content.transform; + +import java.text.DecimalFormat; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Formatter; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.util.EqualsHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Debugs transformers selection and activity.

+ * + * As transformations are frequently composed of lower level transformations, log + * messages include a prefix to identify the transformation. A numeric dot notation + * is used (such as {@code 123.1.2} indicating the second third level transformation + * of the 123rd top level transformation).

+ * + * In order to track of the nesting of transforms, this class has a stack to represent + * the Transformers. Each Transformer calls {@link #pushTransform} at the start of a + * transform and {@link #popTransform} at the end. However the top level transform may + * be selected from a list of available transformers. To record this activity, + * {@link #pushAvailable}, {@link #unavailableTransformer} (to record the reason a + * transformer is rejected), {@link #availableTransformers} (to record the available + * transformers) and {@link #popAvailable} are called.

+ * + * @author Alan Davis + */ +public class TransformerDebug +{ + private static final Log logger = LogFactory.getLog(TransformerDebug.class); + + private enum Call + { + AVAILABLE, + TRANSFORM, + AVAILABLE_AND_TRANSFORM + }; + + private static class ThreadInfo + { + private static final ThreadLocal threadInfo = new ThreadLocal() + { + @Override + protected ThreadInfo initialValue() + { + return new ThreadInfo(); + } + }; + + private final Deque stack = new ArrayDeque(); + private boolean debugOutput = true; + + public static Deque getStack() + { + return threadInfo.get().stack; + } + + public static boolean getDebug() + { + return threadInfo.get().debugOutput; + } + + public static boolean setDebugOutput(boolean debugOutput) + { + ThreadInfo thisThreadInfo = threadInfo.get(); + boolean orig = thisThreadInfo.debugOutput; + thisThreadInfo.debugOutput = debugOutput; + return orig; + } + } + + private static class Frame + { + private static final AtomicInteger uniqueId = new AtomicInteger(0); + + private final int id; + private final String fromUrl; + private final String sourceMimetype; + private final String targetMimetype; + private final long start; + + private Call callType; + private int childId; + private Set unavailableTransformers; +// See debug(String, Throwable) as to why this is commented out +// private Throwable lastThrowable; + + private Frame(Frame parent, String fromUrl, String sourceMimetype, String targetMimetype, Call pushCall) + { + this.id = parent == null ? uniqueId.getAndIncrement() : ++parent.childId; + this.fromUrl = fromUrl; + this.sourceMimetype = sourceMimetype; + this.targetMimetype = targetMimetype; + this.callType = pushCall; + start = System.currentTimeMillis(); + } + } + + private class UnavailableTransformer + { + private final String name; + private final String reason; + private final transient boolean debug; + + UnavailableTransformer(String name, String reason, boolean debug) + { + this.name = name; + this.reason = reason; + this.debug = debug; + } + + @Override + public int hashCode() + { + int hashCode = 37 * name.hashCode(); + hashCode += 37 * reason.hashCode(); + return hashCode; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj instanceof UnavailableTransformer) + { + UnavailableTransformer that = (UnavailableTransformer) obj; + return + EqualsHelper.nullSafeEquals(name, that.name) && + EqualsHelper.nullSafeEquals(reason, that.reason); + } + else + { + return false; + } + } + } + + private final MimetypeService mimetypeService; + + /** + * Constructor + */ + public TransformerDebug(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + /** + * Called prior to working out what transformers are available. + */ + public void pushAvailable(String fromUrl, String sourceMimetype, String targetMimetype) + { + if (isEnabled()) + { + push(null, fromUrl, sourceMimetype, targetMimetype, -1, Call.AVAILABLE); + } + } + + /** + * Called prior to performing a transform. + */ + public void pushTransform(ContentTransformer transformer, String fromUrl, String sourceMimetype, String targetMimetype, long sourceSize) + { + if (isEnabled()) + { + push(getName(transformer), fromUrl, sourceMimetype, targetMimetype, sourceSize, Call.TRANSFORM); + } + } + + private void push(String name, String fromUrl, String sourceMimetype, String targetMimetype, long sourceSize, Call callType) + { + Deque ourStack = ThreadInfo.getStack(); + Frame frame = ourStack.peek(); + + if (callType == Call.TRANSFORM && frame != null && frame.callType == Call.AVAILABLE) + { + frame.callType = Call.AVAILABLE_AND_TRANSFORM; + } + else + { + frame = new Frame(frame, fromUrl, sourceMimetype, targetMimetype, callType); + ourStack.push(frame); + + if (callType == Call.TRANSFORM) + { + // Log the basic info about this transformation + logBasicDetails(frame, sourceSize, name, (ourStack.size() == 1)); + } + } + } + + /** + * Called to identify a transformer that cannot be used during working out + * available transformers. + */ + public void unavailableTransformer(ContentTransformer transformer, long maxSourceSizeKBytes) + { + if (isEnabled()) + { + Deque ourStack = ThreadInfo.getStack(); + Frame frame = ourStack.peek(); + + if (frame != null) + { + String name = getName(transformer); + String reason = String.format("> %,dK", maxSourceSizeKBytes); + boolean debug = (maxSourceSizeKBytes != 0); + if (ourStack.size() == 1) + { + if (frame.unavailableTransformers == null) + { + frame.unavailableTransformers = new HashSet(); + } + frame.unavailableTransformers.add(new UnavailableTransformer(name, reason, debug)); + } + else + { + log("-- " + name + ' ' + reason, debug); + } + } + } + } + + /** + * Called once all available transformers have been identified. + */ + public void availableTransformers(List transformers, long sourceSize, String calledFrom) + { + if (isEnabled()) + { + Deque ourStack = ThreadInfo.getStack(); + Frame frame = ourStack.peek(); + + // Log the basic info about this transformation + logBasicDetails(frame, sourceSize, + calledFrom + ((transformers.size() == 0) ? " NO transformers" : ""), + (ourStack.size() == 1)); + + // Report available and unavailable transformers + char c = 'a'; + int longestNameLength = getLongestTransformerNameLength(transformers, frame); + for (ContentTransformer trans : transformers) + { + String name = getName(trans); + int pad = longestNameLength - name.length(); + log((c == 'a' ? "**" : " ") + (c++) + ") " + + name + spaces(pad+1) + trans.getTransformationTime() + " ms"); + } + if (frame.unavailableTransformers != null) + { + for (UnavailableTransformer unavailable: frame.unavailableTransformers) + { + int pad = longestNameLength - unavailable.name.length(); + log("--" + (c++) + ") " + unavailable.name + spaces(pad+1) + unavailable.reason, + unavailable.debug); + } + } + } + } + + private int getLongestTransformerNameLength(List transformers, + Frame frame) + { + int longestNameLength = 0; + for (ContentTransformer trans : transformers) + { + int length = getName(trans).length(); + if (longestNameLength < length) + longestNameLength = length; + } + if (frame.unavailableTransformers != null) + { + for (UnavailableTransformer unavailable: frame.unavailableTransformers) + { + int length = unavailable.name.length(); + if (longestNameLength < length) + longestNameLength = length; + } + } + return longestNameLength; + } + + private void logBasicDetails(Frame frame, long sourceSize, String message, boolean firstLevel) + { + // Log the source URL, but there is no point if the parent has logged it + if (frame.fromUrl != null && (firstLevel || frame.id != 1)) + { + log(frame.fromUrl, firstLevel); + } + + log(getMimetypeExt(frame.sourceMimetype)+getMimetypeExt(frame.targetMimetype) + String.format("%,dK ", (sourceSize/1024)) + message); + + log(frame.sourceMimetype+' '+frame.targetMimetype, false); + } + + /** + * Called after working out what transformers are available and any + * resulting transform has been called. + */ + public void popAvailable() + { + if (isEnabled()) + { + pop(Call.AVAILABLE); + } + } + + /** + * Called after performing a transform. + */ + public void popTransform() + { + if (isEnabled()) + { + pop(Call.TRANSFORM); + } + } + + private void pop(Call callType) + { + Deque ourStack = ThreadInfo.getStack(); + if (!ourStack.isEmpty()) + { + Frame frame = ourStack.peek(); + if ((frame.callType == callType) || + (frame.callType == Call.AVAILABLE_AND_TRANSFORM && callType == Call.AVAILABLE)) + { + if (ourStack.size() == 1 || logger.isTraceEnabled()) + { + boolean topFrame = ourStack.size() == 1; + log("Finished in " + + (System.currentTimeMillis() - frame.start) + " ms" + + (frame.callType == Call.AVAILABLE ? " Transformer NOT called" : "") + + (topFrame ? "\n" : ""), + topFrame); + } + + ourStack.pop(); + +// See debug(String, Throwable) as to why this is commented out +// if (ourStack.size() >= 1) +// { +// ourStack.peek().lastThrowable = frame.lastThrowable; +// } + } + } + } + + /** + * Indicates if any logging is required. + */ + public boolean isEnabled() + { + return + (logger.isDebugEnabled() && ThreadInfo.getDebug()) || + logger.isTraceEnabled(); + } + + /** + * Enable or disable debug log output. Normally used to hide calls to + * getTransformer as trace rather than debug level log messages. There + * are lots of these and it makes it hard to see what is going on. + * @param debugOutput if {@code true} both debug and trace is generated. Otherwise all output is trace. + * @return the original value. + */ + public static boolean setDebugOutput(boolean debugOutput) + { + return ThreadInfo.setDebugOutput(debugOutput); + } + + /** + * Log a message prefixed with the current transformation reference. + * @param message + */ + public void debug(String message) + { + if (isEnabled() && message != null) + { + log(message); + } + } + + /** + * Log a message prefixed with the current transformation reference + * and include a exception, suppressing the stack trace if repeated + * as we return up the stack of transformers. + * @param message + */ + public void debug(String message, Throwable t) + { + if (isEnabled()) + { + log(message + ' ' + t.getMessage()); + +// // Generally the full stack is not needed as transformer +// // Exceptions get logged as a Error higher up, so including +// // the stack trace has been found not to be needed. Keeping +// // the following code and code that sets lastThrowable just +// // in case we need it after all. +// +// Frame frame = ThreadInfo.getStack().peek(); +// boolean newThrowable = isNewThrowable(frame.lastThrowable, t); +// frame.lastThrowable = t; +// +// if (newThrowable) +// { +// log(message, t, true); +// } +// else +// { +// log(message + ' ' + t.getMessage()); +// } + } + } + +// private boolean isNewThrowable(Throwable lastThrowable, Throwable t) +// { +// while (t != null) +// { +// if (lastThrowable == t) +// { +// return false; +// } +// t = t.getCause(); +// } +// return true; +// } + + private void log(String message) + { + log(message, true); + } + + private void log(String message, boolean debug) + { + log(message, null, debug); + } + + private void log(String message, Throwable t, boolean debug) + { + if (debug && ThreadInfo.getDebug()) + { + logger.debug(getReference()+message, t); + } + else + { + logger.trace(getReference()+message, t); + } + } + + /** + * Sets the cause of a transformation failure, so that only the + * message of the Throwable is reported later rather than the full + * stack trace over and over. + */ + public T setCause(T t) + { +// See debug(String, Throwable) as to why this is commented out +// if (isEnabled()) +// { +// Deque ourStack = ThreadInfo.getStack(); +// if (!ourStack.isEmpty()) +// { +// ourStack.peek().lastThrowable = t; +// } +// } + return t; + } + + private String getReference() + { + StringBuilder sb = new StringBuilder(""); + Frame frame = null; + Iterator iterator = ThreadInfo.getStack().descendingIterator(); + int lengthOfFirstId = 0; + while (iterator.hasNext()) + { + frame = iterator.next(); + if (sb.length() == 0) + { + sb.append(frame.id); + lengthOfFirstId = sb.length(); + } + else + { + sb.append('.'); + sb.append(frame.id); + } + } + if (frame != null) + { + sb.append(spaces(9-sb.length()+lengthOfFirstId)); // Try to pad to level 5 + } + return sb.toString(); + } + + private String getName(ContentTransformer transformer) + { + return + (transformer instanceof AbstractContentTransformer2 + ? ((AbstractContentTransformerLimits)transformer).getBeanName() + : transformer.getClass().getSimpleName())+ + + (transformer instanceof ComplexContentTransformer + ? "<>" + : transformer instanceof FailoverContentTransformer + ? "<>" + : transformer instanceof ProxyContentTransformer + ? (((ProxyContentTransformer)transformer).getWorker() instanceof RuntimeExecutableContentTransformerWorker) + ? "<>" + : "<>" + : ""); + } + + private String getMimetypeExt(String mimetype) + { + StringBuilder sb = new StringBuilder(""); + if (mimetypeService == null) + { + sb.append(mimetype); + sb.append(' '); + } + else + { + String mimetypeExt = mimetypeService.getExtension(mimetype); + sb.append(mimetypeExt); + sb.append(spaces(5-mimetypeExt.length())); // Pad to normal max ext (4) plus 1 + } + return sb.toString(); + } + + private String spaces(int i) + { + StringBuilder sb = new StringBuilder(""); + while (--i >= 0) + { + sb.append(' '); + } + return sb.toString(); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerTest.java index 0e6c3c9ccd..deed319f80 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -35,7 +35,7 @@ import org.alfresco.util.exec.RuntimeExec; public class ImageMagickContentTransformerTest extends AbstractContentTransformerTest { private ImageMagickContentTransformerWorker worker; - private ContentTransformer transformer; + private ProxyContentTransformer transformer; @Override public void setUp() throws Exception @@ -51,11 +51,10 @@ public class ImageMagickContentTransformerTest extends AbstractContentTransforme worker.setExecuter(executer); worker.afterPropertiesSet(); - ProxyContentTransformer transformer = new ProxyContentTransformer(); + transformer = new ProxyContentTransformer(); transformer.setMimetypeService(mimetypeService); + transformer.setTransformerDebug(transformerDebug); transformer.setWorker(worker); - this.transformer = transformer; - } /** @@ -73,10 +72,10 @@ public class ImageMagickContentTransformerTest extends AbstractContentTransforme return; } boolean reliability = transformer.isTransformable( - MimetypeMap.MIMETYPE_IMAGE_GIF, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + MimetypeMap.MIMETYPE_IMAGE_GIF, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); assertEquals("Mimetype should not be supported", false, reliability); reliability = transformer.isTransformable( - MimetypeMap.MIMETYPE_IMAGE_GIF, MimetypeMap.MIMETYPE_IMAGE_JPEG, new TransformationOptions()); + MimetypeMap.MIMETYPE_IMAGE_GIF, -1, MimetypeMap.MIMETYPE_IMAGE_JPEG, new TransformationOptions()); assertEquals("Mimetype should be supported", true, reliability); } } diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java index c06f444544..9f3ffda96f 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java @@ -172,7 +172,8 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont properties.put(VAR_TARGET, targetFile.getAbsolutePath()); // execute the statement - RuntimeExec.ExecutionResult result = executer.execute(properties); + long timeoutMs = options.getTimeoutMs(); + RuntimeExec.ExecutionResult result = executer.execute(properties, timeoutMs); if (result.getExitValue() != 0 && result.getStdErr() != null && result.getStdErr().length() > 0) { throw new ContentIOException("Failed to perform ImageMagick transformation: \n" + result); diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index f555ec67d8..f455265e40 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -76,6 +76,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.QueryParameterDefinition; import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.security.AccessStatus; @@ -2730,7 +2731,9 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider // If there's nothing currently registered to generate thumbnails for the // specified mimetype, then log a message and bail out String nodeMimeType = getMimetype(); - if (!registry.isThumbnailDefinitionAvailable(nodeMimeType, details)) + Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), details)) { logger.info("Unable to create thumbnail '" + details.getName() + "' for " + nodeMimeType + " as no transformer is currently available"); @@ -2825,7 +2828,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider if (contentReader != null) { String mimetype = contentReader.getMimetype(); - List thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumnailDefintions(mimetype); + List thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentReader.getContentUrl(), mimetype, contentReader.getSize()); for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions) { result.add(thumbnailDefinition.getName()); diff --git a/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java index 301a0664b3..a82db6d390 100644 --- a/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java +++ b/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,10 +20,12 @@ package org.alfresco.repo.rendition.executer; import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.service.cmr.rendition.RenditionServiceException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NoTransformerException; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -35,6 +37,42 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend { private static Log logger = LogFactory.getLog(AbstractTransformationRenderingEngine.class); + /** + * This optional {@link Long} parameter specifies the timeout for reading + * the source before an exception is thrown. + */ + public static final String PARAM_TIMEOUT_MS = TransformationOptionLimits.OPT_TIMEOUT_MS; + + /** + * This optional {@link Long} parameter specifies how timeout for reading + * the source before EOF is returned. + */ + public static final String PARAM_READ_LIMIT_TIME_MS = TransformationOptionLimits.OPT_READ_LIMIT_TIME_MS; + + /** + * This optional {@link Long} parameter specifies the maximum number of kbytes of + * the source may be read. An exception is thrown before any are read if larger. + */ + public static final String PARAM_MAX_SOURCE_SIZE_K_BYTES = TransformationOptionLimits.OPT_MAX_SOURCE_SIZE_K_BYTES; + + /** + * This optional {@link Long} parameter specifies how many kbytes of + * the source to read in order to create an image. + */ + public static final String PARAM_READ_LIMIT_K_BYTES = TransformationOptionLimits.OPT_READ_LIMIT_K_BYTES; + + /** + * This optional {@link Integer} parameter specifies the maximum number of pages of + * the source that may be read. An exception is thrown before any are read if larger. + */ + public static final String PARAM_MAX_PAGES = TransformationOptionLimits.OPT_MAX_PAGES; + + /** + * This optional {@link Integer} parameter specifies how many source + * pages should be read in order to create an image. + */ + public static final String PARAM_PAGE_LIMIT = TransformationOptionLimits.OPT_PAGE_LIMIT; + /* Error messages */ private static final String TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN = "Transformer for '%s' source mime type and '%s' target mime type was not found. Operation can't be performed"; private static final String NOT_TRANSFORMABLE_MESSAGE_PATTERN = "Content not transformable for '%s' source mime type and '%s' target mime type. Operation can't be performed"; @@ -48,13 +86,24 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend protected void render(RenderingContext context) { ContentReader contentReader = context.makeContentReader(); + String sourceUrl = contentReader.getContentUrl(); String sourceMimeType = contentReader.getMimetype(); String targetMimeType = getTargetMimeType(context); TransformationOptions options = getTransformOptions(context); - ContentTransformer transformer = this.contentService.getTransformer(sourceMimeType, targetMimeType, options); - + // Log the following getTransform() as trace so we can see the wood for the trees + ContentTransformer transformer; + boolean orig = TransformerDebug.setDebugOutput(false); + try + { + transformer = this.contentService.getTransformer(sourceUrl, sourceMimeType, contentReader.getSize(), targetMimeType, options); + } + finally + { + TransformerDebug.setDebugOutput(orig); + } + // Actually perform the rendition. if (null == transformer) { @@ -63,7 +112,7 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend targetMimeType)); } - if (transformer.isTransformable(sourceMimeType, targetMimeType, options)) + if (transformer.isTransformable(sourceMimeType, contentReader.getSize(), targetMimeType, options)) { ContentWriter contentWriter = context.makeContentWriter(); try @@ -86,4 +135,45 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend } protected abstract TransformationOptions getTransformOptions(RenderingContext context); + + protected TransformationOptions getTransformOptionsImpl(TransformationOptions options, RenderingContext context) + { + Long timeoutMs = context.getCheckedParam(PARAM_TIMEOUT_MS, Long.class); + if (timeoutMs != null) + { + options.setTimeoutMs(timeoutMs); + } + + Long readLimitTimeMs = context.getCheckedParam(PARAM_READ_LIMIT_TIME_MS, Long.class); + if (readLimitTimeMs != null) + { + options.setReadLimitTimeMs(readLimitTimeMs); + } + + Long maxSourceSizeKBytes = context.getCheckedParam(PARAM_MAX_SOURCE_SIZE_K_BYTES, Long.class); + if (maxSourceSizeKBytes != null) + { + options.setMaxSourceSizeKBytes(maxSourceSizeKBytes); + } + + Long readLimitKBytes = context.getCheckedParam(PARAM_READ_LIMIT_K_BYTES, Long.class); + if (readLimitKBytes != null) + { + options.setReadLimitKBytes(readLimitKBytes); + } + + Integer maxPages = context.getCheckedParam(PARAM_MAX_PAGES, Integer.class); + if (maxPages != null) + { + options.setMaxPages(maxPages); + } + + Integer pageLimit = context.getCheckedParam(PARAM_PAGE_LIMIT, Integer.class); + if (pageLimit != null) + { + options.setPageLimit(pageLimit); + } + + return options; + } } diff --git a/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java index b065f09dcc..c0407cdf98 100644 --- a/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java +++ b/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -25,6 +25,7 @@ import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.content.transform.magick.ImageCropOptions; import org.alfresco.repo.content.transform.magick.ImageResizeOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -228,7 +229,7 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine * appended after the various crop and resize options. */ public static final String PARAM_COMMAND_OPTIONS = "commandOptions"; - + /** * This optional {@link Boolean} flag parameter specifies if the engine should * automatically rotate and image based on the EXIF orientation flag. If @@ -249,13 +250,20 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine @Override protected TransformationOptions getTransformOptions(RenderingContext context) { + return getTransformOptionsImpl(new ImageTransformationOptions(), context); + } + + @Override + protected TransformationOptions getTransformOptionsImpl(TransformationOptions options, RenderingContext context) + { + ImageTransformationOptions imageTransformationOptions = (ImageTransformationOptions)options; + String commandOptions = context.getCheckedParam(PARAM_COMMAND_OPTIONS, String.class); ImageResizeOptions imageResizeOptions = getImageResizeOptions(context); ImageCropOptions cropOptions = getImageCropOptions(context); boolean autoOrient = context.getParamWithDefault(PARAM_AUTO_ORIENTATION, true); - ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); imageTransformationOptions.setResizeOptions(imageResizeOptions); imageTransformationOptions.setCropOptions(cropOptions); imageTransformationOptions.setAutoOrient(autoOrient); @@ -263,7 +271,8 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine { imageTransformationOptions.setCommandOptions(commandOptions); } - return imageTransformationOptions; + + return super.getTransformOptionsImpl(options, context); } /* @@ -408,7 +417,21 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine getParamDisplayLabel(PARAM_IS_PERCENT_CROP))); paramList.add(new ParameterDefinitionImpl(PARAM_COMMAND_OPTIONS, DataTypeDefinition.TEXT, false, - getParamDisplayLabel(PARAM_COMMAND_OPTIONS))); + getParamDisplayLabel(PARAM_COMMAND_OPTIONS))); + + paramList.add(new ParameterDefinitionImpl(PARAM_TIMEOUT_MS, DataTypeDefinition.LONG, false, + getParamDisplayLabel(PARAM_TIMEOUT_MS))); + paramList.add(new ParameterDefinitionImpl(PARAM_READ_LIMIT_TIME_MS, DataTypeDefinition.LONG, false, + getParamDisplayLabel(PARAM_READ_LIMIT_TIME_MS))); + paramList.add(new ParameterDefinitionImpl(PARAM_MAX_SOURCE_SIZE_K_BYTES, DataTypeDefinition.LONG, false, + getParamDisplayLabel(PARAM_MAX_SOURCE_SIZE_K_BYTES))); + paramList.add(new ParameterDefinitionImpl(PARAM_READ_LIMIT_K_BYTES, DataTypeDefinition.LONG, false, + getParamDisplayLabel(PARAM_READ_LIMIT_K_BYTES))); + paramList.add(new ParameterDefinitionImpl(PARAM_MAX_PAGES, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_MAX_PAGES))); + paramList.add(new ParameterDefinitionImpl(PARAM_PAGE_LIMIT, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_PAGE_LIMIT))); + return paramList; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java index c371710bab..ae6304f0ea 100644 --- a/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java +++ b/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -22,6 +22,8 @@ package org.alfresco.repo.rendition.executer; import java.util.Collection; import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.rendition.RenditionService; @@ -78,9 +80,16 @@ public class ReformatRenderingEngine extends AbstractTransformationRenderingEngi @Override protected TransformationOptions getTransformOptions(RenderingContext context) { - NodeRef sourceNode = context.getSourceNode(); - NodeRef destinationNode = context.getDestinationNode(); - return new TransformationOptions(sourceNode, null, destinationNode, null); + return getTransformOptionsImpl(new TransformationOptions(), context); + } + + @Override + protected TransformationOptions getTransformOptionsImpl(TransformationOptions options, RenderingContext context) + { + options.setSourceNodeRef(context.getSourceNode()); + options.setTargetNodeRef(context.getDestinationNode()); + + return super.getTransformOptionsImpl(options, context); } /* diff --git a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java index b6bd092622..af63501a13 100644 --- a/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java +++ b/source/java/org/alfresco/repo/rule/RuleServiceCoverageTest.java @@ -1067,7 +1067,7 @@ public class RuleServiceCoverageTest extends TestCase public void testTransformAction() throws Throwable { ContentTransformer transformer = transformerRegistry.getTransformer( - MimetypeMap.MIMETYPE_EXCEL, + MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); if (transformer == null) @@ -1148,7 +1148,7 @@ public class RuleServiceCoverageTest extends TestCase public void testImageTransformAction() throws Throwable { ContentTransformer transformer = transformerRegistry.getTransformer( - MimetypeMap.MIMETYPE_IMAGE_GIF, + MimetypeMap.MIMETYPE_IMAGE_GIF, -1, MimetypeMap.MIMETYPE_IMAGE_JPEG, new TransformationOptions()); if (transformer == null) @@ -1939,7 +1939,7 @@ public class RuleServiceCoverageTest extends TestCase public void testAsyncExecutionWithPotentialLoop() { - if (this.transformerRegistry.getTransformer(MimetypeMap.MIMETYPE_EXCEL, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()) != null) + if (this.transformerRegistry.getTransformer(MimetypeMap.MIMETYPE_EXCEL, -1, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()) != null) { try { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java index cc813b6553..1fa77463d9 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerAndSearcherFactory.java @@ -20,6 +20,7 @@ package org.alfresco.repo.search.impl.lucene; import java.util.List; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.search.SearcherException; import org.alfresco.repo.search.SupportsBackgroundIndexing; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; @@ -42,6 +43,8 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd protected NodeService nodeService; protected FullTextSearchIndexer fullTextSearchIndexer; protected ContentService contentService; + private TransformerDebug transformerDebug; + protected TransactionService transactionService; /** @@ -80,6 +83,15 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd { this.contentService = contentService; } + + /** + * Sets the transformer debug. + * @param transformerDebug + */ + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } /** * Set the transaction service @@ -101,6 +113,7 @@ public class ADMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd // indexer.setLuceneIndexLock(luceneIndexLock); indexer.setFullTextSearchIndexer(fullTextSearchIndexer); indexer.setContentService(contentService); + indexer.setTransformerDebug(transformerDebug); indexer.setTransactionService(transactionService); indexer.setMaxAtomicTransformationTime(getMaxTransformationTime()); return indexer; diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index cf26e509a4..a048659133 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -46,6 +46,7 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.dictionary.IndexTokenisationMode; import org.alfresco.repo.search.IndexerException; import org.alfresco.repo.search.MLAnalysisMode; @@ -74,6 +75,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.Path.ChildAssocElement; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; @@ -129,6 +131,8 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp */ ContentService contentService; + private TransformerDebug transformerDebug; + /** * Call back to make after doing non atomic indexing */ @@ -182,6 +186,15 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp this.contentService = contentService; } + /** + * Helper setter of the transformer debug. + * @param transformerDebug + */ + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + /* * Indexer Implementation */ @@ -1263,61 +1276,78 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp // transform if necessary (it is not a UTF-8 text document) if (!EqualsHelper.nullSafeEquals(reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN) || !EqualsHelper.nullSafeEquals(reader.getEncoding(), "UTF-8")) { - // get the transformer - ContentTransformer transformer = contentService.getTransformer(reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN); - // is this transformer good enough? - if (transformer == null) + try { - // log it - if (s_logger.isInfoEnabled()) - { - s_logger.info("Not indexed: No transformation: \n" - + " source: " + reader + "\n" + " target: " + MimetypeMap.MIMETYPE_TEXT_PLAIN + " at " + nodeService.getPath(nodeRef)); - } - // don't index from the reader - readerReady = false; - // not indexed: no transformation - // doc.add(new Field("TEXT", NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, - // Field.Index.TOKENIZED, Field.TermVector.NO)); - doc.add(new Field(attributeName, NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); - } - else if (indexAtomicPropertiesOnly && transformer.getTransformationTime() > maxAtomicTransformationTime) - { - // only indexing atomic properties - // indexing will take too long, so push it to the background - wereAllAtomic = false; - readerReady = false; - } - else - { - // We have a transformer that is fast enough - ContentWriter writer = contentService.getTempWriter(); - writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - // this is what the analyzers expect on the stream - writer.setEncoding("UTF-8"); - try - { - transformer.transform(reader, writer); - // point the reader to the new-written content - reader = writer.getReader(); - // Check that the reader is a view onto something concrete - if (!reader.exists()) - { - throw new ContentIOException("The transformation did not write any content, yet: \n" - + " transformer: " + transformer + "\n" + " temp writer: " + writer); - } - } - catch (ContentIOException e) + // get the transformer + transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN); + long sourceSize = reader.getSize(); + List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + transformerDebug.availableTransformers(transformers, sourceSize, "ADMLuceneIndexer"); + + if (transformers.isEmpty()) { // log it if (s_logger.isInfoEnabled()) { - s_logger.info("Not indexed: Transformation failed at " + nodeService.getPath(nodeRef), e); + s_logger.info("Not indexed: No transformation: \n" + + " source: " + reader + "\n" + " target: " + MimetypeMap.MIMETYPE_TEXT_PLAIN + " at " + nodeService.getPath(nodeRef)); } // don't index from the reader readerReady = false; - doc.add(new Field(attributeName, NOT_INDEXED_TRANSFORMATION_FAILED, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); + // not indexed: no transformation + // doc.add(new Field("TEXT", NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, + // Field.Index.TOKENIZED, Field.TermVector.NO)); + doc.add(new Field(attributeName, NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); } + // is this transformer good enough? + else if (indexAtomicPropertiesOnly && transformers.get(0).getTransformationTime() > maxAtomicTransformationTime) + { + // only indexing atomic properties + // indexing will take too long, so push it to the background + wereAllAtomic = false; + readerReady = false; + + if (transformerDebug.isEnabled()) + { + transformerDebug.debug("Run later. Transformer average ("+transformers.get(0).getTransformationTime()+" ms) > "+maxAtomicTransformationTime+" ms"); + } + } + else + { + // We have a transformer that is fast enough + ContentTransformer transformer = transformers.get(0); + ContentWriter writer = contentService.getTempWriter(); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + // this is what the analyzers expect on the stream + writer.setEncoding("UTF-8"); + try + { + transformer.transform(reader, writer); + // point the reader to the new-written content + reader = writer.getReader(); + // Check that the reader is a view onto something concrete + if (!reader.exists()) + { + throw new ContentIOException("The transformation did not write any content, yet: \n" + + " transformer: " + transformer + "\n" + " temp writer: " + writer); + } + } + catch (ContentIOException e) + { + // log it + if (s_logger.isInfoEnabled()) + { + s_logger.info("Not indexed: Transformation failed at " + nodeService.getPath(nodeRef), e); + } + // don't index from the reader + readerReady = false; + doc.add(new Field(attributeName, NOT_INDEXED_TRANSFORMATION_FAILED, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); + } + } + } + finally + { + transformerDebug.popAvailable(); } } // add the text field using the stream from the diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java index 8978b6e41a..1da81bc917 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneTest.java @@ -49,6 +49,7 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.DictionaryListener; import org.alfresco.repo.dictionary.DictionaryNamespaceComponent; @@ -155,6 +156,8 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener TenantService tenantService; + TransformerDebug transformerDebug; + private NodeRef rootNodeRef; private NodeRef n1; @@ -271,6 +274,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener contentService = (ContentService) ctx.getBean("contentService"); queryRegisterComponent = (QueryRegisterComponent) ctx.getBean("queryRegisterComponent"); namespacePrefixResolver = (DictionaryNamespaceComponent) ctx.getBean("namespaceService"); + transformerDebug = (TransformerDebug) ctx.getBean("transformerDebug"); indexerAndSearcher = (IndexerAndSearcher) ctx.getBean("admLuceneIndexerAndSearcherFactory"); luceneConfig = (LuceneConfig)ctx.getBean("admLuceneIndexerAndSearcherFactory"); ((LuceneConfig) indexerAndSearcher).setMaxAtomicTransformationTime(1000000); @@ -1316,7 +1320,7 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener buildBaseIndex(); ADMLuceneSearcherImpl searcher = buildSearcher(); - + List expected = new ArrayList(15); SearchParameters sp = new SearchParameters(); @@ -3956,6 +3960,8 @@ public class ADMLuceneTest extends TestCase implements DictionaryListener indexer.setFullTextSearchIndexer(luceneFTS); indexer.setContentService(contentService); indexer.setTransactionService(transactionService); + indexer.setTransformerDebug(transformerDebug); + // indexer.clearIndex(); indexer.createNode(new ChildAssociationRef(null, null, null, rootNodeRef)); indexer.createNode(new ChildAssociationRef(ContentModel.ASSOC_CHILDREN, rootNodeRef, QName.createQName("{namespace}one"), n1)); diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerAndSearcherFactory.java b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerAndSearcherFactory.java index 3be202a280..13536cc506 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerAndSearcherFactory.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerAndSearcherFactory.java @@ -23,6 +23,7 @@ import java.util.List; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.search.AVMSnapShotTriggeredIndexingMethodInterceptor; import org.alfresco.repo.search.IndexMode; import org.alfresco.repo.search.SearcherException; @@ -56,6 +57,8 @@ public class AVMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd private AVMSyncService avmSyncService; private NodeService nodeService; private ContentStore contentStore; + + private TransformerDebug transformerDebug; private FullTextSearchIndexer fullTextSearchIndexer; private AVMSnapShotTriggeredIndexingMethodInterceptor avmSnapShotTriggeredIndexingMethodInterceptor; @@ -124,6 +127,15 @@ public class AVMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd { this.contentStore = contentStore; } + + /** + * Sets the transformer debug. + * @param transformerDebug + */ + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } /** * @param avmSnapShotTriggeredIndexingMethodInterceptor the avmSnapShotTriggeredIndexingMethodInterceptor to set @@ -144,6 +156,7 @@ public class AVMLuceneIndexerAndSearcherFactory extends AbstractLuceneIndexerAnd indexer.setAvmService(avmService); indexer.setAvmSyncService(avmSyncService); indexer.setContentStore(contentStore); + indexer.setTransformerDebug(transformerDebug); indexer.setFullTextSearchIndexer(fullTextSearchIndexer); return indexer; } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java index c243801518..c9b6c6482d 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java @@ -47,6 +47,7 @@ import org.alfresco.repo.avm.util.SimplePath; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.dictionary.IndexTokenisationMode; import org.alfresco.repo.domain.PropertyValue; import org.alfresco.repo.search.IndexMode; @@ -78,6 +79,7 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.repository.datatype.TypeConversionException; import org.alfresco.service.namespace.QName; @@ -127,6 +129,8 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl private ContentStore contentStore; private ContentService contentService; + + private TransformerDebug transformerDebug; private FTSIndexerAware callBack; @@ -183,6 +187,15 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl this.contentService = contentService; } + /** + * Helper setter of the transformer debug. + * @param transformerDebug + */ + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + /** * Are we deleting leaves only (not meta data) * @@ -1124,65 +1137,76 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl // transform if necessary (it is not a UTF-8 text document) if (!EqualsHelper.nullSafeEquals(reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN) || !EqualsHelper.nullSafeEquals(reader.getEncoding(), "UTF-8")) { - // get the transformer - ContentTransformer transformer = contentService.getTransformer(reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN); - // is this transformer good enough? - if (transformer == null) + try { - // log it - if (s_logger.isDebugEnabled()) - { - s_logger.debug("Not indexed: No transformation: \n" + " source: " + reader + "\n" + " target: " + MimetypeMap.MIMETYPE_TEXT_PLAIN); - } - // don't index from the reader - readerReady = false; - // not indexed: no transformation - // doc.add(new Field("TEXT", NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, - // Field.Index.TOKENIZED, Field.TermVector.NO)); - doc.add(new Field(attributeName, NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); - } - // else if (indexAtomicPropertiesOnly - // && transformer.getTransformationTime() > maxAtomicTransformationTime) - // { - // only indexing atomic properties - // indexing will take too long, so push it to the background - // wereAllAtomic = false; - // } - else - { - // We have a transformer that is fast enough - ContentWriter writer = contentService.getTempWriter(); - writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); - // this is what the analyzers expect on the stream - writer.setEncoding("UTF-8"); - try - { + // get the transformer + transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN); + long sourceSize = reader.getSize(); + List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + transformerDebug.availableTransformers(transformers, sourceSize, "AVMLuceneIndexer"); - transformer.transform(reader, writer); - // point the reader to the new-written content - reader = writer.getReader(); - // Check that the reader is a view onto something concrete - if (!reader.exists()) - { - throw new ContentIOException("The transformation did not write any content, yet: \n" - + " transformer: " + transformer + "\n" + " temp writer: " + writer); - } - } - catch (ContentIOException e) + if (transformers.isEmpty()) { // log it if (s_logger.isDebugEnabled()) { - s_logger.debug("Not indexed: Transformation failed", e); + s_logger.debug("Not indexed: No transformation: \n" + " source: " + reader + "\n" + " target: " + MimetypeMap.MIMETYPE_TEXT_PLAIN); } // don't index from the reader readerReady = false; - // not indexed: transformation - // failed - // doc.add(new Field("TEXT", NOT_INDEXED_TRANSFORMATION_FAILED, Field.Store.NO, + // not indexed: no transformation + // doc.add(new Field("TEXT", NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, // Field.Index.TOKENIZED, Field.TermVector.NO)); - doc.add(new Field(attributeName, NOT_INDEXED_TRANSFORMATION_FAILED, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); + doc.add(new Field(attributeName, NOT_INDEXED_NO_TRANSFORMATION, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); } + // is this transformer good enough? + // else if (indexAtomicPropertiesOnly + // && transformers.get(0).getTransformationTime() > maxAtomicTransformationTime) + // { + // only indexing atomic properties + // indexing will take too long, so push it to the background + // wereAllAtomic = false; + // } + else + { + // We have a transformer that is fast enough + ContentTransformer transformer = transformers.get(0); + ContentWriter writer = contentService.getTempWriter(); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + // this is what the analyzers expect on the stream + writer.setEncoding("UTF-8"); + try + { + transformer.transform(reader, writer); + // point the reader to the new-written content + reader = writer.getReader(); + // Check that the reader is a view onto something concrete + if (!reader.exists()) + { + throw new ContentIOException("The transformation did not write any content, yet: \n" + + " transformer: " + transformer + "\n" + " temp writer: " + writer); + } + } + catch (ContentIOException e) + { + // log it + if (s_logger.isDebugEnabled()) + { + s_logger.debug("Not indexed: Transformation failed", e); + } + // don't index from the reader + readerReady = false; + // not indexed: transformation + // failed + // doc.add(new Field("TEXT", NOT_INDEXED_TRANSFORMATION_FAILED, Field.Store.NO, + // Field.Index.TOKENIZED, Field.TermVector.NO)); + doc.add(new Field(attributeName, NOT_INDEXED_TRANSFORMATION_FAILED, Field.Store.NO, Field.Index.TOKENIZED, Field.TermVector.NO)); + } + } + } + finally + { + transformerDebug.popAvailable(); } } // add the text field using the stream from the diff --git a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java index 88f94ec2c1..2557c90200 100644 --- a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,12 +19,14 @@ package org.alfresco.repo.thumbnail; import java.io.Serializable; +import java.util.HashMap; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -42,6 +44,7 @@ import org.apache.commons.logging.LogFactory; * NOTE: This action is used to facilitate the async creation of thumbnails. It is not intended for genereral useage. * * @author Roy Wetherall + * @author Ph Dubois (optional thumbnail creation by mimetype and in general) */ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase { @@ -53,6 +56,12 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase /** Node Service */ private NodeService nodeService; + /** Property turns on and off all thumbnail creation */ + private boolean generateThumbnails = true; + + // Size limitations (in KBytes) indexed by mimetype for thumbnail creation + private HashMap mimetypeMaxSourceSizeKBytes; + /** Action name and parameters */ public static final String NAME = "create-thumbnail"; public static final String PARAM_CONTENT_PROPERTY = "content-property"; @@ -78,12 +87,40 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase this.nodeService = nodeService; } + /** + * Set the maximum size for each mimetype above which thumbnails are not created. + * @param mimetypeMaxSourceSizeKBytes map of mimetypes to max source sizes. + */ + public void setMimetypeMaxSourceSizeKBytes(HashMap mimetypeMaxSourceSizeKBytes) + { + this.mimetypeMaxSourceSizeKBytes = mimetypeMaxSourceSizeKBytes; + } + + /** + * Enable thumbnail creation at all regardless of mimetype. + * @param generateThumbnails a {@code false} value turns off all thumbnail creation. + */ + public void setGenerateThumbnails(boolean generateThumbnails) + { + this.generateThumbnails = generateThumbnails; + } + /** * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) */ @Override protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { + // Check if thumbnailing is generally disabled + if (!generateThumbnails) + { + if (logger.isDebugEnabled()) + { + logger.debug("Thumbnail transformations are not enabled"); + } + return; + } + if (this.nodeService.exists(actionedUponNodeRef) == true) { // Get the thumbnail Name @@ -107,15 +144,31 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase // If there isn't a currently active transformer for this, log and skip Serializable contentProp = nodeService.getProperty(actionedUponNodeRef, contentProperty); - if(contentProp != null && contentProp instanceof ContentData) + if (contentProp == null) { - String mimetype = ((ContentData)contentProp).getMimetype(); - if (!registry.isThumbnailDefinitionAvailable(mimetype, details)) + logger.info("Creation of thumbnail, null content for " + details.getName()); + return; + } + + if(contentProp instanceof ContentData) + { + ContentData content = (ContentData)contentProp; + String mimetype = content.getMimetype(); + if (!registry.isThumbnailDefinitionAvailable(content.getContentUrl(), mimetype, content.getSize(), details)) { logger.info("Unable to create thumbnail '" + details.getName() + "' for " + mimetype + " as no transformer is currently available"); return; } + if (mimetypeMaxSourceSizeKBytes != null) + { + Long maxSourceSizeKBytes = mimetypeMaxSourceSizeKBytes.get(mimetype); + if (maxSourceSizeKBytes != null && maxSourceSizeKBytes > 0 && maxSourceSizeKBytes <= (content.getSize()/1024L)) + { + logger.info("Creation of " + details.getName()+ " thumbnail from '" + mimetype + "' , content is too big ("+(content.getSize()/1024L)+"K >= "+maxSourceSizeKBytes+"K)"); + return; //avoid transform + } + } } // Create the thumbnail @@ -144,5 +197,4 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase paramList.add(new ParameterDefinitionImpl(PARAM_THUMBANIL_NAME, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_THUMBANIL_NAME))); paramList.add(new ParameterDefinitionImpl(PARAM_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_CONTENT_PROPERTY))); } - } diff --git a/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java b/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java index 16f3a6e9e8..af39196908 100644 --- a/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java +++ b/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java @@ -157,7 +157,7 @@ public class SimpleThumbnailer extends TransactionListenerAdapter implements Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); List thumbnailDefinitions = this.thumbnailService.getThumbnailRegistry() - .getThumnailDefintions(contentData.getMimetype()); + .getThumbnailDefinitions(contentData.getContentUrl(), contentData.getMimetype(), contentData.getSize()); for (final ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions) { final NodeRef existingThumbnail = this.thumbnailService.getThumbnailByName(nodeRef, diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 6a2abceb7f..71018cb1bb 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; @@ -190,7 +191,15 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi return new ArrayList(this.thumbnailDefinitions.values()); } + /** + * @deprecated use overloaded version with sourceSize parameter. + */ public List getThumbnailDefinitions(String mimetype) + { + return getThumbnailDefinitions(null, mimetype, -1); + } + + public List getThumbnailDefinitions(String sourceUrl, String mimetype, long sourceSize) { List result = this.mimetypeMap.get(mimetype); @@ -201,7 +210,7 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi for (ThumbnailDefinition thumbnailDefinition : this.thumbnailDefinitions.values()) { - if (isThumbnailDefinitionAvailable(mimetype, thumbnailDefinition)) + if (isThumbnailDefinitionAvailable(sourceUrl, mimetype, sourceSize, thumbnailDefinition)) { result.add(thumbnailDefinition); foundAtLeastOneTransformer = true; @@ -243,17 +252,28 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi * Checks to see if at this moment in time, the specified {@link ThumbnailDefinition} * is able to thumbnail the source mimetype. Typically used with Thumbnail Definitions * retrieved by name, and/or when dealing with transient {@link ContentTransformer}s. - * @param thumbnailDefinition The {@link ThumbnailDefinition} to check for + * @param sourceUrl The URL of the source (optional) * @param sourceMimeType The source mimetype + * @param sourceSize the size (in bytes) of the source. Use -1 if unknown. + * @param thumbnailDefinition The {@link ThumbnailDefinition} to check for */ - public boolean isThumbnailDefinitionAvailable(String sourceMimeType, ThumbnailDefinition thumbnailDefinition) + public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimeType, long sourceSize, ThumbnailDefinition thumbnailDefinition) { - return this.contentService.getTransformer( - sourceMimeType, - thumbnailDefinition.getMimetype(), - thumbnailDefinition.getTransformationOptions() - ) != null - ; + // Log the following getTransform() as trace so we can see the wood for the trees + boolean orig = TransformerDebug.setDebugOutput(false); + try + { + return this.contentService.getTransformer( + sourceUrl, + sourceMimeType, + sourceSize, + thumbnailDefinition.getMimetype(), thumbnailDefinition.getTransformationOptions() + ) != null; + } + finally + { + TransformerDebug.setDebugOutput(orig); + } } /** diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java index 9aa85dea5a..cf25c4233d 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -27,6 +27,7 @@ import org.alfresco.repo.content.transform.magick.ImageResizeOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.content.transform.swf.SWFTransformationOptions; import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.rendition.executer.AbstractTransformationRenderingEngine; import org.alfresco.repo.rendition.executer.ImageRenderingEngine; import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; import org.alfresco.service.cmr.rendition.RenditionDefinition; @@ -144,6 +145,13 @@ public class ThumbnailRenditionConvertor // putParameterIfNotNull(ImageRenderingEngine.PARAM_ASSOC_NAME, assocDetails.getAssociationName(), parameters); // putParameterIfNotNull(ImageRenderingEngine.PARAM_ASSOC_TYPE, assocDetails.getAssociationType(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_TIMEOUT_MS, transformationOptions.getTimeoutMs(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_READ_LIMIT_TIME_MS, transformationOptions.getReadLimitTimeMs(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_MAX_SOURCE_SIZE_K_BYTES, transformationOptions.getMaxSourceSizeKBytes(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_READ_LIMIT_K_BYTES, transformationOptions.getReadLimitKBytes(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_MAX_PAGES, transformationOptions.getMaxPages(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_PAGE_LIMIT, transformationOptions.getPageLimit(), parameters); + if (transformationOptions instanceof SWFTransformationOptions) { SWFTransformationOptions swfTransformationOptions = (SWFTransformationOptions)transformationOptions; diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java index 7f6e27af93..85db20e12e 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java @@ -109,7 +109,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest // Check that it is working ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); - if (!transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, MimetypeMap.MIMETYPE_IMAGE_JPEG, + 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."); @@ -542,8 +542,8 @@ 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(MimetypeMap.MIMETYPE_HTML, def - .getMimetype(), def.getTransformationOptions()); + 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, @@ -650,7 +650,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest public void testRegistry() { ThumbnailRegistry thumbnailRegistry = this.thumbnailService.getThumbnailRegistry(); - List defs = thumbnailRegistry.getThumbnailDefinitions(MimetypeMap.MIMETYPE_HTML); + List defs = thumbnailRegistry.getThumbnailDefinitions(null, MimetypeMap.MIMETYPE_HTML, -1); System.out.println("Definitions ..."); for (ThumbnailDefinition def : defs) { diff --git a/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java index 3ae49eaeec..23d57bb828 100644 --- a/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,6 +18,8 @@ */ package org.alfresco.repo.thumbnail; +import java.io.Serializable; +import java.util.HashMap; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; @@ -29,6 +31,7 @@ import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; 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.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.thumbnail.ThumbnailService; @@ -43,6 +46,7 @@ import org.apache.commons.logging.LogFactory; * * @author Roy Wetherall * @author Neil McErlean + * @author Ph Dubois (optional thumbnail creation by mimetype and in general) */ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase { @@ -58,6 +62,12 @@ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase /** Node Service */ private NodeService nodeService; + /** Property turns on and off all thumbnail creation */ + private boolean generateThumbnails = true; + + // Size limitations indexed by mime type for thumbnail creation + private HashMap mimetypeMaxSourceSizeKBytes; + /** Action name and parameters */ public static final String NAME = "update-thumbnail"; public static final String PARAM_CONTENT_PROPERTY = "content-property"; @@ -93,12 +103,40 @@ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase this.nodeService = nodeService; } + /** + * Set the maximum size for each mimetype above which thumbnails are not created. + * @param mimetypeMaxSourceSizeKBytes map of mimetypes to max source sizes. + */ + public void setMimetypeMaxSourceSizeKBytes(HashMap mimetypeMaxSourceSizeKBytes) + { + this.mimetypeMaxSourceSizeKBytes = mimetypeMaxSourceSizeKBytes; + } + + /** + * Enable thumbnail creation at all regardless of mimetype. + * @param generateThumbnails a {@code false} value turns off all thumbnail creation. + */ + public void setGenerateThumbnails(boolean generateThumbnails) + { + this.generateThumbnails = generateThumbnails; + } + /** * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) */ @Override protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { + // Check if thumbnailing is generally disabled + if (!generateThumbnails) + { + if (logger.isDebugEnabled()) + { + logger.debug("Thumbnail transformations are not enabled"); + } + return; + } + // Get the thumbnail NodeRef thumbnailNodeRef = (NodeRef)action.getParameterValue(PARAM_THUMBNAIL_NODE); if (thumbnailNodeRef == null) @@ -128,6 +166,26 @@ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase contentProperty = ContentModel.PROP_CONTENT; } + Serializable contentProp = nodeService.getProperty(actionedUponNodeRef, contentProperty); + if (contentProp == null) + { + logger.info("Creation of thumbnail, null content for " + details.getName()); + return; + } + + if(contentProp instanceof ContentData) + { + ContentData content = (ContentData)contentProp; + if (mimetypeMaxSourceSizeKBytes != null) + { + Long maxSourceSizeKBytes = mimetypeMaxSourceSizeKBytes.get(content.getMimetype()); + if (maxSourceSizeKBytes != null && maxSourceSizeKBytes < (content.getSize()/1024L)) + { + logger.info("Creation of thumbnail, '" + details.getName() + " , content too big ("+maxSourceSizeKBytes+"K)"); + return; //avoid transform + } + } + } // Create the thumbnail this.thumbnailService.updateThumbnail(thumbnailNodeRef, details.getTransformationOptions()); } diff --git a/source/java/org/alfresco/service/cmr/repository/ContentService.java b/source/java/org/alfresco/service/cmr/repository/ContentService.java index 03272e1e00..dc1388a9d3 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentService.java @@ -231,17 +231,24 @@ public interface ContentService * The transformation options provide a finer grain way of discovering the correct transformer, * since the values and type of the options provided are considered by the transformer when * deciding whether it can satisfy the transformation request. - * + * @param sourceUrl TODO * @param sourceMimetype the source mimetype + * @param sourceSize the source size (bytes). Ignored if negative. * @param targetMimetype the target mimetype * @param options the transformation options + * * @return ContentTransformer a transformer that can be used, or null if one was not available * * @see ContentAccessor#getMimetype() */ - @Auditable(parameters = {"sourceMimetype", "targetMimetype", "options"}) - public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options); + @Auditable(parameters = {"sourceMimetype", "sourceSize", "targetMimetype", "options"}) + public ContentTransformer getTransformer(String sourceUrl, String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options); + /** + * @deprecated use overloaded method with sourceSize parameter. + */ + public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** * Fetch all the transformers that are capable of transforming the content in the * given source mimetype to the given target mimetype with the provided transformation @@ -256,6 +263,7 @@ public interface ContentService * as well as their current behaviour. For these reasons, this list should not be cached. * * @param sourceMimetype the source mimetype + * @param sourceSize the source size (bytes). Ignored if negative. * @param targetMimetype the target mimetype * @param options the transformation options * @return ContentTransformers a List of the transformers that can be used, or the empty list if none were available @@ -263,9 +271,14 @@ public interface ContentService * @since 3.5 * @see ContentAccessor#getMimetype() */ - @Auditable(parameters = {"sourceMimetype", "targetMimetype", "options"}) - public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options); + @Auditable(parameters = {"sourceMimetype", "sourceSize", "targetMimetype", "options"}) + public List getActiveTransformers(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options); + /** + * @deprecated use overloaded method with sourceSize parameter. + */ + public List getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** * Fetch the transformer that is capable of transforming image content. * diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java new file mode 100644 index 0000000000..53a18c2de5 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.service.cmr.repository; + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.util.EqualsHelper; + +/** + * Represents maximum source values (that result in exceptions if exceeded) or + * limits on source values (that result in EOF (End Of File) being returned + * early). Options exist for elapse time, KBytes read or number of pages read. + * + * @author Alan Davis + */ +public class TransformationOptionLimits +{ + public static final String OPT_TIMEOUT_MS = "timeoutMs"; + public static final String OPT_READ_LIMIT_TIME_MS = "readLimitTimeMs"; + + public static final String OPT_MAX_SOURCE_SIZE_K_BYTES = "maxSourceSizeKBytes"; + public static final String OPT_READ_LIMIT_K_BYTES = "readLimitKBytes"; + + public static final String OPT_MAX_PAGES = "maxPages"; + public static final String OPT_PAGE_LIMIT = "pageLimit"; + + public static final String TIME_MESSAGE = "Both timeoutMs and readLimitTimeMs should not be set."; + public static final String KBYTES_MESSAGE = "Both maxSourceSizeKBytes and readLimitKBytes should not be set."; + public static final String PAGES_MESSAGE = "Both maxPages and pageLimit should not be set."; + + private TransformationOptionPair time = new TransformationOptionPair(); + private TransformationOptionPair kbytes = new TransformationOptionPair(); + private TransformationOptionPair pages = new TransformationOptionPair(); + + public TransformationOptionLimits() + { + time = new TransformationOptionPair(); + kbytes = new TransformationOptionPair(); + pages = new TransformationOptionPair(); + } + + private TransformationOptionLimits(TransformationOptionLimits a, TransformationOptionLimits b) + { + time = a.time.combine(b.time); + kbytes = a.kbytes.combine(b.kbytes); + pages = a.pages.combine(b.pages); + } + + // --------------- Time --------------- + public TransformationOptionPair getTimePair() + { + return time; + } + + public long getTimeoutMs() + { + return time.getMax(); + } + + public void setTimeoutMs(long timeoutMs) + { + time.setMax(timeoutMs, TIME_MESSAGE); + } + + public long getReadLimitTimeMs() + { + return time.getLimit(); + } + + public void setReadLimitTimeMs(long readLimitTimeMs) + { + time.setLimit(readLimitTimeMs, TIME_MESSAGE); + } + + // --------------- KBytes --------------- + public TransformationOptionPair getKBytesPair() + { + return kbytes; + } + + public long getMaxSourceSizeKBytes() + { + return kbytes.getMax(); + } + + public void setMaxSourceSizeKBytes(long maxSourceSizeKBytes) + { + kbytes.setMax(maxSourceSizeKBytes, KBYTES_MESSAGE); + } + + public long getReadLimitKBytes() + { + return kbytes.getLimit(); + } + + public void setReadLimitKBytes(long readLimitKBytes) + { + kbytes.setLimit(readLimitKBytes, KBYTES_MESSAGE); + } + + // --------------- Pages --------------- + public TransformationOptionPair getPagesPair() + { + return pages; + } + + public int getMaxPages() + { + return (int)pages.getMax(); + } + + public void setMaxPages(int maxPages) + { + pages.setMax(maxPages, PAGES_MESSAGE); + } + + public int getPageLimit() + { + return (int)pages.getLimit(); + } + + public void setPageLimit(int pageLimit) + { + pages.setLimit(pageLimit, PAGES_MESSAGE); + } + + // --------------- Map --------------- + public Map toMap(Map optionsMap) + { + time.toMap(optionsMap, OPT_TIMEOUT_MS, OPT_READ_LIMIT_TIME_MS); + kbytes.toMap(optionsMap, OPT_MAX_SOURCE_SIZE_K_BYTES, OPT_READ_LIMIT_K_BYTES); + pages.toMap(optionsMap, OPT_MAX_PAGES, OPT_PAGE_LIMIT); + return optionsMap; + } + + public static Map removeFromMap(Map optionsMap) + { + optionsMap.remove(OPT_TIMEOUT_MS); + optionsMap.remove(OPT_READ_LIMIT_TIME_MS); + optionsMap.remove(OPT_MAX_SOURCE_SIZE_K_BYTES); + optionsMap.remove(OPT_READ_LIMIT_K_BYTES); + optionsMap.remove(OPT_MAX_PAGES); + optionsMap.remove(OPT_PAGE_LIMIT); + return optionsMap; + } + + public void set(Map optionsMap) + { + time.set(optionsMap, OPT_TIMEOUT_MS, OPT_READ_LIMIT_TIME_MS, TIME_MESSAGE); + kbytes.set(optionsMap, OPT_MAX_SOURCE_SIZE_K_BYTES, OPT_READ_LIMIT_K_BYTES, KBYTES_MESSAGE); + pages.set(optionsMap, OPT_MAX_PAGES, OPT_PAGE_LIMIT, PAGES_MESSAGE); + } + + public String toString() + { + return toMap(new HashMap()).toString(); + } + + /** + * Returns a TransformationOptionLimits that has getter methods that combine the + * the values from the getter methods of this and the supplied TransformationOptionLimits. + */ + public TransformationOptionLimits combine(final TransformationOptionLimits that) + { + return new TransformationOptionLimits(this, that) + { + @Override + public void setTimeoutMs(long timeoutMs) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setReadLimitTimeMs(long readLimitTimeMs) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setMaxSourceSizeKBytes(long maxSourceSizeKBytes) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setReadLimitKBytes(long readLimitKBytes) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setMaxPages(int maxPages) + { + throw new UnsupportedOperationException(); + } + + @Override + public void setPageLimit(int pageLimit) + { + throw new UnsupportedOperationException(); + } + + @Override + public void set(Map optionsMap) + { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int hashCode() + { + int hashCode = 37 * time.hashCode(); + hashCode += 37 * kbytes.hashCode(); + hashCode += 37 * pages.hashCode(); + return hashCode; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj instanceof TransformationOptionLimits) + { + TransformationOptionLimits that = (TransformationOptionLimits) obj; + return + EqualsHelper.nullSafeEquals(time, that.time) && + EqualsHelper.nullSafeEquals(kbytes, that.kbytes) && + EqualsHelper.nullSafeEquals(pages, that.pages); + } + else + { + return false; + } + } +} diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimitsTest.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimitsTest.java new file mode 100644 index 0000000000..9dbc520b10 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimitsTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.service.cmr.repository; + + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +/** + * Test TransformationOptionLimits + */ +public class TransformationOptionLimitsTest +{ + private TransformationOptionLimits limits; + + @Before + public void setUp() throws Exception + { + limits = new TransformationOptionLimits(); + } + + @Test + public void testTimeoutMs() throws Exception + { + long value = 1234; + limits.setTimeoutMs(value); + long actual = limits.getTimeoutMs(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testReadLimitTimeMs() throws Exception + { + long value = 1234; + limits.setReadLimitTimeMs(value); + long actual = limits.getReadLimitTimeMs(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testMaxSourceSizeKBytes() throws Exception + { + long value = 1234; + limits.setMaxSourceSizeKBytes(value); + long actual = limits.getMaxSourceSizeKBytes(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testReadLimitKBytes() throws Exception + { + long value = 1234; + limits.setReadLimitKBytes(value); + long actual = limits.getReadLimitKBytes(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testMaxPages() throws Exception + { + int value = 1234; + limits.setMaxPages(value); + int actual = limits.getMaxPages(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testPageLimit() throws Exception + { + int value = 1234; + limits.setPageLimit(value); + int actual = limits.getPageLimit(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testTimeException() throws Exception + { + String message = null; + limits.setTimeoutMs(1); + try + { + limits.setReadLimitTimeMs(1); + } + catch (IllegalArgumentException e) + { + message = e.getMessage(); + } + assertEquals("Wrong exception message", TransformationOptionLimits.TIME_MESSAGE, message); + } + + @Test + public void testKBytesException() throws Exception + { + String message = null; + limits.setMaxSourceSizeKBytes(1); + try + { + limits.setReadLimitKBytes(1); + } + catch (IllegalArgumentException e) + { + message = e.getMessage(); + } + assertEquals("Wrong exception message", TransformationOptionLimits.KBYTES_MESSAGE, message); + } + + @Test + public void testPageException() throws Exception + { + String message = null; + limits.setPageLimit(1); + try + { + limits.setMaxPages(1); + } + catch (IllegalArgumentException e) + { + message = e.getMessage(); + } + assertEquals("Wrong exception message", TransformationOptionLimits.PAGES_MESSAGE, message); + } + + @Test + public void testMapMax() throws Exception + { + limits.setTimeoutMs(123); + limits.setMaxSourceSizeKBytes(456); + limits.setMaxPages(789); + + Map optionsMap = new HashMap(); + limits.toMap(optionsMap); + + TransformationOptionLimits actual = new TransformationOptionLimits(); + actual.set(optionsMap); + + assertEquals("Did not match original values", limits, actual); + } + + @Test + public void testMapLimit() throws Exception + { + limits.setReadLimitTimeMs(123); + limits.setReadLimitKBytes(456); + limits.setPageLimit(789); + + Map optionsMap = new HashMap(); + limits.toMap(optionsMap); + + TransformationOptionLimits actual = new TransformationOptionLimits(); + actual.set(optionsMap); + + assertEquals("Did not match original values", limits, actual); + } + + @Test + public void testTimePair() throws Exception + { + int value = 1234; + limits.setTimeoutMs(value); + + long actual = limits.getTimePair().getMax(); + + assertEquals("Returned TransformationOptionPair did not contain set value", value, actual); + } + + + @Test + public void testKBytesPair() throws Exception + { + int value = 1234; + limits.setMaxSourceSizeKBytes(value); + + long actual = limits.getKBytesPair().getMax(); + + assertEquals("Returned TransformationOptionPair did not contain set value", value, actual); + } + + + @Test + public void testPagePair() throws Exception + { + int value = 1234; + limits.setMaxPages(value); + + long actual = limits.getPagesPair().getMax(); + + assertEquals("Returned TransformationOptionPair did not contain set value", value, actual); + } + + @Test + public void testCombineOrder() throws Exception + { + limits.setReadLimitTimeMs(123); + limits.setReadLimitKBytes(45); + limits.setMaxPages(789); + + TransformationOptionLimits second = new TransformationOptionLimits(); + second.setReadLimitTimeMs(12); + second.setReadLimitKBytes(456); + second.setMaxPages(789); + + TransformationOptionLimits combined = limits.combine(second); + TransformationOptionLimits combinedOtherWay = second.combine(limits); + assertEquals("The combine order should not matter", combined, combinedOtherWay); + } + + @Test + public void testCombine() throws Exception + { + limits.setReadLimitTimeMs(123); // limit > + limits.setReadLimitKBytes(45); // limit < + limits.setMaxPages(789); // max = + + TransformationOptionLimits second = new TransformationOptionLimits(); + second.setTimeoutMs(12); // max < + second.setMaxSourceSizeKBytes(456); // max > + second.setMaxPages(789); // max = + + TransformationOptionLimits combined = limits.combine(second); + + assertEquals("Expected the lower value", 12, combined.getTimeoutMs()); // max < + assertEquals("Expected the lower value", 45, combined.getReadLimitKBytes()); // limit < + assertEquals("Expected the lower value", 789, combined.getMaxPages()); // max = + } + + @Test + public void testCombineDynamic() throws Exception + { + limits.setReadLimitTimeMs(123); + limits.setReadLimitKBytes(45); + limits.setMaxPages(789); + + TransformationOptionLimits second = new TransformationOptionLimits(); + second.setReadLimitTimeMs(12); + second.setReadLimitKBytes(456); + second.setMaxPages(789); + + TransformationOptionLimits combined = limits.combine(second); + + // Test dynamic change of value + limits.setReadLimitKBytes(4560); + assertEquals("Expected the lower value", 456, combined.getReadLimitKBytes()); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetTimeoutMs() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.setTimeoutMs(1); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetReadLimitTimeMs() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.setReadLimitTimeMs(1); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetMaxSourceSizeKBytes() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.setMaxSourceSizeKBytes(1); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetReadLimitKBytes() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.setReadLimitKBytes(1); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetMaxPages() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.setMaxPages(1); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetPageLimit() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.setPageLimit(1); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetMap() throws Exception + { + TransformationOptionLimits combined = limits.combine(limits); // may combine with itself + combined.set(null); + } +} + diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java new file mode 100644 index 0000000000..f355c888ef --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.service.cmr.repository; + +import java.io.IOException; +import java.util.Map; + +import org.alfresco.repo.content.transform.TransformerDebug; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A pair of transformation options that specify + * A) a max value over which the source is not read (throws an Exception) or + * B) a limit over which no more of the source is read (returns EOF) + * + * Each pair represents a values such as an elapse time, KBytes read or number of pages read. + * It is only meaningful for a either the max or limit value to be set. + * + * There is one pair of values for each transformer and another pair passed in via the + * options parameter for each individual transformation. The later is for specific types of + * transformation, such as thumbnail generation. When this occurs values are combined, by + * using the lowest of the four values. + * + * @author Alan Davis + */ +public class TransformationOptionPair +{ + /** + * Action to take place for a given pair of values. + */ + public enum Action + { + THROW_EXCEPTION + { + public void throwIOExceptionIfRequired(String message, TransformerDebug transformerDebug) throws IOException + { + throw transformerDebug.setCause(new IOException(message)); + } + }, + RETURN_EOF + { + public void throwIOExceptionIfRequired(String message, TransformerDebug transformerDebug) throws IOException + { + if (transformerDebug.isEnabled()) + { + transformerDebug.debug(message + " Returning EOF"); + } + } + }; + + public abstract void throwIOExceptionIfRequired(String message, TransformerDebug transformerDebug) throws IOException; + }; + + private long max = -1; + private long limit = -1; + + public long getMax() + { + return max; + } + + public void setMax(long max, String exceptionMessage) + { + if (max >= 0 && limit >= 0) + { + throw new IllegalArgumentException(exceptionMessage); + } + this.max = max; + } + + public long getLimit() + { + return limit; + } + + public void setLimit(long limit, String exceptionMessage) + { + if (max >= 0 && limit >= 0) + { + throw new IllegalArgumentException(exceptionMessage); + } + this.limit = limit; + } + + public long getValue() + { + return minSet(getMax(), getLimit()); + } + + public Action getAction() + { + return + (getMax() >= 0) ? Action.THROW_EXCEPTION : + (getLimit() >= 0) ? Action.RETURN_EOF + : null; + } + + /** + * Returns the lower of the two value supplied, ignoring values less than + * 0 unless both are less than zero. + */ + private long minSet(long value1, long value2) + { + if (value1 < 0) + { + return value2; + } + else if (value2 < 0) + { + return value1; + } + return Math.min(value1, value2); + } + + public Map toMap(Map optionsMap, String optMaxKey, String optLimitKey) + { + optionsMap.put(optMaxKey, getMax()); + optionsMap.put(optLimitKey, getLimit()); + return optionsMap; + } + + public void set(Map optionsMap, String optMaxKey, String optLimitKey, + String exceptionMessage) + { + long max = nvl((Long)optionsMap.get(optMaxKey)); + long limit = nvl((Long)optionsMap.get(optLimitKey)); + if (max >= 0 && limit >= 0) + { + throw new IllegalArgumentException(exceptionMessage); + } + if (max >= 0 || limit >= 0) + { + this.limit = limit; + this.max = max; + } + } + + private long nvl(Long l) + { + return l == null ? -1 : l; + } + + /** + * Returns a TransformationOptionPair that has getter methods that combine the + * the values from the getter methods of this and the supplied TransformationOptionPair. + */ + public TransformationOptionPair combine(final TransformationOptionPair that) + { + return new TransformationOptionPair() + { + /** + * Combines max values of this TransformationOptionPair and the supplied + * one to return the max to be used in a transformation. The limit + * value is discarded (-1 is returned) if the combined limit value is lower. + */ + @Override + public long getMax() + { + long max = minSet(TransformationOptionPair.this.getMax(), that.getMax()); + long limit = minSet(TransformationOptionPair.this.getLimit(), that.getLimit()); + + return (max >= 0 && (limit < 0 || limit >= max)) + ? max + : -1; + } + + @Override + public void setMax(long max, String exceptionMessage) + { + throw new UnsupportedOperationException(); + } + + /** + * Combines limit values of this TransformationOptionPair and the supplied + * one to return the limit to be used in a transformation. The limit + * value is discarded (-1 is returned) if the combined max value is lower. + */ + @Override + public long getLimit() + { + long max = minSet(TransformationOptionPair.this.getMax(), that.getMax()); + long limit = minSet(TransformationOptionPair.this.getLimit(), that.getLimit()); + + return (limit >= 0 && (max < 0 || max >= limit)) + ? limit + : -1; + } + + @Override + public void setLimit(long limit, String exceptionMessage) + { + throw new UnsupportedOperationException(); + } + + @Override + public void set(Map optionsMap, String optMaxKey, String optLimitKey, + String exceptionMessage) + { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int hashCode() + { + return (int) ((max > 0) ? max : limit); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj instanceof TransformationOptionPair) + { + TransformationOptionPair that = (TransformationOptionPair) obj; + return max == that.max && limit == that.limit; + } + else + { + return false; + } + } +} diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptionPairTest.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptionPairTest.java new file mode 100644 index 0000000000..8a39cf0075 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptionPairTest.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2005-2011 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 . + */ +package org.alfresco.service.cmr.repository; + + +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.service.cmr.repository.TransformationOptionPair.Action; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test TransformationOptionPair + */ +public class TransformationOptionPairTest +{ + TransformationOptionPair pair; + + @Before + public void setUp() throws Exception + { + pair = new TransformationOptionPair(); + } + + @Test + public void testUnset() throws Exception + { + long value = -1; + long actual = pair.getMax(); + + assertEquals("Getter did not return unset value", value, actual); + assertEquals("getValue() did not return set value", value, pair.getValue()); + assertEquals("Expected action not returned", null, pair.getAction()); + } + + @Test + public void testMax() throws Exception + { + long value = 1234; + pair.setMax(value, null); + long actual = pair.getMax(); + + assertEquals("Getter did not return set value", value, actual); + assertEquals("getValue() did not return set value", value, pair.getValue()); + assertEquals("Expected action not returned", Action.THROW_EXCEPTION, pair.getAction()); + } + + @Test + public void testLimit() throws Exception + { + long value = 1234; + pair.setLimit(value, null); + long actual = pair.getLimit(); + assertEquals("Getter did not return set value", value, actual); + assertEquals("getValue() did not return set value", value, pair.getValue()); + assertEquals("Expected action not returned", Action.RETURN_EOF, pair.getAction()); + } + + @Test + public void testMaxAlreadySet() throws Exception + { + String message = "Oh no the other value is set"; + String actual = null; + pair.setMax(1234, null); + try + { + pair.setLimit(111, message); + } + catch (IllegalArgumentException e) + { + actual = e.getMessage(); + } + assertEquals("Expected an IllegalArgumentException message", message, actual); + } + + @Test + public void testLimitAlreadySet() throws Exception + { + String message = "Oh no the other value is set"; + String actual = null; + pair.setLimit(1234, null); + try + { + pair.setMax(111, message); + } + catch (IllegalArgumentException e) + { + actual = e.getMessage(); + } + assertEquals("Expected an IllegalArgumentException message", message, actual); + } + + @Test + public void testSetMaxMultipleTimes() throws Exception + { + long value = 1234; + pair.setMax(1, null); + pair.setMax(2, null); // Should be no exception + pair.setMax(value, null); + long actual = pair.getMax(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testSetLimitMultipleTimes() throws Exception + { + long value = 1234; + pair.setLimit(1, null); + pair.setLimit(2, null); // Should be no exception + pair.setLimit(value, null); + long actual = pair.getLimit(); + assertEquals("Getter did not return set value", value, actual); + } + + @Test + public void testSetClearSet() throws Exception + { + // Test there is no exception if we clear the other value first + String message = "Oh no the other value is set"; + pair.setLimit(1, message); + pair.setLimit(-1, message); + pair.setMax(1, message); + pair.setMax(-1, message); + pair.setLimit(1, message); + } + + @Test + public void testMapMax() throws Exception + { + String maxKey = "Max"; + String limitKey = "Limit"; + String message = "Oh no the other value is set"; + pair.setMax(123, null); + + Map optionsMap = new HashMap(); + pair.toMap(optionsMap, maxKey, limitKey); + + TransformationOptionPair actual = new TransformationOptionPair(); + actual.set(optionsMap, maxKey, limitKey, message); + + assertEquals("Did not match original values", pair, actual); + } + + @Test + public void testMapLimit() throws Exception + { + String maxKey = "Max"; + String limitKey = "Limit"; + String message = "Oh no the other value is set"; + pair.setLimit(123, null); + + Map optionsMap = new HashMap(); + pair.toMap(optionsMap, maxKey, limitKey); + + TransformationOptionPair actual = new TransformationOptionPair(); + actual.set(optionsMap, maxKey, limitKey, message); + + assertEquals("Did not match original values", pair, actual); + } + + @Test + public void testMapBothSet() throws Exception + { + String maxKey = "Max"; + String limitKey = "Limit"; + String message = "Oh no the other value is set"; + pair.setLimit(123, null); + + Map optionsMap = new HashMap(); + pair.toMap(optionsMap, maxKey, limitKey); + optionsMap.put(maxKey, 456L); // Introduce error + + String actual = null; + TransformationOptionPair pair2 = new TransformationOptionPair(); + try + { + pair2.set(optionsMap, maxKey, limitKey, message); + } + catch (IllegalArgumentException e) + { + actual = e.getMessage(); + } + assertEquals("Expected an IllegalArgumentException message", message, actual); + } + + @Test + public void testMapNeitherSet() throws Exception + { + // Original value should not be changed if keys don't exist + long value = 1234; + String maxKey = "Max"; + String limitKey = "Limit"; + String message = "Oh no the other value is set"; + pair.setLimit(value, null); + + Map optionsMap = new HashMap(); + optionsMap.put("AnotherKey", 456L); + + pair.set(optionsMap, maxKey, limitKey, message); + long actual = pair.getLimit(); + assertEquals("Original value should not be changed", value, actual); + } + + @Test + public void testCombineOrder() throws Exception + { + TransformationOptionPair second = new TransformationOptionPair(); + + pair.setMax(123, null); + second.setMax(12, null); + TransformationOptionPair combined = pair.combine(second); + + TransformationOptionPair combinedOtherWay = second.combine(pair); + assertEquals("The combine order should not matter", combined, combinedOtherWay); + } + + @Test + public void testCombineMax() throws Exception + { + TransformationOptionPair second = new TransformationOptionPair(); + + pair.setMax(123, null); + second.setMax(12, null); + TransformationOptionPair combined = pair.combine(second); + + assertEquals("Expected the lower value", 12, combined.getValue()); + assertEquals("Expected the lower value", 12, combined.getMax()); + } + + @Test + public void testCombineLimit() throws Exception + { + TransformationOptionPair second = new TransformationOptionPair(); + + pair.setLimit(123, null); + second.setLimit(12, null); + TransformationOptionPair combined = pair.combine(second); + + assertEquals("Expected the lower value", 12, combined.getValue()); + assertEquals("Expected the lower value", 12, combined.getLimit()); + } + + @Test + public void testCombineMaxWins() throws Exception + { + // Try both max and limit values where max is lower + TransformationOptionPair second = new TransformationOptionPair(); + + pair.setLimit(123, null); + second.setMax(12, null); + TransformationOptionPair combined = pair.combine(second); + + assertEquals("Expected the lower value", 12, combined.getValue()); + assertEquals("Expected the lower value", 12, combined.getMax()); + assertEquals("Expected unset value", -1, combined.getLimit()); + } + + @Test + public void testCombineLimitWins() throws Exception + { + // Try both max and limit values where limit is lower + TransformationOptionPair second = new TransformationOptionPair(); + + pair.setMax(123, null); + second.setLimit(12, null); + TransformationOptionPair combined = pair.combine(second); + + assertEquals("Expected the lower value", 12, combined.getValue()); + assertEquals("Expected the lower value", 12, combined.getLimit()); + assertEquals("Expected unset value", -1, combined.getMax()); + } + + @Test + public void testCombineDynamicChange() throws Exception + { + TransformationOptionPair second = new TransformationOptionPair(); + + pair.setMax(123, null); + second.setMax(1234, null); + TransformationOptionPair combined = pair.combine(second); + + // Test dynamic changes of value + pair.setMax(45, null); + assertEquals("Expected the lower value", 45, combined.getMax()); + assertEquals("Expected an unset value", -1, combined.getLimit()); + + second.setMax(-1, null); + second.setLimit(10, null); + assertEquals("Expected an unset value", -1, combined.getMax()); + assertEquals("Expected the lower value", 10, combined.getLimit()); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetMax() throws Exception + { + TransformationOptionPair combined = pair.combine(pair); // may combine with itself + combined.setMax(1, null); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetLimit() throws Exception + { + TransformationOptionPair combined = pair.combine(pair); // may combine with itself + combined.setLimit(1, null); + } + + @Test(expected=UnsupportedOperationException.class) + public void testCombineSetMap() throws Exception + { + TransformationOptionPair combined = pair.combine(pair); // may combine with itself + combined.set(null, null, null, null); + } +} diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java index c68f3a2500..845417ad50 100644 --- a/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2011 Alfresco Software Limited. * * This file is part of Alfresco * @@ -58,6 +58,9 @@ public class TransformationOptions /** The include embedded resources yes/no */ private Boolean includeEmbedded; + + /** Time, KBytes and page limits */ + private TransformationOptionLimits limits = new TransformationOptionLimits(); /** * Default construtor @@ -91,12 +94,22 @@ public class TransformationOptions * @param optionsMap options map */ public TransformationOptions(Map optionsMap) + { + set(optionsMap); + } + + /** + * Sets options from the supplied map. + * @param optionsMap + */ + public void set(Map optionsMap) { this.sourceNodeRef = (NodeRef)optionsMap.get(OPT_SOURCE_NODEREF); this.sourceContentProperty = (QName)optionsMap.get(OPT_SOURCE_CONTENT_PROPERTY); this.targetNodeRef = (NodeRef)optionsMap.get(OPT_TARGET_NODEREF); this.targetContentProperty = (QName)optionsMap.get(OPT_TARGET_CONTENT_PROPERTY); this.includeEmbedded = (Boolean)optionsMap.get(OPT_INCLUDE_EMBEDDED); + limits.set(optionsMap); } /** @@ -207,7 +220,161 @@ public class TransformationOptions return includeEmbedded; } - /** + // --------------- Time --------------- + + /** + * Gets the timeout (ms) on the InputStream after which an IOExecption is thrown + * to terminate very slow transformations or a subprocess is terminated (killed). + * @return timeoutMs in milliseconds. If less than or equal to zero (the default) + * there is no timeout. + */ + public long getTimeoutMs() + { + return limits.getTimeoutMs(); + } + + /** + * Sets a timeout (ms) on the InputStream after which an IOExecption is thrown + * to terminate very slow transformations or to terminate (kill) a subprocess. + * @param timeoutMs in milliseconds. If less than or equal to zero (the default) + * there is no timeout. + * If greater than zero the {@code readLimitTimeMs} must not be set. + */ + public void setTimeoutMs(long timeoutMs) + { + limits.setTimeoutMs(timeoutMs); + } + + /** + * Gets the limit in terms of the amount of data read (by time) to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @return readLimitBytes if less than or equal to zero (the default) there is no limit. + */ + public long getReadLimitTimeMs() + { + return limits.getReadLimitTimeMs(); + } + + // --------------- KBytes --------------- + + /** + * Sets a limit in terms of the amount of data read (by time) to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @param readLimitBytes if less than or equal to zero (the default) there is no limit. + * If greater than zero the {@code timeoutMs} must not be set. + */ + public void setReadLimitTimeMs(long readLimitTimeMs) + { + limits.setReadLimitTimeMs(readLimitTimeMs); + } + + /** + * Gets the maximum source content size, to skip transformations where + * the source is just too large to expect it to perform. If the source is larger + * the transformer indicates it is not available. + * @return maxSourceSizeKBytes if less than or equal to zero (the default) there is no limit. + */ + public long getMaxSourceSizeKBytes() + { + return limits.getMaxSourceSizeKBytes(); + } + + /** + * Sets a maximum source content size, to skip transformations where + * the source is just too large to expect it to perform. If the source is larger + * the transformer indicates it is not available. + * @param maxSourceSizeKBytes if less than or equal to zero (the default) there is no limit. + * If greater than zero the {@code readLimitKBytes} must not be set. + */ + public void setMaxSourceSizeKBytes(long maxSourceSizeKBytes) + { + limits.setMaxSourceSizeKBytes(maxSourceSizeKBytes); + } + + /** + * Gets the limit in terms of the about of data read to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @return readLimitKBytes if less than or equal to zero (the default) no limit should be applied. + */ + public long getReadLimitKBytes() + { + return limits.getReadLimitKBytes(); + } + + /** + * Sets a limit in terms of the about of data read to limit transformations where + * only the start of the content is needed. After this limit is reached the InputStream reports + * end of file. + * @param readLimitKBytes if less than or equal to zero (the default) there is no limit. + * If greater than zero the {@code maxSourceSizeKBytes} must not be set. + */ + public void setReadLimitKBytes(long readLimitKBytes) + { + limits.setReadLimitKBytes(readLimitKBytes); + } + + // --------------- Pages --------------- + + /** + * Get the maximum number of pages read before an exception is thrown. + * @return If less than or equal to zero (the default) no limit should be applied. + */ + public int getMaxPages() + { + return limits.getMaxPages(); + } + + /** + * Set the number of pages read from the source before an exception is thrown. + * + * @param maxPages the number of pages to be read from the source. If less than or equal to zero + * (the default) no limit is applied. + */ + public void setMaxPages(int maxPages) + { + limits.setMaxPages(maxPages); + } + + /** + * Get the page limit before returning EOF. + * @return If less than or equal to zero (the default) no limit should be applied. + */ + public int getPageLimit() + { + return limits.getPageLimit(); + } + + /** + * Set the number of pages read from the source before returning EOF. + * + * @param pageLimit the number of pages to be read from the source. If less + * than or equal to zero (the default) no limit is applied. + */ + public void setPageLimit(int pageLimit) + { + limits.setPageLimit(pageLimit); + } + + /** + * Returns max and limit values for time, size and pages in a single operation. + */ + public TransformationOptionLimits getLimits() + { + return limits; + } + + /** + * Sets max and limit values for time, size and pages in a single operation. + */ + public void setLimits(TransformationOptionLimits limits) + { + this.limits = limits; + } + + /** * Convert the transformation options into a map. *

* Basic options (optional) are: @@ -217,6 +384,12 @@ public class TransformationOptions *

  • {@link #OPT_TARGET_NODEREF}
  • *
  • {@link #OPT_TARGET_CONTENT_PROPERTY}
  • *
  • {@link #OPT_INCLUDE_EMBEDDED}
  • + *
  • {@link TransformationOptionLimits#OPT_TIMEOUT_MS}
  • + *
  • {@link TransformationOptionLimits#OPT_READ_LIMIT_TIME_MS}
  • + *
  • {@link TransformationOptionLimits#OPT_MAX_SOURCE_SIZE_K_BYTES = "maxSourceSizeKBytes"; + *
  • {@link TransformationOptionLimits#OPT_READ_LIMIT_K_BYTES}
  • + *
  • {@link TransformationOptionLimits#OPT_MAX_PAGES}
  • + *
  • {@link TransformationOptionLimits#OPT_PAGE_LIMIT}
  • * *

    * Override this method to append option values to the map. Derived classes should call @@ -230,9 +403,25 @@ public class TransformationOptions optionsMap.put(OPT_TARGET_NODEREF, targetNodeRef); optionsMap.put(OPT_TARGET_CONTENT_PROPERTY, targetContentProperty); optionsMap.put(OPT_INCLUDE_EMBEDDED, includeEmbedded); + limits.toMap(optionsMap); return optionsMap; } + public String toString(boolean includeLimits) + { + Map map = toMap(); + if (!includeLimits) + { + TransformationOptionLimits.removeFromMap(map); + } + return map.toString(); + } + + public String toString() + { + return toMap().toString(); + } + public static TypeConverter.Converter relaxedBooleanTypeConverter = new TypeConverter.Converter() { public Boolean convert(String source)