diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 781b59b8e8..f6ea0ba147 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -115,6 +115,9 @@ + + + @@ -262,6 +265,9 @@ + + + @@ -287,6 +293,9 @@ ${content.transformer.default.readLimitKBytes} ${content.transformer.default.pageLimit} ${content.transformer.default.maxPages} + + + diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 0993bab943..3485eb18ba 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -1332,7 +1332,11 @@ - + + + + Marker aspect to prevent the creation of renditions for a node. + Failed Thumbnail Source diff --git a/config/alfresco/rendition-services-context.xml b/config/alfresco/rendition-services-context.xml index 85735d07ed..79d9b70c13 100644 --- a/config/alfresco/rendition-services-context.xml +++ b/config/alfresco/rendition-services-context.xml @@ -45,6 +45,7 @@ + @@ -77,8 +78,28 @@ - - + + + + + + + + + + + + + + + + + + + ${cifs.sessionTimeout} + + + + + ${cifs.maximumVirtualCircuitsPerSession} + + diff --git a/config/alfresco/subsystems/fileServers/default/file-servers.properties b/config/alfresco/subsystems/fileServers/default/file-servers.properties index 536fbcbd3f..8ec5771f04 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers.properties +++ b/config/alfresco/subsystems/fileServers/default/file-servers.properties @@ -39,6 +39,9 @@ cifs.disableNativeCode=false # Session timeout, in seconds. Defaults to 15 minutes, to match the default Windows client setting. # If no I/O is received within that time the session is closed by the server cifs.sessionTimeout=900 +# Maximum virtual circuits per session +# Should only be changed when using Terminal Server clients +cifs.maximumVirtualCircuitsPerSession=16 # Can be mapped to non-privileged ports, then use firewall rules to forward requests from the standard ports cifs.tcpipSMB.port=445 diff --git a/source/java/org/alfresco/cmis/renditions/CMISRenditionServiceTest.java b/source/java/org/alfresco/cmis/renditions/CMISRenditionServiceTest.java index a609b553c1..28cbc25906 100644 --- a/source/java/org/alfresco/cmis/renditions/CMISRenditionServiceTest.java +++ b/source/java/org/alfresco/cmis/renditions/CMISRenditionServiceTest.java @@ -35,6 +35,7 @@ import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TransformationOptions; /** * @author Stas Sokolovsky @@ -207,9 +208,12 @@ public class CMISRenditionServiceTest extends BaseCMISTest contentWriter.setMimetype(mimetype); contentWriter.setLocale(Locale.ENGLISH); - if (contentService.isTransformable(contentReader, contentWriter)) + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(textDocument); + + if (contentService.isTransformable(contentReader, contentWriter, options)) { - contentService.transform(contentReader, contentWriter); + contentService.transform(contentReader, contentWriter, options); } fileFolderService.delete(textDocument); diff --git a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java index 0c2c9f4196..f171737737 100644 --- a/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/auth/cifs/EnterpriseCifsAuthenticator.java @@ -979,7 +979,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implement // Failed to allocate a UID - throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + throw new SMBSrvException(SMBStatus.NTTooManySessions, SMBStatus.SRVTooManyUIDs, SMBStatus.ErrSrv); } else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { diff --git a/source/java/org/alfresco/filesys/config/CIFSConfigBean.java b/source/java/org/alfresco/filesys/config/CIFSConfigBean.java index 4e5da2c7b8..9424a620be 100644 --- a/source/java/org/alfresco/filesys/config/CIFSConfigBean.java +++ b/source/java/org/alfresco/filesys/config/CIFSConfigBean.java @@ -19,6 +19,7 @@ package org.alfresco.filesys.config; import org.alfresco.jlan.server.auth.ICifsAuthenticator; +import org.alfresco.jlan.smb.server.VirtualCircuitList; // TODO: Auto-generated Javadoc /** @@ -89,6 +90,10 @@ public class CIFSConfigBean /** The session timeout. */ private Integer sessionTimeout; + // Maximum virtual circuits per session + + private int m_maxVC = VirtualCircuitList.DefMaxCircuits; + /** * Checks if is server enabled. * @@ -351,6 +356,15 @@ public class CIFSConfigBean return tcpipSMB; } + /** + * Return the maxmimum virtual circuits per session + * + * @return int + */ + public int getMaximumVirtualCircuits() { + return m_maxVC; + } + /** * Sets the tcpip smb. * @@ -509,4 +523,12 @@ public class CIFSConfigBean this.sessionTimeout = sessionTimeout; } + /** + * Set the maximum virtual circuits per session + * + * @param maxVC int + */ + public void setMaximumVirtualCircuits( int maxVC) { + m_maxVC = maxVC; + } } diff --git a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java index 9d00e53aef..0c1e012afe 100644 --- a/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java +++ b/source/java/org/alfresco/filesys/config/ServerConfigurationBean.java @@ -77,6 +77,7 @@ import org.alfresco.jlan.server.filesys.cache.hazelcast.ClusterConfigSection; import org.alfresco.jlan.server.filesys.cache.hazelcast.HazelCastClusterFileStateCache; import org.alfresco.jlan.server.thread.ThreadRequestPool; import org.alfresco.jlan.smb.server.CIFSConfigSection; +import org.alfresco.jlan.smb.server.VirtualCircuitList; import org.alfresco.jlan.util.IPAddress; import org.alfresco.jlan.util.MemorySize; import org.alfresco.jlan.util.Platform; @@ -334,12 +335,21 @@ public class ServerConfigurationBean extends AbstractServerConfigurationBean imp } // Check for a server comment + String comment = cifsConfigBean.getServerComment(); if (comment != null && comment.length() > 0) { cifsConfig.setComment(comment); } + // Set the maximum virtual circuits per session + + if ( cifsConfigBean.getMaximumVirtualCircuits() < VirtualCircuitList.MinCircuits || + cifsConfigBean.getMaximumVirtualCircuits() > VirtualCircuitList.MaxCircuits) + throw new AlfrescoRuntimeException("Invalid virtual circuits value, valid range is " + VirtualCircuitList.MinCircuits + " - " + VirtualCircuitList.MaxCircuits); + else + cifsConfig.setMaximumVirtualCircuits( cifsConfigBean.getMaximumVirtualCircuits()); + // Check for a bind address // Check if the network adapter name has been specified diff --git a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java index 015db34dea..9801efe104 100644 --- a/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/ImageTransformActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -78,6 +78,7 @@ public class ImageTransformActionExecuter extends TransformActionExecuter // create some options for the transform ImageTransformationOptions imageOptions = new ImageTransformationOptions(); imageOptions.setCommandOptions(convertCommand); + imageOptions.setSourceNodeRef(sourceNodeRef); // check if the transformer is going to work, i.e. is available if (!this.imageMagickContentTransformer.isTransformable(contentReader.getMimetype(), contentReader.getSize(), contentWriter diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java index 5e5cf635ad..af566c308c 100644 --- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -172,7 +172,9 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase throw new RuleServiceException(CONTENT_READER_NOT_FOUND_MESSAGE); } - if (null == contentService.getTransformer(contentReader.getContentUrl(), contentReader.getMimetype(), contentReader.getSize(), mimeType, new TransformationOptions())) + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(actionedUponNodeRef); + if (null == contentService.getTransformer(contentReader.getContentUrl(), contentReader.getMimetype(), contentReader.getSize(), mimeType, options)) { throw new RuleServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, contentReader.getMimetype(), mimeType)); } diff --git a/source/java/org/alfresco/repo/content/ContentServiceImpl.java b/source/java/org/alfresco/repo/content/ContentServiceImpl.java index 34e7de94d3..c36dd94300 100644 --- a/source/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/source/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -33,7 +33,6 @@ import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; import org.alfresco.repo.content.filestore.FileContentStore; -import org.alfresco.repo.content.transform.AbstractContentTransformerLimits; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.ContentTransformerRegistry; import org.alfresco.repo.content.transform.TransformerDebug; @@ -41,7 +40,6 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -93,6 +91,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa private RetryingTransactionHelper transactionHelper; private ApplicationContext applicationContext; protected TransformerDebug transformerDebug; + private MimetypeService mimetypeService; + /** a registry of all available content transformers */ private ContentTransformerRegistry transformerRegistry; @@ -190,6 +190,15 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa this.transformerDebug = transformerDebug; } + /** + * Helper setter of the mimetypeService. + * @param mimetypeService + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + /** * Service initialise */ @@ -539,7 +548,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa public void transform(ContentReader reader, ContentWriter writer) { // Call transform with no options - TransformationOptions options = null; + TransformationOptions options = new TransformationOptions(); this.transform(reader, writer, options); } @@ -577,11 +586,11 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa throw new AlfrescoRuntimeException("The content writer mimetype must be set: " + writer); } + long sourceSize = reader.getSize(); try { // look for a transformer - transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype); - long sourceSize = reader.getSize(); + transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype, options); List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.transform(...)"); @@ -597,7 +606,11 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } finally { - transformerDebug.popAvailable(); + if (transformerDebug.isEnabled()) + { + transformerDebug.popAvailable(); + debugActiveTransformers(sourceMimetype, targetMimetype, sourceSize, options); + } } } @@ -623,7 +636,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa try { // look for a transformer - transformerDebug.pushAvailable(sourceUrl, sourceMimetype, targetMimetype); + transformerDebug.pushAvailable(sourceUrl, sourceMimetype, targetMimetype, options); List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.getTransformer(...)"); return (transformers.isEmpty()) ? null : transformers.get(0); @@ -633,6 +646,74 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa transformerDebug.popAvailable(); } } + + /** + * Checks if the file just uploaded into Share is a special "debugTransformers.txt" file and + * if it is creates TransformerDebug that lists all the supported mimetype transformation for + * each transformer. + */ + private void debugActiveTransformers(String sourceMimetype, String targetMimetype, + long sourceSize, TransformationOptions transformOptions) + { + // check the file name, but do faster tests first + if (sourceSize == 18 && + MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && + MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype) && + "debugTransformers.txt".equals(transformerDebug.getFileName(transformOptions, true, 0))) + { + debugActiveTransformers(); + } + } + + /** + * Creates TransformerDebug that lists all the supported mimetype transformation for each transformer. + */ + private void debugActiveTransformers() + { + try + { + transformerDebug.pushMisc(); + transformerDebug.debug("Active and inactive transformers (list not reduced by 'explicit' settings)"); + TransformationOptions options = new TransformationOptions(); + for (ContentTransformer transformer: transformerRegistry.getTransformers()) + { + try + { + transformerDebug.pushMisc(); + int mimetypePairCount = 0; + boolean first = true; + for (String sourceMimetype : mimetypeService.getMimetypes()) + { + for (String targetMimetype : mimetypeService.getMimetypes()) + { + if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) + { + long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes( + sourceMimetype, targetMimetype, options); + boolean explicit = transformer.isExplicitTransformation(sourceMimetype, + targetMimetype, options); + transformerDebug.activeTransformer(++mimetypePairCount, transformer, + sourceMimetype, targetMimetype, maxSourceSizeKBytes, explicit, first); + first = false; + } + } + } + if (first) + { + transformerDebug.inactiveTransformer(transformer); + } + } + finally + { + transformerDebug.popMisc(); + } + } + } + finally + { + transformerDebug.popMisc(); + } + } /** * {@inheritDoc} @@ -642,8 +723,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa try { long maxSourceSize = 0; - transformerDebug.pushAvailable(null, sourceMimetype, targetMimetype); - List transformers = getActiveTransformers(sourceMimetype, 0, targetMimetype, options); + transformerDebug.pushAvailable(null, sourceMimetype, targetMimetype, options); + List transformers = getActiveTransformers(sourceMimetype, -1, targetMimetype, options); for (ContentTransformer transformer: transformers) { long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes(sourceMimetype, targetMimetype, options); diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java index 2748c44ef5..680cb9e32c 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-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,6 +19,7 @@ package org.alfresco.repo.content.transform; import java.util.Map; +import java.util.Properties; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.cmr.repository.ContentIOException; @@ -28,6 +29,7 @@ import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; /** * Provides basic services for {@link org.alfresco.repo.content.transform.ContentTransformer} @@ -44,15 +46,16 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo private static final Log logger = LogFactory.getLog(AbstractContentTransformer2.class); private ContentTransformerRegistry registry; - private double averageTime = 0.0; + private Properties properties; + private double averageTime; private long count = 0L; /** - * All transformers start with an average transformation time of 0.0ms. + * All transformers start with an average transformation time of 0.0 ms, + * unless there is an Alfresco global property {@code .initialTime}. */ protected AbstractContentTransformer2() { - averageTime = 0.0; } /** @@ -65,6 +68,64 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo this.registry = registry; } + /** + * The Alfresco global properties. + */ + public void setProperties(Properties properties) + { + this.properties = properties; + } + + /** + * Sets the averageTime and count (if global properties were used to set the averageTime). + * Both default to 0. The property names use the transformer bean name with a ".time" + * or ".count" suffix. Spring bean configuration is not being used as we don't wish to + * break existing transformers that know nothing about these properties. + */ + private void setAverageTimeFromAlfrescoGlobalProperties() + { + String beanName = getBeanName(); + averageTime = Long.valueOf(getPositiveLongProperty(beanName+".time", 0L)); + if (averageTime > 0.0) + { + // This normally is a large number so that it does not change much if used. + count = Long.valueOf(getPositiveLongProperty(beanName+".count", 10000)); + } + } + + /** + * Returns a positive long value from an optional Alfresco global property. + * Invalid values are ignored but a log message is issued. + * @param name of the property + * @param defaultValue if the property does not exist or is negative + * @return the value + */ + private long getPositiveLongProperty(String name, long defaultValue) + { + long value = defaultValue; + if (properties != null) + { + String property = properties.getProperty(name); + if (property != null) + { + try + { + value = Long.valueOf(property); + if (value < 0) + { + value = defaultValue; + throw new NumberFormatException(); + } + } + catch (NumberFormatException e) + { + logger.warn("Alfresco global property "+name+" is must be a positive Java long value. Using "+defaultValue); + } + } + } + return value; + } + @Override public String toString() { @@ -78,6 +139,8 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo /** * Registers this instance with the {@link #setRegistry(ContentTransformerRegistry) registry} * if it is present. + * + * THIS IS A CUSTOME SPRING INIT METHOD */ public void register() { @@ -88,6 +151,8 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo return; } + setAverageTimeFromAlfrescoGlobalProperties(); + // register this instance for the fallback case registry.addTransformer(this); } @@ -159,7 +224,8 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo { if (transformerDebug.isEnabled()) { - transformerDebug.pushTransform(this, reader.getContentUrl(), reader.getMimetype(), writer.getMimetype(), reader.getSize()); + transformerDebug.pushTransform(this, reader.getContentUrl(), reader.getMimetype(), + writer.getMimetype(), reader.getSize(), options); } // Check the transformability diff --git a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java index 7b38d4af1e..da96bb4cf8 100644 --- a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java @@ -33,6 +33,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.TransformationOptionLimits; import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; @@ -314,37 +315,55 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple ContentWriter writer, TransformationOptions options) throws Exception { - ContentReader currentReader = reader; - - Iterator transformerIterator = transformers.iterator(); - Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); - while (transformerIterator.hasNext()) + NodeRef origSourceNodeRef = options.getSourceNodeRef(); + try { - ContentTransformer transformer = transformerIterator.next(); - // determine the target mimetype. This is the final target if we are on the last transformation - ContentWriter currentWriter = null; - if (!transformerIterator.hasNext()) + ContentReader currentReader = reader; + + Iterator transformerIterator = transformers.iterator(); + Iterator intermediateMimetypeIterator = intermediateMimetypes.iterator(); + boolean first = true; + while (transformerIterator.hasNext()) { - currentWriter = writer; + ContentTransformer transformer = transformerIterator.next(); + // determine the target mimetype. This is the final target if we are on the last transformation + ContentWriter currentWriter = null; + if (!transformerIterator.hasNext()) + { + currentWriter = writer; + } + else + { + String nextMimetype = intermediateMimetypeIterator.next(); + // make a temp file writer with the correct extension + String sourceExt = getMimetypeService().getExtension(currentReader.getMimetype()); + String targetExt = getMimetypeService().getExtension(nextMimetype); + File tempFile = TempFileProvider.createTempFile( + "ComplextTransformer_intermediate_" + sourceExt + "_", + "." + targetExt); + currentWriter = new FileContentWriter(tempFile); + currentWriter.setMimetype(nextMimetype); + + // Must clear the sourceNodeRef to avoid transformers thinking the temporary file + // is the original node. Not done for the first transformer as the name will be + // correct. + if (!first) + { + options.setSourceNodeRef(null); + } + first = false; + } + // transform + transformer.transform(currentReader, currentWriter, options); + // move the source on + currentReader = currentWriter.getReader(); } - else - { - String nextMimetype = intermediateMimetypeIterator.next(); - // make a temp file writer with the correct extension - String sourceExt = getMimetypeService().getExtension(currentReader.getMimetype()); - String targetExt = getMimetypeService().getExtension(nextMimetype); - File tempFile = TempFileProvider.createTempFile( - "ComplextTransformer_intermediate_" + sourceExt + "_", - "." + targetExt); - currentWriter = new FileContentWriter(tempFile); - currentWriter.setMimetype(nextMimetype); - } - // transform - transformer.transform(currentReader, currentWriter, options); - // move the source on - currentReader = currentWriter.getReader(); + // done + } + finally + { + options.setSourceNodeRef(origSourceNodeRef); } - // done } public List getIntermediateMimetypes() diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java index b3b91eef97..df6c47acb7 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java @@ -70,6 +70,11 @@ public class ContentTransformerRegistry } } + public List getTransformers() + { + return Collections.unmodifiableList(transformers); + } + /** * @deprecated use overloaded version with sourceSize parameter. */ diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java index 0861d5ea0f..b56b64a0b0 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java @@ -18,7 +18,6 @@ */ package org.alfresco.repo.content.transform; -import java.text.NumberFormat; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; @@ -27,7 +26,12 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.util.EqualsHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -108,6 +112,7 @@ public class TransformerDebug private final String fromUrl; private final String sourceMimetype; private final String targetMimetype; + private final TransformationOptions options; private final boolean origDebugOutput; private final long start; @@ -117,12 +122,14 @@ public class TransformerDebug // 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, boolean origDebugOutput) + private Frame(Frame parent, String fromUrl, String sourceMimetype, String targetMimetype, + TransformationOptions options, Call pushCall, boolean origDebugOutput) { this.id = parent == null ? uniqueId.getAndIncrement() : ++parent.childId; this.fromUrl = fromUrl; this.sourceMimetype = sourceMimetype; this.targetMimetype = targetMimetype; + this.options = options; this.callType = pushCall; this.origDebugOutput = origDebugOutput; start = System.currentTimeMillis(); @@ -171,35 +178,53 @@ public class TransformerDebug } } + private final NodeService nodeService; private final MimetypeService mimetypeService; /** * Constructor */ - public TransformerDebug(MimetypeService mimetypeService) + public TransformerDebug(NodeService nodeService, MimetypeService mimetypeService) { + this.nodeService = nodeService; this.mimetypeService = mimetypeService; } /** * Called prior to working out what transformers are available. */ - public void pushAvailable(String fromUrl, String sourceMimetype, String targetMimetype) + public void pushAvailable(String fromUrl, String sourceMimetype, String targetMimetype, + TransformationOptions options) { if (isEnabled()) { - push(null, fromUrl, sourceMimetype, targetMimetype, -1, Call.AVAILABLE); + push(null, fromUrl, sourceMimetype, targetMimetype, -1, options, Call.AVAILABLE); } } /** * Called prior to performing a transform. */ - public void pushTransform(ContentTransformer transformer, String fromUrl, String sourceMimetype, String targetMimetype, long sourceSize) + public void pushTransform(ContentTransformer transformer, String fromUrl, String sourceMimetype, + String targetMimetype, long sourceSize, TransformationOptions options) { if (isEnabled()) { - push(getName(transformer), fromUrl, sourceMimetype, targetMimetype, sourceSize, Call.TRANSFORM); + push(getName(transformer), fromUrl, sourceMimetype, targetMimetype, sourceSize, + options, Call.TRANSFORM); + } + } + + /** + * Adds a new level to the stack to get a new request number or nesting number. + * Called prior to working out what transformers are active + * and prior to listing the supported mimetypes for an active transformer. + */ + public void pushMisc() + { + if (isEnabled()) + { + push(null, null, null, null, -1, null, Call.AVAILABLE); } } @@ -214,7 +239,8 @@ public class TransformerDebug } } - private void push(String name, String fromUrl, String sourceMimetype, String targetMimetype, long sourceSize, Call callType) + private void push(String name, String fromUrl, String sourceMimetype, String targetMimetype, + long sourceSize, TransformationOptions options, Call callType) { Deque ourStack = ThreadInfo.getStack(); Frame frame = ourStack.peek(); @@ -227,7 +253,7 @@ public class TransformerDebug { // Create a new frame. Logging level is set to trace if the file size is 0 boolean origDebugOutput = ThreadInfo.setDebugOutput(ThreadInfo.getDebugOutput() && sourceSize != 0); - frame = new Frame(frame, fromUrl, sourceMimetype, targetMimetype, callType, origDebugOutput); + frame = new Frame(frame, fromUrl, sourceMimetype, targetMimetype, options, callType, origDebugOutput); ourStack.push(frame); if (callType == Call.TRANSFORM) @@ -310,6 +336,24 @@ public class TransformerDebug } } + public void inactiveTransformer(ContentTransformer transformer) + { + log(getName(transformer)+' '+ms(transformer.getTransformationTime())+" INACTIVE"); + } + + public void activeTransformer(int mimetypePairCount, ContentTransformer transformer, String sourceMimetype, + String targetMimetype, long maxSourceSizeKBytes, boolean explicit, boolean firstMimetypePair) + { + if (firstMimetypePair) + { + log(getName(transformer)+' '+ms(transformer.getTransformationTime())); + } + String i = Integer.toString(mimetypePairCount); + log(spaces(5-i.length())+mimetypePairCount+") "+getMimetypeExt(sourceMimetype)+getMimetypeExt(targetMimetype)+ + ' '+fileSize((maxSourceSizeKBytes > 0) ? maxSourceSizeKBytes*1024 : maxSourceSizeKBytes)+ + (explicit ? " EXPLICIT" : "")); + } + private int getLongestTransformerNameLength(List transformers, Frame frame) { @@ -320,7 +364,7 @@ public class TransformerDebug if (longestNameLength < length) longestNameLength = length; } - if (frame.unavailableTransformers != null) + if (frame != null && frame.unavailableTransformers != null) { for (UnavailableTransformer unavailable: frame.unavailableTransformers) { @@ -337,13 +381,14 @@ public class TransformerDebug // 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(frame.fromUrl, false); } - - log(getMimetypeExt(frame.sourceMimetype)+getMimetypeExt(frame.targetMimetype) + - ((sourceSize >= 0) ? fileSize(sourceSize)+' ' : "") + message); - log(frame.sourceMimetype+' '+frame.targetMimetype, false); + + String fileName = getFileName(frame.options, firstLevel, sourceSize); + log(getMimetypeExt(frame.sourceMimetype)+getMimetypeExt(frame.targetMimetype) + + ((fileName != null) ? fileName+' ' : "")+ + ((sourceSize >= 0) ? fileSize(sourceSize)+' ' : "") + message); } /** @@ -354,7 +399,7 @@ public class TransformerDebug { if (isEnabled()) { - pop(Call.AVAILABLE); + pop(Call.AVAILABLE, false); } } @@ -365,10 +410,22 @@ public class TransformerDebug { if (isEnabled()) { - pop(Call.TRANSFORM); + pop(Call.TRANSFORM, false); } } + /** + * Removes a frame from the stack. Called prior to working out what transformers are active + * and prior to listing the supported mimetypes for an active transformer. + */ + public void popMisc() + { + if (isEnabled()) + { + pop(Call.AVAILABLE, ThreadInfo.getStack().size() > 1); + } + } + /** * Called after returning from a nested isTransformable. */ @@ -380,7 +437,7 @@ public class TransformerDebug } } - private void pop(Call callType) + private void pop(Call callType, boolean suppressFinish) { Deque ourStack = ThreadInfo.getStack(); if (!ourStack.isEmpty()) @@ -389,7 +446,7 @@ public class TransformerDebug if ((frame.callType == callType) || (frame.callType == Call.AVAILABLE_AND_TRANSFORM && callType == Call.AVAILABLE)) { - if (ourStack.size() == 1 || logger.isTraceEnabled()) + if (!suppressFinish && (ourStack.size() == 1 || logger.isTraceEnabled())) { boolean topFrame = ourStack.size() == 1; log("Finished in " + @@ -576,6 +633,36 @@ public class TransformerDebug : "<>" : ""); } + + + public String getFileName(TransformationOptions options, boolean firstLevel, long sourceSize) + { + String fileName = null; + if (options != null) + { + try + { + NodeRef sourceNodeRef = options.getSourceNodeRef(); + fileName = (String)nodeService.getProperty(sourceNodeRef, ContentModel.PROP_NAME); + } + catch (RuntimeException e) + { + ; // ignore (normally InvalidNodeRefException) but we should ignore other RuntimeExceptions too + } + } + if (fileName == null) + { + if (!firstLevel) + { + fileName = "<>"; + } + else if (sourceSize < 0) + { + // fileName = "<>"; commented out as it does not add to debug readability + } + } + return fileName; + } private String getMimetypeExt(String mimetype) { diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 10b468503f..1be6efa773 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.TransformationOptions; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.QueryParameterDefinition; import org.alfresco.service.cmr.security.AccessPermission; @@ -2425,6 +2426,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider { ParameterCheck.mandatoryString("Mimetype", mimetype); ParameterCheck.mandatory("Destination Node", destination); + final NodeRef sourceNodeRef = nodeRef; // the delegate definition for transforming a document Transformer transformer = new Transformer() @@ -2433,9 +2435,12 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider ContentWriter writer) { ScriptNode transformedNode = null; - if (contentService.isTransformable(reader, writer)) + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(sourceNodeRef); + + if (contentService.isTransformable(reader, writer, options)) { - contentService.transform(reader, writer); + contentService.transform(reader, writer, options); transformedNode = newInstance(nodeRef, services, scope); } return transformedNode; @@ -2550,6 +2555,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider private ScriptNode transformImage(String mimetype, final String options, NodeRef destination) { ParameterCheck.mandatoryString("Mimetype", mimetype); + final NodeRef sourceNodeRef = nodeRef; // the delegate definition for transforming an image Transformer transformer = new Transformer() @@ -2558,6 +2564,8 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider ContentWriter writer) { ImageTransformationOptions imageOptions = new ImageTransformationOptions(); + imageOptions.setSourceNodeRef(sourceNodeRef); + if (options != null) { imageOptions.setCommandOptions(options); @@ -2738,7 +2746,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider logger.info("Unable to create thumbnail '" + details.getName() + "' as there is no content"); return null; } - if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), details)) + if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), nodeRef, details)) { logger.info("Unable to create thumbnail '" + details.getName() + "' for " + nodeMimeType + " as no transformer is currently available"); diff --git a/source/java/org/alfresco/repo/node/NodeServiceTest.java b/source/java/org/alfresco/repo/node/NodeServiceTest.java index 6828ffd175..2cb30b3a41 100644 --- a/source/java/org/alfresco/repo/node/NodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/NodeServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -1181,12 +1181,20 @@ public class NodeServiceTest extends TestCase logger.debug("Found child nodes with deleted parent node (after): " + childNodeIds); // workaround recovery: force collection of any orphan nodes (ALF-12358 + ALF-13066) - for (NodeRef nodeRef : nodesAtRisk) + for (final NodeRef nodeRef : nodesAtRisk) { - if (nodeService.exists(nodeRef)) + txnService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.getPath(nodeRef); // ignore return - } + @Override + public Void execute() throws Throwable + { + if (nodeService.exists(nodeRef)) + { + nodeService.getPath(nodeRef); // ignore return + } + return null; + } + }); } // check again ... @@ -1273,12 +1281,20 @@ public class NodeServiceTest extends TestCase logger.debug("Found child nodes with deleted parent node (after): " + childNodeIds); // workaround recovery: force collection of any orphan nodes (ALF-12358 + ALF-13066) - for (NodeRef nodeRef : childNodeRefs) + for (final NodeRef nodeRef : childNodeRefs) { - if (nodeService.exists(nodeRef)) + txnService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { - nodeService.getPath(nodeRef); // ignore return - } + @Override + public Void execute() throws Throwable + { + if (nodeService.exists(nodeRef)) + { + nodeService.getPath(nodeRef); // ignore return + } + return null; + } + }); } // check again ... diff --git a/source/java/org/alfresco/repo/rendition/RenditionPreventionRegistry.java b/source/java/org/alfresco/repo/rendition/RenditionPreventionRegistry.java new file mode 100644 index 0000000000..0ac540a934 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionPreventionRegistry.java @@ -0,0 +1,103 @@ +/* + * 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.rendition; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * This class holds a registry of content class names (types and aspects) which if they are present on a sourceNode will prevent any + * renditions from being created for that node. + * + * @author Neil Mc Erlean + * @since 4.0.1 + */ +public class RenditionPreventionRegistry +{ + private final Set registeredContentClasses = new HashSet(); + private NamespaceService namespaceService; + + public void setNamespaceService(NamespaceService service) + { + this.namespaceService = service; + } + + public void register(String contentClass) + { + QName qname = QName.createQName(contentClass, namespaceService); + registeredContentClasses.add(qname); + } + + /** + * @return a Set of QNames of types/aspects which will prevent renditions from occurring. + */ + public Set getRegisteredQNames() + { + return Collections.unmodifiableSet(registeredContentClasses); + } + + /** + * Checks if the specified type/aspect is registered as a marker for rendition prevention. + * @param contentClassName aspect name. + * @return true if this class will prevent renditions, else false + */ + public boolean isContentClassRegistered(String contentClassName) + { + QName qname = QName.createQName(contentClassName, namespaceService); + return isContentClassRegistered(qname); + } + + /** + * Checks if the specified type/aspect is registered as a marker for rendition prevention. + * @param contentClassName aspect name. + * @return true if this aspect will prevent renditions, else false + */ + public boolean isContentClassRegistered(QName aspectQName) + { + return registeredContentClasses.contains(aspectQName); + } + + /** + * A utility class which ensures that the specified aspect/type name is registered. + */ + public static class SelfRegisteringClassName + { + private final String contentClassName; + private RenditionPreventionRegistry registry; + + public SelfRegisteringClassName(String className) + { + this.contentClassName = className; + } + + public void setRegistry(RenditionPreventionRegistry registry) + { + this.registry = registry; + } + + public void register() + { + registry.register(contentClassName); + } + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java index d54059b8ad..b0e21a544c 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java @@ -37,6 +37,7 @@ import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition; import org.alfresco.service.cmr.rendition.RenderCallback; import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionPreventedException; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.rendition.RenditionServiceException; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -67,6 +68,11 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti private RenditionDefinitionPersisterImpl renditionDefinitionPersister; + /** + * @since 4.0.1 + */ + private RenditionPreventionRegistry renditionPreventionRegistry; + /** * Injects the RenditionDefinitionPersister bean. * @param renditionDefinitionPersister @@ -76,6 +82,14 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti this.renditionDefinitionPersister = renditionDefinitionPersister; } + /** + * @since 4.0.1 + */ + public void setRenditionPreventionRegistry(RenditionPreventionRegistry registry) + { + this.renditionPreventionRegistry = registry; + } + /** * Injects the ServiceRegistry bean. * @param serviceRegistry @@ -180,6 +194,8 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti */ public ChildAssociationRef render(NodeRef sourceNode, RenditionDefinition definition) { + checkSourceNodeForPreventionClass(sourceNode); + ChildAssociationRef result = executeRenditionAction(sourceNode, definition, false); if (log.isDebugEnabled()) @@ -193,6 +209,8 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti public void render(NodeRef sourceNode, RenditionDefinition definition, RenderCallback callback) { + checkSourceNodeForPreventionClass(sourceNode); + // The asynchronous render can't return a ChildAssociationRef as it is created // asynchronously after this method returns. definition.setCallback(callback); @@ -208,6 +226,8 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti */ public ChildAssociationRef render(NodeRef sourceNode, final QName renditionDefinitionQName) { + checkSourceNodeForPreventionClass(sourceNode); + RenditionDefinition rendDefn = AuthenticationUtil.runAs( new AuthenticationUtil.RunAsWork() { @@ -231,6 +251,8 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti */ public void render(NodeRef sourceNode, final QName renditionDefinitionQName, RenderCallback callback) { + checkSourceNodeForPreventionClass(sourceNode); + RenditionDefinition rendDefn = AuthenticationUtil.runAs( new AuthenticationUtil.RunAsWork() { @@ -247,8 +269,41 @@ public class RenditionServiceImpl implements RenditionService, RenditionDefiniti this.render(sourceNode, rendDefn, callback); } - - + + /** + * This method checks whether the specified source node is of a content class which has been registered for rendition prevention. + * + * @param sourceNode the node to check. + * @throws RenditionPreventedException if the source node is configured for rendition prevention. + * @since 4.0.1 + * @see RenditionPreventionRegistry + */ + private void checkSourceNodeForPreventionClass(NodeRef sourceNode) + { + // A node's content class is its type and all its aspects. + // We'll not check the source node for null and leave that to the rendering action. + if (sourceNode != null && nodeService.exists(sourceNode)) + { + Set nodeContentClasses = nodeService.getAspects(sourceNode); + nodeContentClasses.add(nodeService.getType(sourceNode)); + + for (QName contentClass : nodeContentClasses) + { + if (renditionPreventionRegistry.isContentClassRegistered(contentClass)) + { + StringBuilder msg = new StringBuilder(); + msg.append("Node ").append(sourceNode) + .append(" cannot be renditioned as it is of class ").append(contentClass); + if (log.isDebugEnabled()) + { + log.debug(msg.toString()); + } + throw new RenditionPreventedException(msg.toString()); + } + } + } + } + /** * This method delegates the execution of the specified RenditionDefinition * to the {@link ActionService action service}. diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index 2715b170e7..bd7f63e34f 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -923,6 +923,48 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest }); } + /** + * Tests that source nodes with a suitable marker aspect on them are not renditioned. + * + * @since 4.0.1 + */ + public void testSuitablyMarkedNodesDoNotGetRenditioned() throws Exception + { + setComplete(); + endTransaction(); + + this.renditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Initially the node that provides the content + // should not have the rn:renditioned aspect on it. + assertFalse("Source node has unexpected renditioned aspect.", nodeService.hasAspect( + nodeWithImageContent, RenditionModel.ASPECT_RENDITIONED)); + + //Add the marker aspect to prevent rendition + nodeService.addAspect(nodeWithImageContent, RenditionModel.ASPECT_PREVENT_RENDITIONS, null); + + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + action.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG); + + ChildAssociationRef renditionAssoc = null; + boolean expectedExceptionThrown = false; + try + { + renditionAssoc = renditionService.render(nodeWithImageContent, action); + } catch (RenditionServiceException expected) + { + expectedExceptionThrown = true; + } + assertTrue("Expected exception was not thrown.", expectedExceptionThrown); + + return renditionAssoc == null ? null : renditionAssoc.getChildRef(); + } + }); + } + public void testSuccessfulAsynchronousRendition() throws Exception { // There are two relevant threads here: the JUnit test thread and the background diff --git a/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java index c0407cdf98..ed2625453b 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-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -256,6 +256,8 @@ public class ImageRenderingEngine extends AbstractTransformationRenderingEngine @Override protected TransformationOptions getTransformOptionsImpl(TransformationOptions options, RenderingContext context) { + options.setSourceNodeRef(context.getSourceNode()); + options.setTargetNodeRef(context.getDestinationNode()); ImageTransformationOptions imageTransformationOptions = (ImageTransformationOptions)options; String commandOptions = context.getCheckedParam(PARAM_COMMAND_OPTIONS, String.class); 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 9c48ebc522..ebb01f8298 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -1279,9 +1279,11 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp try { // get the transformer - transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN); + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(nodeRef); + transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN, options); long sourceSize = reader.getSize(); - List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); transformerDebug.availableTransformers(transformers, sourceSize, "ADMLuceneIndexer"); if (transformers.isEmpty()) @@ -1322,7 +1324,7 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp writer.setEncoding("UTF-8"); try { - transformer.transform(reader, writer); + transformer.transform(reader, writer, options); // point the reader to the new-written content reader = writer.getReader(); // Check that the reader is a view onto something concrete 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 e8511695b8..0cfdbfcb3c 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java @@ -1140,9 +1140,11 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl try { // get the transformer - transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN); + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(banana); // TODO check it is OK to use this noderef + transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN, options); long sourceSize = reader.getSize(); - List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, new TransformationOptions()); + List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); transformerDebug.availableTransformers(transformers, sourceSize, "AVMLuceneIndexer"); if (transformers.isEmpty()) @@ -1177,7 +1179,7 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl writer.setEncoding("UTF-8"); try { - transformer.transform(reader, writer); + transformer.transform(reader, writer, options); // point the reader to the new-written content reader = writer.getReader(); // Check that the reader is a view onto something concrete diff --git a/source/java/org/alfresco/repo/site/SiteAspect.java b/source/java/org/alfresco/repo/site/SiteAspect.java index 2fec100ae9..187c697f4a 100644 --- a/source/java/org/alfresco/repo/site/SiteAspect.java +++ b/source/java/org/alfresco/repo/site/SiteAspect.java @@ -18,8 +18,13 @@ */ package org.alfresco.repo.site; +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies.OnMoveNodePolicy; +import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.Behaviour; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; @@ -28,6 +33,7 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; /** * Site aspect behaviour bean. @@ -37,7 +43,7 @@ import org.alfresco.service.namespace.QName; * * @author Nick Burch */ -public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy +public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy, NodeServicePolicies.OnUpdatePropertiesPolicy { /** Services */ private DictionaryService dictionaryService; @@ -79,14 +85,43 @@ public class SiteAspect implements NodeServicePolicies.OnMoveNodePolicy */ public void init() { - this.policyComponent.bindClassBehaviour( - OnMoveNodePolicy.QNAME, + this.policyComponent.bindClassBehaviour(OnMoveNodePolicy.QNAME, SiteModel.TYPE_SITE, new JavaBehaviour(this, "onMoveNode", Behaviour.NotificationFrequency.EVERY_EVENT)); this.policyComponent.bindClassBehaviour(OnMoveNodePolicy.QNAME, SiteModel.ASPECT_SITE_CONTAINER, new JavaBehaviour(this, "onMoveNode", Behaviour.NotificationFrequency.EVERY_EVENT)); + + this.policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME, + SiteModel.TYPE_SITE, + new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.EVERY_EVENT)); + + this.policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME, + SiteModel.ASPECT_SITE_CONTAINER, + new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.EVERY_EVENT)); + } + + public void onUpdateProperties(NodeRef nodeRef, + Map before, + Map after) + { + String beforeName = (String)before.get(ContentModel.PROP_NAME); + String afterName = (String)after.get(ContentModel.PROP_NAME); + + if(beforeName != null && !beforeName.equals(afterName)) + { + // Deny renames + QName type = nodeService.getType(nodeRef); + if (dictionaryService.isSubClass(type, SiteModel.TYPE_SITE)) + { + throw new SiteServiceException("Sites can not be renamed."); + } + else + { + throw new SiteServiceException("Site containers can not be renamed."); + } + } } /** diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index d67b97e982..e276bb12c5 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -772,7 +772,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic // get the sites that match the specified names StringBuilder query = new StringBuilder(128); query.append("+PARENT:\"").append(siteRoot.toString()).append('"'); - + final boolean filterIsPresent = filter != null && filter.length() > 0; // The filter string is only used in the Lucene query if it restricts results. // A search for name/title/description = "*" does not need to be put into the Lucene query. @@ -919,7 +919,8 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic // Only search for "st:site" nodes. final Set searchTypeQNames = new HashSet(1); searchTypeQNames.add(SiteModel.TYPE_SITE); - +// searchTypeQNames.addAll(dictionaryService.getSubTypes(SiteModel.TYPE_SITE, true)); + // get canned query final String cQBeanName = "siteGetChildrenCannedQueryFactory"; GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject(cQBeanName); diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index 4342790818..cec0837e80 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -106,6 +106,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest private NodeArchiveService nodeArchiveService; private PermissionService permissionService; private SiteService siteService; + /** * There are some tests which need access to the unproxied SiteServiceImpl */ @@ -142,7 +143,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest this.siteServiceImpl = (SiteServiceImpl) applicationContext.getBean("siteService"); // Small 's' this.sysAdminParams = (SysAdminParams)this.applicationContext.getBean("sysAdminParams"); - // Create the test users createUser(USER_ONE, "UserOne"); createUser(USER_TWO, "UserTwo"); @@ -2070,7 +2070,43 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest assertNotNull("sites list was null.", sites); assertEquals(preexistingSitesCount+1, sites.size()); } + + private SiteInfo createSite(String siteShortName, String componentId, SiteVisibility visibility) + { + // Create a public site + SiteInfo siteInfo = this.siteService.createSite(TEST_SITE_PRESET, + siteShortName, + TEST_TITLE, + TEST_DESCRIPTION, + visibility); + NodeRef siteContainer = this.siteService.createContainer(siteShortName, componentId, ContentModel.TYPE_FOLDER, null); + return siteInfo; + } + public void testRenameSite() + { + // test that changing the name of a site generates an appropriate exception + + try + { + String siteName = GUID.generate(); + + SiteInfo siteInfo = createSite(siteName, "doclib", SiteVisibility.PUBLIC); + NodeRef childRef = siteInfo.getNodeRef(); + + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, siteName + "Renamed"); + + nodeService.addProperties(childRef, props); + + fail("Should have caught rename"); + } + catch(SiteServiceException e) + { + assertTrue(e.getMessage().contains("can not be renamed")); + } + } + private void validatePermissionsOnRelocatedNode(SiteInfo fromSite, SiteInfo toSite, NodeRef relocatedNode, Map expectedPermissions) { diff --git a/source/java/org/alfresco/repo/template/BaseContentNode.java b/source/java/org/alfresco/repo/template/BaseContentNode.java index 21cd898b27..7d714b1228 100644 --- a/source/java/org/alfresco/repo/template/BaseContentNode.java +++ b/source/java/org/alfresco/repo/template/BaseContentNode.java @@ -37,8 +37,10 @@ import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.FileTypeImageSize; +import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.webdav.WebDavService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; @@ -592,7 +594,8 @@ public abstract class BaseContentNode implements TemplateContent { // get the content reader ContentService contentService = services.getContentService(); - ContentReader reader = contentService.getReader(getNodeRef(), property); + NodeRef nodeRef = getNodeRef(); + ContentReader reader = contentService.getReader(nodeRef, property); if (reader == null) { return ""; // Caller of this method returns "" if there is an IOException @@ -603,10 +606,13 @@ public abstract class BaseContentNode implements TemplateContent writer.setMimetype("text/plain"); writer.setEncoding(reader.getEncoding()); + TransformationOptions options = new TransformationOptions(); + options.setSourceNodeRef(nodeRef); + // try and transform the content - if (contentService.isTransformable(reader, writer)) + if (contentService.isTransformable(reader, writer, options)) { - contentService.transform(reader, writer); + contentService.transform(reader, writer, options); ContentReader resultReader = writer.getReader(); if (resultReader != null && reader.exists()) diff --git a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java index f0c7496f89..db2eadef3e 100644 --- a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java @@ -155,7 +155,7 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase { ContentData content = (ContentData)contentProp; String mimetype = content.getMimetype(); - if (!registry.isThumbnailDefinitionAvailable(content.getContentUrl(), mimetype, content.getSize(), details)) + if (!registry.isThumbnailDefinitionAvailable(content.getContentUrl(), mimetype, content.getSize(), actionedUponNodeRef, details)) { logger.debug("Unable to create thumbnail '" + details.getName() + "' for " + mimetype + " as no transformer is currently available"); diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 2b43de6e3c..0be52ab5d8 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -33,6 +33,8 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.thumbnail.ThumbnailException; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; @@ -254,10 +256,16 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi * @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 sourceNodeRef which is set in a copy of the thumbnailDefinition transformation options, + * so that it may be used by transformers and debug. * @param thumbnailDefinition The {@link ThumbnailDefinition} to check for */ - public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimetype, long sourceSize, ThumbnailDefinition thumbnailDefinition) + public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimetype, long sourceSize, NodeRef sourceNodeRef, ThumbnailDefinition thumbnailDefinition) { + // Copy the thumbnail's TransformationOptions and set the sourceNodeRef, for use by transformers and debug. + TransformationOptions options = new TransformationOptions(thumbnailDefinition.getTransformationOptions()); + options.setSourceNodeRef(sourceNodeRef); + // Log the following getTransform() as trace so we can see the wood for the trees boolean orig = TransformerDebug.setDebugOutput(false); try @@ -266,7 +274,7 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi sourceUrl, sourceMimetype, sourceSize, - thumbnailDefinition.getMimetype(), thumbnailDefinition.getTransformationOptions() + thumbnailDefinition.getMimetype(), options ) != null; } finally diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java index ecd8f15362..87320ba925 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java @@ -34,6 +34,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.magick.ImageResizeOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.rendition.MockedTestServiceRegistry; import org.alfresco.repo.rendition.RenditionServiceImpl; import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; import org.alfresco.repo.rendition.executer.ImageRenderingEngine; @@ -84,6 +85,7 @@ public class ThumbnailServiceImplParameterTest }; renditionService.setActionService(mockActionService); + renditionService.setServiceRegistry(new MockedTestServiceRegistry()); ThumbnailServiceImpl thumbs = new ThumbnailServiceImpl() { diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionPreventedException.java b/source/java/org/alfresco/service/cmr/rendition/RenditionPreventedException.java new file mode 100644 index 0000000000..83eb9ca454 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionPreventedException.java @@ -0,0 +1,78 @@ +/* + * 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.service.cmr.rendition; + +import org.alfresco.repo.rendition.RenditionPreventionRegistry; + +/** + * This exception is thrown if an attempt is made to render a node which has a {@link RenditionPreventionRegistry content class} + * registered to prevent rendition. + * + * @author Neil Mc Erlean + * @since 4.0.1 + */ +public class RenditionPreventedException extends RenditionServiceException +{ + private static final long serialVersionUID = 1L; + + /** + * Constructs a Rendition Service Exception with the specified message. + * + * @param message the message string + */ + public RenditionPreventedException(String message) + { + super(message); + } + + /** + * Constructs a Rendition Service Exception with the specified message and source exception. + * + * @param message the message string + * @param source the source exception + */ + public RenditionPreventedException(String message, Throwable source) + { + super(message, source); + } + + /** + * Constructs a Rendition Service Exception with the specified message and {@link RenditionDefinition}. + * + * @param message the message string. + * @param renditionDefinition the rendition definition. + */ + public RenditionPreventedException(String message, RenditionDefinition renditionDefinition) + { + super(message); + } + + /** + * Constructs a Rendition Service Exception with the specified message, {@link RenditionDefinition} and + * source exception + * . + * @param message the message string. + * @param renditionDefinition the rendition definition. + * @param source the source exception. + */ + public RenditionPreventedException(String message, RenditionDefinition renditionDefinition, Throwable source) + { + super(message, source); + } +} diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionService.java b/source/java/org/alfresco/service/cmr/rendition/RenditionService.java index ad6b42538b..932ce968d5 100644 --- a/source/java/org/alfresco/service/cmr/rendition/RenditionService.java +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionService.java @@ -22,12 +22,10 @@ package org.alfresco.service.cmr.rendition; import java.util.List; import org.alfresco.repo.rendition.RenditionDefinitionPersister; -import org.alfresco.service.PublicService; +import org.alfresco.service.NotAuditable; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; -import org.alfresco.service.Auditable; -import org.alfresco.service.NotAuditable; /** * The Rendition service. diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java index 845417ad50..5d06f96f13 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-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -63,12 +63,20 @@ public class TransformationOptions private TransformationOptionLimits limits = new TransformationOptionLimits(); /** - * Default construtor + * Default constructor */ public TransformationOptions() { } + /** + * Deep clone constructor + */ + public TransformationOptions(TransformationOptions options) + { + this(options.toMap()); + } + /** * Constructor *