Category Archives: Alfresco

Improving TIFF preview in Alfresco Share

In my experience I’ve noticed that one of the great features of Alfresco Share that is really appreciated by the users is the documents preview in the document library page. Recently I came across some TIFF documents whose preview quality was rather poor if not completely unusable. This was caused by ImageMagick doing a ugly conversion of multi-page TIFFs to PNGs. This article explains how to tweak Alfresco 3.3.x to get a better preview of multi-page TIFF documents in Share customizing the Alfresco transformers and using the last version of ImageMagick.

How the preview works

The preview mechanism is as simple as astonishing:

  • a Flash player embedded within the preview page is used to render some SWF content that represents the document to be previewed
  • the SWF content is dynamically retrieved from Alfresco by calling a webscript given the document node reference
  • when that webscript is called, it creates an SWF node through a chain of transformations from the document format to application/x-shockwave-flash
  • the SWF node is then stored as a child of the document node for further retrieval so that transformations are not unecessarily repeated
  • eventually, the webscript will send the SWF content back to the caller

I drew a simple sequence diagram that can help understand the interactions.
Trasnformations sequence diagram

This is what happens with document formats except for SWF documents and images (jpeg, gif, png, etc.), in which case the document content is returned as is (no conversion is needed for the Flash player to render those formats).

If you use Firebug to analyze the HTTP requests when the preview page is loaded you’ll see that urls follow this pattern depending on the document type:

  • for jpg, png and gif images: http://localhost:8080/share/proxy/alfresco/api/node/workspace/SpacesStore/fca7a769-5eea-4607-960d-de7f82382e98/content?c=force
  • for all other image formats: http://localhost:8080/share/proxy/alfresco/api/node/workspace/SpacesStore/19f7e50c-d16f-48fe-9851-b8b125d9a0b3/content/thumbnails/imgpreview?c=force
  • for all other formats but SWF: http://localhost:8080/share/proxy/alfresco/api/node/workspace/SpacesStore/19f7e50c-d16f-48fe-9851-b8b125d9a0b3/content/thumbnails/webpreview?c=force

All these requests are forwarded by Share’s proxy to the same webscript org/alfresco/cmis/content.get (see description at http://localhost:8080/alfresco/s/script/org/alfresco/cmis/content.get). As the description states, it streams the content of the specified document or its rendition:

  1. for images like jpg, png and gif no rendition takes place at all and document content is streamed as is
  2. for all other images, the document is converted to PNG: this is what happens by default with TIFF files
  3. in the last case, the document is converted to SWF

Since conversions of images in Alfresco are done by ImageMagick I supposed that this was the reason of the poor conversion of multi-page TIFFs to PNGs. In fact, I did some tests with the convert command and I found that I was using an old version of ImageMagick: 6.2.8 (RedHat Enterprise 5.4 comes with this package only). Anyway, I also did some tests on Windows with a more recent version of ImageMagick (6.5.7) and the best I could get was the preview of the first page of a multi-page TIF image. ImageMagick generates several files – one per page – from a single multi-page TIF file; I’m not completely sure, but I guess this is not what Alfresco expects.

From TIF to SWF

Anyway, I knew that Alfresco uses OpenOffice to convert documents into PDF files and Swftools to convert PDFs into SWFs; I supposed that if TIFF files could have been converted into PDFs then they could be transformed to SWFs as well. I had to figure out:

  • how to convert a multi-page TIF to PDF using some command line tool
  • how to configure Alfresco to convert TIF to PDF and then to SWF
  • how to tell Share to treat TIF files as other document formats

The answer to the first problem came soon when I tried a recent version of ImageMagick (6.6.4) to transform TIF files straight to PDF: in this case ImageMagick does a very good job and produces just one output file.

Understanding every single detail of a software is not always necessary in order to apply small changes, and this was the case. In a few minutes I wrote some xml files, changed a Javascript file in Share and start the whole thing up. And it worked!

The whole point is to learn how transformations are done in Alfresco: this is rather easy to do because every Alfresco component is injected and configured by Spring. Spring simplifies the definition and construction of components and makes it very easy to read how components are related to each other without even knowing what components do.

As always, the first step is to open the folder $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco and start reading some xml files that describe the transformation components (hint: search for files that contain the word transformer). You’ll see that at least two files define most of the transformers: content-services-context.xml and swf-transform-context.xml. I’m not going to drill down each transformer that is defined there because this will take too long but if you are interested I really suggest you to open these files and read the source code of referenced Java classes: you’ll see how easy the rendition mechanism is.

You may know that starting from version 3.2 Alfresco has the concept of subsystems. Subsystems are a very elastic way of configuring Spring beans and semplify extension and override. However, this is a very tough topic and it would deserve a separate post, so please refer to Alfresco documentation for a general overview of subsystems.

Alfresco transformers somehow rely on the subsystem mechanism. There’re two types of transformers (using my own terminology):

  • simple trasnformers, these do the hard job of transforming content from one format to another
  • complex transformers, these are built on top simple transformers to create a chain of transformations

Those of the first kind are configured through some sort of subsystem while those of the second kind are configured via the old way (in one of the two XML files I mentioned before).

We’re going to declare a complex transformer that explicitly converts from image/tiff format to application/x-shockwave-flash. This one in turn will rely on two simple transformers to perform a chain of renditions to application/pdf and to application/x-shockwave-flash.

We start defining a Spring context file under $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco/extension, lets call it tiff-transform-context.xml:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
   <!-- Import the imagemagick transformer worker from the third party subsystem -->
   <bean id="transformer.worker.Tiff2pdf" class="org.alfresco.repo.management.subsystems.SubsystemProxyFactory">
      <property name="sourceApplicationContextFactory">
         <ref bean="thirdparty" />
      </property>
      <property name="sourceBeanName">
         <value>transformer.worker.Tiff2pdf</value>
      </property>
      <property name="interfaces">
         <list>
            <value>org.alfresco.repo.content.transform.ContentTransformerWorker</value>
         </list>
      </property>
   </bean>

   <bean id="transformer.Tiff2pdf" class="org.alfresco.repo.content.transform.ProxyContentTransformer"
      parent="baseContentTransformer">
      <property name="worker">
         <ref bean="transformer.worker.Tiff2pdf"/>
      </property>
   </bean>

   <bean id="transformer.complex.Tiff2swf"
        class="org.alfresco.repo.content.transform.ComplexContentTransformer"
        parent="baseContentTransformer" >
      <property name="transformers">
         <list>
            <ref bean="transformer.Tiff2pdf" />
            <ref bean="transformer.Pdf2swf" />
         </list>
      </property>
      <property name="intermediateMimetypes">
         <list>
            <value>application/pdf</value>
         </list>
      </property>
      <property name="explicitTransformations">
         <list>
         	<bean class="org.alfresco.repo.content.transform.ExplictTransformationDetails" >
                <property name="sourceMimetype"><value>image/tiff</value></property>
                <property name="targetMimetype"><value>application/x-shockwave-flash</value></property>
            </bean>
         </list>
      </property>
   </bean>
 </beans>

Now a bit of explanation about the beans involved here:

  • transformer.complex.Tiff2swf defines the complex transformer that defines the chain of transformers
  • transformer.Tiff2pdf represents the transformer from TIFF to PDF; actually this ia proxy that relies on the bean declared in the thirdparty subsystem
  • transformer.worker.Tiff2pdf is a sort of placeholder for that bean
  • transformer.Pdf2swf is the transformer from PDF to SWF and is defined by default in Alfresco (provided that you install Swftools and configure it in Alfresco)

Then we need to extend the thirdparty subsystem. Creates the folder $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco/extension/subsystems/thirdparty/default/default and add two files:

  • tiff-transform-context.xml
  • tiff-transform.properties

This is the content of tiff-transform-context.xml:

<?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.worker.Tiff2pdf" class="org.alfresco.repo.content.transform.RuntimeExecutableContentTransformerWorker">
      <property name="mimetypeService">
         <ref bean="mimetypeService" />
      </property>
      <property name="checkCommand">
         <bean class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandMap">
                <map>
                    <entry key=".*">
                        <value>${img.exe} -version</value>
                    </entry>
                </map>
            </property>
         </bean>
      </property>
      <property name="transformCommand">
         <bean class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandMap">
                <map>
                    <entry key=".*">
                        <value>${img.exe} ${source} ${target}</value>
                    </entry>
                </map>
            </property>
            <property name="errorCodes">
               <value>1</value>
            </property>
         </bean>
      </property>
      <property name="explicitTransformations">
         <list>
         	<bean class="org.alfresco.repo.content.transform.ExplictTransformationDetails" >
                <property name="sourceMimetype"><value>image/tiff</value></property>
                <property name="targetMimetype"><value>application/pdf</value></property>
            </bean>
         </list>
      </property>
   </bean>
 </beans>

Here is the content of tiff-transform.properties:

# External executable locations
img.exe=${img.root}/bin/convert

Transformer transformer.worker.Tiff2pdf is responsable for transforming a document using the ${img.exe} command; the value of ${img.exe} is set inside tiff-transform.properties where we reference the variable ${img.root} (which in turn is set in alfresco-global.properties). This transformer explicitly handles transformations from image/tiff to application/x-shockwave-flash.

The last thing to do now is to fix Share to make it request the SWF preview instead of the PNG thumbnail. This can be done easily by modifying the file web-preview.js under $TOMCAT_HOME/webapps/share/components/preview. Find method _resolvePreview and change it like this:

      _resolvePreview: function WP__resolvePreview(event)
      {
         var ps = this.options.previews,
            webpreview = "webpreview", imgpreview = "imgpreview",
            nodeRefAsLink = this.options.nodeRef.replace(":/", ""),
            argsNoCache = "?c=force&noCacheToken=" + new Date().getTime(),
            preview, url;
         
         if (this.options.mimeType.match(/^image\/jpeg$|^image\/png$|^image\/gif$/))         
         {
            /* The content matches an image mimetype that the web-previewer can handle without a preview */
            url = Alfresco.constants.PROXY_URI + "api/node/" + nodeRefAsLink + "/content" + argsNoCache;
            return (
            {
               url: url,
               paging: false
            });
         }
         else if (this.options.mimeType.match(/application\/x-shockwave-flash/))
         {
            url = Alfresco.constants.PROXY_URI + "api/node/content/" + nodeRefAsLink + argsNoCache + "&a=true";
            return (
            {
               url: url,
               paging: false
            });
         }
         else
         {
            if (this.options.mimeType.match(/^image\/tiff$/))
                preview = webpreview;
            else
                preview = Alfresco.util.arrayContains(ps, webpreview) ? webpreview : (Alfresco.util.arrayContains(ps, imgpreview) ? imgpreview : null);
            if (preview !== null)
            {
               url = Alfresco.constants.PROXY_URI + "api/node/" + nodeRefAsLink + "/content/thumbnails/" + preview + argsNoCache;
               return (
               {
                  url: url,
                  paging: true
               });
            }
            return null;
         }
      },

Once you have applied your changes, restart Alfresco, enter Share, refresh the preview page (make sure that browser cache is cleaned) and multi-page TIFFs should be previewed correctly.

Some news

The embedded SWF player in Share is SWFObject, a project that has now moved to Google Code.

The sequence diagram was drawn with Gliffy.

Sudo like tool for Alfresco – security aspects

In my first post in this blog I proposed a way to execute some javascript code with the admin privileges within the Alfresco (web)scripts.
As Peter Monks pointed out in his comment, there’re some risks concerning security you’d better be aware of if you intend to use this extension in your projects.
As Peter suggested, if users can author their own scripts then they can potentially submit code that runs with administrator privileges, which is an obvious security flow.
Also, attention must be paid in case the eval statement is used within the sudo argument function: avoid this kind of practice if the eval argument itself depends on some webscript input parameter since this could potentially lead to code injection. So how to cope with these problems?
My solution is to create a “sudoers” group (as in the Unix OSs) so that only users that belong to this group can execute the sudo function. Here is how I would change the Sudo bean:

public class Sudo extends BaseScopableProcessorExtension {
    private AuthorityService authorityService;

    public void sudo(final Function func) throws Exception  {
        final Context cx = Context.getCurrentContext();
        final Scriptable scope = getScope();
        String user = AuthenticationUtil.getRunAsUser();

        Set<String> groups = authorityService.getContainingAuthorities(AuthorityType.GROUP, user, false);
        if (!groups.contains("GROUP_SUDOERS"))
            throw new Exception("User '" + user + "' cannot use sudo");

        RunAsWork<Object> raw = new RunAsWork<Object>() {
            public Object doWork() throws Exception {
                func.call(cx, scope, scope, new Object[] {});
                return null;
            }
        };

        AuthenticationUtil.runAs(raw, AuthenticationUtil.getAdminUserName());
    }
}

We used the authorityService service to get the set of groups the current user belongs to and then we checked that the SUDOERS group is one of those. If you use this version of the Sudo bean, remember to update the Spring bean definition (file sudo-script-services-context.xml):

<?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="Sudo" parent="baseJavaScriptExtension" class="eu.fabiostrozzi.sudo.ws.js.Sudo">
        <property name="extensionName">
            <value>sudoUtils</value>
        </property>
       <property name="authorityService">
             <ref bean="AuthorityService" />
       </property>
    </bean>
</beans>

This is by no means a fully fledged solution but surely reduces risks if, for instance, users that can author scripts are not added to the SUDOERS group.

Sudo like tool for Alfresco webscripts

This year we at GetConnected worked a lot on integration solutions based on Alfresco. Integrating customers’ softwares with Alfresco means, first of all, facingĀ  with different permissions models: the Alfresco’s one and that of the external software. Most of the times they differ and it couldn’t be otherwise: althought Alfresco is an extendable, general purpose product, external softwares target specific problem and have ad-hoc solutions. Continue reading “Sudo like tool for Alfresco webscripts” »