diff --git a/config/alfresco/bootstrap/renderingActionSpace.xml b/config/alfresco/bootstrap/renderingActionSpace.xml new file mode 100644 index 0000000000..d2b6fe9563 --- /dev/null +++ b/config/alfresco/bootstrap/renderingActionSpace.xml @@ -0,0 +1,18 @@ + + + + + + workspace + SpacesStore + rendering_actions_space + ${spaces.rendition.rendering_actions.name} + ${spaces.rendition.rendering_actions.name} + ${spaces.rendition.rendering_actions.description} + + + + + \ No newline at end of file diff --git a/config/alfresco/bootstrap/systemRenditionDefinitions.acp b/config/alfresco/bootstrap/systemRenditionDefinitions.acp deleted file mode 100644 index 6637b71576..0000000000 Binary files a/config/alfresco/bootstrap/systemRenditionDefinitions.acp and /dev/null differ diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index 0ecf791056..371a3f0410 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -569,7 +569,7 @@ /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/systemRenditionDefinitions.acp + alfresco/bootstrap/renderingActionSpace.xml alfresco/messages/bootstrap-spaces diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 172849a217..be14ae1154 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -2036,7 +2036,7 @@ /${spaces.company_home.childname}/${spaces.dictionary.childname} - alfresco/bootstrap/systemRenditionDefinitions.acp + alfresco/bootstrap/renderingActionSpace.xml diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index 668c0f8700..61c02df622 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -48,19 +48,107 @@ - + + + - + - medium - doclib - webpreview - imgpreview - avatar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java index 7e6be85894..ebddb7325f 100644 --- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -59,6 +59,7 @@ 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.StoreRef; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; @@ -331,7 +332,7 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest }); // Sleep to let the asynchronous action queue perform the updates to the renditions. // TODO Is there a better way? - Thread.sleep(20000); + Thread.sleep(30000); // Get the renditions and check their content for the new title transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() @@ -1612,24 +1613,27 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest /** * This test method ensures that all the 'built-in' renditionDefinitions are - * available after startup. + * available after startup and that their configuration is correct. * * @throws Exception */ - public void testEnsureBuiltinRenditionDefinitionsAvailable() throws Exception + public void testBuiltinRenditionDefinitions() throws Exception { - final List renditionLocalNames = Arrays.asList(new String[] { "medium", "doclib", "imgpreview", - "webpreview", "avatar" }); - for (String renditionLocalName : renditionLocalNames) - { - validateRenditionDefinition(renditionLocalName); - } + final RenditionDefinition mediumRenditionDef = loadAndValidateRenditionDefinition("medium"); + final RenditionDefinition doclibRenditionDef = loadAndValidateRenditionDefinition("doclib"); + final RenditionDefinition imgpreviewRenditionDef = loadAndValidateRenditionDefinition("imgpreview"); + final RenditionDefinition webpreviewRenditionDef = loadAndValidateRenditionDefinition("webpreview"); + final RenditionDefinition avatarRenditionDef = loadAndValidateRenditionDefinition("avatar"); + + assertEquals(MimetypeMap.MIMETYPE_IMAGE_JPEG, mediumRenditionDef.getParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE)); + assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG, doclibRenditionDef.getParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE)); + assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG, imgpreviewRenditionDef.getParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE)); + assertEquals(MimetypeMap.MIMETYPE_FLASH, webpreviewRenditionDef.getParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE)); + assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG, avatarRenditionDef.getParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE)); } - private void validateRenditionDefinition(String renditionLocalName) + private RenditionDefinition loadAndValidateRenditionDefinition(String renditionLocalName) { - System.out.println("Validating rendition definition: " + renditionLocalName); - QName renditionQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, renditionLocalName); RenditionDefinition renditionDefinition = renditionService.loadRenditionDefinition(renditionQName); assertNotNull("'" + renditionLocalName + "' rendition definition was missing.", renditionDefinition); @@ -1639,8 +1643,10 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest assertNotNull("'" + renditionLocalName + "' renditionDefinition had null renderingActionName parameter", renditionDefinition.getParameterValue("renderingActionName")); - assertEquals("RunAs param was wrong for " + renditionLocalName, AuthenticationUtil.getSystemUserName(), - renditionDefinition.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS)); + // All builtin renditions should be "runas" system + assertEquals(AuthenticationUtil.getSystemUserName(), renditionDefinition.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS)); + + return renditionDefinition; } /** diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java index fae722e433..9f45220f97 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java @@ -39,6 +39,9 @@ public class ThumbnailDefinition /** Path to placeholder thumbnail */ private String placeHolderResourcePath; + /** Username to run the thumbnailrendition as */ + private String runAs; + /** * Default constructor */ @@ -141,7 +144,17 @@ public class ThumbnailDefinition public String getName() { return name; - } + } + + public void setRunAs(String runAs) + { + this.runAs = runAs; + } + + public String getRunAs() + { + return this.runAs; + } /** * diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 917bdf02b8..556eeaef73 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -23,13 +23,19 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; 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.thumbnail.ThumbnailException; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; /** * Registry of all the thumbnail details available @@ -37,7 +43,7 @@ import org.alfresco.service.namespace.QName; * @author Roy Wetherall * @author Neil McErlean */ -public class ThumbnailRegistry +public class ThumbnailRegistry implements ApplicationContextAware, ApplicationListener { /** Content service */ private ContentService contentService; @@ -45,11 +51,6 @@ public class ThumbnailRegistry /** Rendition service */ private RenditionService renditionService; - private List thumbnails; - - /** This flag indicates whether the thumbnail definitions have been lazily loaded or not. */ - private boolean thumbnailDefinitionsInited = false; - /** Map of thumbnail definition */ private Map thumbnailDefinitions = new HashMap(); @@ -58,6 +59,8 @@ public class ThumbnailRegistry private ThumbnailRenditionConvertor thumbnailRenditionConvertor; + private RegistryLifecycle lifecycle = new RegistryLifecycle(); + public void setThumbnailRenditionConvertor( ThumbnailRenditionConvertor thumbnailRenditionConvertor) { @@ -89,12 +92,47 @@ public class ThumbnailRegistry this.renditionService = renditionService; } - public void setThumbnails(final List thumbnails) + /** + * This method is used to inject the thumbnail definitions. + * @param thumbnailDefinitions + */ + public void setThumbnailDefinitions(final List thumbnailDefinitions) { - this.thumbnails = thumbnails; + for (ThumbnailDefinition td : thumbnailDefinitions) + { + String thumbnailName = td.getName(); + if (thumbnailName == null) + { + throw new ThumbnailException("When adding a thumbnail details object make sure the name is set."); + } - // We'll not populate the data fields in the ThumbnailRegistry here, instead preferring - // to do it lazily later. + this.thumbnailDefinitions.put(thumbnailName, td); + } + } + + /** + * Those thumbnail definitions that are injected by Spring are converted + * to rendition definitions and saved. + */ + private void initThumbnailDefinitions() + { + AuthenticationUtil.runAs(new RunAsWork() { + public Void doWork() throws Exception + { + for (String thumbnailDefName : thumbnailDefinitions.keySet()) + { + final ThumbnailDefinition thumbnailDefinition = thumbnailDefinitions.get(thumbnailDefName); + + // Built-in thumbnailDefinitions do not provide any non-standard values + // for the ThumbnailParentAssociationDetails object. Hence the null + RenditionDefinition renditionDef = thumbnailRenditionConvertor.convert(thumbnailDefinition, null); + + renditionService.saveRenditionDefinition(renditionDef); + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); } /** @@ -104,36 +142,11 @@ public class ThumbnailRegistry */ public List getThumbnailDefinitions() { - if (thumbnailDefinitionsInited == false) - { - this.initThumbnailDefinitions(); - thumbnailDefinitionsInited = true; - } return new ArrayList(this.thumbnailDefinitions.values()); } - private void initThumbnailDefinitions() - { - for (String thumbnailDefinitionName : this.thumbnails) - { - QName qName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailDefinitionName); - RenditionDefinition rAction = renditionService - .loadRenditionDefinition(qName); - - ThumbnailDefinition thDefn = thumbnailRenditionConvertor.convert(rAction); - - thumbnailDefinitions.put(thumbnailDefinitionName, thDefn); - } - } - public List getThumbnailDefinitions(String mimetype) { - if (thumbnailDefinitionsInited == false) - { - this.initThumbnailDefinitions(); - thumbnailDefinitionsInited = true; - } - List result = this.mimetypeMap.get(mimetype); if (result == null) @@ -163,6 +176,7 @@ public class ThumbnailRegistry * @return * @deprecated Use {@link #getThumbnailDefinitions(String)} instead. */ + @Deprecated public List getThumnailDefintions(String mimetype) { return this.getThumbnailDefinitions(mimetype); @@ -175,11 +189,6 @@ public class ThumbnailRegistry */ public void addThumbnailDefinition(ThumbnailDefinition thumbnailDetails) { - if (thumbnailDefinitionsInited == false) - { - this.initThumbnailDefinitions(); - thumbnailDefinitionsInited = true; - } String thumbnailName = thumbnailDetails.getName(); if (thumbnailName == null) { @@ -197,11 +206,48 @@ public class ThumbnailRegistry */ public ThumbnailDefinition getThumbnailDefinition(String thumbnailName) { - if (thumbnailDefinitionsInited == false) - { - this.initThumbnailDefinitions(); - thumbnailDefinitionsInited = true; - } return this.thumbnailDefinitions.get(thumbnailName); } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) + */ + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + /* (non-Javadoc) + * @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent) + */ + public void onApplicationEvent(ApplicationContextEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * This class hooks in to the spring application lifecycle and ensures that any + * ThumbnailDefinitions injected by spring are converted to RenditionDefinitions + * and saved. + */ + private class RegistryLifecycle extends AbstractLifecycleBean + { + /* (non-Javadoc) + * @see org.alfresco.util.AbstractLifecycleBean#onBootstrap(org.springframework.context.ApplicationEvent) + */ + @Override + protected void onBootstrap(ApplicationEvent event) + { + initThumbnailDefinitions(); + } + + /* (non-Javadoc) + * @see org.alfresco.util.AbstractLifecycleBean#onShutdown(org.springframework.context.ApplicationEvent) + */ + @Override + protected void onShutdown(ApplicationEvent event) + { + // Intentionally empty + } + } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java index a5685b055a..235914b22d 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java @@ -32,19 +32,104 @@ import org.alfresco.service.cmr.rendition.RenditionDefinition; import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; /** - * A helper class to convert {@link TransformationOptions transformationOptions} (a thumbnail-specific - * class) to rendition-specific parameters and vice versa. + * A helper class to convert {@link ThumbnailDefinition thumbnail definition} and + * {@link TransformationOptions transformationOptions} (thumbnail-specific + * classes) to rendition-specific parameters and vice versa. + * + * The Thumbnail Service exposes parameters as simple data types on its various method + * signatures. See for example ThumbnailDefinition.createThumbnail(...) or updateThumbnail(...). + * The RenditionService replaces this approach with one based on the ActionService where + * parameters are added as a Map on the Action/RenditionDefinition object. + * + * @see ThumbnailService#createThumbnail(org.alfresco.service.cmr.repository.NodeRef, QName, String, TransformationOptions, String) + * @see ThumbnailService#createThumbnail(org.alfresco.service.cmr.repository.NodeRef, QName, String, TransformationOptions, String, ThumbnailParentAssociationDetails) + * @see ThumbnailService#updateThumbnail(org.alfresco.service.cmr.repository.NodeRef, TransformationOptions) + * @see RenditionDefinition * * @author Neil McErlean */ public class ThumbnailRenditionConvertor { + private RenditionService renditionService; + + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + /** Given the specified {@link ThumbnailDefinition thumbnailDefinition} and + * {@link ThumbnailParentAssociationDetails assocDetails}, + * create and return an equivalent {@link RenditionDefinition} object. + * + * @param thumbnailDefinition + * @param assocDetails + * @return + */ + public RenditionDefinition convert(ThumbnailDefinition thumbnailDefinition, ThumbnailParentAssociationDetails assocDetails) + { + // We must always have a valid name for a thumbnail definition + if (thumbnailDefinition == null || thumbnailDefinition.getName() == null + || thumbnailDefinition.getName().trim().length() == 0) + { + throw new IllegalArgumentException("Thumbnail Definition and Name must be non-null and non-empty."); + } + + TransformationOptions transformationOptions = thumbnailDefinition.getTransformationOptions(); + Map parameters = this.convert(transformationOptions, assocDetails); + + // Extract parameters defined directly within the ThumbnailDefinition object. + putParameterIfNotNull(AbstractRenderingEngine.PARAM_MIME_TYPE, thumbnailDefinition.getMimetype(), parameters); + putParameterIfNotNull(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH, thumbnailDefinition.getPlaceHolderResourcePath(), parameters); + putParameterIfNotNull(AbstractRenderingEngine.PARAM_RUN_AS, thumbnailDefinition.getRunAs(), parameters); + + QName namespacedRenditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailDefinition.getName()); + + // The built-in RenditionDefinitions are all non-composites. + // They are either "imageRenderingEngine" or "reformat" + boolean isImageThumbnail = isImageBasedRendition(thumbnailDefinition); + + String renderingEngineName = isImageThumbnail ? ImageRenderingEngine.NAME : ReformatRenderingEngine.NAME; + + RenditionDefinition renditionDef = renditionService.createRenditionDefinition(namespacedRenditionName, renderingEngineName); + for (String paramName : parameters.keySet()) + { + renditionDef.setParameterValue(paramName, parameters.get(paramName)); + } + + return renditionDef; + } + + /** + * This method examines the various data values on the thumbnail definition and + * works out if it is an 'image' rendition or a 'reformat' rendition + * @param thumbnailDefinition + * @return true for an image-based RenditionDefinition, else false + */ + private boolean isImageBasedRendition(ThumbnailDefinition thumbnailDefinition) + { + final TransformationOptions transformationOptions = thumbnailDefinition.getTransformationOptions(); + + return transformationOptions != null && transformationOptions instanceof ImageTransformationOptions; + } + + /** Given the specified {@link TransformationOptions transformationOptions} and + * {@link ThumbnailParentAssociationDetails assocDetails}, + * create and return a parameter Map which contains the equivalent {@link RenditionDefinition} + * configuration. + * + * @param transformationOptions + * @param assocDetails + * @return + */ public Map convert(TransformationOptions transformationOptions, ThumbnailParentAssociationDetails assocDetails) { Map parameters = new HashMap(); - + // parameters common to all transformations putParameterIfNotNull(AbstractRenderingEngine.PARAM_SOURCE_CONTENT_PROPERTY, transformationOptions.getSourceContentProperty(), parameters); putParameterIfNotNull(AbstractRenderingEngine.PARAM_TARGET_CONTENT_PROPERTY, transformationOptions.getTargetContentProperty(), parameters); @@ -111,8 +196,6 @@ public class ThumbnailRenditionConvertor thDefn.setPlaceHolderResourcePath((String)placeHolderResourcePathParam); } - //TODO src/target contentProp & nodeRef - TransformationOptions transformationOptions = null; Serializable flashVersion = renditionDefinition.getParameterValue(ReformatRenderingEngine.PARAM_FLASH_VERSION); if (flashVersion != null) @@ -132,7 +215,6 @@ public class ThumbnailRenditionConvertor Serializable xsize = renditionDefinition.getParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH); if (xsize != null) { - // Saved actions with int parameters seem to be coming back as Longs. TODO Investigate resizeOptions.setWidth(((Long) xsize).intValue()); } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java index dc4bcc01ca..01575ba27d 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java @@ -28,6 +28,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.content.transform.swf.SWFTransformationOptions; import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; import org.alfresco.repo.rendition.executer.ImageRenderingEngine; import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -202,20 +203,19 @@ public class ThumbnailServiceImpl implements ThumbnailService // We're prepending the cm namespace here. QName thumbnailQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, localThumbnailName); - - RenditionDefinition renderingAction = renditionService.loadRenditionDefinition(thumbnailQName); - if (renderingAction == null) - { - // If the provided thumbnailName does not map to a built-in rendition definition - // then we must create a dynamic rendition definition for this thumbnail. - // To do this we must have a renderingEngineName. - // - // The transformation will either be a imageRenderingEngine or a reformat (pdf2swf) - String renderingEngineName = getRenderingEngineNameFor(transformationOptions); - - renderingAction = renditionService.createRenditionDefinition(thumbnailQName, renderingEngineName); - } + + // Convert the TransformationOptions and ThumbnailParentAssocDetails to + // rendition-style parameters Map params = thumbnailRegistry.getThumbnailRenditionConvertor().convert(transformationOptions, assocDetails); + // Add the other parameters given in this method signature. + params.put(AbstractRenderingEngine.PARAM_SOURCE_CONTENT_PROPERTY, contentProperty); + params.put(AbstractRenderingEngine.PARAM_MIME_TYPE, mimetype); + + // Create the renditionDefinition + String renderingEngineName = getRenderingEngineNameFor(transformationOptions); + RenditionDefinition renderingAction = renditionService.createRenditionDefinition(thumbnailQName, renderingEngineName); + + // Set the parameters for (String key : params.keySet()) { renderingAction.setParameterValue(key, params.get(key));