Wednesday, September 19, 2012

Getting closer to Alfresco on the command line


I recently kicked of a small GPL project to work with the Alfresco repository from the command line. Check out the tools here http://code.google.com/p/alfresco-shell-tools/.

Since its early stage a month ago I am already using the project at several customers to get my admin tasks done more easily.



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.