getActions()
+ {
+ return this.actions.getActions();
+ }
- /**
- * @see org.alfresco.service.cmr.action.CompositeAction#getAction(int)
- */
- public Action getAction(int index)
- {
- return this.actions.get(index);
- }
+ /**
+ * @return
+ * @see org.alfresco.service.cmr.action.ActionList#hasActions()
+ */
+ public boolean hasActions()
+ {
+ return this.actions.hasActions();
+ }
- /**
- * @see org.alfresco.service.cmr.action.CompositeAction#removeAction(org.alfresco.service.cmr.action.Action)
- */
- public void removeAction(Action action)
- {
- this.actions.remove(action);
- }
+ /**
+ * @param action
+ * @return
+ * @see org.alfresco.service.cmr.action.ActionList#indexOfAction(org.alfresco.service.cmr.action.Action)
+ */
+ public int indexOfAction(Action action)
+ {
+ return this.actions.indexOfAction(action);
+ }
- /**
- * @see org.alfresco.service.cmr.action.CompositeAction#removeAllActions()
- */
- public void removeAllActions()
- {
- this.actions.clear();
- }
+ /**
+ * @param action
+ * @see org.alfresco.service.cmr.action.ActionList#removeAction(org.alfresco.service.cmr.action.Action)
+ */
+ public void removeAction(Action action)
+ {
+ this.actions.removeAction(action);
+ }
+ /**
+ * @see org.alfresco.service.cmr.action.ActionList#removeAllActions()
+ */
+ public void removeAllActions()
+ {
+ this.actions.removeAllActions();
+ }
+
+ /**
+ * @param index
+ * @param action
+ * @see org.alfresco.service.cmr.action.ActionList#setAction(int,
+ * org.alfresco.service.cmr.action.Action)
+ */
+ public void setAction(int index, Action action)
+ {
+ this.actions.setAction(index, action);
+ }
}
diff --git a/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java b/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java
index a1cf45c930..fc278940d8 100644
--- a/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java
+++ b/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java
@@ -29,6 +29,7 @@ import org.alfresco.service.cmr.action.ParameterizedItem;
*
* @author Roy Wetherall
*/
+@SuppressWarnings("serial")
public abstract class ParameterizedItemImpl implements ParameterizedItem, Serializable
{
/**
diff --git a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java
index 5fbff070f3..cb322faea7 100644
--- a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java
+++ b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java
@@ -98,7 +98,7 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra
{
if (this.actionDefinition == null)
{
- this.actionDefinition = new ActionDefinitionImpl(this.name);
+ this.actionDefinition = createActionDefinition(this.name);
((ActionDefinitionImpl)this.actionDefinition).setTitleKey(getTitleKey());
((ActionDefinitionImpl)this.actionDefinition).setDescriptionKey(getDescriptionKey());
((ActionDefinitionImpl)this.actionDefinition).setAdhocPropertiesAllowed(getAdhocPropertiesAllowed());
@@ -109,6 +109,18 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra
return this.actionDefinition;
}
+ /**
+ * This method returns an instance of an ActionDefinition implementation class. By default
+ * this will be an {@link ActionDefinitionImpl}, but this could be overridden.
+ *
+ * @param name
+ * @return
+ */
+ protected ActionDefinition createActionDefinition(String name)
+ {
+ return new ActionDefinitionImpl(name);
+ }
+
/**
* @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef)
*/
diff --git a/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java
new file mode 100644
index 0000000000..2013090650
--- /dev/null
+++ b/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2005-2010 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.admin.patch.impl;
+
+import org.alfresco.repo.admin.patch.AbstractPatch;
+import org.alfresco.repo.domain.qname.QNameDAO;
+import org.alfresco.repo.importer.ImporterBootstrap;
+import org.alfresco.repo.search.Indexer;
+import org.alfresco.repo.search.IndexerAndSearcher;
+import org.alfresco.repo.search.impl.lucene.LuceneQueryParser;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.ResultSetRow;
+import org.alfresco.service.cmr.search.SearchParameters;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.QName;
+import org.springframework.extensions.surf.util.I18NUtil;
+
+/**
+ * A patch to update the value of a QName.
+ * This patch will only succeed if the target QName has not been used i.e. if there is no content
+ * that actually references the QName.
+ *
+ * A property 'reindexClass' can be optionally injected. If it is not injected then the QName is
+ * updated and no reindexing is requested by this patch.
+ * If it is set to either 'TYPE' or 'ASPECT' (as appropriate) then that String will be used to
+ * locate out-of-date references to the old QName and have them reindexed in a targetted way.
+ *
+ * Please refer to the implementation in this class for the details of how this is achieved.
+ *
+ * @author Neil McErlean
+ */
+public class QNamePatch extends AbstractPatch
+{
+ private static final String MSG_SUCCESS = "patch.QNamePatch.result";
+
+ /* Injected properties */
+ private String qnameStringBefore;
+ private String qnameStringAfter;
+ private String reindexClass;
+
+ /* Injected services */
+ private ImporterBootstrap importerBootstrap;
+ private IndexerAndSearcher indexerAndSearcher;
+ private QNameDAO qnameDAO;
+
+ /**
+ * Sets the importerBootstrap.
+ * @param importerBootstrap.
+ */
+ public void setImporterBootstrap(ImporterBootstrap importerBootstrap)
+ {
+ this.importerBootstrap = importerBootstrap;
+ }
+
+ /**
+ * Sets the IndexerAndSearcher.
+ * @param indexerAndSearcher
+ */
+ public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher)
+ {
+ this.indexerAndSearcher = indexerAndSearcher;
+ }
+
+ /**
+ * Sets the QNameDAO.
+ * @param qnameDAO
+ */
+ public void setQnameDAO(QNameDAO qnameDAO)
+ {
+ this.qnameDAO = qnameDAO;
+ }
+
+ /**
+ * Sets the QName to be patched.
+ * @param qnameStringBefore the long-form QName to be patched from. {namespaceURI}localName
+ */
+ public void setQnameBefore(String qnameStringBefore)
+ {
+ this.qnameStringBefore = qnameStringBefore;
+ }
+
+ /**
+ * Sets the new QName value to be used.
+ * @param qnameStringAfter the long-form QName to be patched to. {namespaceURI}localName
+ */
+ public void setQnameAfter(String qnameStringAfter)
+ {
+ this.qnameStringAfter = qnameStringAfter;
+ }
+
+ /**
+ * Sets a value for the class to reindex. This will be used in the Lucene query below and
+ * should be either "TYPE" or "ASPECT" or not set if reindexing is not required.
+ * @param reindexClass "TYPE" or "ASPECT" or not set.
+ */
+ public void setReindexClass(String reindexClass)
+ {
+ this.reindexClass = reindexClass;
+ }
+
+ @Override
+ protected String applyInternal() throws Exception
+ {
+ // We don't need to catch the potential InvalidQNameException here as it will be caught
+ // in AbstractPatch and correctly handled there
+ QName qnameBefore = QName.createQName(this.qnameStringBefore);
+ QName qnameAfter = QName.createQName(this.qnameStringAfter);
+
+ if (qnameDAO.getQName(qnameBefore) != null)
+ {
+ qnameDAO.updateQName(qnameBefore, qnameAfter);
+ }
+
+ // Optionally perform a focussed reindexing of the removed QName.
+ if ("TYPE".equals(reindexClass) ||
+ "ASPECT".equals(reindexClass))
+ {
+ reindex(reindexClass + ":" + LuceneQueryParser.escape(qnameStringBefore), importerBootstrap.getStoreRef());
+ }
+
+ return I18NUtil.getMessage(MSG_SUCCESS, qnameBefore, qnameAfter);
+ }
+
+ private int reindex(String query, StoreRef store)
+ {
+ SearchParameters sp = new SearchParameters();
+ sp.setLanguage(SearchService.LANGUAGE_LUCENE);
+ sp.setQuery(query);
+ sp.addStore(store);
+ Indexer indexer = indexerAndSearcher.getIndexer(store);
+ ResultSet rs = null;
+ int count = 0;
+ try
+ {
+ rs = searchService.query(sp);
+ count = rs.length();
+ for (ResultSetRow row : rs)
+ {
+ indexer.updateNode(row.getNodeRef());
+ }
+ }
+ finally
+ {
+ if (rs != null)
+ {
+ rs.close();
+ }
+ }
+ return count;
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java
index 281f72fab7..8bf5ba48cd 100644
--- a/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java
+++ b/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java
@@ -16,6 +16,7 @@
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
+
package org.alfresco.repo.content.metadata.xml;
import java.io.Serializable;
@@ -26,37 +27,38 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.alfresco.repo.content.selector.ContentWorkerSelector;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter;
import org.alfresco.repo.content.metadata.MetadataExtracter;
+import org.alfresco.repo.content.selector.ContentWorkerSelector;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.namespace.QName;
-import org.springframework.extensions.surf.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.extensions.surf.util.PropertyCheck;
/**
* A metadata extractor that selects an appropiate workder for the extraction.
*
- * The {@linkplain #setSelectors(List) selectors} are used to find an extracter most
- * appropriate of a given XML document. The chosen extracter is then asked to extract
- * the values, passing through the {@linkplain MetadataExtracter.OverwritePolicy overwrite policy}
- * as {@linkplain #setOverwritePolicy(String)} on this instance. The overwrite policy of the
- * embedded extracters is not relevant unless they are used separately in another context.
+ * The {@linkplain #setSelectors(List) selectors} are used to find an extracter
+ * most appropriate of a given XML document. The chosen extracter is then asked
+ * to extract the values, passing through the
+ * {@linkplain MetadataExtracter.OverwritePolicy overwrite policy} as
+ * {@linkplain #setOverwritePolicy(String)} on this instance. The overwrite
+ * policy of the embedded extracters is not relevant unless they are used
+ * separately in another context.
*
* @see ContentWorkerSelector
* @see MetadataExtracter
- *
* @since 2.1
* @author Derek Hulley
*/
public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
{
- public static String[] SUPPORTED_MIMETYPES = new String[] {MimetypeMap.MIMETYPE_XML};
-
+ public static String[] SUPPORTED_MIMETYPES = new String[] { MimetypeMap.MIMETYPE_XML };
+
private static Log logger = LogFactory.getLog(XPathMetadataExtracter.class);
-
+
private List> selectors;
/**
@@ -68,11 +70,11 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
}
/**
- * Sets the list of metadata selectors to use to find the extracter to use, given
- * some content. The evaluations are done in the order that they occur in the
- * list.
+ * Sets the list of metadata selectors to use to find the extracter to use,
+ * given some content. The evaluations are done in the order that they occur
+ * in the list.
*
- * @param selectors A list of selectors
+ * @param selectors A list of selectors
*/
public void setSelectors(List> selectors)
{
@@ -88,9 +90,10 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
}
/**
- * It is not possible to have any default mappings, but something has to be returned.
+ * It is not possible to have any default mappings, but something has to be
+ * returned.
*
- * @return Returns an empty map
+ * @return Returns an empty map
*/
@Override
protected Map> getDefaultMapping()
@@ -102,25 +105,22 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
* Selects and extracter to perform the work and redirects to it.
*/
@Override
- public Map extract(
- ContentReader reader,
- OverwritePolicy overwritePolicy,
- Map destination,
- Map> mapping)
+ public Map extract(ContentReader reader, OverwritePolicy overwritePolicy,
+ Map destination, Map> mapping)
{
// Check the content length
if (reader.getSize() == 0)
{
- // There is no content. We don't spoof any properties so there can be nothing extracted.
+ // There is no content. We don't spoof any properties so there can
+ // be nothing extracted.
if (logger.isDebugEnabled())
{
- logger.debug("\n" +
- "XML document has zero length, so bypassing extraction: \n" +
- " Document: " + reader);
+ logger.debug("\n" + "XML document has zero length, so bypassing extraction: \n" + " Document: "
+ + reader);
}
return destination;
}
-
+
MetadataExtracter extracter = null;
// Select a worker
for (ContentWorkerSelector selector : selectors)
@@ -138,9 +138,8 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
{
if (reader.isChannelOpen())
{
- logger.error("Content reader not closed by MetadataExtractor selector: \n" +
- " reader: " + reader + "\n" +
- " selector: " + selector);
+ logger.error("Content reader not closed by MetadataExtractor selector: \n" + " reader: "
+ + reader + "\n" + " selector: " + selector);
}
}
// Just take the first successful one
@@ -148,10 +147,8 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
{
if (logger.isDebugEnabled())
{
- logger.debug("\n" +
- "Found metadata extracter to process XML document: \n" +
- " Selector: " + selector + "\n" +
- " Document: " + reader);
+ logger.debug("\n" + "Found metadata extracter to process XML document: \n" + " Selector: "
+ + selector + "\n" + " Document: " + reader);
}
break;
}
@@ -162,9 +159,7 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
{
if (logger.isDebugEnabled())
{
- logger.debug("\n" +
- "No working metadata extractor could be found: \n" +
- " Document: " + reader);
+ logger.debug("\n" + "No working metadata extractor could be found: \n" + " Document: " + reader);
}
// There will be no properties extracted
modifiedProperties = destination;
@@ -180,26 +175,22 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter
{
if (reader.isChannelOpen())
{
- logger.error("Content reader not closed by MetadataExtractor: \n" +
- " Reader: " + reader + "\n" +
- " extracter: " + extracter);
+ logger.error("Content reader not closed by MetadataExtractor: \n" + " Reader: " + reader + "\n"
+ + " extracter: " + extracter);
}
}
}
// Done
if (logger.isDebugEnabled())
{
- logger.debug("\n" +
- "XML metadata extractor redirected: \n" +
- " Reader: " + reader + "\n" +
- " Extracter: " + extracter + "\n" +
- " Metadata: " + modifiedProperties);
+ logger.debug("\n" + "XML metadata extractor redirected: \n" + " Reader: " + reader + "\n"
+ + " Extracter: " + extracter + "\n" + " Metadata: " + modifiedProperties);
}
return modifiedProperties;
}
/**
- * This is not required as the
+ * This is not required as the
*/
protected Map extractRaw(ContentReader reader) throws Throwable
{
diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java b/source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java
new file mode 100644
index 0000000000..4570a967b0
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.content.transform.magick;
+
+/**
+ * DTO used to store options for ImageMagick cropping.
+ *
+ * @author Nick Smith
+ */
+public class ImageCropOptions
+{
+ private int height = -1;
+ private int width = -1;
+ private int xOffset = 0;
+ private int yOffset = 0;
+ private boolean isPercentageCrop = false;
+ private String gravity = null;
+
+ /**
+ * Gets the height of the cropped image. By default this is in pixels but if
+ * isPercentageCrop
is set to true then it changes to
+ * percentage.
+ *
+ * @return the height
+ */
+ public int getHeight()
+ {
+ return this.height;
+ }
+
+ /**
+ * Sets the height of the cropped image. By default this is in pixels but if
+ * isPercentageCrop
is set to true then it changes to
+ * percentage.
+ *
+ * @param height the height to set
+ */
+ public void setHeight(int height)
+ {
+ this.height = height;
+ }
+
+ /**
+ * Sets the width of the cropped image. By default this is in pixels but if
+ * isPercentageCrop
is set to true then it changes to
+ * percentage.
+ *
+ * @return the width
+ */
+ public int getWidth()
+ {
+ return this.width;
+ }
+
+ /**
+ * Sets the width of the cropped image. By default this is in pixels but if
+ * isPercentageCrop
is set to true then it changes to
+ * percentage.
+ *
+ * @param width the width to set
+ */
+ public void setWidth(int width)
+ {
+ this.width = width;
+ }
+
+ /**
+ * Gets the horizontal offset. By default this starts fromt he top-left
+ * corner of the image and moves right, however the gravity
+ * property can change this.
+ *
+ * @return the xOffset
+ */
+ public int getXOffset()
+ {
+ return this.xOffset;
+ }
+
+ /**
+ * Sets the horizontal offset. By default this starts fromt he top-left
+ * corner of the image and moves right, however the gravity
+ * property can change this.
+ *
+ * @param xOffset the xOffset to set
+ */
+ public void setXOffset(int xOffset)
+ {
+ this.xOffset = xOffset;
+ }
+
+ /**
+ * Gets the vertical offset. By default this starts fromt he top-left corner
+ * of the image and moves down, however the gravity
property
+ * can change this.
+ *
+ * @return the yOffset
+ */
+ public int getYOffset()
+ {
+ return this.yOffset;
+ }
+
+ /**
+ * Sets the vertical offset. By default this starts fromt he top-left corner
+ * of the image and moves down, however the gravity
property
+ * can change this.
+ *
+ * @param yOffset the yOffset to set
+ */
+ public void setYOffset(int yOffset)
+ {
+ this.yOffset = yOffset;
+ }
+
+ /**
+ * @return the isPercentageCrop
+ */
+ public boolean isPercentageCrop()
+ {
+ return this.isPercentageCrop;
+ }
+
+ /**
+ * @param isPercentageCrop the isPercentageCrop to set
+ */
+ public void setPercentageCrop(boolean isPercentageCrop)
+ {
+ this.isPercentageCrop = isPercentageCrop;
+ }
+
+ /**
+ * Sets the 'gravity' which determines how the offset is applied. It affects
+ * both the origin of offset and the direction(s).
+ *
+ * @param gravity the gravity to set
+ */
+ public void setGravity(String gravity)
+ {
+ this.gravity = gravity;
+ }
+
+ /**
+ * Gets the 'gravity' which determines how the offset is applied. It affects
+ * both the origin of offset and the direction(s).
+ *
+ * @return the gravity
+ */
+ public String getGravity()
+ {
+ return this.gravity;
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java
index 6a736e4725..73e6c3a7e7 100644
--- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java
+++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java
@@ -149,8 +149,13 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont
if (options instanceof ImageTransformationOptions)
{
ImageTransformationOptions imageOptions = (ImageTransformationOptions)options;
+ ImageCropOptions cropOptions = imageOptions.getCropOptions();
ImageResizeOptions resizeOptions = imageOptions.getResizeOptions();
String commandOptions = imageOptions.getCommandOptions();
+ if (cropOptions != null)
+ {
+ commandOptions = commandOptions + " " + getImageCropCommandOptions(cropOptions);
+ }
if (resizeOptions != null)
{
commandOptions = commandOptions + " " + getImageResizeCommandOptions(resizeOptions);
@@ -173,6 +178,59 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont
}
}
+ /**
+ * Gets the imagemagick command string for the image crop options provided
+ *
+ * @param imageResizeOptions image resize options
+ * @return String the imagemagick command options
+ */
+ private String getImageCropCommandOptions(ImageCropOptions cropOptions)
+ {
+ StringBuilder builder = new StringBuilder(32);
+ String gravity = cropOptions.getGravity();
+ if(gravity!=null)
+ {
+ builder.append("-gravity ");
+ builder.append(gravity);
+ builder.append(" ");
+ }
+ builder.append("-crop ");
+ int width = cropOptions.getWidth();
+ if (width > -1)
+ {
+ builder.append(width);
+ }
+
+ int height = cropOptions.getHeight();
+ if (height > -1)
+ {
+ builder.append("x");
+ builder.append(height);
+ }
+
+ if (cropOptions.isPercentageCrop())
+ {
+ builder.append("%");
+ }
+ appendOffset(builder, cropOptions.getXOffset());
+ appendOffset(builder, cropOptions.getYOffset());
+ builder.append(" +repage");
+ return builder.toString();
+ }
+
+ /**
+ * @param builder
+ * @param xOffset
+ */
+ private void appendOffset(StringBuilder builder, int xOffset)
+ {
+ if(xOffset>=0)
+ {
+ builder.append("+");
+ }
+ builder.append(xOffset);
+ }
+
/**
* Gets the imagemagick command string for the image resize options provided
*
diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java b/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java
index 6c727fcbcb..85d7d6d65c 100644
--- a/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java
+++ b/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java
@@ -33,6 +33,9 @@ public class ImageTransformationOptions extends TransformationOptions
/** Image resize options */
private ImageResizeOptions resizeOptions;
+ /** Image crop options */
+ private ImageCropOptions cropOptions;
+
/**
* Set the command string options
*
@@ -84,4 +87,20 @@ public class ImageTransformationOptions extends TransformationOptions
return msg.toString();
}
+
+ /**
+ * @param cropOptions the cropOptions to set
+ */
+ public void setCropOptions(ImageCropOptions cropOptions)
+ {
+ this.cropOptions = cropOptions;
+ }
+
+ /**
+ * @return the cropOptions
+ */
+ public ImageCropOptions getCropOptions()
+ {
+ return this.cropOptions;
+ }
}
diff --git a/source/java/org/alfresco/repo/rendition/AllRenditionTests.java b/source/java/org/alfresco/repo/rendition/AllRenditionTests.java
new file mode 100644
index 0000000000..fd6d779f9e
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/AllRenditionTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2005-2010 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 org.alfresco.repo.thumbnail.ThumbnailServiceImplParameterTest;
+import org.alfresco.repo.thumbnail.ThumbnailServiceImplTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * This class is a holder for the various test classes associated with the Rendition Service.
+ * It is not (at the time of writing) intended to be incorporated into the automatic build
+ * which will find the various test classes and run them individually.
+ *
+ * @author Neil McErlean
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ RenditionServiceImplTest.class,
+ ThumbnailServiceImplParameterTest.class,
+ ThumbnailServiceImplTest.class,
+ StandardRenditionLocationResolverTest.class,
+ RenditionServiceIntegrationTest.class,
+ RenditionServicePermissionsTest.class
+})
+public class AllRenditionTests
+{
+ // Intentionally empty
+}
diff --git a/source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java b/source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java
new file mode 100644
index 0000000000..bf1e7290f5
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2005-2010 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.List;
+
+import org.alfresco.repo.action.ActionListImpl;
+import org.alfresco.repo.rendition.executer.CompositeRenderingEngine;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ActionList;
+import org.alfresco.service.cmr.action.CompositeAction;
+import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition;
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * @author Nick Smith
+ */
+public class CompositeRenditionDefinitionImpl extends RenditionDefinitionImpl implements CompositeRenditionDefinition
+{
+ /**
+ * Serial Version UID
+ */
+ private static final long serialVersionUID = -770880495976834168L;
+
+ private final ActionList actions = new ActionListImpl();
+
+ /**
+ * @param nodeRef
+ * @param id
+ */
+ public CompositeRenditionDefinitionImpl(String id, QName renditionName)
+ {
+ super(id, renditionName, CompositeRenderingEngine.NAME);
+ }
+
+ public CompositeRenditionDefinitionImpl(CompositeAction compositeAction)
+ {
+ super(compositeAction, CompositeRenderingEngine.NAME);
+ for (Action action : compositeAction.getActions())
+ {
+ RenditionDefinition subDefinition;
+ if (action instanceof CompositeAction)
+ {
+ CompositeAction compAction = (CompositeAction) action;
+ subDefinition = new CompositeRenditionDefinitionImpl(compAction);
+ }
+ else
+ {
+ subDefinition = new RenditionDefinitionImpl(action);
+ }
+ addAction(subDefinition);
+ }
+ }
+
+ /**
+ * @param index
+ * @param action
+ * @see org.alfresco.service.cmr.action.ActionList#addAction(int,
+ * org.alfresco.service.cmr.action.Action)
+ */
+ public void addAction(int index, RenditionDefinition action)
+ {
+ this.actions.addAction(index, action);
+ }
+
+ /**
+ * @param action
+ * @see org.alfresco.service.cmr.action.ActionList#addAction(org.alfresco.service.cmr.action.Action)
+ */
+ public void addAction(RenditionDefinition action)
+ {
+ this.actions.addAction(action);
+ }
+
+ /**
+ * @param index
+ * @return
+ * @see org.alfresco.service.cmr.action.ActionList#getAction(int)
+ */
+ public RenditionDefinition getAction(int index)
+ {
+ return this.actions.getAction(index);
+ }
+
+ /**
+ * @return
+ * @see org.alfresco.service.cmr.action.ActionList#getActions()
+ */
+ public List getActions()
+ {
+ return this.actions.getActions();
+ }
+
+ /**
+ * @return
+ * @see org.alfresco.service.cmr.action.ActionList#hasActions()
+ */
+ public boolean hasActions()
+ {
+ return this.actions.hasActions();
+ }
+
+ /**
+ * @param action
+ * @return
+ * @see org.alfresco.service.cmr.action.ActionList#indexOfAction(org.alfresco.service.cmr.action.Action)
+ */
+ public int indexOfAction(RenditionDefinition action)
+ {
+ return this.actions.indexOfAction(action);
+ }
+
+ /**
+ * @param action
+ * @see org.alfresco.service.cmr.action.ActionList#removeAction(org.alfresco.service.cmr.action.Action)
+ */
+ public void removeAction(RenditionDefinition action)
+ {
+ this.actions.removeAction(action);
+ }
+
+ /**
+ * @see org.alfresco.service.cmr.action.ActionList#removeAllActions()
+ */
+ public void removeAllActions()
+ {
+ this.actions.removeAllActions();
+ }
+
+ /**
+ * @param index
+ * @param action
+ * @see org.alfresco.service.cmr.action.ActionList#setAction(int,
+ * org.alfresco.service.cmr.action.Action)
+ */
+ public void setAction(int index, RenditionDefinition action)
+ {
+ this.actions.setAction(index, action);
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java
new file mode 100644
index 0000000000..e891e42803
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2005-2010 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 static org.mockito.Mockito.mock;
+
+import java.util.Collection;
+
+import org.alfresco.cmis.CMISDictionaryService;
+import org.alfresco.cmis.CMISQueryService;
+import org.alfresco.cmis.CMISServices;
+import org.alfresco.mbeans.VirtServerRegistry;
+import org.alfresco.repo.forms.FormService;
+import org.alfresco.repo.imap.ImapService;
+import org.alfresco.repo.lock.JobLockService;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.attributes.AttributeService;
+import org.alfresco.service.cmr.audit.AuditService;
+import org.alfresco.service.cmr.avm.AVMService;
+import org.alfresco.service.cmr.avm.deploy.DeploymentService;
+import org.alfresco.service.cmr.avm.locking.AVMLockingService;
+import org.alfresco.service.cmr.avmsync.AVMSyncService;
+import org.alfresco.service.cmr.coci.CheckOutCheckInService;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.invitation.InvitationService;
+import org.alfresco.service.cmr.lock.LockService;
+import org.alfresco.service.cmr.ml.ContentFilterLanguagesService;
+import org.alfresco.service.cmr.ml.EditionService;
+import org.alfresco.service.cmr.ml.MultilingualContentService;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.CopyService;
+import org.alfresco.service.cmr.repository.CrossRepositoryCopyService;
+import org.alfresco.service.cmr.repository.MimetypeService;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.ScriptService;
+import org.alfresco.service.cmr.repository.TemplateService;
+import org.alfresco.service.cmr.rule.RuleService;
+import org.alfresco.service.cmr.search.CategoryService;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.cmr.security.AuthorityService;
+import org.alfresco.service.cmr.security.MutableAuthenticationService;
+import org.alfresco.service.cmr.security.OwnableService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.security.PublicServiceAccessService;
+import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.cmr.tagging.TaggingService;
+import org.alfresco.service.cmr.thumbnail.ThumbnailService;
+import org.alfresco.service.cmr.version.VersionService;
+import org.alfresco.service.cmr.view.ExporterService;
+import org.alfresco.service.cmr.view.ImporterService;
+import org.alfresco.service.cmr.workflow.WorkflowService;
+import org.alfresco.service.descriptor.DescriptorService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.wcm.asset.AssetService;
+import org.alfresco.wcm.preview.PreviewURIService;
+import org.alfresco.wcm.sandbox.SandboxService;
+import org.alfresco.wcm.webproject.WebProjectService;
+
+public class MockedTestServiceRegistry implements ServiceRegistry
+{
+ private final ActionService actionService = mock(ActionService.class);
+ private final ContentService contentService = mock(ContentService.class);
+ private final NodeService nodeService = mock(NodeService.class);
+ private final TemplateService templateService = mock(TemplateService.class);
+ private final PersonService personService = mock(PersonService.class);
+ private final MutableAuthenticationService authenticationService = mock(MutableAuthenticationService.class);
+ private final NamespaceService namespaceService = mock(NamespaceService.class);
+
+ public boolean isServiceProvided(QName service)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+
+ public WorkflowService getWorkflowService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public WebProjectService getWebProjectService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public VirtServerRegistry getVirtServerRegistry()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public VersionService getVersionService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public TransactionService getTransactionService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ThumbnailService getThumbnailService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public TemplateService getTemplateService()
+ {
+ return this.templateService;
+ }
+
+
+ public TaggingService getTaggingService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public SiteService getSiteService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Collection getServices()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public Object getService(QName service)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public SearchService getSearchService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ScriptService getScriptService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public SandboxService getSandboxService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public RuleService getRuleService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public RetryingTransactionHelper getRetryingTransactionHelper()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public PublicServiceAccessService getPublicServiceAccessService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public PreviewURIService getPreviewURIService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public PersonService getPersonService()
+ {
+ return personService;
+ }
+
+
+ public PermissionService getPermissionService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public OwnableService getOwnableService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public NodeService getNodeService()
+ {
+ return nodeService;
+ }
+
+
+ public NamespaceService getNamespaceService()
+ {
+ // TODO Auto-generated method stub
+ return namespaceService;
+ }
+
+
+ public MultilingualContentService getMultilingualContentService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public MimetypeService getMimetypeService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public LockService getLockService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public JobLockService getJobLockService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public InvitationService getInvitationService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ImporterService getImporterService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ImapService getImapService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public FormService getFormService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public FileFolderService getFileFolderService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ExporterService getExporterService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public EditionService getEditionService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public DictionaryService getDictionaryService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public DescriptorService getDescriptorService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public DeploymentService getDeploymentService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CrossRepositoryCopyService getCrossRepositoryCopyService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CopyService getCopyService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ContentService getContentService()
+ {
+ // TODO Auto-generated method stub
+ return contentService;
+ }
+
+
+ public ContentFilterLanguagesService getContentFilterLanguagesService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CheckOutCheckInService getCheckOutCheckInService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CategoryService getCategoryService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CMISServices getCMISService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CMISQueryService getCMISQueryService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public CMISDictionaryService getCMISDictionaryService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public AuthorityService getAuthorityService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public MutableAuthenticationService getAuthenticationService()
+ {
+ // TODO Auto-generated method stub
+ return authenticationService;
+ }
+
+
+ public AuditService getAuditService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public AttributeService getAttributeService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public AssetService getAssetService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public ActionService getActionService()
+ {
+ // TODO Auto-generated method stub
+ return actionService;
+ }
+
+
+ public AVMSyncService getAVMSyncService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public AVMService getAVMService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public AVMLockingService getAVMLockingService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+ public AVMService getAVMLockingAwareService()
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java b/source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java
new file mode 100644
index 0000000000..94bc09818f
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2005-2010 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 static org.alfresco.model.ContentModel.PROP_NODE_DBID;
+import static org.alfresco.model.ContentModel.PROP_NODE_REF;
+import static org.alfresco.model.ContentModel.PROP_NODE_UUID;
+import static org.alfresco.model.ContentModel.PROP_STORE_IDENTIFIER;
+import static org.alfresco.model.ContentModel.PROP_STORE_NAME;
+import static org.alfresco.model.ContentModel.PROP_STORE_PROTOCOL;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.RenditionModel;
+import org.alfresco.repo.action.ParameterDefinitionImpl;
+import org.alfresco.repo.action.executer.ActionExecuter;
+import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
+import org.alfresco.repo.rendition.executer.AbstractRenderingEngine;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.action.ParameterDefinition;
+import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
+import org.alfresco.service.cmr.rendition.NodeLocator;
+import org.alfresco.service.cmr.rendition.RenderCallback;
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.cmr.rendition.RenditionService;
+import org.alfresco.service.cmr.rendition.RenditionServiceException;
+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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.extensions.surf.util.ParameterCheck;
+
+/*
+ * This class is the action executer for the perform-rendition action. All renditions are
+ * executed by this class as wrapping them all in a containing action facilitates
+ * asynchronous renditions.
+ *
+ * Some of the logic is executed directly by this class (handling of rendition-related
+ * aspects and associations) and the rest is executed by subordinate actions called
+ * from within this action (the actual rendering code). These subordinate actions are
+ * renditionDefinitions.
+ *
+ * @author Neil McErlean
+ * @since 3.3
+ */
+public class PerformRenditionActionExecuter extends ActionExecuterAbstractBase
+{
+ private static final Log log = LogFactory.getLog(PerformRenditionActionExecuter.class);
+
+ /** Action name and parameters */
+ public static final String NAME = "perform-rendition";
+ public static final String PARAM_RENDITION_DEFINITION = "renditionDefinition";
+
+ private static final String DEFAULT_RUN_AS_NAME = AuthenticationUtil.getSystemUserName();
+
+ private static final List unchangedProperties = Arrays.asList(PROP_NODE_DBID, PROP_NODE_REF, PROP_NODE_UUID,
+ PROP_STORE_IDENTIFIER, PROP_STORE_NAME, PROP_STORE_PROTOCOL);
+ /**
+ * Default {@link NodeLocator} simply returns the source node.
+ */
+ private final static NodeLocator defaultNodeLocator = new NodeLocator()
+ {
+ public NodeRef getNode(NodeRef sourceNode, Map params)
+ {
+ return sourceNode;
+ }
+ };
+
+ /*
+ * Injected beans
+ */
+ private RenditionLocationResolver renditionLocationResolver;
+ private ActionService actionService;
+ private NodeService nodeService;
+ private RenditionService renditionService;
+
+ private final NodeLocator temporaryParentNodeLocator;
+ private final QName temporaryRenditionLinkType;
+
+ public PerformRenditionActionExecuter(NodeLocator temporaryParentNodeLocator, QName temporaryRenditionLinkType)
+ {
+ this.temporaryParentNodeLocator = temporaryParentNodeLocator != null ? temporaryParentNodeLocator
+ : defaultNodeLocator;
+ this.temporaryRenditionLinkType = temporaryRenditionLinkType != null ? temporaryRenditionLinkType
+ : RenditionModel.ASSOC_RENDITION;
+ }
+
+ public PerformRenditionActionExecuter()
+ {
+ this(null, null);
+ }
+
+ /**
+ * Injects the actionService bean.
+ *
+ * @param actionService
+ * the actionService.
+ */
+ public void setActionService(ActionService actionService)
+ {
+ this.actionService = actionService;
+ }
+
+ /**
+ * Injects the nodeService bean.
+ *
+ * @param nodeService
+ * the nodeService.
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * Injects the renditionService bean.
+ *
+ * @param renditionService
+ */
+ public void setRenditionService(RenditionService renditionService)
+ {
+ this.renditionService = renditionService;
+ }
+
+ public void setRenditionLocationResolver(RenditionLocationResolver renditionLocationResolver)
+ {
+ this.renditionLocationResolver = renditionLocationResolver;
+ }
+
+ @Override
+ protected void executeImpl(final Action containingAction, final NodeRef actionedUponNodeRef)
+ {
+ final RenditionDefinition renditionDefinition = getRenditionDefinition(containingAction);
+ if (log.isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Rendering node ").append(actionedUponNodeRef).append(" with rendition definition ").append(
+ renditionDefinition.getRenditionName());
+ log.debug(msg.toString());
+ }
+
+ Serializable runAsParam = renditionDefinition.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS);
+ String runAsName = runAsParam == null ? DEFAULT_RUN_AS_NAME : (String) runAsParam;
+
+ // Renditions should all be created by system by default.
+ // When renditions are created by a user and are to be created under a
+ // node
+ // other than the source node, it is possible that the user will not
+ // have
+ // permissions to create content under that node.
+ // For that reason, we execute all perform-rendition actions as system
+ // by default.
+ AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
+ {
+ public Void doWork() throws Exception
+ {
+ ChildAssociationRef result = null;
+ try
+ {
+ setTemporaryRenditionProperties(actionedUponNodeRef, renditionDefinition);
+
+ // Adds the 'Renditioned' aspect to the source node if it
+ // doesn't exist.
+ if (!nodeService.hasAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED))
+ {
+ nodeService.addAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
+ }
+ ChildAssociationRef tempRendAssoc = executeRendition(actionedUponNodeRef, renditionDefinition);
+ result = createOrUpdateRendition(actionedUponNodeRef, tempRendAssoc, renditionDefinition);
+ containingAction.setParameterValue(PARAM_RESULT, result);
+ } catch (Throwable t)
+ {
+ notifyCallbackOfException(renditionDefinition, t);
+ throwWrappedException(t);
+ }
+ if (result != null)
+ {
+ notifyCallbackOfResult(renditionDefinition, result);
+ }
+ return null;
+ }
+ }, runAsName);
+ }
+
+ private RenditionDefinition getRenditionDefinition(final Action containingAction)
+ {
+ Serializable rendDefObj = containingAction.getParameterValue(PARAM_RENDITION_DEFINITION);
+ ParameterCheck.mandatory(PARAM_RENDITION_DEFINITION, rendDefObj);
+ return (RenditionDefinition) rendDefObj;
+ }
+
+ // Rendition has failed. If there is a callback, it needs to be notified
+ private void notifyCallbackOfException(RenditionDefinition renditionDefinition, Throwable t)
+ {
+ if (renditionDefinition != null)
+ {
+ RenderCallback callback = renditionDefinition.getCallback();
+ if (callback != null)
+ {
+ callback.handleFailedRendition(t);
+ }
+ }
+ }
+
+ // and rethrow Exception
+ private void throwWrappedException(Throwable t)
+ {
+ if (t instanceof AlfrescoRuntimeException)
+ {
+ throw (AlfrescoRuntimeException) t;
+ } else
+ {
+ throw new RenditionServiceException(t.getMessage(), t);
+ }
+ }
+
+ private void notifyCallbackOfResult(RenditionDefinition renditionDefinition, ChildAssociationRef result)
+ {
+ // Rendition was successful. Notify the callback object.
+ if (renditionDefinition != null)
+ {
+ RenderCallback callback = renditionDefinition.getCallback();
+ if (callback != null)
+ {
+ callback.handleSuccessfulRendition(result);
+ }
+ }
+ }
+
+ /**
+ * This method sets the renditionParent and rendition assocType.
+ *
+ * @param sourceNode
+ * @param definition
+ */
+ private void setTemporaryRenditionProperties(NodeRef sourceNode, RenditionDefinition definition)
+ {
+ // Set the parent and assoc type for the temporary rendition to be
+ // created.
+ NodeRef parent = temporaryParentNodeLocator.getNode(sourceNode, definition.getParameterValues());
+ definition.setRenditionParent(parent);
+ definition.setRenditionAssociationType(temporaryRenditionLinkType);
+ }
+
+ /**
+ * @param sourceNode
+ * @param definition
+ * @return
+ */
+ private ChildAssociationRef executeRendition(NodeRef sourceNode, RenditionDefinition definition)
+ {
+ actionService.executeAction(definition, sourceNode);
+ // Extract the result from the action
+ Serializable serializableResult = definition.getParameterValue(ActionExecuter.PARAM_RESULT);
+ return (ChildAssociationRef) serializableResult;
+ }
+
+ private ChildAssociationRef createOrUpdateRendition(NodeRef sourceNode, ChildAssociationRef tempRendition,
+ RenditionDefinition renditionDefinition)
+ {
+ NodeRef tempRenditionNode = tempRendition.getChildRef();
+ RenditionLocation location = getDestinationParentAssoc(sourceNode, renditionDefinition, tempRenditionNode);
+ QName renditionQName = renditionDefinition.getRenditionName();
+ if (log.isDebugEnabled())
+ {
+ final String lineBreak = System.getProperty("line.separator", "\n");
+ StringBuilder msg = new StringBuilder();
+ msg.append("Creating/updating rendition based on:").append(lineBreak).append(" sourceNode: ").append(
+ sourceNode).append(lineBreak).append(" tempRendition: ").append(tempRendition).append(
+ lineBreak).append(" parentNode: ").append(location.getParentRef()).append(lineBreak).append(
+ " childName: ").append(location.getChildName()).append(lineBreak).append(
+ " renditionDefinition.name: ").append(renditionQName);
+ log.debug(msg.toString());
+ }
+ ChildAssociationRef primaryAssoc = findOrCreatePrimaryRenditionAssociation(sourceNode, renditionDefinition,
+ location);
+
+ // Copy relevant properties from the temporary node to the new rendition
+ // node.
+ NodeRef renditionNode = primaryAssoc.getChildRef();
+ transferNodeProperties(tempRenditionNode, renditionNode);
+
+ // Set the name property on the rendition if it has not already been
+ // set.
+ String renditionName = getRenditionName(tempRenditionNode, location, renditionDefinition);
+ nodeService.setProperty(renditionNode, ContentModel.PROP_NAME, renditionName);
+
+ // Delete the temporary rendition.
+ nodeService.removeChildAssociation(tempRendition);
+
+ // Handle the rendition aspects
+ manageRenditionAspects(sourceNode, primaryAssoc);
+ ChildAssociationRef renditionAssoc = renditionService.getRenditionByName(sourceNode, renditionQName);
+ if (renditionAssoc == null)
+ {
+ String msg = "A rendition of type: " + renditionQName + " should have been created for source node: "
+ + sourceNode;
+ throw new RenditionServiceException(msg);
+ }
+ return renditionAssoc;
+ }
+
+ private void manageRenditionAspects(NodeRef sourceNode, ChildAssociationRef renditionParentAssoc)
+ {
+ NodeRef renditionNode = renditionParentAssoc.getChildRef();
+ NodeRef primaryParent = renditionParentAssoc.getParentRef();
+
+ // If the rendition is located directly underneath its own source node
+ if (primaryParent.equals(sourceNode))
+ {
+ // It should be a 'hidden' rendition.
+ nodeService.addAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION, null);
+ nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION);
+ // We remove the other aspect to cover the potential case where a
+ // rendition
+ // has been updated in a different location.
+ } else
+ {
+ // Renditions stored underneath any node other than their source are
+ // 'visible'.
+ nodeService.addAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION, null);
+ nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION);
+ }
+ }
+
+ private String getRenditionName(NodeRef tempRenditionNode, RenditionLocation location,
+ RenditionDefinition renditionDefinition)
+ {
+ // If a location name is set then use it.
+ String locName = location.getChildName();
+ if (locName != null && locName.length() > 0)
+ {
+ return locName;
+ }
+ // Else if the temporary rendition specifies a name property use that.
+ Serializable tempName = nodeService.getProperty(tempRenditionNode, ContentModel.PROP_NAME);
+ if (tempName != null)
+ {
+ return (String) tempName;
+ }
+ // Otherwise use the rendition definition local name.
+ return renditionDefinition.getRenditionName().getLocalName();
+ }
+
+ private ChildAssociationRef findOrCreatePrimaryRenditionAssociation(NodeRef sourceNode,
+ RenditionDefinition renditionDefinition, RenditionLocation location)
+ {
+ // Get old Rendition if exists.
+ QName renditionName = renditionDefinition.getRenditionName();
+ ChildAssociationRef oldRenditionAssoc = renditionService.getRenditionByName(sourceNode, renditionName);
+ // If no rendition already exists create anew rendition node and
+ // association.
+ if (oldRenditionAssoc == null)
+ {
+ return getSpecifiedRenditionOrCreateNewRendition(sourceNode, location, renditionName);
+ }
+ // If a rendition exists and is already in the correct location then
+ // return that renditions primary parent association
+ NodeRef oldRendition = oldRenditionAssoc.getChildRef();
+ if (renditionLocationMatches(oldRendition, location))
+ {
+ return nodeService.getPrimaryParent(oldRendition);
+ }
+ // If the old rendition is in the wrong location and the 'orphan
+ // existing rendition' param is set to true or the RenditionLocation
+ // specifies a destination NodeRef then ldelete the old
+ // rendition association and create a new rendition node.
+ if (orphanExistingRendition(renditionDefinition, location))
+ {
+ orphanRendition(oldRenditionAssoc);
+ return getSpecifiedRenditionOrCreateNewRendition(sourceNode, location, renditionName);
+ }
+ // If the old rendition is in the wrong place and the 'orphan existing
+ // rendition' param is not set to true then move the existing rendition
+ // to the correct location.
+ return moveRendition(oldRendition, location, renditionName);
+ }
+
+ private ChildAssociationRef moveRendition(NodeRef renditionNode, RenditionLocation location, QName associationName)
+ {
+ ChildAssociationRef assoc = nodeService.moveNode(renditionNode, location.getParentRef(),
+ ContentModel.ASSOC_CONTAINS, associationName);
+ return assoc;
+ }
+
+ private void orphanRendition(ChildAssociationRef oldRenditionAssoc)
+ {
+ NodeRef oldRendition = oldRenditionAssoc.getChildRef();
+ nodeService.removeAspect(oldRendition, RenditionModel.ASPECT_HIDDEN_RENDITION);
+ nodeService.removeAspect(oldRendition, RenditionModel.ASPECT_VISIBLE_RENDITION);
+ nodeService.removeChildAssociation(oldRenditionAssoc);
+ }
+
+ private boolean orphanExistingRendition(RenditionDefinition renditionDefinition, RenditionLocation location)
+ {
+ if (location.getChildRef() != null)
+ return true;
+ else
+ return AbstractRenderingEngine.getParamWithDefault(RenditionService.PARAM_ORPHAN_EXISTING_RENDITION,
+ Boolean.FALSE, renditionDefinition);
+ }
+
+ private boolean renditionLocationMatches(NodeRef oldRendition, RenditionLocation location)
+ {
+ NodeRef destination = location.getChildRef();
+ if (destination != null)
+ {
+ return destination.equals(oldRendition);
+ }
+ ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(oldRendition);
+ NodeRef oldParent = oldParentAssoc.getParentRef();
+ if (oldParent.equals(location.getParentRef()))
+ {
+ String childName = location.getChildName();
+ if (childName == null)
+ return true;
+ else
+ {
+ Serializable oldName = nodeService.getProperty(oldRendition, ContentModel.PROP_NAME);
+ return childName.equals(oldName);
+ }
+ }
+ return false;
+ }
+
+ private ChildAssociationRef getSpecifiedRenditionOrCreateNewRendition(NodeRef sourceNode,
+ RenditionLocation location, QName renditionName)
+ {
+ NodeRef destination = location.getChildRef();
+ if (destination != null)
+ return nodeService.getPrimaryParent(destination);
+ else
+ return createNewRendition(sourceNode, location, renditionName);
+ }
+
+ private ChildAssociationRef createNewRendition(NodeRef sourceNode, RenditionLocation location, QName renditionName)
+ {
+ NodeRef parentRef = location.getParentRef();
+ boolean parentIsSource = parentRef.equals(sourceNode);
+ QName renditionType = RenditionModel.ASSOC_RENDITION;
+ QName assocTypeQName = parentIsSource ? renditionType : ContentModel.ASSOC_CONTAINS;
+ QName nodeTypeQName = ContentModel.TYPE_CONTENT;
+ ChildAssociationRef primaryAssoc = nodeService.createNode(parentRef, assocTypeQName, renditionName,
+ nodeTypeQName);
+
+ // If the new rendition is not directly under the source node then add
+ // the rendition association.
+ if (parentIsSource == false)
+ {
+ NodeRef rendition = primaryAssoc.getChildRef();
+ nodeService.addChild(sourceNode, rendition, renditionType, renditionName);
+ }
+ return primaryAssoc;
+ }
+
+ /**
+ * @param sourceNode
+ * @param targetNode
+ */
+ private void transferNodeProperties(NodeRef sourceNode, NodeRef targetNode)
+ {
+ if (log.isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Transferring some properties from ").append(sourceNode).append(" to ").append(targetNode);
+ log.debug(msg.toString());
+ }
+
+ Map newProps = nodeService.getProperties(sourceNode);
+ for (QName propKey : unchangedProperties)
+ {
+ newProps.remove(propKey);
+ }
+ nodeService.setProperties(targetNode, newProps);
+ }
+
+ private RenditionLocation getDestinationParentAssoc(NodeRef sourceNode, RenditionDefinition definition,
+ NodeRef tempRendition)
+ {
+ return renditionLocationResolver.getRenditionLocation(sourceNode, definition, tempRendition);
+ }
+
+ @Override
+ protected void addParameterDefinitions(List paramList)
+ {
+ paramList.add(new ParameterDefinitionImpl(PARAM_RENDITION_DEFINITION, DataTypeDefinition.ANY, true,
+ getParamDisplayLabel(PARAM_RENDITION_DEFINITION)));
+
+ paramList.add(new ParameterDefinitionImpl(PARAM_RESULT, DataTypeDefinition.CHILD_ASSOC_REF, false,
+ getParamDisplayLabel(PARAM_RESULT)));
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java b/source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java
new file mode 100644
index 0000000000..fb544a7a80
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2010 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 org.alfresco.repo.action.ActionDefinitionImpl;
+import org.alfresco.service.cmr.rendition.RenderingEngineDefinition;
+
+/**
+ * @author Nick Smith
+ * @since 3.3
+ */
+public class RenderingEngineDefinitionImpl extends ActionDefinitionImpl implements RenderingEngineDefinition
+{
+ /**
+ * Serial version UID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public RenderingEngineDefinitionImpl(String name)
+ {
+ super(name);
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java
new file mode 100644
index 0000000000..c48fbccf6c
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2005-2010 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 org.alfresco.repo.action.ActionImpl;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.rendition.RenderCallback;
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * @author Nick Smith
+ * @author Neil McErlean
+ * @since 3.3
+ */
+public class RenditionDefinitionImpl extends ActionImpl implements RenditionDefinition
+{
+
+ /**
+ * Serial version UID
+ */
+ private static final long serialVersionUID = 4336392868488634875L;
+ protected static final String RENDITION_DEFINITION_NAME = "renderingActionName";
+
+ public NodeRef renditionParent;
+ public QName renditionAssociationType;
+ private RenderCallback renderCallback;
+
+ /**
+ * @param id the action id
+ * @param renditionName a unique name for the rendering action.
+ * @param renderingEngineName the name of the rendering action definition
+ */
+ public RenditionDefinitionImpl(String id, QName renditionName, String renderingEngineName)
+ {
+ super(null, id, renderingEngineName);
+ setParameterValue(RENDITION_DEFINITION_NAME, renditionName);
+ }
+
+ public RenditionDefinitionImpl(Action action)
+ {
+ super(action);
+ }
+
+ public RenditionDefinitionImpl(Action action, String renderingEngineName)
+ {
+ super(action, renderingEngineName);
+ }
+
+ /*
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionDefinition#getRenditionName()
+ */
+ public QName getRenditionName()
+ {
+ return (QName) getParameterValue(RENDITION_DEFINITION_NAME);
+ }
+
+ /*
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionDefinition#getRenditionParent
+ * ()
+ */
+ public NodeRef getRenditionParent()
+ {
+ return this.renditionParent;
+ }
+
+ /*
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionDefinition#setRenditionParent
+ * (org.alfresco.service.cmr.repository.NodeRef)
+ */
+ public void setRenditionParent(NodeRef renditionParent)
+ {
+ this.renditionParent = renditionParent;
+ }
+
+ /*
+ * @seeorg.alfresco.service.cmr.rendition.RenditionDefinition#
+ * getRenditionAssociationType()
+ */
+ public QName getRenditionAssociationType()
+ {
+ return this.renditionAssociationType;
+ }
+
+ /*
+ * @seeorg.alfresco.service.cmr.rendition.RenditionDefinition#
+ * setRenditionAssociationType(org.alfresco.service.namespace.QName)
+ */
+ public void setRenditionAssociationType(QName renditionAssociationType)
+ {
+ this.renditionAssociationType = renditionAssociationType;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionDefinition#setCallback(org.alfresco.service.cmr.rendition.RenderCallback)
+ */
+ public void setCallback(RenderCallback callback)
+ {
+ this.renderCallback = callback;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionDefinition#setCallback(org.alfresco.service.cmr.rendition.RenderCallback)
+ */
+ public RenderCallback getCallback()
+ {
+ return this.renderCallback;
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java
new file mode 100644
index 0000000000..206910c568
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2005-2010 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.List;
+
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class provides the implementation of RenditionDefinition persistence.
+ *
+ * @author Nick Smith
+ * @author Neil McErlean
+ * @since 3.3
+ */
+public interface RenditionDefinitionPersister
+{
+ /**
+ * This method serializes the {@link RenditionDefinition} and stores it in
+ * the repository. {@link RenditionDefinition}s saved in this way may be
+ * retrieved using the load()
method.
+ *
+ * @param renditionDefinition The {@link RenditionDefinition} to be
+ * persisted.
+ */
+ void saveRenditionDefinition(RenditionDefinition renditionDefinition);
+
+ /**
+ * This method retrieves a {@link RenditionDefinition} that has been stored
+ * in the repository using the save()
method. If no
+ * {@link RenditionDefinition} exists in the repository with the specified
+ * rendition name then this method returns null.
+ *
+ * @param renditionName The unique identifier used to specify the
+ * {@link RenditionDefinition} to retrieve.
+ * @return The specified {@link RenditionDefinition} or null.
+ */
+ RenditionDefinition loadRenditionDefinition(QName renditionName);
+
+ /**
+ * This method retrieves the {@link RenditionDefinition}s that have been
+ * stored in the repository using the save()
method.
+ *
+ * If there are no such {@link RenditionDefinition}s, an empty list is
+ * returned.
+ *
+ * @return The {@link RenditionDefinition}s.
+ */
+ List loadRenditionDefinitions();
+
+ /**
+ * This method retrieves the stored {@link RenditionDefinition}s that have
+ * been registered for the specified rendering engine name.
+ *
+ * If there are no such rendering {@link RenditionDefinition}s, an empty
+ * list is returned.
+ *
+ * @param renderingEngineName the name of a rendering engine. This is
+ * usually the spring bean name.
+ * @return The {@link RenditionDefinition}s.
+ * @throws NullPointerException if the renderingEngineName is null.
+ * @see #saveRenditionDefinition(RenditionDefinition)
+ */
+ List loadRenditionDefinitions(String renderingEngineName);
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java
new file mode 100644
index 0000000000..f2c5f43087
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2005-2010 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.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.action.ActionModel;
+import org.alfresco.repo.action.RuntimeActionService;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.CompositeAction;
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.cmr.rendition.RenditionServiceException;
+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.cmr.repository.StoreRef;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * This class provides the implementation of RenditionDefinition persistence.
+ *
+ * @author Nick Smith
+ * @author Neil McErlean
+ * @since 3.3
+ */
+public class RenditionDefinitionPersisterImpl implements RenditionDefinitionPersister
+{
+ /** Reference to the rendering action space node */
+ private static final StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
+ protected static final NodeRef RENDERING_ACTION_ROOT_NODE_REF = new NodeRef(SPACES_STORE, "rendering_actions_space");
+
+ /* Injected services */
+ private NodeService nodeService;
+ private RuntimeActionService runtimeActionService;
+
+ /**
+ * Injects the NodeService bean.
+ *
+ * @param nodeService the NodeService.
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * Injects the RuntimeActionService bean.
+ *
+ * @param runtimeActionService the RuntimeActionService.
+ */
+ public void setRuntimeActionService(RuntimeActionService runtimeActionService)
+ {
+ this.runtimeActionService = runtimeActionService;
+ }
+
+ public List loadRenditionDefinitions()
+ {
+ checkRenderingActionRootNodeExists();
+
+ // Note that in the call to getChildAssocs below, only the specified
+ // types are included.
+ // Subtypes of the type action:action will not be returned.
+ Set actionTypes = new HashSet();
+ actionTypes.add(ActionModel.TYPE_ACTION);
+
+ List childAssocs = nodeService.getChildAssocs(RENDERING_ACTION_ROOT_NODE_REF, actionTypes);
+
+ List renderingActions = new ArrayList(childAssocs.size());
+ for (ChildAssociationRef actionAssoc : childAssocs)
+ {
+ Action nextAction = runtimeActionService.createAction(actionAssoc.getChildRef());
+ renderingActions.add(new RenditionDefinitionImpl(nextAction));
+ }
+
+ return renderingActions;
+ }
+
+ public List loadRenditionDefinitions(String renditionEngineName)
+ {
+ if (renditionEngineName == null)
+ {
+ throw new NullPointerException("Unexpected null renditionEngineName");
+ }
+
+ List allRenditionDefinitions = this.loadRenditionDefinitions();
+
+ List filteredRenditionDefinitions = new ArrayList();
+ for (RenditionDefinition renderAction : allRenditionDefinitions)
+ {
+ if (renditionEngineName.equals(renderAction.getActionDefinitionName()))
+ {
+ filteredRenditionDefinitions.add(renderAction);
+ }
+ }
+
+ return filteredRenditionDefinitions;
+ }
+
+
+ public RenditionDefinition loadRenditionDefinition(QName renderingActionName)
+ {
+ NodeRef actionNode = findActionNode(renderingActionName);
+ if (actionNode != null)
+ {
+ Action action = runtimeActionService.createAction(actionNode);
+ if (action instanceof CompositeAction)
+ {
+ CompositeAction compAction = (CompositeAction) action;
+ return new CompositeRenditionDefinitionImpl(compAction);
+ }
+ else
+ {
+ return new RenditionDefinitionImpl(action);
+ }
+ }
+ else
+ return null;
+ }
+
+ public void saveRenditionDefinition(RenditionDefinition renderingAction)
+ {
+ NodeRef actionNodeRef = findOrCreateActionNode(renderingAction);
+
+ // TODO Serialize using JSON content instead.
+ // The current serialization mechanism creates a complex content model
+ // structure which is verbose and a JSON-based approach using a simplified
+ // content model perhaps could offer performance improvements.
+ runtimeActionService.saveActionImpl(actionNodeRef, renderingAction);
+ }
+
+ private NodeRef findActionNode(QName renderingActionName)
+ {
+ checkRenderingActionRootNodeExists();
+ List childAssocs = nodeService.getChildAssocs(//
+ RENDERING_ACTION_ROOT_NODE_REF,//
+ ContentModel.ASSOC_CONTAINS,//
+ renderingActionName);
+ if (childAssocs.isEmpty())
+ {
+ return null;
+ }
+ else
+ {
+ if (childAssocs.size() > 1)
+ {//
+ throw new RenditionServiceException("Multiple rendering actions with the name: " + renderingActionName
+ + " exist!");
+ }
+ return childAssocs.get(0).getChildRef();
+ }
+ }
+
+ private NodeRef findOrCreateActionNode(RenditionDefinition renderingAction)
+ {
+ QName actionName = renderingAction.getRenditionName();
+ NodeRef actionNode = findActionNode(actionName);
+ if (actionNode == null)
+ {
+ actionNode = runtimeActionService.createActionNodeRef(//
+ renderingAction,//
+ RENDERING_ACTION_ROOT_NODE_REF,//
+ ContentModel.ASSOC_CONTAINS,//
+ actionName);
+ }
+ return actionNode;
+ }
+
+ /**
+ * This method checks whether the folder containing Rendering Action nodes
+ * exists.
+ *
+ * @throws RenditionServiceException if the folder node does not exist.
+ */
+ private void checkRenderingActionRootNodeExists()
+ {
+ if (nodeService.exists(RENDERING_ACTION_ROOT_NODE_REF) == false)
+ {
+ throw new RenditionServiceException("Unable to find rendering action root node.");
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionLocation.java b/source/java/org/alfresco/repo/rendition/RenditionLocation.java
new file mode 100644
index 0000000000..4cd3c1ed76
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionLocation.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005-2010 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 org.alfresco.service.cmr.repository.NodeRef;
+
+public interface RenditionLocation
+{
+ NodeRef getParentRef();
+
+ NodeRef getChildRef();
+
+ String getChildName();
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java b/source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java
new file mode 100644
index 0000000000..71e81b048c
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2005-2010 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 org.alfresco.service.cmr.repository.NodeRef;
+
+public class RenditionLocationImpl implements RenditionLocation
+{
+ private final NodeRef parentRef;
+ private final NodeRef childRef;
+ private final String childName;
+
+ public RenditionLocationImpl(NodeRef parentRef, NodeRef childRef, String childName)
+ {
+ this.parentRef = parentRef;
+ this.childRef = childRef;
+ this.childName = childName;
+ }
+
+ public String getChildName()
+ {
+ return childName;
+ }
+
+ public NodeRef getParentRef()
+ {
+ return parentRef;
+ }
+
+ public NodeRef getChildRef()
+ {
+ return childRef;
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java b/source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java
new file mode 100644
index 0000000000..71dc3e6d79
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005-2010 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 org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.cmr.repository.NodeRef;
+
+public interface RenditionLocationResolver
+{
+
+ RenditionLocation getRenditionLocation(NodeRef sourceNode, RenditionDefinition definition, NodeRef tempRenditionLocation);
+
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java
new file mode 100644
index 0000000000..59b4ff8f55
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright (C) 2005-2010 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.RenditionModel;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ActionDefinition;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+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.RenditionService;
+import org.alfresco.service.cmr.rendition.RenditionServiceException;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.ContentReader;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.alfresco.util.GUID;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/*
+ * @author Nick Smith
+ * @author Neil McErlean
+ * @since 3.3
+ */
+public class RenditionServiceImpl implements RenditionService, RenditionDefinitionPersister
+{
+ private static final Log log = LogFactory.getLog(RenditionServiceImpl.class);
+
+ private ActionService actionService;
+ private ContentService contentService;
+ private DictionaryService dictionaryService;
+ private NodeService nodeService;
+
+ private RenditionDefinitionPersisterImpl renditionDefinitionPersister;
+
+ /**
+ * Injects the RenditionDefinitionPersister bean.
+ * @param renditionDefinitionPersister
+ */
+ public void setRenditionDefinitionPersister(RenditionDefinitionPersisterImpl renditionDefinitionPersister)
+ {
+ this.renditionDefinitionPersister = renditionDefinitionPersister;
+ }
+
+ /**
+ * Injects the ServiceRegistry bean.
+ * @param serviceRegistry
+ */
+ public void setServiceRegistry(ServiceRegistry serviceRegistry)
+ {
+ this.contentService = serviceRegistry.getContentService();
+ this.nodeService = serviceRegistry.getNodeService();
+ }
+
+ /**
+ * Injects the ActionService bean.
+ * @param actionService
+ */
+ public void setActionService(ActionService actionService)
+ {
+ this.actionService = actionService;
+ }
+
+ /**
+ * Injects the DictionaryService bean.
+ * @param dictionaryService
+ */
+ public void setDictionaryService(DictionaryService dictionaryService)
+ {
+ this.dictionaryService = dictionaryService;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#getRenderingEngineDefinition(java.lang.String)
+ */
+ public RenderingEngineDefinition getRenderingEngineDefinition(String name)
+ {
+ ActionDefinition actionDefinition = actionService.getActionDefinition(name);
+ if (actionDefinition instanceof RenderingEngineDefinition)
+ {
+ return (RenderingEngineDefinition) actionDefinition;
+ }
+ else
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#getRenderingEngineDefinitions()
+ */
+ public List getRenderingEngineDefinitions()
+ {
+ List results = new ArrayList();
+ List actionDefs = actionService.getActionDefinitions();
+ for (ActionDefinition actionDef : actionDefs)
+ {
+ if (actionDef instanceof RenderingEngineDefinition)
+ {
+ RenderingEngineDefinition renderingDef = (RenderingEngineDefinition) actionDef;
+ results.add(renderingDef);
+ }
+ }
+ return results;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionService#createRenditionDefinition
+ * (org.alfresco.service.namespace.QName, java.lang.String)
+ */
+ public RenditionDefinition createRenditionDefinition(QName renderingActionName, String actionDefinitionName)
+ {
+ if (log.isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Creating rendition definition ")
+ .append(renderingActionName)
+ .append(" ")
+ .append(actionDefinitionName);
+ log.debug(msg.toString());
+ }
+ return new RenditionDefinitionImpl(GUID.generate(), renderingActionName, actionDefinitionName);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#createCompositeRenditionDefinition(org.alfresco.service.namespace.QName)
+ */
+ public CompositeRenditionDefinition createCompositeRenditionDefinition(QName renditionName)
+ {
+ if (log.isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Creating composite rendition definition ")
+ .append(renditionName);
+ log.debug(msg.toString());
+ }
+ return new CompositeRenditionDefinitionImpl(GUID.generate(), renditionName);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rendition.RenditionDefinition)
+ */
+ public ChildAssociationRef render(NodeRef sourceNode, RenditionDefinition definition)
+ {
+ ChildAssociationRef result = createAndExecuteRenditionAction(sourceNode, definition, false);
+
+ return result;
+ }
+
+ public void render(NodeRef sourceNode, RenditionDefinition definition,
+ RenderCallback callback)
+ {
+ // The asynchronous render can't return a ChildAssociationRef as it is created
+ // asynchronously after this method returns.
+ definition.setCallback(callback);
+
+ createAndExecuteRenditionAction(sourceNode, definition, true);
+
+ return;
+ }
+
+ private ChildAssociationRef createAndExecuteRenditionAction(NodeRef sourceNode,
+ RenditionDefinition definition, boolean asynchronous)
+ {
+ if (log.isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ if (asynchronous)
+ {
+ msg.append("Asynchronously");
+ }
+ else
+ {
+ msg.append("Synchronously");
+ }
+ msg.append(" rendering node ").append(sourceNode)
+ .append(" with ").append(definition.getRenditionName());
+ log.debug(msg.toString());
+ }
+ Action performRenditionAction = actionService.createAction(PerformRenditionActionExecuter.NAME);
+ performRenditionAction.setParameterValue(PerformRenditionActionExecuter.PARAM_RENDITION_DEFINITION, definition);
+
+ final boolean checkConditions = false;
+ actionService.executeAction(performRenditionAction, sourceNode, checkConditions, asynchronous);
+
+ ChildAssociationRef result = (ChildAssociationRef)performRenditionAction.getParameterValue(PerformRenditionActionExecuter.PARAM_RESULT);
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionService#saveRenditionDefinition
+ * (org.alfresco.service.cmr.rendition.RenditionDefinition)
+ */
+ public void saveRenditionDefinition(RenditionDefinition renderingAction)
+ {
+ this.renditionDefinitionPersister.saveRenditionDefinition(renderingAction);
+ }
+
+ /*
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionService#loadRenderingAction
+ * (org.alfresco.service.namespace.QName)
+ */
+ public RenditionDefinition loadRenditionDefinition(QName renderingActionName)
+ {
+ return this.renditionDefinitionPersister.loadRenditionDefinition(renderingActionName);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#loadRenditionDefinitions()
+ */
+ public List loadRenditionDefinitions()
+ {
+ return this.renditionDefinitionPersister.loadRenditionDefinitions();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionService#loadRenderingActions
+ * (java.lang.String)
+ */
+ public List loadRenditionDefinitions(String renditionEngineName)
+ {
+ return this.loadRenditionDefinitions(renditionEngineName);
+ }
+
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionService#getRenditions(org
+ * .alfresco.service.cmr.repository.NodeRef)
+ */
+ public List getRenditions(NodeRef node)
+ {
+ List result = new ArrayList();
+
+ // Check that the node has the renditioned aspect applied
+ if (nodeService.hasAspect(node, RenditionModel.ASPECT_RENDITIONED) == true)
+ {
+ // Get all the renditions that match the given rendition name
+ result = nodeService.getChildAssocs(node, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL);
+ }
+ return result;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see
+ * org.alfresco.service.cmr.rendition.RenditionService#getRenditions(org
+ * .alfresco.service.cmr.repository.NodeRef, java.lang.String)
+ */
+ public List getRenditions(NodeRef node, String mimeTypePrefix)
+ {
+ List allRenditions = this.getRenditions(node);
+ List filteredResults = new ArrayList();
+
+ for (ChildAssociationRef chAssRef : allRenditions)
+ {
+ NodeRef renditionNode = chAssRef.getChildRef();
+
+ QName contentProperty = ContentModel.PROP_CONTENT;
+ Serializable contentPropertyName = nodeService.getProperty(renditionNode,
+ ContentModel.PROP_CONTENT_PROPERTY_NAME);
+ if (contentPropertyName != null)
+ {
+ contentProperty = (QName) contentPropertyName;
+ }
+
+ ContentReader reader = contentService.getReader(renditionNode, contentProperty);
+ if (reader != null && reader.exists())
+ {
+ String readerMimeType = reader.getMimetype();
+ if (readerMimeType.startsWith(mimeTypePrefix))
+ {
+ filteredResults.add(chAssRef);
+ }
+
+ }
+ }
+ return filteredResults;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#getRenditionByName(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName)
+ */
+ public ChildAssociationRef getRenditionByName(NodeRef node, QName renditionName)
+ {
+ List renditions = new ArrayList();
+
+ // Check that the node has the renditioned aspect applied
+ if (nodeService.hasAspect(node, RenditionModel.ASPECT_RENDITIONED) == true)
+ {
+ // Get all the renditions that match the given rendition name -
+ // there should only be 1 (or 0)
+ renditions = this.nodeService.getChildAssocs(node, RenditionModel.ASSOC_RENDITION, renditionName);
+ }
+ if (renditions.isEmpty())
+ {
+ return null;
+ }
+ else
+ {
+ return renditions.get(0);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#isRendition(org.alfresco.service.cmr.repository.NodeRef)
+ */
+ public boolean isRendition(NodeRef node)
+ {
+ final QName aspectToCheckFor = RenditionModel.ASPECT_RENDITION;
+
+ Set existingAspects = nodeService.getAspects(node);
+ for (QName nextAspect : existingAspects)
+ {
+ if (nextAspect.equals(aspectToCheckFor) || dictionaryService.isSubClass(nextAspect, aspectToCheckFor))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.service.cmr.rendition.RenditionService#getSourceNode(org.alfresco.service.cmr.repository.NodeRef)
+ */
+ public ChildAssociationRef getSourceNode(NodeRef renditionNode)
+ {
+ // In normal circumstances only a node which is itself a rendition can have
+ // a source node - as linked by the rn:rendition association.
+ //
+ // However there are some circumstances where a node which is not
+ // technically a rendition can still have a source. One such example is the case
+ // of thumbnail nodes created in a pre-3.3 Alfresco which have not been patched
+ // to have the correct rendition aspect applied.
+ // This will also occur *during* execution of the webscript patch and so the
+ // decision was made not to throw an exception or log a warning if such a
+ // situation is encountered.
+
+ // A rendition node should have 1 and only 1 source node.
+ List parents = nodeService.getParentAssocs(renditionNode,
+ RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL);
+ if (parents.size() > 1)
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("NodeRef ")
+ .append(renditionNode)
+ .append(" unexpectedly has ")
+ .append(parents.size())
+ .append(" rendition parents.");
+ if (log.isWarnEnabled())
+ {
+ log.warn(msg.toString());
+ }
+ throw new RenditionServiceException(msg.toString());
+ }
+ else
+ {
+ return parents.isEmpty() ? null : parents.get(0);
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java
new file mode 100644
index 0000000000..6327395e9c
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2005-2010 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.alfresco.repo.action.ActionDefinitionImpl;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.action.ActionDefinition;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.rendition.RenderingEngineDefinition;
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * @author Nick Smith
+ */
+public class RenditionServiceImplTest extends TestCase
+{
+ private final static String ENGINE_NAME = "Engine Name";
+
+ private ServiceRegistry serviceRegistry = new MockedTestServiceRegistry();
+ private ActionService actionService = mock(ActionService.class);
+
+ private final RenditionDefinitionPersisterImpl renditionDefinitionPersister = mock(RenditionDefinitionPersisterImpl.class);
+ private RenditionServiceImpl renditionService;
+
+ private final QName ACTION_NAME = QName.createQName(NamespaceService.ALFRESCO_URI, "testName");
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ renditionService = new RenditionServiceImpl();
+ renditionService.setServiceRegistry(serviceRegistry);
+ renditionService.setActionService(actionService);
+ renditionService.setRenditionDefinitionPersister(renditionDefinitionPersister);
+ }
+
+ public void testGetRenderingEngineDefinition() throws Exception
+ {
+ // Check returns null when unknown name specified.
+ assertNull(renditionService.getRenderingEngineDefinition(""));
+
+ // Check returns null if action service returns an ActionDefinition
+ // which does not implement RenderingActionDefinition.
+ ActionDefinition actionDefinition = new ActionDefinitionImpl(ENGINE_NAME);
+ when(actionService.getActionDefinition(ENGINE_NAME)).thenReturn(actionDefinition);
+ assertNull(renditionService.getRenderingEngineDefinition(ENGINE_NAME));
+
+ // Check returns the definition if the action service returns an
+ // ActionDefinition
+ // which does implement RenderingActionDefinition.
+ ActionDefinition renderingDefinition = new RenderingEngineDefinitionImpl(ENGINE_NAME);
+ when(actionService.getActionDefinition(ENGINE_NAME)).thenReturn(renderingDefinition);
+ assertSame(renderingDefinition, renditionService.getRenderingEngineDefinition(ENGINE_NAME));
+ }
+
+ public void testGetRenderingEngineDefinitions() throws Exception
+ {
+ LinkedList actionDefs = new LinkedList();
+ when(actionService.getActionDefinitions()).thenReturn(actionDefs);
+
+ // Check case where no action definitions returned.
+ List engineDefs = renditionService.getRenderingEngineDefinitions();
+ assertTrue("The list of rendering action definitions should be empty!", engineDefs.isEmpty());
+
+ // Check that when the action service returns a rendering engine
+ // definition then the rendering service includes this in the list of
+ // returned values.
+ ActionDefinition renderingDefinition = new RenderingEngineDefinitionImpl(ENGINE_NAME);
+ actionDefs.add(renderingDefinition);
+ engineDefs = renditionService.getRenderingEngineDefinitions();
+ assertEquals(1, engineDefs.size());
+ assertSame(renderingDefinition, engineDefs.get(0));
+
+ // Check that when the action service returns a non-rendering action
+ // definition then the rendering service does not include it.
+ ActionDefinition actionDefinition = new ActionDefinitionImpl(ENGINE_NAME);
+ actionDefs.add(actionDefinition);
+ engineDefs = renditionService.getRenderingEngineDefinitions();
+ assertEquals(1, engineDefs.size());
+ assertSame(renderingDefinition, engineDefs.get(0));
+ }
+
+ public void testCreateRenditionDefinition() throws Exception
+ {
+ RenditionDefinition renderingAction = renditionService.createRenditionDefinition(ACTION_NAME, ENGINE_NAME);
+ assertNotNull(renderingAction);
+ assertEquals(ENGINE_NAME, renderingAction.getActionDefinitionName());
+ assertEquals(ACTION_NAME, renderingAction.getRenditionName());
+ String id = renderingAction.getId();
+ assertNotNull(id);
+ assertTrue(id.length() > 0);
+ }
+
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java
new file mode 100644
index 0000000000..3aece0840a
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java
@@ -0,0 +1,1753 @@
+/*
+ * Copyright (C) 2005-2010 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.awt.image.BufferedImage;
+import java.io.File;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.imageio.ImageIO;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.RenditionModel;
+import org.alfresco.repo.action.executer.ExporterActionExecuter;
+import org.alfresco.repo.content.MimetypeMap;
+import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
+import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
+import org.alfresco.repo.model.Repository;
+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.rendition.executer.TemplatingRenderingEngine;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.service.cmr.action.Action;
+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.RenditionService;
+import org.alfresco.service.cmr.rendition.RenditionServiceException;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.ContentData;
+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.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.service.namespace.RegexQNamePattern;
+import org.alfresco.util.BaseAlfrescoSpringTest;
+import org.springframework.extensions.surf.util.Pair;
+
+/**
+ * @author Neil McErlean
+ * @author Nick Smith
+ * @since 3.3
+ */
+@SuppressWarnings("deprecation")
+public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
+{
+ private final static QName REFORMAT_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
+ ReformatRenderingEngine.NAME + System.currentTimeMillis());
+ private final static QName RESCALE_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
+ ImageRenderingEngine.NAME + System.currentTimeMillis());
+ private final static String QUICK_CONTENT = "The quick brown fox jumps over the lazy dog";
+ private final static String FM_TEMPLATE = "/org/alfresco/repo/rendition/renditionTestTemplate.ftl";
+
+ private NodeRef nodeWithDocContent;
+ private NodeRef nodeWithImageContent;
+ private NodeRef nodeWithFreeMarkerContent;
+ private NodeRef testTargetFolder;
+
+ private NodeRef renditionNode = null;
+
+ private RenditionService renditionService;
+ private Repository repositoryHelper;
+ private RetryingTransactionHelper transactionHelper;
+ private NamespaceService namespaceService;
+
+ @Override
+ protected void onSetUpInTransaction() throws Exception
+ {
+ super.onSetUpInTransaction();
+ this.renditionService = (RenditionService) this.applicationContext.getBean("renditionService");
+ this.repositoryHelper = (Repository) this.applicationContext.getBean("repositoryHelper");
+ this.namespaceService= (NamespaceService) this.applicationContext.getBean("namespaceService");
+ this.transactionHelper = (RetryingTransactionHelper) this.applicationContext
+ .getBean("retryingTransactionHelper");
+
+ // Set the current security context as admin
+ AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
+
+ NodeRef companyHome = this.repositoryHelper.getCompanyHome();
+
+ // Create the test folder used for these tests
+ this.testTargetFolder = createNode(companyHome, "testFolder", ContentModel.TYPE_FOLDER);
+
+ // Create the node used as a content supplier for tests
+ this.nodeWithDocContent = createContentNode(companyHome, "testDocContent");
+
+ // Put some known PDF content in it.
+ File pdfQuickFile = AbstractContentTransformerTest.loadQuickTestFile("pdf");
+ assertNotNull("Failed to load required test file.", pdfQuickFile);
+
+ nodeService.setProperty(nodeWithDocContent, ContentModel.PROP_CONTENT, new ContentData(null,
+ MimetypeMap.MIMETYPE_PDF, 0L, null));
+ ContentWriter writer = contentService.getWriter(nodeWithDocContent, ContentModel.PROP_CONTENT, true);
+ writer.setMimetype(MimetypeMap.MIMETYPE_PDF);
+ writer.setEncoding("UTF-8");
+ writer.putContent(pdfQuickFile);
+
+ // Put the titled aspect on it - used for testing rendition updates
+ Map titledProps = new HashMap();
+ titledProps.put(ContentModel.PROP_TITLE, "Original test title");
+ titledProps.put(ContentModel.PROP_DESCRIPTION, "Dummy description");
+ nodeService.addAspect(nodeWithDocContent, ContentModel.ASPECT_TITLED, titledProps);
+
+
+ // Create a test image
+ this.nodeWithImageContent = createContentNode(companyHome, "testImageNode");
+ // Stream some well-known image content into the node.
+ URL url = RenditionServiceIntegrationTest.class.getClassLoader().getResource("images/gray21.512.png");
+ assertNotNull("url of test image was null", url);
+ File imageFile = new File(url.getFile());
+ assertTrue(imageFile.exists());
+
+ nodeService.setProperty(nodeWithImageContent, ContentModel.PROP_CONTENT, new ContentData(null,
+ MimetypeMap.MIMETYPE_IMAGE_PNG, 0L, null));
+ writer = contentService.getWriter(nodeWithImageContent, ContentModel.PROP_CONTENT, true);
+ writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG);
+ writer.setEncoding("UTF-8");
+ writer.putContent(imageFile);
+
+ // Create a test template node.
+ this.nodeWithFreeMarkerContent = createFreeMarkerNode(companyHome);
+ }
+
+ private NodeRef createContentNode(NodeRef companyHome, String name)
+ {
+ return createNode(companyHome, name, ContentModel.TYPE_CONTENT);
+ }
+
+ private NodeRef createNode(NodeRef companyHome, String name, QName type)
+ {
+ Map props = new HashMap();
+ String fullName = name + System.currentTimeMillis();
+ props.put(ContentModel.PROP_NAME, fullName);
+ QName docContentQName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, fullName);
+ NodeRef node = nodeService.createNode(companyHome,
+ ContentModel.ASSOC_CONTAINS,
+ docContentQName,
+ type,
+ props)
+ .getChildRef();
+ return node;
+ }
+
+ private NodeRef createFreeMarkerNode(NodeRef companyHome)
+ {
+ NodeRef fmNode = createContentNode(companyHome, "testFreeMarkerNode");
+ nodeService.setProperty(fmNode, ContentModel.PROP_CONTENT, new ContentData(null,
+ MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null));
+
+ URL url = getClass().getResource(FM_TEMPLATE);
+ assertNotNull("The url is null", url);
+ File templateFile = new File(url.getFile());
+ assertTrue("The template file does not exist", templateFile.exists());
+
+ ContentWriter fmWriter = contentService.getWriter(fmNode, ContentModel.PROP_CONTENT, true);
+ fmWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ fmWriter.setEncoding("UTF-8");
+ fmWriter.putContent(templateFile);
+ return fmNode;
+ }
+
+ @Override
+ protected void onTearDownInTransaction() throws Exception
+ {
+ nodeService.deleteNode(nodeWithImageContent);
+ nodeService.deleteNode(nodeWithDocContent);
+ nodeService.deleteNode(nodeWithFreeMarkerContent);
+ nodeService.deleteNode(testTargetFolder);
+ }
+
+ public void testRenderFreeMarkerTemplate() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+ final QName renditionName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI,
+ TemplatingRenderingEngine.NAME);
+
+ this.renditionNode = transactionHelper
+ .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ // create test model
+ RenditionDefinition definition = renditionService.createRenditionDefinition(renditionName,
+ TemplatingRenderingEngine.NAME);
+ definition.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_NODE,
+ nodeWithFreeMarkerContent);
+ ChildAssociationRef renditionAssoc = renditionService
+ .render(nodeWithDocContent, definition);
+ assertNotNull("The rendition association was null", renditionAssoc);
+ return renditionAssoc.getChildRef();
+ }
+ });
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ String output = readTextContent(renditionNode);
+ assertNotNull("The rendition content was null.", output);
+ // check the output contains root node Id as expected.
+ assertTrue(output.contains(nodeWithDocContent.getId()));
+ return null;
+ }
+ });
+ }
+
+ public void testRenderFreemarkerTemplatePath() throws Exception
+ {
+ //TODO displayName paths.
+ this.setComplete();
+ this.endTransaction();
+ final QName renditionName1 = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI,
+ TemplatingRenderingEngine.NAME + "_UpdateOnAnyPropChange");
+ final QName renditionName2 = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI,
+ TemplatingRenderingEngine.NAME + "_UpdateOnContentPropChange");
+
+ final Pair renditions = transactionHelper
+ .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ ChildAssociationRef parentAssoc = nodeService.getPrimaryParent(nodeWithFreeMarkerContent);
+ QName assocName = parentAssoc.getQName();
+ String templatePath="/app:company_home/"+assocName.toPrefixString(namespaceService);
+
+ // create test model 1 - rendition to update on any property change
+ RenditionDefinition definition1 = renditionService.createRenditionDefinition(renditionName1,
+ TemplatingRenderingEngine.NAME);
+ definition1.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_PATH,
+ templatePath);
+ definition1.setParameterValue(AbstractRenderingEngine.PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, Boolean.TRUE);
+
+ // create test model 2 - rendition to update on content property change
+ RenditionDefinition definition2 = renditionService.createRenditionDefinition(renditionName2,
+ TemplatingRenderingEngine.NAME);
+ definition2.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_PATH,
+ templatePath);
+ definition2.setParameterValue(AbstractRenderingEngine.PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, Boolean.FALSE);
+
+ // We need to save these renditions in order to have them eligible
+ // for automatic update.
+ if (null == renditionService.loadRenditionDefinition(renditionName1))
+ {
+ renditionService.saveRenditionDefinition(definition1);
+ }
+ if (null == renditionService.loadRenditionDefinition(renditionName2))
+ {
+ renditionService.saveRenditionDefinition(definition2);
+ }
+
+ ChildAssociationRef renditionAssoc1 = renditionService
+ .render(nodeWithDocContent, definition1);
+ assertNotNull("The rendition association was null", renditionAssoc1);
+
+ ChildAssociationRef renditionAssoc2 = renditionService
+ .render(nodeWithDocContent, definition2);
+ assertNotNull("The rendition association was null", renditionAssoc2);
+
+ Pair result = new Pair(renditionAssoc1.getChildRef(), renditionAssoc2.getChildRef());
+ return result;
+ }
+ });
+
+ final String titleInitialValue = "Original test title";
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ assertEquals("The source node should have title " + titleInitialValue, titleInitialValue,
+ nodeService.getProperty(nodeWithDocContent, ContentModel.PROP_TITLE));
+
+ String output1 = readTextContent(renditions.getFirst());
+ assertNotNull("The rendition content was null.", output1);
+
+ assertRenditionContainsTitle(titleInitialValue,
+ output1);
+
+ // check the output contains root node Id as expected.
+ assertTrue(output1.contains(nodeWithDocContent.getId()));
+
+ String output2 = readTextContent(renditions.getSecond());
+ assertNotNull("The rendition content was null.", output2);
+
+ assertRenditionContainsTitle(titleInitialValue,
+ output2);
+
+ return null;
+ }
+ });
+
+ // Now change some properties on the source node and ensure that the renditions
+ // are updated appropriately
+ final String updatedTitle = "updatedTitle";
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ nodeService.setProperty(nodeWithDocContent, ContentModel.PROP_TITLE, updatedTitle);
+ return null;
+ }
+ });
+ // Sleep to let the asynchronous action queue perform the updates to the renditions.
+ // TODO Is there a better way?
+ Thread.sleep(20000);
+
+ // Get the renditions and check their content for the new title
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ String output1 = readTextContent(renditions.getFirst());
+ assertNotNull("The rendition content was null.", output1);
+
+ assertRenditionContainsTitle(updatedTitle, output1);
+
+ String output2 = readTextContent(renditions.getSecond());
+ assertNotNull("The rendition content was null.", output2);
+
+ assertRenditionContainsTitle(titleInitialValue, output2);
+
+ return null;
+ }
+ });
+ }
+
+ private void assertRenditionContainsTitle(final String titleValue, String output)
+ {
+ final String titleMarker = "TestTitle=";
+ final String beforeAfterMarker = "xxx";
+ int indexOfTitleMarker = output.indexOf(titleMarker);
+ int indexOfStartMarker = output.indexOf(beforeAfterMarker, indexOfTitleMarker);
+ int indexOfEndMarker = output.indexOf(beforeAfterMarker, indexOfStartMarker + beforeAfterMarker.length());
+ final String titleAsTakenFromRendition = output.substring(indexOfStartMarker + beforeAfterMarker.length(), indexOfEndMarker);
+
+ assertEquals("The rendition should contain title " + titleValue, titleValue, titleAsTakenFromRendition);
+ }
+
+ /**
+ * This test method uses the RenditionService to render a test document (of
+ * type PDF) into a different format (of type plain_text) and place the
+ * rendition under the source node.
+ */
+ public void testRenderDocumentInAnotherFormatInSitu() throws Exception
+ {
+ this.setComplete();
+ this.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(
+ nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED));
+ // and no renditions
+ assertTrue("Renditions should have been empty", renditionService.getRenditions(
+ nodeWithDocContent).isEmpty());
+ assertNull("Renditions should have been null", renditionService.getRenditionByName(
+ nodeWithDocContent, REFORMAT_RENDER_DEFN_NAME));
+
+ validateRenderingActionDefinition(ReformatRenderingEngine.NAME);
+
+ RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN);
+
+ // Render the content and put the result underneath
+ // the content node
+ ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, action);
+ NodeRef rendition = renditionAssoc.getChildRef();
+ assertEquals("The parent node was not correct", nodeWithDocContent, renditionAssoc
+ .getParentRef());
+ validateRenditionAssociation(renditionAssoc, REFORMAT_RENDER_DEFN_NAME);
+
+ // The rendition node should have no other
+ // parent-associations - in this case
+ assertEquals("Wrong value for rendition node parent count.",
+ 1, nodeService.getParentAssocs(rendition).size());
+
+ // Now the source content node should have the
+ // renditioned aspect
+ assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect(
+ nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED));
+ // and one rendition
+
+ assertEquals("Renditions size wrong", 1, renditionService.getRenditions(nodeWithDocContent)
+ .size());
+ assertNotNull("Renditions should not have been null", renditionService.getRenditionByName(
+ nodeWithDocContent, REFORMAT_RENDER_DEFN_NAME));
+
+ return rendition;
+ }
+ });
+
+ // Now in a separate transaction, we'll check that the reformatted
+ // content is actually there.
+ assertNotNull("The rendition node was null.", renditionNode);
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ String contentAsString = readTextContent(renditionNode);
+ assertTrue("Wrong content in rendition", contentAsString.contains(QUICK_CONTENT));
+ return null;
+ }
+ });
+ }
+
+ /**
+ * This test method uses the RenditionService to render a test document (of
+ * type PDF) into a different format (of type
+ * application/x-shockwave-flash).
+ */
+ public void testRenderPdfDocumentToFlash() throws Exception
+ {
+ this.setComplete();
+ this.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(
+ nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED));
+
+ validateRenderingActionDefinition(ReformatRenderingEngine.NAME);
+
+ RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_FLASH);
+ action.setParameterValue(ReformatRenderingEngine.PARAM_FLASH_VERSION, "9");
+
+ // Render the content and put the result underneath
+ // the content node
+ ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, action);
+
+ assertEquals("The parent node was not correct", nodeWithDocContent, renditionAssoc
+ .getParentRef());
+ validateRenditionAssociation(renditionAssoc, REFORMAT_RENDER_DEFN_NAME);
+
+ // The rendition node should have no other
+ // parent-associations - in this case
+ assertEquals("Wrong value for rendition node parent count.", 1, nodeService
+ .getParentAssocs(renditionNode).size());
+
+ // Now the source content node should have the
+ // renditioned aspect
+ assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect(
+ nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED));
+ return renditionNode;
+ }
+ });
+
+ // Now in a separate transaction, we'll check that the reformatted
+ // content is actually there.
+ assertNotNull("The rendition node was null.", renditionNode);
+ }
+
+ public void testCompositeReformatAndResizeRendition() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+
+ final QName renditionName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "composite");
+ final int newX = 20;
+ final int newY = 30;
+
+ renditionNode = transactionHelper.doInTransaction(new RetryingTransactionHelper.//
+ RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ CompositeRenditionDefinition compositeDefinition = makeCompositeReformatAndResizeDefinition(
+ renditionName, newX, newY);
+ ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent,
+ compositeDefinition);
+ validateRenditionAssociation(renditionAssoc, renditionName);
+ return renditionAssoc.getChildRef();
+ }
+
+ });
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.//
+ RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ List renditions = renditionService.getRenditions(nodeWithDocContent);
+ assertEquals("There should only be one rendition", 1, renditions.size());
+
+ ChildAssociationRef renditionAssoc = renditions.get(0);
+ assertEquals("The association name should match the composite rendition name",
+ renditionName, renditionAssoc.getQName());
+
+ NodeRef rendition = renditionAssoc.getChildRef();
+ ContentReader reader = contentService.getReader(rendition, ContentModel.PROP_CONTENT);
+ assertEquals("The mimetype is wrong", MimetypeMap.MIMETYPE_IMAGE_JPEG, reader.getMimetype());
+
+ assertNotNull("Reader to rendered image was null", reader);
+ BufferedImage img = ImageIO.read(reader.getContentInputStream());
+
+ assertEquals("Rendered image had wrong height", newY, img.getHeight());
+ assertEquals("Rendered image had wrong width", newX, img.getWidth());
+ return null;
+ }
+ });
+
+ }
+
+ /**
+ * This test method used the RenditionService to render a test document (of
+ * type PDF) into a different format (of type plain_text) and place the
+ * rendition under the specified folder.
+ */
+ public void testRenderDocumentInAnotherFormatUnderSpecifiedFolder() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void 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(nodeWithDocContent,
+ RenditionModel.ASPECT_RENDITIONED));
+
+ validateRenderingActionDefinition(ReformatRenderingEngine.NAME);
+
+ // Create the rendering action.
+ RenditionDefinition definition = makeReformatAction(ContentModel.TYPE_CONTENT,
+ MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ Serializable targetFolderName = nodeService.getProperty(testTargetFolder, ContentModel.PROP_NAME);
+ String path = "${companyHome}/" + targetFolderName +"/test.txt";
+ definition.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, path);
+
+ // Perform the action with an explicit destination folder
+ ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, definition);
+ NodeRef rendition = renditionAssoc.getChildRef();
+ // A secondary parent
+ assertEquals("The parent node was not correct", nodeWithDocContent, renditionAssoc.getParentRef());
+
+ validateRenditionAssociation(renditionAssoc, REFORMAT_RENDER_DEFN_NAME);
+
+ // The rendition node should have 2 parents: the containing
+ // folder and the source node
+ List parentAssocs = nodeService.getParentAssocs(rendition);
+ assertEquals("Wrong value for rendition node parent count.", 2, parentAssocs.size());
+
+ // Check the parent nodeRefs are correct
+ List parents = new ArrayList(2);
+ parents.add(parentAssocs.get(0).getParentRef());
+ parents.add(parentAssocs.get(1).getParentRef());
+ assertTrue("Missing containing folder as parent", parents.contains(testTargetFolder));
+ assertTrue("Missing source node as parent", parents.contains(nodeWithDocContent));
+
+ // Now the source content node should have the rn:renditioned
+ // aspect
+ assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect(nodeWithDocContent,
+ RenditionModel.ASPECT_RENDITIONED));
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * This test method used the RenditionService to render a test image (of
+ * type PNG) as a cropped image of the same type.
+ */
+ @SuppressWarnings("unused")
+ public void testRenderCropImage() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+
+ final int originalImageWidth = 512;
+ final int originalImageHeight = 512;
+
+ // Create a rendition of an existing image with specified absolute x and
+ // y scale.
+ final int imageNewXSize = 36;
+ final int imageNewYSize = 47;
+ final Map parameterValues = new HashMap();
+ parameterValues.put(ImageRenderingEngine.PARAM_CROP_WIDTH, imageNewXSize);
+ parameterValues.put(ImageRenderingEngine.PARAM_CROP_HEIGHT, imageNewYSize);
+
+ ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions();
+ final NodeRef newRenditionNode = performImageRendition(parameterValues);
+
+ // Assert that the rendition is of the correct size and has reasonable
+ // content.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+
+ // The rescaled image rendition is a child of the original test
+ // node.
+ List children = nodeService.getChildAssocs(nodeWithImageContent,
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)),
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME)));
+
+ // There should only be one child of the image node: the
+ // rendition we've just created.
+ assertEquals("Unexpected number of children", 1, children.size());
+
+ NodeRef newImageRendition = children.get(0).getChildRef();
+ assertEquals(newRenditionNode, newImageRendition);
+
+ ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT);
+ assertNotNull("Reader to rendered image was null", reader);
+ BufferedImage img = ImageIO.read(reader.getContentInputStream());
+
+ assertEquals("Rendered image had wrong height", imageNewYSize, img.getHeight());
+ assertEquals("Rendered image had wrong width", imageNewXSize, img.getWidth());
+
+
+ ContentReader srcReader = contentService.getReader(nodeWithImageContent, ContentModel.PROP_CONTENT);
+ BufferedImage srcImg = ImageIO.read(srcReader.getContentInputStream());
+
+ // The upper left pixel of the image should be pure black.
+ int rgbAtTopLeft = img.getRGB(1, 1);
+ int expRgbAtTopLeft = img.getRGB(1, 1);
+ assertEquals("Incorrect image content.", expRgbAtTopLeft, rgbAtTopLeft);
+
+ // The lower right pixel of the image should be pure white
+ int rightIndex = img.getWidth() - 1;
+ int bottomIndex = img.getHeight() - 1;
+ int rgbAtBottomRight = img.getRGB(rightIndex, bottomIndex);
+ int expRgbAtBottomRight = srcImg.getRGB(rightIndex, bottomIndex);
+ assertEquals("Incorrect image content.", expRgbAtBottomRight, rgbAtBottomRight);
+
+ return null;
+ }
+ });
+
+ // Create a rendition of the same image, this time cropping by 50/25%
+ parameterValues.clear();
+ parameterValues.put(ImageRenderingEngine.PARAM_CROP_WIDTH, 50); // 256 picels
+ parameterValues.put(ImageRenderingEngine.PARAM_CROP_HEIGHT, 25); // 128 pixels
+ parameterValues.put(ImageRenderingEngine.PARAM_IS_PERCENT_CROP, true);
+
+ final NodeRef secondRenditionNode = performImageRendition(parameterValues);
+
+ // Assert that the rendition is of the correct size and has reasonable
+ // content.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ // The rescaled image rendition is a child of the original test
+ // node.
+ List children = nodeService.getChildAssocs(nodeWithImageContent,
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)),
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME)));
+
+ // There should only be one child of the image node: the
+ // rendition we've just created.
+ assertEquals("Unexpected number of children", 1, children.size());
+
+ NodeRef newImageRendition = children.get(0).getChildRef();
+ assertEquals(secondRenditionNode, newImageRendition);
+
+ ContentReader srcReader = contentService.getReader(nodeWithImageContent, ContentModel.PROP_CONTENT);
+ BufferedImage srcImg = ImageIO.read(srcReader.getContentInputStream());
+
+ ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT);
+ assertNotNull("Reader to rendered image was null", reader);
+ BufferedImage img = ImageIO.read(reader.getContentInputStream());
+
+ assertEquals("Rendered image had wrong height", 128, img.getHeight());
+ assertEquals("Rendered image had wrong width", 256, img.getWidth());
+
+ // The upper left pixel of the image should be pure black.
+ int rgbAtTopLeft = img.getRGB(1, 1);
+ int expRgbAtTopLeft = srcImg.getRGB(1, 1);
+ assertEquals("Incorrect image content.", expRgbAtTopLeft, rgbAtTopLeft);
+
+ // The lower right pixel of the image should be pure white
+ int widthIndex = img.getWidth() - 1;
+ int heightIndex = img.getHeight() - 1;
+ int rgbAtBottomRight = img.getRGB(widthIndex, heightIndex);
+ int expRgbAtBottomRight = srcImg.getRGB(widthIndex, heightIndex);
+ assertEquals("Incorrect image content.", expRgbAtBottomRight, rgbAtBottomRight);
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * This test method used the RenditionService to render a test image (of
+ * type PNG) as a rescaled image of the same type.
+ */
+ public void testRenderRescaledImage() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+
+ final int originalImageWidth = 512;
+ final int originalImageHeight = 512;
+
+ // Create a rendition of an existing image with specified absolute x and
+ // y scale.
+ final Integer imageNewXSize = new Integer(36);
+ final Integer imageNewYSize = new Integer(48);
+ final Map parameterValues = new HashMap();
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, imageNewXSize);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, imageNewYSize);
+
+ final NodeRef newRenditionNode = performImageRendition(parameterValues);
+
+ // Assert that the rendition is of the correct size and has reasonable
+ // content.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ // The rescaled image rendition is a child of the original test
+ // node.
+ List children = nodeService.getChildAssocs(nodeWithImageContent,
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)),
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME)));
+
+ // There should only be one child of the image node: the
+ // rendition we've just created.
+ assertEquals("Unexpected number of children", 1, children.size());
+
+ NodeRef newImageRendition = children.get(0).getChildRef();
+ assertEquals(newRenditionNode, newImageRendition);
+
+ ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT);
+ assertNotNull("Reader to rendered image was null", reader);
+ BufferedImage img = ImageIO.read(reader.getContentInputStream());
+
+ assertEquals("Rendered image had wrong height", imageNewYSize, new Integer(img.getHeight()));
+ assertEquals("Rendered image had wrong width", imageNewXSize, new Integer(img.getWidth()));
+
+ // The upper left pixel of the image should be pure black.
+ int rgbAtTopLeft = img.getRGB(1, 1);
+ assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000"));
+
+ // The lower right pixel of the image should be pure white
+ int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
+ assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff"));
+
+ return null;
+ }
+ });
+
+ // Create a rendition of the same image, this time rescaling by 200%
+ parameterValues.clear();
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 200);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 200);
+ parameterValues.put(ImageRenderingEngine.PARAM_IS_PERCENT_RESIZE, true);
+
+ final NodeRef secondRenditionNode = performImageRendition(parameterValues);
+
+ // Assert that the rendition is of the correct size and has reasonable
+ // content.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ // The rescaled image rendition is a child of the original test
+ // node.
+ List children = nodeService.getChildAssocs(nodeWithImageContent,
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)),
+ new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME)));
+
+ // There should only be one child of the image node: the
+ // rendition we've just created.
+ assertEquals("Unexpected number of children", 1, children.size());
+
+ NodeRef newImageRendition = children.get(0).getChildRef();
+ assertEquals(secondRenditionNode, newImageRendition);
+
+ ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT);
+ assertNotNull("Reader to rendered image was null", reader);
+ BufferedImage img = ImageIO.read(reader.getContentInputStream());
+
+ assertEquals("Rendered image had wrong height", originalImageWidth * 2, img.getHeight());
+ assertEquals("Rendered image had wrong width", originalImageHeight * 2, img.getWidth());
+
+ // The upper left pixel of the image should be pure black.
+ int rgbAtTopLeft = img.getRGB(1, 1);
+ assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000"));
+
+ // The lower right pixel of the image should be pure white
+ int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
+ assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff"));
+
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Tests that the ReformatActionExecutor can be used to render images into
+ * different formats.
+ *
+ * @throws Exception
+ */
+ public void testReformatImage() 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));
+
+ RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN);
+
+ // Set output Mimetype to JPEG.
+ action.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE,
+ MimetypeMap.MIMETYPE_IMAGE_JPEG);
+
+ ChildAssociationRef renditionAssoc = renditionService.render(nodeWithImageContent, action);
+ return renditionAssoc.getChildRef();
+ }
+ });
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ ContentReader reader = contentService.getReader(renditionNode, ContentModel.PROP_CONTENT);
+ assertNotNull("Reader to rendered image was null", reader);
+ assertEquals(MimetypeMap.MIMETYPE_IMAGE_JPEG, reader.getMimetype());
+
+ BufferedImage img = ImageIO.read(reader.getContentInputStream());
+
+ assertEquals("Rendered image had wrong height", 512, img.getHeight());
+ assertEquals("Rendered image had wrong width", 512, img.getWidth());
+
+ // The upper left pixel of the image should be pure black.
+ int rgbAtTopLeft = img.getRGB(1, 1);
+ assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000"));
+
+ // The lower right pixel of the image should be pure white
+ int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1);
+ assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff"));
+
+ return null;
+ }
+ });
+ }
+
+ public void testSuccessfulAsynchronousRendition() throws Exception
+ {
+ // There are two relevant threads here: the JUnit test thread and the background
+ // asynchronousActionExecution thread. It is this second thread that will do
+ // the rendering work and I want to make sure that any failures on that thread
+ // are returned to the JUnit thread in order to fail the test.
+ // I also need to ensure that the asynchronous rendering is complete before
+ // asserting anything about the results.
+ // The countdown latch below is the mechanism by which the JUnit test code
+ // waits for the completion of the other thread.
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AsyncResultsHolder results = new AsyncResultsHolder();
+ final RenderCallback callback = new RenderCallback()
+ {
+ public void handleFailedRendition(Throwable t)
+ {
+ results.setMessage("Rendition failed unexpectedly.");
+ latch.countDown();
+ }
+
+ public void handleSuccessfulRendition(
+ ChildAssociationRef primaryParentOfNewRendition)
+ {
+ results.setAssoc(primaryParentOfNewRendition);
+ latch.countDown();
+ }
+ };
+
+ // We're performing this on a valid piece of content.
+ // We expect this to succeed.
+ performAsyncRendition(nodeWithImageContent, callback, latch, results);
+
+ assertNotNull("ChildAssociationRef was null.", results.getAssoc());
+ // We'll simply assert that the association has the correct parent.
+ assertEquals(nodeWithImageContent, results.getAssoc().getParentRef());
+ assertNull(results.getThrowable());
+ }
+
+
+ /**
+ *
+ * @throws Exception
+ * @see {@link #testSuccessfulAsynchronousRendition()}
+ */
+ public void testFailedAsynchronousRendition() throws Exception
+ {
+ // see comment in method above for explanation of the countdown latch.
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AsyncResultsHolder results = new AsyncResultsHolder();
+ final RenderCallback callback = new RenderCallback()
+ {
+ public void handleFailedRendition(Throwable t)
+ {
+ results.setThrowable(t);
+ latch.countDown();
+ }
+
+ public void handleSuccessfulRendition(
+ ChildAssociationRef primaryParentOfNewRendition)
+ {
+ results.setMessage("Rendition succeeded unexpectedly.");
+ latch.countDown();
+ }
+ };
+
+ // We're performing the render on an invalid node. We expect this to fail.
+ performAsyncRendition(testTargetFolder, callback, latch, results);
+
+ assertNull(results.getAssoc());
+ assertEquals("Expected a RenditionServiceException", RenditionServiceException.class, results.getThrowable().getClass());
+ }
+
+ /**
+ * This method performs an asynchronous rendition and calls back the result to the
+ * provided callback object. It uses the provided latch to coordinate the JUnit and
+ * the asynchronous action thread and uses the provided MutableString to send back
+ * any error messages for JUnit reporting.
+ *
+ * This method blocks until the action service thread either completes its work
+ * or times out.
+ *
+ * @param callback
+ * @param latch
+ * @param failureMessage
+ * @param nodeToRender
+ * @throws InterruptedException
+ */
+ private void performAsyncRendition(final NodeRef nodeToRender, final RenderCallback callback, CountDownLatch latch,
+ final AsyncResultsHolder failureMessage) throws InterruptedException
+ {
+ setComplete();
+ endTransaction();
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN);
+
+ action.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG);
+
+ renditionService.render(nodeToRender, action, callback);
+ return null;
+ }
+ });
+
+ // Now wait for the actionService thread to complete the rendering.
+ // We'll arbitrarily timeout after 30 seconds, which should be plenty for the
+ // action to execute.
+ boolean endedNormally = latch.await(30, TimeUnit.SECONDS);
+
+ String failedString = failureMessage.getValue();
+ if (failedString != null)
+ {
+ fail(failedString);
+ }
+ if (endedNormally == false)
+ {
+ fail("ActionService thread took too long to perform rendering.");
+ }
+ }
+
+ /**
+ * A simple struct class to allow the return of failure messages, exception objects
+ * and ChildAssociationRef results from asynchronous calls.
+ */
+ private static class AsyncResultsHolder
+ {
+ private String message;
+ private ChildAssociationRef assoc;
+ private Throwable throwable;
+
+ public synchronized String getValue()
+ {
+ return message;
+ }
+ public synchronized void setMessage(String message)
+ {
+ this.message = message;
+ }
+ public synchronized ChildAssociationRef getAssoc()
+ {
+ return assoc;
+ }
+ public synchronized void setAssoc(ChildAssociationRef assoc)
+ {
+ this.assoc = assoc;
+ }
+ public synchronized Throwable getThrowable()
+ {
+ return throwable;
+ }
+ public synchronized void setThrowable(Throwable throwable)
+ {
+ this.throwable = throwable;
+ }
+ }
+
+ public void testGetRenditionsForNode() throws Exception
+ {
+ setComplete();
+ endTransaction();
+
+ this.renditionNode = transactionHelper
+ .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ // Initially the node that provides the content
+ // should have no renditions
+ assertTrue("Test node should have no renditions initially", renditionService.getRenditions(
+ nodeWithImageContent).isEmpty());
+
+ // Create 4 arbitrary rendition definitions
+ QName rendName1 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "definition1"
+ + System.currentTimeMillis());
+ RenditionDefinition action1 = renditionService.createRenditionDefinition(rendName1,
+ ReformatRenderingEngine.NAME);
+ action1.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE,
+ MimetypeMap.MIMETYPE_IMAGE_GIF);
+
+ QName rendName2 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "definition2"
+ + System.currentTimeMillis());
+ RenditionDefinition action2 = renditionService.createRenditionDefinition(rendName2,
+ ReformatRenderingEngine.NAME);
+ action2.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE,
+ MimetypeMap.MIMETYPE_IMAGE_JPEG);
+
+ QName rendName3 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "definition3"
+ + System.currentTimeMillis());
+ RenditionDefinition action3 = renditionService.createRenditionDefinition(rendName3,
+ ImageRenderingEngine.NAME);
+ action3.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 64);
+ action3.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 64);
+ action3.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE,
+ MimetypeMap.MIMETYPE_IMAGE_PNG);
+
+ // The 4th intentionally reuses the rendition name
+ // from the third
+ QName rendName4 = rendName3;
+ RenditionDefinition action4 = renditionService.createRenditionDefinition(rendName4,
+ ImageRenderingEngine.NAME);
+ action4.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 128);
+ action4.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 128);
+ action4.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE,
+ MimetypeMap.MIMETYPE_IMAGE_PNG);
+
+ // Execute the 4 renditions.
+ ChildAssociationRef createdRendition1 = renditionService.render(nodeWithImageContent,
+ action1);
+ ChildAssociationRef createdRendition2 = renditionService.render(nodeWithImageContent,
+ action2);
+ ChildAssociationRef createdRendition3 = renditionService.render(nodeWithImageContent,
+ action3);
+ ChildAssociationRef createdRendition4 = renditionService.render(nodeWithImageContent,
+ action4);
+
+ // Now validate the getRenditions methods
+ List allRenditions = renditionService
+ .getRenditions(nodeWithImageContent);
+ ChildAssociationRef retrievedRendition1 = renditionService.getRenditionByName(
+ nodeWithImageContent, rendName1);
+ ChildAssociationRef retrievedRendition2 = renditionService.getRenditionByName(
+ nodeWithImageContent, rendName2);
+ ChildAssociationRef retrievedRendition3 = renditionService.getRenditionByName(
+ nodeWithImageContent, rendName3);
+ ChildAssociationRef retrievedRendition4 = renditionService.getRenditionByName(
+ nodeWithImageContent, rendName4);
+
+ // allRenditions should contain only 3 renditions.
+ // The 4th should have replaced the 3rd.
+ assertEquals(3, allRenditions.size());
+ assertTrue(allRenditions.contains(createdRendition1));
+ assertTrue(allRenditions.contains(createdRendition2));
+ assertTrue(allRenditions.contains(createdRendition4));
+ for (ChildAssociationRef rendition : allRenditions)
+ {
+ assertNotSame(createdRendition3, rendition);
+ }
+
+ assertEquals(createdRendition1, retrievedRendition1);
+ assertEquals(createdRendition2, retrievedRendition2);
+ assertEquals(createdRendition4, retrievedRendition3);
+ assertEquals(createdRendition4, retrievedRendition4);
+
+ // CMIS-style filters. image/*
+ List imageRenditions = renditionService.getRenditions(
+ nodeWithImageContent, "image");
+ assertEquals(3, imageRenditions.size());
+
+ List imageSlashJRenditions = renditionService.getRenditions(
+ nodeWithImageContent, "image/j");
+ assertEquals(1, imageSlashJRenditions.size());
+
+ return null;
+ }
+ });
+ }
+
+ private NodeRef performImageRendition(final Map parameterValues)
+ {
+ final NodeRef newRenditionNode = transactionHelper
+ .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ validateRenderingActionDefinition(ImageRenderingEngine.NAME);
+
+ // Create the rendering action.
+ RenditionDefinition action = renditionService.createRenditionDefinition(
+ RESCALE_RENDER_DEFN_NAME, ImageRenderingEngine.NAME);
+
+ // Set the parameters. Can't call
+ // action.setParameterValues as we don't want
+ // to obliterate existing parameters such as the
+ // name
+ for (String s : parameterValues.keySet())
+ {
+ action.setParameterValue(s, parameterValues.get(s));
+ }
+
+ ChildAssociationRef renditionAssoc = renditionService.render(nodeWithImageContent, action);
+
+ validateRenditionAssociation(renditionAssoc, RESCALE_RENDER_DEFN_NAME);
+
+ return renditionAssoc.getChildRef();
+ }
+ });
+ return newRenditionNode;
+ }
+
+ /**
+ * Checks that the saveRenderingAction Method creates the proper node in the
+ * repository.
+ */
+ public void testSaveRenderingAction() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+
+ final List savedRenditionsToDelete = new ArrayList();
+ try
+ {
+ // Check that if no node exists already then a new node is created.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ assertTrue("The rendering action space has not been created in the repository!",//
+ nodeService.exists(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF));
+ List childAssocs = nodeService.getChildAssocs(//
+ RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,//
+ ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME);
+ assertTrue("There should be no persisted rendering actions of name: " + REFORMAT_RENDER_DEFN_NAME
+ + " at the start of this test!",//
+ childAssocs.isEmpty());
+ RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ savedRenditionsToDelete.add(action);
+
+ renditionService.saveRenditionDefinition(action);
+ List results = nodeService.getChildAssocs(//
+ RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,//
+ ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME);
+ assertEquals(
+ "There should be one persisted rendering action of name: " + REFORMAT_RENDER_DEFN_NAME,//
+ 1, results.size());
+ return null;
+ }
+ });
+
+ // Check that if a node already exists then that node is updated.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ List childAssocs = nodeService.getChildAssocs(//
+ RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,//
+ ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME);
+ assertEquals(
+ "There should be one persisted rendering action of name: " + REFORMAT_RENDER_DEFN_NAME,//
+ 1, childAssocs.size());
+ NodeRef actionNode = childAssocs.get(0).getChildRef();
+
+ RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN);
+ savedRenditionsToDelete.add(action);
+
+ renditionService.saveRenditionDefinition(action);
+ List results = nodeService.getChildAssocs(//
+ RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,//
+ ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME);
+ assertEquals(
+ "There should be one persisted rendering action of name: " + REFORMAT_RENDER_DEFN_NAME,//
+ 1, results.size());
+ assertEquals("The node in which the action is stored should be the same.",//
+ actionNode, results.get(0).getChildRef());
+ return null;
+ }
+ });
+ }
+ finally
+ {
+ cleanUpPersistedActions(savedRenditionsToDelete);
+ }
+ }
+
+ /**
+ * This is not a real test method. It is used to create the ACP file that we have
+ * added to the RenditionService for importing at startup.
+ * This method should remain here as it will be needed if we need to change the
+ * contents of the ACP.
+ *
+ * @throws Exception
+ * @deprecated
+ */
+ public void off_test_CleanPersistedRenditionsAndCreateExportedACP() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+
+ // Check that if no node exists already then a new node is created.
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ // Delete all renditionDefinitions and the folder that holds
+ // them.
+ // We can't delete and recreate the
+ // RENDERING_ACTION_ROOT_NODE_REF and recreate it
+ // here because we need to keep its well-known nodeRef id.
+
+ assertTrue("The rendering action space has not been created in the repository!",//
+ nodeService.exists(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF));
+
+ // Clean up all existing saved actions here so as to ensure a
+ // clean export.
+ // Also delete any old acp file
+ for (ChildAssociationRef chRef : nodeService
+ .getChildAssocs(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF))
+ {
+ System.out.println("Deleting rendition Definition "
+ + nodeService.getProperty(chRef.getChildRef(), ContentModel.PROP_NAME));
+ nodeService.deleteNode(chRef.getChildRef());
+ }
+
+ // Create the rendition definitions.
+
+ // 1. "medium"
+ Map parameterValues = new HashMap();
+ parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 100);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 100);
+ parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true);
+ parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH,
+ "alfresco/thumbnail/thumbnail_placeholder_medium.jpg");
+ parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName());
+
+ RenditionDefinition action = createAction(ImageRenderingEngine.NAME, "medium", parameterValues);
+ renditionService.saveRenditionDefinition(action);
+
+ // 2. "doclib"
+ parameterValues.clear();
+ parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_PNG);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 100);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 100);
+ parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true);
+ parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH,
+ "alfresco/thumbnail/thumbnail_placeholder_doclib.png");
+ parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName());
+
+ action = createAction(ImageRenderingEngine.NAME, "doclib", parameterValues);
+ renditionService.saveRenditionDefinition(action);
+
+ // 3. "webpreview"
+ parameterValues.clear();
+ parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_FLASH);
+ parameterValues.put(ReformatRenderingEngine.PARAM_FLASH_VERSION, "9");
+ parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName());
+ // no placeholder
+
+ action = createAction("reformat", "webpreview", parameterValues);
+ renditionService.saveRenditionDefinition(action);
+
+ // 4. "imgpreview"
+ parameterValues.clear();
+ parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_PNG);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 480);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 480);
+ parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true);
+ parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH,
+ "alfresco/thumbnail/thumbnail_placeholder_imgpreview.png");
+ parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName());
+
+ action = createAction(ImageRenderingEngine.NAME, "imgpreview", parameterValues);
+ renditionService.saveRenditionDefinition(action);
+
+ // 5. "avatar"
+ parameterValues.clear();
+ parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_PNG);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 64);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 64);
+ parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true);
+ parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true);
+ parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH,
+ "alfresco/thumbnail/thumbnail_placeholder_avatar.png");
+ parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName());
+
+ action = createAction(ImageRenderingEngine.NAME, "avatar", parameterValues);
+ renditionService.saveRenditionDefinition(action);
+ return null;
+ }
+
+ private RenditionDefinition createAction(String renderingEngineName, String renditionLocalName,
+ Map parameterValues)
+ {
+ QName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, renditionLocalName);
+ RenditionDefinition action = renditionService.createRenditionDefinition(renditionName,
+ renderingEngineName);
+ for (String paramKey : parameterValues.keySet())
+ {
+ action.setParameterValue(paramKey, parameterValues.get(paramKey));
+ }
+ return action;
+ }
+ });
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ Action exportAction = actionService.createAction("export");
+ exportAction.setParameterValue(ExporterActionExecuter.PARAM_STORE,
+ StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.toString());
+ exportAction.setParameterValue(ExporterActionExecuter.PARAM_PACKAGE_NAME, "systemRenditionDefinitions");
+ exportAction.setParameterValue(ExporterActionExecuter.PARAM_ENCODING, "UTF-8");
+ exportAction.setParameterValue(ExporterActionExecuter.PARAM_DESTINATION_FOLDER,
+ RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF);
+ exportAction.setParameterValue(ExporterActionExecuter.PARAM_INCLUDE_SELF, true);
+ exportAction.setParameterValue(ExporterActionExecuter.PARAM_INCLUDE_CHILDREN, true);
+
+ actionService.executeAction(exportAction, RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF);
+
+ return null;
+ }
+ });
+
+ transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ List