Extending Alfresco

Alfresco provides an extension mechanism that keeps customizations separate from the files that are a part of the Alfresco distribution. It is important to keep your customizations separate from Alfresco's in order to help streamline future upgrades and to simplify troubleshooting.

Understanding the Extension Mechanism

When you implement Alfresco, you will inevitably identify things you want to tweak and potentially define an entirely new functionality that you would like to add to the platform. The approach you will use to extend Alfresco with your own customizations is the same you use when extending other Spring or JSF-based web applications. You will extend the existing or write new Spring beans, JSF-managed beans, Java classes, JSPs, tag libraries, and other files. The custom files are deployed as a part of the Alfresco web application. The trick is to override or extend Alfresco with your own code, while keeping those extensions and customizations separate from the Alfresco core. This means you have to know what goes where and the answer to that depends on what's being extended.

When implementing your solution, you are likely to create or customize one or more of the following types of files:

  • Standard Java web application files (JSP pages, tag libraries, resource bundles, and Java classes)
  • Framework files (Spring beans, JSF-managed beans, and their associated configuration files)
  • Alfresco configuration files (properties files and XML files)
  • Solution-specific files (content models, business process definitions, web scripts, JavaScript, and Freemarker templates)

Let's look at how each of these areas is extended.

Standard Java Web Application Files

Given that you probably have experience developing Java web applications, there is not much need to go into detail here. The Java classes you develop as a part of your customization will go into a JAR file. The JAR file will reside in the WEB-INF|lib of the Alfresco web application. Therefore, you should name it such that it is easy to spot among the hundred or so JAR files distributed as part of Alfresco. Similarly, any tag libraries that you develop will reside in WEB-INF alongside Alfresco's taglibs.

Custom JSP pages should be kept separate from Alfresco's to make them easier to identify. One way to do this is to create an extension directory under jsp, but it is completely up to you. The same goes for scripts, images, and CSS.

Framework Files

Alfresco makes heavy use of the Spring Framework for Inversion of Control (IoC) and the MyFaces implementation of the JavaServer Faces (JSF) standard for web client user interface components and navigation. Each of these frameworks can be extended.

Spring Configuration Files

Spring configuration files always end in -context.xml. That's how Spring knows to read them when the application starts up. Alfresco's Spring configuration files reside in WEB-INF|classes|alfresco.

You can override Alfresco's Spring bean configurations with your own by creating custom Spring configuration files. Custom Spring configuration files must follow the *-context.xml naming convention. They need to reside in a directory called alfresco|extension, but that directory can be anywhere on the Classpath. The convention is to use either the shared|classes directory in $TOMCAT_HOME or WEB-INF|classes.

Note

If you are curious, Alfresco (or more specifically, Spring) knows to load custom context files in the alfresco|extension directory because that's what is configured in WEB-INF|classes|alfresco|application-context.xml. You can find other interesting Spring configuration files by searching web.xml for context.xml.

Let's look at an example. Here's the out of the box Alfresco Spring bean that's used to tell Alfresco about the default custom content model file:

<bean id="extension.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
    <property name="models">
        <list>
            <value>alfresco/model/defaultCustomModel.xml</value>
        </list>
    </property>
</bean>

This Spring bean happens to reside in a file called core-services-context.xml, but that's not important. Suppose you want to configure Alfresco with an additional custom content model, don't worry that you haven't yet learned what a content model XML file is; the point is that there is a bean defined as part of the out of the box configuration with a property ("models", in this case) we might want to set. To do that, you'd create a new file following the *-context.xml naming convention. In that file, you would place a bean configuration that sets the model's property the way you want it. The question you have to ask is: Do you want to completely override the exact same bean that Alfresco has configured out of the box, or do you want to create a new instance of a bean that would co-exist with Alfresco's?

In this example, you could override Alfresco's extension.dictionaryBootstrap bean by adding your own bean to your context file with the matching bean ID, and then set the model's property as needed like this:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- Registration of new models -->
<bean id="extension.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
        <property name="models">
            <list>
                <value>alfresco/extension/model/scModel.xml
                </value>
                <value>alfresco/extension/model/scWorkflowModel.xml
                </value>
            </list>
        </property>
</bean>
</beans>

The custom Spring configuration file could be named anything you want as long as it ends with -context.xml. In this example, the models property points to two custom model XML files called scModel.xml and scWorkflowModel.xml. Your custom bean would override Alfresco's extension.dictionaryBootstrap bean because it loads the custom context files last.

For this particular bean, the problem with overriding extension.dictionaryBootstrap is that someone else might develop an add-on module for Alfresco that overrides the same bean. The one that gets loaded last will win, which may not be what you want. So instead, in this case, the better approach is to create a unique bean ID such as this:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>
    <!-- Registration of new models -->
    <bean id="someco.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
        <property name="models">
            <list>
                <value>alfresco/extension/model/scModel.xml</value>
                <value>alfresco/extension/model/scWorkflowModel.xml</value>
            </list>
        </property>
    </bean>
</beans>

When adding new beans to your context files, make sure you are making a conscious decision regarding whether you are deliberately overriding a bean with your own definition, or are simply creating a new instance of a bean instead. Neither one is always right; it just depends on what you are trying to do.

Note

If you aren't familiar with Spring, you might be wondering about the "parent" and "depends-on" attributes. A parent bean is like a template: The child inherits the parent bean's configuration and can override the parent settings. The depends-on attribute is used to make sure that Spring instantiates the dependent bean first. Learn more about the Spring Framework at http://www.springframework.org.

There are many Spring beans configured in Alfresco out of the box. The Appendix includes a list of the beans that are considered "public services". In addition to the Appendix, you can also use the files distributed with Alfresco. The out of the box Spring bean configuration files are named *-context.xml, and reside in WEB-INF|classes|alfresco. Within the Alfresco distribution, the extensions|extension directory contains a set of sample files. If you wanted to set up some scheduled actions, for example, you could copy scheduled-actions-services-context.xml.sample from the distribution to your extension directory, rename it to scheduled-actions-services-context.xml, and then modify it as needed.

JavaServer Faces Configuration Files

Alfresco uses JavaServer Faces (JSF) for its UI component model. Alfresco uses the MyFaces implementation of the JSF specification. Note that for the rest of the book, it will be referred to simply as "JSF" even though, technically, JSF is a specification and MyFaces is the implementation of that spec.

Like Spring, JSF relies on XML files to configure the framework. Alfresco's JSF files reside in the WEB-INF directory, and all begin with "faces". Custom JSF components and navigation rules are declared in a faces configuration file. Unfortunately, there is not as much flexibility in the location of your JSF configuration files as there is with Spring. For JSF, it comes down to two choices: WEB-INF|faces-config-custom.xml or faces-config.xml in META-INF within a JAR file that resides in Alfresco's

WEB-INF|lib directory. Which one you use depends on what you are trying to do. If you are overriding Alfresco's existing JSF navigation rules, you have to go the META-INF route. If you are overriding Alfresco's existing JSF component declarations, you have to go the WEB-INF route. If you aren't overriding anything, you can pick either.

For example, let's suppose you want to override the login.jsp page with a custom version. As previously discussed, the new JSP should go into jsp|extension to keep it separate from Alfresco's JSPs. Alfresco uses JSF to configure navigation rules related to login. If you search the files within the WEB-INF directory for the string login.jsp, you'll see that the JSF file that defines the navigation rules related to login is faces-config-navigation.xml. In this example, the goal is to use a custom login page. That means, the existing navigation rules pointing to the out of the box login.jsp page have to be overridden. To change the navigation to use the custom login.jsp page, copy the appropriate navigation-rule elements (there are two) into a new file called faces-config.xml and change the from-view-id and to-view-id elements to reference the custom JSP as follows:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>

   <navigation-rule>
      <description>
         The decision rule used by the NavigationHandler to
         determine which view must be displayed after the
         current view, login.jsp is processed.
      </description>
      <from-view-id>/jsp/extension/login.jsp</from-view-id>
      <navigation-case>
         <description>
             Indicates to the NavigationHandler that the browse.jsp
             view must be displayed if the Action referenced by a 
             UICommand component on the login.jsp view returns 
             the outcome "success".
         </description>
         <from-outcome>success</from-outcome>
         <to-view-id>/jsp/browse/browse.jsp</to-view-id>
      </navigation-case>
   </navigation-rule>

   <!-- rule to get back to the login page from anywhere -->
   <navigation-rule>
      <from-view-id>/jsp/*</from-view-id>
      <navigation-case>
         <from-outcome>logout</from-outcome>
         <to-view-id>/jsp/extension/login.jsp</to-view-id>
      </navigation-case>
      <navigation-case>
         <from-outcome>relogin</from-outcome>
         <to-view-id>/jsp/extension/relogin.jsp</to-view-id>
      </navigation-case>
   </navigation-rule>   
</faces-config>

Because this Faces configuration overrides existing navigation rules, the file has to be called faces-config.xml and it has to reside in the META-INF directory of a JAR file placed in Alfresco's WEB-INF|lib directory.

Note that this isn't a complete example for customizing the login page. There's another step involving an Alfresco configuration file. Alfresco configuration files are covered in the next section.

Alfresco Configuration Files

Not all configurations are handled through standard frameworks such as Spring and JSF. Some configuration changes are made using properties files or Alfresco configuration XML. For example, the most commonly customized properties file is custom-repository.properties. In it, you can find things such as the username and password used to connect to the underlying relational database, the database driver, and the data directory file path. Like the Spring files, there are sample properties files in the extensions|extension directory of the Alfresco distribution.

The full out of the box repository.properties file resides in WEB-INF|classes|alfresco. To override any of the properties in that file, add custom values to a file called custom-repository.properties in the alfresco|extension directory. This file is specifically named in the repository-properties Spring bean so if, for some reason, you have more than one or you want to change the name, you must specify the updated properties file location in the configuration for the repository-properties bean. By default, that bean is configured in custom-repository-context.xml.

When you browse the WEB-INF|classes|alfresco directory, you'll see several XML files that are not Spring configuration files (they don't end in context.xml). These are configuration files that use Alfresco's configuration mechanism. The list of these files can be found in the configuration for the webClientConfigSource bean that resides in WEB-INF|classes|alfresco|web-client-application-context.xml.

The best example of a file of this type is web-client-config.xml. An earlier section discussed overriding the default login page by creating a new login.jsp and modifying the JSF navigation rule. In addition to the navigation rules, Alfresco has a setting that tells it what the initial login page should be. That setting is in web-client-config.xml. A portion of the relevant section of that file is shown here:

<config>
        <admin>
                <initial-password>admin</initial-password>
        </admin>

        <client>
                <!-- the error page the client will use -->
                <error-page>/jsp/error.jsp</error-page>

                <!-- the login page the client will use -->
                <login-page>/jsp/login.jsp</login-page>

To complete the custom login example, the login-page element needs to be updated to point to the custom login JSP. The override has to happen in a file named web-client-config-custom.xml residing in alfresco|extension. It can't be named anything else because the webClientConfigSource bean names it explicitly. In this case, the web-client-config-custom.xml file would contain the following:

<config>
        <client>
                <!-- the login page the client will use -->
                <login-page>/jsp/extension/login.jsp</login-page>

Note that only the settings that need to be overridden are repeated in web-client-config-custom.xml. In this case, that's just the login-page element.

If you look at web-client-config.xml, you'll notice a few different types of elements. Depending on what's being configured, Alfresco configuration XML might use elements with specific names (admin, client, and login-page in the previous example). In other cases, an element named config is used with evaluator and condition attributes to define exactly what's being configured. Consider the configuration that specifies what languages should be shown in the drop-down box on the login page. The language configuration in the out of the box web-client-config.xml file looks like this:

<config evaluator="string-compare" condition="Languages">
        <!-- the list of available language files -->
        <languages>
                <language locale="en_US">English</language>
        </languages>
</config>

To override or extend this list, the config element can be copied to web-client-config-custom.xml and updated as needed:

<config evaluator="string-compare" condition="Languages">
        <languages>
                <language locale="ca_ES">Catalan</language>
                <language locale="hr_HR">Croatian</language>
                <language locale="cs_CZ">Czech</language>
                <language locale="da_DK">Danish</language>
        </languages>
</config>

In this case, four new languages would be added to the dropdown for a total of five languages. If, instead, you wanted to replace English with only these four choices, you would add the replace attribute to the config element like this:

<config evaluator="string-compare" condition="Languages" replace="true">
        <languages>
        <language locale="ca_ES">Catalan</language>
        <language locale="hr_HR">Croatian</language>
        <language locale="cs_CZ">Czech</language>
<language locale="da_DK">Danish</language>
</languages>
</config>

Solution-Specific Files

There are several types of files that, conceptually, sit on top of the Alfresco platform as a part of the solution you are implementing with Alfresco. These include files such as content models, business process definitions, web scripts, server-side JavaScript, and FreeMarker templates. Alfresco ships several of these with the product. Some are purely meant to be used as examples, while others are functional pieces of the platform. With the exception of content models that can import and extend Alfresco's out of the box content models, there is no need to extend these files directly. Instead, your custom versions of these types of files will sit alongside Alfresco's.

These files can live on the file system or in the repository. When they reside on the file system, they need to be on the classpath. So they should be placed in the alfresco|extension directory. When stored in the repository, each type of file has a designated folder within the |Company Home|Data Dictionary folder.

For example, suppose you write a JavaScript file to implement the business logic for an action. Ignoring the details on actions for the moment, you can choose to store that JavaScript file either on the file system, perhaps in a directory called scripts in the alfresco|extension directory, or in the repository within the Scripts folder under Data Dictionary.

Business process definitions can live on the file system and in the repository, but they can also be deployed directly to the JBoss jBPM engine. This will be discussed in the Advanced Workflow chapter.

Avoid Modifying Alfresco Code and Configuration

Now that you've got the full source code for Alfresco, it may be tempting to make changes directly to the Alfresco web application. Avoid this temptation at all costs. As soon as you recompile an Alfresco class, tag library, JSP page, or even a configuration file, you've complicated your life unnecessarily.

"But this is open source," you may say, "Why can't I change it if I want to?". Yes, it is open source and, yes, you can change it to your heart's content. But when you do so, be prepared to effectively take ownership of that code going forward. It will be up to you to figure out whether a problem is in your code or Alfresco's. You will also have to devote more time to upgrades than you would have, had you kept your customizations separate from Alfresco's code. In the U.S., a phenomenon that seems to be peculiar to antique shop owners is the posting of a sign that reads, "You break it, you bought it". That certainly applies to modifying the Alfresco source code directly.

There are three situations when, try as you might, you may have to touch Alfresco's files. First, you may identify a bug in the Alfresco source as well as a fix. In this situation, the best thing to do is to put the fix in place as a temporary measure, file a JIRA ticket, and attach the fix to the ticket. Alfresco will confirm the bug and implement the fix (sometimes using your code unchanged and sometimes with its own). Enterprise customers can usually get a patch right away. Labs users may have to run on their own fix until the problem is resolved in a future build.

The second situation is that sometimes (and this is increasingly rare) there may be a configuration change or customization that cannot be implemented through the standard extension mechanism. This is really no different than the previous scenario. If this happens, treat it like a bug. Make the change then file a ticket. Alfresco's intent is for the product to be extensible without the need to change the core installation, so anything less than that is a bug.

The third situation is that you might identify a product enhancement that cannot be implemented without modifying the source. In this case, it is a very good idea to talk to Alfresco (either your representative or via the forums) before you start coding, particularly if it is a big change. They might already be working on the same enhancement. Or, they might be able to gauge demand for the enhancement as well as provide implementation advice. If this enhancement is critical to your solution but no one else is interested in it, you'll have a hard decision to make because it may be a long time before the enhancement gets incorporated into the product. Moving forward without Alfresco's commitment to incorporate the enhancement into the product could mean supporting your own fork of the Alfresco source for the foreseeable future.