This is a follow-up to the post Add an XML to PDF Transformer to Alfresco. In this post I looked into the options to add and use an XSL-FO processor using configuration files and some JavaScript. The post ended with the remark that Composite Renditions are not exposed in the JavaScript API. This post provides an example of a custom action that creates a PDF rendition based on XSL-FO.
Note: in order to make these examples work, you need to follow the configuration steps in the previous post. This post assumes you are familiar with customizing Alfresco using Java.
Composite Rendition
A Composite Rendition allows you to create a rendition from one mimetype to another using one or more intermediate transformations. With XSL-FO for example you would normally first transform the XML document into XSL-FO and then transform the result to the desired output format, for example PDF.
Custom Action
To create PDF renditions for XML files using XSL-FO you can write a custom action in Java that creates a Composite Rendition to first transform the XML document into an XSL-FO document and then transforms the result into a PDF. The PDF is then stored as a rendition associated to the XML document.
The following Java class demonstrates this approach. It is a custom action that takes a single parameter to pass the stylesheet:
package com.someco.action;
import java.util.List;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.rendition.executer.ReformatRenderingEngine;
import org.alfresco.repo.rendition.executer.XSLTRenderingEngine;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
public class FoRenditionActionExecuter extends ActionExecuterAbstractBase {
public static final String NAME = "fo";
public static final String PARAM_TEMPLATE_REF = "template";
RenditionService renditionService;
@Override
protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {
RenditionDefinition xslDefinition = renditionService
.createRenditionDefinition(QName.createQName(
NamespaceService.CONTENT_MODEL_1_0_URI,
"xslRenderingDefinition"), XSLTRenderingEngine.NAME);
NodeRef template = (NodeRef) action
.getParameterValue(PARAM_TEMPLATE_REF);
xslDefinition.setParameterValue(
XSLTRenderingEngine.PARAM_TEMPLATE_NODE, template);
xslDefinition.setParameterValue(
ReformatRenderingEngine.PARAM_MIME_TYPE, "text/xsl");
RenditionDefinition pdfDefinition = renditionService
.createRenditionDefinition(QName.createQName(
NamespaceService.CONTENT_MODEL_1_0_URI,
"pdfRenderingDefinition"), ReformatRenderingEngine.NAME);
pdfDefinition.setParameterValue(
ReformatRenderingEngine.PARAM_MIME_TYPE,
MimetypeMap.MIMETYPE_PDF);
CompositeRenditionDefinition compositeDefinition = renditionService
.createCompositeRenditionDefinition(QName.createQName(
NamespaceService.CONTENT_MODEL_1_0_URI,
"compRenderingDefinition"));
compositeDefinition.addAction(xslDefinition);
compositeDefinition.addAction(pdfDefinition);
renditionService.render(actionedUponNodeRef, compositeDefinition);
}
@Override
protected void addParameterDefinitions(List paramList) {
paramList.add(new ParameterDefinitionImpl(PARAM_TEMPLATE_REF,
DataTypeDefinition.NODE_REF, true,
getParamDisplayLabel(PARAM_TEMPLATE_REF)));
}
public RenditionService getRenditionService() {
return renditionService;
}
public void setRenditionService(RenditionService renditionService) {
this.renditionService = renditionService;
}
}
In order to make the custom action available in Alfresco we need to add it as a Spring Bean:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN'
'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<bean id="fo" class="com.someco.action.FoRenditionActionExecuter"
parent="action-executer">
<property name="publicAction">
<value>false</value>
</property>
<property name="renditionService">
<ref bean="RenditionService" />
</property>
</bean>
</beans>
Execute the action
Next restart Alfresco. You can test the custom action by writing a small JavaScript that executes the action by providing an XML document and an XSL stylesheet that creates XSL-FO output. The FOP distribution provides some examples that you can use. The following code demonstrates how to execute the action using JavaScript:
var fo = actions.create("fo");
fo.parameters.template = companyhome.childByNamePath("/XML/projectteam2fo.xsl");
fo.execute(document);
This code creates a PDF rendition for the given document (in this case a file called projectteam.xml, an example provided by FOP). The rendition is stored as a child of the source document.
Custom Transformer Java Implementation
Once you have started writing Java code, you might as well want to replace the transformer configuration with a Java implementation. The Apache FOP Java library is already on Alfresco’s class path, so a Java implementation does not require a separate install of Apache FOP.
The following class is a basic implementation to provide the custom transformer in Java to replace the transformer defined in the file custom-transformer-context.xml:
package com.someco.transform;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.AbstractContentTransformer2;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
public class FoContentTransformer extends AbstractContentTransformer2 {
@Override
public boolean isTransformable(String sourceMimetype,
String targetMimetype, TransformationOptions options) {
if (sourceMimetype.equalsIgnoreCase(MimeConstants.MIME_XSL_FO)
&& targetMimetype.equalsIgnoreCase(MimetypeMap.MIMETYPE_PDF))
return true;
return false;
}
@Override
protected void transformInternal(ContentReader reader,
ContentWriter writer, TransformationOptions options)
throws Exception {
FopFactory fopFactory = FopFactory.newInstance();
OutputStream out = new BufferedOutputStream(
writer.getContentOutputStream());
try {
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
Source src = new StreamSource(reader.getContentInputStream());
Result res = new SAXResult(fop.getDefaultHandler());
transformer.transform(src, res);
} finally {
out.close();
}
}
}
Next update the file custom-transformer-context.xml. Replace the current transformer with a Spring Brean that loads the Java implementation:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN'
'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<bean id="transformer.FOP.PDF"
class="com.someco.transform.FoContentTransformer"
parent="baseContentTransformer" />
</beans>
Restart Alfresco and try to run the JavaScript code to create the rendition to PDF.
Read the follow-up post Persisting Alfresco Renditions to learn how to persist renditions to allow Alfresco to update the rendition on any properties update.
Last updated: October 14, 2011
Alfresco’s CMO Todd Barr about