diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index b79021e03d..4b55940383 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -239,6 +239,21 @@ + + + + + + + + + + + + + + class="org.alfresco.repo.content.transform.FailoverContentTransformer" + parent="baseContentTransformer" > + + + + + + Transformers are considered to have failed of they throw an exception. + * + * @author Neil McErlean + */ +public class FailoverContentTransformer extends AbstractContentTransformer2 implements InitializingBean +{ + private static Log logger = LogFactory.getLog(FailoverContentTransformer.class); + private List transformers; + + public FailoverContentTransformer() + { + // Intentionally empty + } + + /** + * The list of transformers to use. There must be at least one, but for failover behaviour to work + * there should be at least two. + * + * @param transformers list of transformers. + */ + public void setTransformers(List transformers) + { + this.transformers = transformers; + } + + /** + * Ensures that required properties have been set + */ + public void afterPropertiesSet() throws Exception + { + if (transformers == null || transformers.size() == 0) + { + throw new AlfrescoRuntimeException("At least one inner transformer must be supplied: " + this); + } + if (getMimetypeService() == null) + { + throw new AlfrescoRuntimeException("'mimetypeService' is a required property"); + } + } + + /** + * + * @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) + { + // For this transformer to be considered operational, there must be at least one transformer + // in the chain that can perform for us. + boolean result = false; + + for (ContentTransformer ct : this.transformers) + { + if (ct.isTransformable(sourceMimetype, targetMimetype, options)) + { + result = true; + break; + } + } + + return result; + } + + public boolean isExplicitTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + boolean result = true; + for (ContentTransformer ct : this.transformers) + { + if (ct.isExplicitTransformation(sourceMimetype, targetMimetype, options) == false) + { + result = false; + } + } + return result; + } + + + /** + * @see org.alfresco.repo.content.transform.AbstractContentTransformer2#transformInternal(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, org.alfresco.service.cmr.repository.TransformationOptions) + */ + @Override + public void transformInternal( + ContentReader reader, + ContentWriter writer, + TransformationOptions options) throws Exception + { + final String outputMimetype = writer.getMimetype(); + final String outputFileExt = getMimetypeService().getExtension(outputMimetype); + + // We need to keep a reference to thrown exceptions as we're going to catch them and + // 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; + ContentTransformer transf = transformers.get(i); + ContentWriter currentWriter = null; + File tempFile = null; + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Transformation attempt " + oneBasedCount + " of " + transformers.size() + ": " + transf); + } + + // We can't know in advance which transformer in the sequence will work - if any. + // Therefore we can't write into the ContentWriter stream. + // So make a temporary file writer with the current transformer name. + tempFile = TempFileProvider.createTempFile( + "FailoverTransformer_intermediate_" + transf.getClass().getSimpleName() + "_", + "." + outputFileExt); + currentWriter = new FileContentWriter(tempFile); + currentWriter.setMimetype(outputMimetype); + currentWriter.setEncoding(writer.getEncoding()); + + // attempt to transform + transf.transform(reader, currentWriter, options); + + // TODO Could add a check for zero-length output and treat that as a failure + // final long writtenSize = currentWriter.getSize(); + } + catch (Exception are) + { + transformationException = are; + + if (logger.isDebugEnabled()) + { + logger.debug("Transformation " + oneBasedCount + " was unsuccessful."); + if (i != transformers.size() - 1) + { + // We don't log the last exception as we're going to throw it. + logger.debug("The below exception is provided for information purposes only.", are); + } + } + + // Set a new reader to refresh the input stream. + reader = reader.getReader(); + // and move to the next transformer + continue; + } + // No need to close input or output streams + + // At this point the current transformation was successful i.e. it did not throw an exception. + + // Now we must copy the content from the temporary file into the ContentWriter stream. + if (tempFile != null) + { + writer.putContent(tempFile); + } + + if (logger.isInfoEnabled()) + { + logger.info("Transformation was successful"); + } + return; + } + // At this point we have tried all transformers in the sequence without apparent success. + if (transformationException != null) + { + if (logger.isDebugEnabled()) + { + logger.debug("All transformations were unsuccessful. Throwing latest exception.", transformationException); + } + throw transformationException; + } + } +} diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest-context.xml b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest-context.xml new file mode 100644 index 0000000000..648264cd08 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest-context.xml @@ -0,0 +1,31 @@ + + + + + + + true + + + + + + false + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java new file mode 100644 index 0000000000..3eae740c83 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.content.transform; + +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.TransformationOptions; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Tests the FailoverContentTransformer. + * + * @see org.alfresco.repo.content.transform.FailoverContentTransformer + * + * @author Neil McErlean + */ +public class FailoverContentTransformerTest extends AbstractContentTransformerTest +{ + private static final String sourceMimeType = MimetypeMap.MIMETYPE_PDF; + private static final String targetMimeType = MimetypeMap.MIMETYPE_IMAGE_PNG; + + private static ApplicationContext failoverAppContext = + new ClassPathXmlApplicationContext(new String[] {"classpath:org/alfresco/repo/content/transform/FailoverContentTransformerTest-context.xml"}, + ApplicationContextHelper.getApplicationContext()); + + private FailoverContentTransformer transformer; + + @Override + public void setUp() throws Exception + { + super.setUp(); + ApplicationContextHelper.getApplicationContext(); + + transformer = (FailoverContentTransformer) failoverAppContext.getBean("transformer.failover.Test-FailThenSucceed"); + transformer.setMimetypeService(mimetypeService); + } + + /** + * @return Returns the same transformer regardless - it is allowed + */ + protected ContentTransformer getTransformer(String sourceMimetype, String targetMimetype) + { + return transformer; + } + + public void testReliability() throws Exception + { + // The MIME types here are rather arbitrary + + boolean reliability = transformer.isTransformable(sourceMimeType, targetMimeType, new TransformationOptions()); + assertEquals("Mimetype should be supported", true, reliability); + } +} + +/** + * This dummy class is used only for test purposes within this source file. + * + * @author Neil McErlean + */ +class DummyTestContentTransformer extends AbstractContentTransformer2 implements BeanNameAware +{ + private static Log logger = LogFactory.getLog(DummyTestContentTransformer.class); + + /** Bean name for logging */ + private String springBeanName; + private boolean alwaysFail; + + public void setAlwaysFail(boolean value) + { + this.alwaysFail = value; + } + + @Override + protected void transformInternal(ContentReader reader, + ContentWriter writer, TransformationOptions options) + throws Exception + { + // Do not actually perform any transformation. The test above is only interested in whether + // an exception is thrown and handled. + if (logger.isInfoEnabled()) + { + logger.info(springBeanName + " is attempting a transformation"); + } + + reader.getContentString(); + + if (alwaysFail) + { + throw new AlfrescoRuntimeException("Test code intentionally failed method call."); + } + else + { + return; + } + } + + public boolean isTransformable(String sourceMimetype, + String targetMimetype, TransformationOptions options) + { + // We'll arbitrarily claim to be able to transform PDF to PNG + return (MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) && + MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype)); + } + + public void setBeanName(String name) + { + this.springBeanName = name; + } +} diff --git a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java new file mode 100644 index 0000000000..97bbb31f05 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.content.transform; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +import javax.imageio.ImageIO; + +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.TransformationOptions; +import org.alfresco.util.TempFileProvider; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; + +/** + * Makes use of the {@link http://www.pdfbox.org/ PDFBox} library to + * perform conversions from PDF files to images. + * + * @author Neil McErlean + */ +public class PdfBoxPdfToImageContentTransformer extends AbstractContentTransformer2 +{ + private static Log logger = LogFactory.getLog(PdfBoxPdfToImageContentTransformer.class); + + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + // only support PDF -> PNG + return (MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) == true && + MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype) == true); + } + + @SuppressWarnings("unchecked") + protected void transformInternal( + ContentReader reader, + ContentWriter writer, + TransformationOptions options) throws Exception + { + PDDocument document = null; + try + { + File file = TempFileProvider.createTempFile("pdfToImage", ".pdf"); + reader.getContent(file); + + document = PDDocument.load(file); + + if (document.isEncrypted()) + { + String msg = "PDF document is encrypted."; + if (logger.isInfoEnabled()) + { + logger.info(msg); + } + throw new AlfrescoRuntimeException(msg); + } + + final int resolution = 16; //TODO A rather arbitrary number for resolution (DPI) here. + + List pages = document.getDocumentCatalog().getAllPages(); + PDPage page = (PDPage)pages.get(0); + BufferedImage img = page.convertToImage(BufferedImage.TYPE_INT_ARGB, resolution); + + File outputFile = TempFileProvider.createTempFile("pdfToImageOutput", ".png"); + ImageIO.write(img, "png", outputFile); + + writer.putContent(outputFile); + } + catch (FileNotFoundException e1) + { + throw new AlfrescoRuntimeException("Unable to create image from pdf file.", e1); + } + catch (IOException e) + { + throw new AlfrescoRuntimeException("Unable to create image from pdf file.", e); + } + finally + { + if( document != null ) + { + document.close(); + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java index 62c0ea756e..5a848e0916 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java @@ -102,8 +102,8 @@ public class PdfToImageContentTransformer extends AbstractContentTransformer2 logger.info(msg.toString()); } - PDFPage page = pdffile.getPage(0); - + PDFPage page = pdffile.getPage(0, true); + //get the width and height for the doc at the default zoom int width=(int)page.getBBox().getWidth(); int height=(int)page.getBBox().getHeight();