Showing posts with label Alfresco. Show all posts
Showing posts with label Alfresco. Show all posts

Friday, June 8, 2012

How to add custom Share site presets (the nice way) - Alfresco Hack #5

[See here about my post about the benefits of the b2b marketplace for Alfresco modules and solutions.]
[Check out our newest module for email management and archiving with Alfresco]
[See here about our even newer module for automatic trashcan management with Alfresco]

This blog post will explain in depth a way to add custom site presets to Alfresco share as nice as possible. Nice here means, to play well with others:
  • use the extension mechanism
  • do not overwrite any existing configuration files
  • do not overwrite any Alfresco Spring beans
This way, many modules can be deployed independently to Alfresco without interfering, each providing a new site preset.

Currently, as of Alfresco 4.0dCE and 4.0.1EE it is not possible to add presets through the share extension mechanism alone.

A common requirement of anybody who likes to customize Alfresco share is to provide custom site presets. For example, show the links-Page besides the document library per default. Or to present a specialized page targeting a specific use case. A common requirement for ourselves and the ecm Market vendors as well.


What is a share site preset?


A site preset contains the initial configuration of a site: The site dashboard layout, and any preconfigured site dashlets. The initial set of site pages can be set as well. Examples for existing site pages are the document library page, the calendar, the wiki, the datalist page. It is an easy way to add your custom requirements to a site. So, to sum it up, a site preset is maybe better unterstood as a site type, because it captures all behaviour that makes it special (or different) from others. And of course, multiple sites can be instantiated from the site type.


How to create a share site preset?


The steps are simple as noted below - but to play nice with other, care must be taken to not override files and spring beans:
  1. Create a preset file containing the XML configuration of your site : my-custom-presets.xml. A good starting point is to copy the existing presets.xml and remove all unnecessary configuration 
  2. Create an extension to augment the presets UI dialog controller with your new site data.
  3. Add our new presets file to the list of preset files that the share preset manager recognizes.
Steps 1 and 2 can be done without overwriting any files using the existing share extension mechanism, but step 3 would need to overwrite the presets manager bean named webframework.presets.manager to add the new preset. This is the presets manager bean (it is part of surf located in the spring-surf-1.0.0.jar) as it comes with alfresco:



   <!-- Presets manager - configured to walk a search path for preset definition files -->
   <bean id="webframework.presets.manager" class="org.springframework.extensions.surf.PresetsManager">
      <property name="modelObjectService" ref="webframework.service.modelobject" />
      <property name="searchPath" ref="webframework.presets.searchpath" />
      <property name="files">
         <list>
            <value>presets.xml</value>
         </list>
      </property>
   </bean>

Out of the box, it would be possible to place a file named presets.xml in a package named alfresco/site-data/presets or alfresco/web-extension/site-data/presets - which is resolved by the the search path. But because the file name is fixed to presets.xml it is not possible to deploy multiple presets without overwriting each other. But to the rescue comes the tool I introduced in the blog post Augmenting Alfresco Spring Bean configuration without overwriting beans. This litte class will add the my-custom-presets.xml file the the presets manager bean on startup dynamically, so that the original bean must not be overridden:

<bean id="ecm4u.custom.presets" class="de.ecm4u.alfresco.utils.spring.AddToListPropertyPostProcessor">
    <property name="beanName" value="webframework.presets.manager" />
    <property name="propertyName" value="files" />
    <property name="position" value="-1" />
    <property name="additionalProperties">
        <list>
            <value>my-custom-presets.xml</value>
        </list>
    </property>
</bean>

A module using this tooling plays nice with others, because no overwriting as taken place.

Back to step 2,  creating the share extension to augment the site creation controller with the new site:

Place a file my-custom-extension.xml into alfresco/site-data/extensions/ with the following contents. It is a share extension with a single package customization.


<extension>
    <modules>
        <module>
            <id>my-custom-site-preset</id>
            <auto-deploy>true</auto-deploy>
            <customizations>
                <customization>             <targetPackageRoot>org.alfresco.modules</targetPackageRoot>                <sourcePackageRoot>ecm4u.samples.custom.modules</sourcePackageRoot>
                </customization>
            </customizations>
        </module>
    </modules>
</extension>



To finally make your site preset available for selection in the share create site dialog, a short javascript snippet is needed. Add this snippet to a file named create-site.get.js into a package webscripts.ecm4u.sampels.custom.modules:

if(model.sitePresets) {
    model.sitePresets.push({
        id: "my-custom-preset-id",
        name: "My custom site"
      });
}

Back to step 1, creating the presets description file:

How a presets file might actually look like is shown here. Add this file at alfresco/site-data/presets and name it my-custom-presets.xml:

Please note, that the preset id has to match the id parameter of the script customization above.

<?xml version='1.0' encoding='UTF-8'?>
<presets>
   <preset id="my-custom-preset-id">
      <components>         
         <!-- title -->
         <component>
            <scope>page</scope>
            <region-id>title</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/title/collaboration-title</url>
         </component>
         <!-- navigation -->
         <component>
            <scope>page</scope>
            <region-id>navigation</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/navigation/collaboration-navigation</url>
         </component>
         <!-- dashboard components -->
         <component>
            <scope>page</scope>
            <region-id>full-width-dashlet</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/dynamic-welcome</url>
            <properties>
               <dashboardType>site</dashboardType>
            </properties>
         </component>
         <component>
            <scope>page</scope>
            <region-id>component-1-1</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/colleagues</url>
            <properties>
               <height>504</height>
            </properties>
         </component>
         <component>
            <scope>page</scope>
            <region-id>component-2-1</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/docsummary</url>
         </component>
         <component>
            <scope>page</scope>
            <region-id>component-2-2</region-id>
            <source-id>site/${siteid}/dashboard</source-id>
            <url>/components/dashlets/activityfeed</url>
         </component>
      </components>
      <pages>
         <page id="site/${siteid}/dashboard">
            <title>Collaboration lodda  Site Dashboard</title>
            <description>Collaboration lodda site's dashboard page</description>
            <template-instance>dashboard-2-columns-wide-right</template-instance>
            <authentication>user</authentication>
            <properties>
               <sitePages>[{"pageId":"documentlibrary"}, {"pageId":"links"}]</sitePages>
            </properties>
         </page>
      </pages>
   </preset>
</presets>


Quite some lengthy steps, to sum up:

  • a presets file describing your site preset configuration
  • a share extension configuration
  • a spring bean definition to use the Spring AddToListPropertyPostProcessor
But you will be able to let your users to create sites of a new type. New site types are a perfect way to introduce new functionality and use case into Alfresco. And because no files will be overwritten using this approach, it is possible to create different AMP files, each of it containing a new site type.


Let me know if it works - but also if it not works of course;)


One thing that comes to mind - the share extension has to be activated. To activate the module automatically on share startup, add this to your shared/classes/alfresco/web-extension/share-config-custom.xml file:



        <config evaluator="string-compare" condition="WebFramework">
                <web-framework>
                        <module-deployment>
                                <mode>manual</mode>
                                <enable-auto-deploy-modules>true</enable-auto-deploy-modules>
                        </module-deployment>
                </web-framework>
        </config>




  


Friday, May 18, 2012

Augment Alfresco Spring Bean configuration (without overwriting beans) - Alfresco Hack #4


[See here my post about the benefits of the b2b marketplace for Alfresco modules and solutions.]

...continuing my Alfresco hacks series posts...

Development with Alfresco is fun - due to the facts that Alfresco is build upon the Spring framework and that it is open source almost every part of it can be changed or extended just by changing the spring bean configuration. This is fine and sufficient if you have full control over the configuration. But more and more modules and real solutions are built on top of Alfresco. And these extensions can even be combined, which leads to an even more complex and hard-to-control configuration. It is important to add your own code without overwriting any of the Alfresco Spring beans - to play well with others

Warning: Heavy developer stuff ahead! You definitely need to know what you are doing here...

Augment list properties of existing Alfresco Spring Beans


Sometimes, the ootb configuration and extensibility mechanism of Alfresco does not cover (yet) the area that you would like to customize or extend. But often, it would almost be possible just by adding a value to a list property of an existing Alfresco bean. Examples are:

  • Add another Share site preset definition file (I will show an example in my next post)
  • Add additional search paths for share/surf
  • Add a method interceptor to the core public services
  • Add a property decorator or user permissions for share (bean applicationScriptUtils)
The basic idea is to use a Spring BeanFactoryPostProcessor. A BeanFactoryPostProcessor is able to change the bean configuration just before a bean gets instantiated. This way, an additional list value can be added to a bean property, just as if it would have been configured in xml.

Example: Add a permission to the share document library repository response


The requirement is to provide a custom permission to the share document library, because an action evaluator should be configured to only show the action if the user has the actual permission to carry out the action.

The applicationScriptUtils bean controls which permissions are given back to share during document library browsing and as described here the bean applicationScriptUtils can be overwritten to configure it.

Also the map-merge feature of Spring will not solve this completely as discussed on the linked blog.

Using the BeanFactoryPostProcessor will allow to configure it, without any overwriting of beans and compatible with other extensions which have the same requirement.


<bean id="ecm4u.test.CustomPermission"
class="de.ecm4u.alfresco.utils.spring.AddToListPropertyPostProcessor">
<property name="beanName" value="applicationScriptUtils" />
<property name="propertyName" value="userPermissions" />
                <property name="position" value="-1" /> <!-- -1 means end of list -->
<property name="additionalProperties">
<list>
<value>CustomPermission</value>
</list>
</property>
</bean>

If you would like to try it out - although it is easy to implement a BeanFactoryPostProcessor - the source code is provided below. I have used it in modules and projects with success.

AddToListProperty BeanFactoryPostProcessor source code



package de.ecm4u.alfresco.utils.spring;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.core.Ordered;


/**
* This {@link BeanFactoryPostProcessor} adds additional properties to a list
* property of an already defined bean.
*


* Copyright 2010 Lothar Maerkle
* 
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
*  the License.
* 

* @author lothar.maerkle@ecm4u.de
*/
public class AddToListPropertyPostProcessor implements BeanFactoryPostProcessor, Ordered {
    private static final Log LOGGER = LogFactory.getLog(AddToListPropertyPostProcessor.class);
    private String beanName;
    private String propertyName;
    private List<Object> additionalProperties;
    /**
    * Controls where the additional properties are added to the list of the target bean.
    * The position is an index, negativ values are interpreted from the end of the list.
    * Eg. 0 means haed of the list, -1 means append to the end of the list.
    */
    private int position = -1;
    @SuppressWarnings({ "unchecked" })
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] beans = beanFactory.getBeanDefinitionNames();
        for (String bName : beans) {
            if (bName.equals(beanName)) {
                BeanDefinition def = beanFactory.getBeanDefinition(beanName);
                // add mappings
                if (additionalProperties != null && !additionalProperties.isEmpty()) {
                    List<Object> mapped = (List<Object>)  def.getPropertyValues().getPropertyValue(propertyName).getValue();
                    if (mapped == null) {
                           mapped = new ArrayList<Object>();
                 }
                 if (position >= 0) {
                     // correct overflow positive size
                     position = position <= mapped.size() ? position : mapped.size();
                     for (int v = 0; v < additionalProperties.size(); v++) {
                         mapped.add(position + v, mapped);
                     }
                  } else if (position < 0) {
                      // correct overflow negative size
                      position = (Math.abs(position) - 1) <= mapped.size() ? position: (mapped.size() + 1) * (-1);
                      int size = mapped.size();
                      for (int v = 0; v < additionalProperties.size(); v++) {
                          mapped.add(size + position + v + 1, additionalProperties.get(v) );
                      }
                  }
                  if (LOGGER.isInfoEnabled()) {
                       LOGGER.info("Added properties: bean=" + bName + ", property=" + propertyName + ", mappings="+ additionalProperties + ", position="+ position);
                  }
              }

         }
    }

    // last comment for 1000 lines, thank you for your understanding
    public int getOrder() {
       return Integer.MAX_VALUE;
    }

    public final String getBeanName() {
        return beanName;
    }

    public final void setBeanName(String beanName) {
         this.beanName = beanName;
    }
    public final String getPropertyName() {
        return propertyName;
    }

    public final void setPropertyName(String propertyName) {
        this.propertyName = propertyName;
    }
    public final List<Object> getAdditionalProperties() {
        return additionalProperties;
    }

    public final void setAdditionalProperties(List<Object> additionalProperties) {
        this.additionalProperties = additionalProperties;
    }

    public final int getPosition() {
        return position;
    }
    public final void setPosition(int position) {
        this.position = position;
    }

}

Thursday, April 12, 2012

ecm marketplace for Alfresco modules, solutions and services

There was no update on my blog about Alfresco for quite a long time - but I have a very good excuse: I was busy founding and building up a new company. The company is named ecm4u (see http://www.ecm4u.de) and it offers modules, solutions and services for and connected to the field of ECM/DMS and the Alfresco system in particular.

Recently, we launched the ecm Market (see http://www.ecm-market.de) which is a B2B marketplace for tools, modules, complete solutions and services compatible with Alfresco.


Who could benefit from the ecm Market?

First: Companies that use Alfresco today

When Alfresco is already up and running, the users start to like it and usually soon request more features. Most requirements will be small ones that improve usability, for example:
  • Sending a document link to a collegue
  • Sending document as attachment
  • Zipping up a space
  • Changing multiple properties in one go
  • ....
Modules that extend Alfresco with those features can be found at the ecm Market productivity category for example. The marketplace rules ensures instant availability and a ready-to-use package for download and the installation of the module. Using the ecm Market also provides safety, as it allows to compare similar modules regarding functionality. Last but not least, because every product is presented with a given price. It is also possible to return and refund a module if it does not work as expected and described. All ecm Market modules also come with support, so that it is assured that any arising problems are handled and resolved.


Second: Companies that think of introducing Alfresco

In the first place, it is usually a single specific use case that make companies think of using Alfresco. Typical use cases could be:
    • Simple collaboration needs: distributed employees need to collaborate and work together, exchange documents
    • The SubCon Case: Plenty of subcontractors are delivering parts within a big project. They are also competitors, so each subcontractor has its own specific area in Alfresco without being able to see the quotes and numbers of each other.
    • Managing invoices and contracts
    • Fulfilling quality process requirements
    For collaboration requirements, Alfresco can be used out-of-the-box, as it is very strong in collaboration, sharing content and working together. For the other use cases, Alfresco needs to be extended to fulfill the requirements. This has to be done by a system integrator with a deep knowledge of Alfresco and will result in a straight software-tailoring-IT project. While a software development project will allow to perfectly customize Alfresco to your needs, you always take the good with the bad. IT projects can be risky in terms of timeline and budget, as we all know. Deep customization will make updates to future releases of the main product a very difficult task. Using a prepackaged module that contains a part of the required functionality can help to reduce the project risk in terms of budget and a shortened timeline. Most modules of the ecm Market will have received much more testing, because they are used multiple times by many customers, thus providing better quality at a lower cost.


    Third: System Integrators


    The point of view of system integrators is usually providing high quality services by experts. Software developers, architects and analysts work together to implement business requirements for a customer. Although the requirements for a customer in a project can be very specific, there are always recurring or cross-cutting issues and solutions that can be applied again in different contexts and therefore reused. In the early stage of a project, in my opinion, it is no (nearly no) additional effort to identify those requirements and to factor them out in a separate module (or code-library) that can be reused in the next project. Or, even better, companies can benefit by offering these modules one the ecm Market. By doing so, further development could be funded and the module can be improved with new features or a more robust implementation. Also, new regions and markets can be addressed by the ecm Market - a rather difficult task for a single company that has to rely on its individual marketing and sales efforts. All system integrators are invited to improve the number and range of available products at the ecm Market. A large marketplace has advantages for all participants. Registration and information on how to become a vendor on the ecm Market is available in both English and German language following the above links.


    Fourth: The Alfresco Community and ecosystem

    I am convinced, that the ecm Market will improve and spread the usage of Alfresco. Small and medium sized companies, who do not currently use Alfresco, will be able to introduce it at lower costs and risks. Why? Because they will be able to find a module specific (or close) to their first and primary use case. This will lower the entry of doing a quick pilot project with the Community version. And as soon as the system usage and its importance grows within the company, upgrade to an Enterprise Version to have it covered with the appropriate level of support and features needed for operation in an enterprise environment as a first line system.

    Friday, March 6, 2009

    Speeding up PDF indexing - Alfresco Hack #3

    What is your document type distribution like? If the majority of your stored documents in Alfresco are PDFs, this article is for you. It describes how to speed up the full-text indexing process dramatically by factor of 4! But relax, no coding required:)

    To estimate the impact on your installation, some rough distribution numbers can be gathered like this:

    dmc@alfresco: find ./alf_data/contentstore -type f -exec file -inb {} \;| sort |uniq -c|sort -nr
    
    Output for example from our installation:
    image/png          28 %
    application/msword 13 %
    application/pdf    10 %
    image/gif           8 %
    other              41 %
    

    The PDF format is the most used text format after microsoft word. From these numbers it is clear, that a speed up of PDF indexing would be a great benefit for the overall system. The indexing process first extracts the plain text content from the PDF document by the help of a pdf->text transformer based on the PDFBox library. The extracted text is then fed to the lucene indexer component.

    For testing the text extraction, I created a small PDF document collection consisting of 15 PDF documents, total of 15MB. To rule out java startup and library loading times, I used this small sample program to time the PDFBox text stripper:

    package de.dmc.alfresco.pdfbox;
    
    import java.io.IOException;
    import org.pdfbox.pdmodel.PDDocument;
    import org.pdfbox.util.PDFTextStripper;
    
    public class ExtractText {
            // Extract the text from the pdfs given on the command line
     public static void main(String[] args) throws IOException {
      long start = System.currentTimeMillis();
      for (String pdfFile : args) {
       PDDocument document = PDDocument.load(pdfFile);
       PDFTextStripper stripper = new PDFTextStripper();
       stripper.getText(document);
       document.close();
      }
      long stop = System.currentTimeMillis();
      long diff = stop - start;
      System.err.printf("pdfs: %d, total: %d seconds, average: %.3f seconds per document\n", args.length, diff/1000, diff/1000d/args.length );
     }
    }
    
    Output on my 2Ghz dual core:
    pdfs: 15, total: 10 seconds, average: 0.727 seconds per document
    
    Results: about a whole small second is spend on text extraction in every pdf added to Alfresco!
    In our projects, we replaced the PDFBox transformer with the pdftotext console tool.
    lothar@lothar-laptop:~/devenv/lib/collections/pdf$ time for pdffile in *.pdf; do pdftotext $pdffile - >/dev/null;done
    
    real 0m2.738s
    user 0m2.460s
    sys 0m0.136s
    
    Results: about 3 seconds for all 15 pdf files! average: 0.2 seconds per document

    Configuration of the transformer:

    Add this configuration to shared/classes/alfresco/extension/pdf-transformer-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>
            <!-- disable standard pdfbox text transformer -->
            <bean id="transformer.PdfBox" class="java.lang.String"/>
            <!-- has the above injected, is newly created below -->
        <bean id="transformer.complex.OpenOffice.PdfBox" class="java.lang.String"/>
    
            <!-- pdftotext command line binary -->
            <bean id="transformer.PdfToTextTool"
                    class="org.alfresco.repo.content.transform.RuntimeExecutableContentTransformer"
                    parent="baseContentTransformer">
                    <property name="transformCommand">
                            <bean name="transformer.pdftotext.Command"
                                    class="org.alfresco.util.exec.RuntimeExec">
                                    <property name="commandMap">
                                            <map>
                                                    <entry key="Linux.*">
                                                            <value>${catalina.base}/webapps/alfresco/WEB-INF/bin/pdftotext-linux -enc UTF-8 ${options} ${source} ${target}</value>
                                                    </entry>
                                                    <entry key="Windows.*">
                                                            <value>${catalina.base}/webapps/alfresco/WEB-INF/bin/pdftotext-win32.exe -enc UTF-8 ${options} ${source} ${target}</value>
                                                    </entry>
                                            </map>
                                    </property>
                                    <property name="defaultProperties">
                                            <props>
                                                    <prop key="options"></prop>
                                            </props>
                                    </property>
                            </bean>
                    </property>
                    <!-- ensure executable bits of binaries on unix -->
                    <property name="checkCommand">
                            <bean name="transformer.pdftotext.checkCommand"
                                    class="org.alfresco.util.exec.RuntimeExec">
                                    <property name="commandMap">
                                            <map>
                                                    <entry key="Linux.*">
                                                            <value>chmod 775 ${catalina.base}/webapps/alfresco/WEB-INF/bin/pdftotext-linux</value>
                                                    </entry>
                                                    <entry key="Windows.*">
                                                    <!--  dummy value -->
                                                            <value>cmd.exe /C dir</value>
                                                    </entry>
                                            </map>
                                    </property>
                                    <property name="defaultProperties">
                                            <props>
                                                    <prop key="options"></prop>
                                            </props>
                                    </property>
                            </bean>
                    </property>
                    <property name="explicitTransformations">
                            <list>
                                    <bean
                                            class="org.alfresco.repo.content.transform.ContentTransformerRegistry$TransformationKey">
                                            <constructor-arg>
                                                    <value>application/pdf</value>
                                            </constructor-arg>
                                            <constructor-arg>
                                                    <value>text/plain</value>
                                            </constructor-arg>
                                    </bean>
                            </list>
                    </property>
            </bean>
    
       <!-- replaces bean transformer.complex.OpenOffice.PdfBox -->
       <bean id="transformer.complex.OpenOffice.PdfToTextTool"
            class="org.alfresco.repo.content.transform.ComplexContentTransformer"
            parent="baseContentTransformer" >
          <property name="transformers">
             <list>
                <ref bean="transformer.OpenOffice" />
                <ref bean="transformer.PdfToTextTool" />
             </list>
          </property>
          <property name="intermediateMimetypes">
             <list>
                <value>application/pdf</value>
             </list>
          </property>
       </bean>
    </beans>
    

    Let me know how it worked for you!

    Tuesday, February 3, 2009

    Indexing Freemind MindMaps with Alfresco - Alf Hack # 2

    The idea of this Alfresco hack is to use a command line tool for text extraction of the Freemind .mm file. Steps to include this into Alfresco will be:
    1. Add Mimetype application/x-freemind for .mm
    2. Add transformer from appplication/x-freemind to text/plain
    This article will talk about the second step. For adding a new MIME type please refer to the Alfresco Wiki. The MIME type of Freemind mid maps is application/x-freemind. There is also a nice blog post about adding the freemind MIME type and a nice map integration available.

    Extract the text

    An example shows how Freemind stores this sample map in a XML file:
    <map version="0.7.1">
      <node text="Alfresco Hack No 2">
        <node text="Explore how Freemind XML looks like" position="right">
        </node>
      </node>
    </map>
    
    Quite simple XML without namespaces. The text of the map nodes is stored in a the value of the attribute text. To extract the text I will use a quick-and-dirty XSLT:
    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output omit-xml-declaration="yes" indent="no"/>
        <xsl:template match="/">
         <xsl:call-template name="t1"/>
       </xsl:template>
       <xsl:template name="t1">
         <xsl:for-each select="//node">
           <xsl:value-of select="@TEXT"/>
           <xsl:value-of select="' '"/>
         </xsl:for-each>
      </xsl:template>
    </xsl:stylesheet>
    
    
    Throwing this XSLT on the Freemind XML results in the extracted text:
    Alfresco Hack No 2 Explore how Freemind XML looks like

    Add transformer to Alfresco
    To keep things simple, I will use the Alfrescos feature to do content transformations with external tools or programs. This is done by configuring a RuntimeExecutableContentTransformer bean. But first, the command line of the external tool has to be figured out. I will use the xmlstarlet command line tool from http://xmlstar.sourceforge.net/. Depending on your linux distribution the executable will be called just xml or xmlstarlet. There is also a Windows version available from the download page. Transforming the above XSLT to xmlstarlets commandline results in:
    xmlstarlet sel -t -m //node -v @TEXT -o ' ' Alfresco\ Hack\ No\ 2.mm
    Sadly, the output always go to stdout and no output file can be specified. But this is required for the RuntimeExecutableContentTransformer, so a simple script wrapper can be used. I put the following to a file /home/lothar/bin/freemind2text.sh (made executable with chmod 775) which will be configured to the transformer bean:
    #!/bin/bash
    # save arguments to variables
    SOURCE=$1
    TARGET=$2
    
    # to see what gets extracted append arguments to logfile
    echo "from $SOURCE to $TARGET" >>/tmp/freemindtransform.log
    
    # call xmlstarlet tool and redirect output to $TARGET
    xmlstarlet sel --text --encoding UTF-8 -t -m //node -v @TEXT -o ' ' "$SOURCE" > "$TARGET"
    
    Now we are ready to configure the RuntimeExecutableContentTransformer 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="transformer.freemindToText" class="org.alfresco.repo.content.transform.RuntimeExecutableContentTransformer" parent="baseContentTransformer">
        <property name="transformCommand">
          <bean name="transformer.freemind.Command" class="org.alfresco.util.exec.RuntimeExec">
            <property name="commandMap">
              <map>
                <entry key="Linux.*">
                  <value>/home/lothar/bin/freemind2text.sh ${source} ${target}</value>
                </entry>
                <entry key="Windows.*">
                  <value>...whatever windows needs here....</value>
                </entry>
              </map>
            </property>
            <property name="defaultProperties">
              <props>
                <prop key="options"/>
              </props>
            </property>
          </bean>
        </property>
        <property name="explicitTransformations">
          <list>
            <bean class="org.alfresco.repo.content.transform.ContentTransformerRegistry$TransformationKey">
              <constructor-arg>
                <value>application/x-freemind</value>
              </constructor-arg>
              <constructor-arg>
                <value>text/plain</value>
              </constructor-arg>
            </bean>
          </list>
        </property>
      </bean>
    </beans>
    
    


    Finished!
    Now indexing of Freemind mindmaps will take place. On the plus side: No Java coding, just configuration of the standard Alfresco features. On the down side: ...is there anything? Anybody who could contribute the Windows batch file wrapper for the xmlstarlet call?

    Tuesday, January 27, 2009

    Groovy Scripting for Alfresco - Alf Hack # 1

    This is the first post of my Alfresco Hacks series, showing some very useful tricks for the development with Alfresco. After working with Alfresco since beginning of 2007 with Version 1.4, I feel now well prepared for sharing some code. I do appreciate any feedback, feel free to comment and add suggestions. I'm tired of doing the somewhat lengthy process of editing java source, compiling, alfresco.war building, deploying to tomcat and finally starting tomcat. Just to try something out, this is too tedious. Therefore I gave the Groovy Server from http://iterative.com/GroovyServer.tar.gz a try. It will give you access to a Groovy shell using:
    telnet localhost 6789
    The simple steps I did:
    • build the groovyserver.jar
    • copy groovyserver.jar, groovy-all*.jar, jline*.jar to WEB-INF/lib/
    • add this to a spring context file grooyserver-context.xml in the extensions directory:
    <bean id="groovyService" abstract="true" method="initialize" method="destroy">
    <property name="bindings">
    <map>
    <entry key="ServiceRegistry" ref="ServiceRegistry">
    </map>
    </property>
    </bean>
    
    <bean id="groovyShellService" class="com.iterative.groovy.service.GroovyShellService" parent="groovyService">
    <property name="socket" value="6789">
    <property name="launchAtStart" value="true">
    </bean>
    
    After starting Alfresco up, the Groovy shell can be access with a telnet client connection to port 6789 on the Alfresco server:
    lothar@lothar-laptop:~$ telnet localhost 6789
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    Groovy Shell (1.6-RC-2, JVM: 1.6.0_11)
    Type 'go' to execute statements; Type 'help' for more information.
    groovy>
    
    As an example, searching for the term "alfresco":
    import org.alfresco.service.cmr.repository.StoreRef;
    import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
    workspaceStoreRef = new StoreRef("workspace://SpacesStore");
    ServiceRegistry.getAuthenticationService().authenticate("admin", "admin".toCharArray());
    retryTXService = ServiceRegistry.getRetryingTransactionHelper();
    def doWork() {
    results = ServiceRegistry.getSearchService().query(workspaceStoreRef, "lucene", "alfresco");
    for(r in results) { out.println(r.document); }
    }
    def sow = [ execute: { doWork() } ] as RetryingTransactionCallback<Void>;
    retryTXService.doInTransaction(sow);
    go
    
    Looks simple? Not at the first glance, but it is easy. The real work has to be put into the doWork() function lines 6 to 9. The rest can stay the same, it is just plumbing code. Conclusion: Now the Alfresco API is just a very small step away. Would like to try the VersionService? Just do a ServiceRegistry.getVersionService()..... and fire up your Groovy script. Other links to Alfresco and Groovy: WebScripts with Groovy: http://gradecak.blogspot.com/2008/04/alfresco-webscripts-with-groovy.html Alfresco with Grails: http://forge.alfresco.com/projects/minigrails/