On almost every topic
Using Alfresco Composite Rendition to Render PDF

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

  1. lewesde reblogged this from bpeters
  2. walpage reblogged this from bpeters
  3. bpeters posted this