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;
    }

}

No comments: