Sunteți pe pagina 1din 151

API Development Course

Copyright (c) 2007 by Alfresco and others.


Information in this document is subject to change without notice. No part of this document may
be reproduced or transmitted in any form or by any means, electronic or mechanical, for any
purpose, without the express written permission of Alfresco. The trademarks, service marks,
logos or other intellectual property rights of Alfresco and others used in this documentation
(Trademarks) are the property of Alfresco and their respective owners. The furnishing of this
document does not give you license to these patents, trademarks, copyrights or other intellectual
property except as expressly provided in any written agreement from Alfresco.
The United States export control laws and regulations, including the Export Administration
Regulations of the U.S. Department of Commerce, and other applicable laws and regulations
apply to this documentation which prohibit the export or re-export of content, products, services,
and technology to certain countries and persons. You agree to comply with all export laws,
regulations and restrictions of the United States and any foreign agency or authority and assume
sole responsibility for any such unauthorized exportation.
If you need technical support for this product, contact Customer Support by email at
support@alfresco.com. If you have comments or suggestions about this documentation, contact
us at documentation@alfresco.com.
This edition applies to version 2.1 of the licensed program.

Contents
Introduction................................................................................................................................ 6
Welcome.............................................................................................................................. 7
How this course is delivered....................................................................................... 7
Course Schedule......................................................................................................... 7
How Prepared are You?..............................................................................................8
Topics not Covered..................................................................................................... 8
Introductions.................................................................................................................8
Getting Started...........................................................................................................................9
Alfresco SDK..................................................................................................................... 10
Introduction.................................................................................................................10
Downloading and Unpacking the Alfresco SDK........................................................ 10
Importing the Alfresco SDK projects into Eclipse......................................................11
Associating Source Code and Javadocs with the Alfresco Libraries......................... 14
SDK samples............................................................................................................. 16
SVN Repository......................................................................................................... 19
SDK Reference.......................................................................................................... 20
Best Practices....................................................................................................................22
Coding Standards...................................................................................................... 22
Alfresco Module Packages (AMP).............................................................................22
Alfresco Repository Architecture....................................................................................... 24
Out of the Box........................................................................................................... 24
Service & Component Architecture........................................................................... 25
Repository Foundation Services API.........................................................................27
Repository APIs......................................................................................................... 28
Repository Server Protocols...................................................................................... 29
Terminology................................................................................................................30
Developing against the Alfresco Repository........................................................................33
Spring Framework............................................................................................................. 34
Introduction.................................................................................................................34
Bean Factory..............................................................................................................34
Inversion of Control (IoC).......................................................................................... 35
Application Context.................................................................................................... 39
Foundation Services API................................................................................................... 40
Introduction.................................................................................................................40
Access to Repository Foundation Services...............................................................40
FirstFoundationClient walkthrough............................................................................ 40
Other Foundation Services........................................................................................ 48
JCR API............................................................................................................................. 50
Introduction.................................................................................................................50
JCR Compliance Levels............................................................................................ 50
JCR Repository Model...............................................................................................50

API Development Course 3

FirstJCRClient Walkthrough.......................................................................................51
JCR Transactions...................................................................................................... 53
Web Services API............................................................................................................. 55
Introduction.................................................................................................................55
Available Web Services.............................................................................................55
Access to Web Services in Java...............................................................................55
Web Services Data Types.........................................................................................56
Content Manipulation Language (CML).....................................................................56
FirstWebServiceClient Walkthrough.......................................................................... 57
Separating Concerns using AOP...................................................................................... 61
Public Services and AOP proxies............................................................................. 61
Security Enforcement.................................................................................................61
Transaction Management.......................................................................................... 64
Extending the Alfresco Repository....................................................................................... 69
Repository Policies............................................................................................................ 70
Introduction.................................................................................................................70
Available Policies....................................................................................................... 70
Policy Types...............................................................................................................71
Custom Aspect with Behaviour Howto...................................................................... 71
Repository Actions.............................................................................................................79
Introduction.................................................................................................................79
Repository Action Howto........................................................................................... 79
Repository Action with Parameters Howto................................................................ 85
Content Transformers........................................................................................................91
Introduction.................................................................................................................91
Content Transformer Howto...................................................................................... 91
ContentTransformerRegistry...................................................................................... 95
Further Reading......................................................................................................... 97
Metadata Extractors...........................................................................................................98
Introduction.................................................................................................................98
MetadataExtracterRegistry.........................................................................................98
Metadata Extractor Howto......................................................................................... 99
Further Reading....................................................................................................... 103
Extending the Alfresco Web Client..................................................................................... 105
JavaServer Faces............................................................................................................106
Introduction...............................................................................................................106
Login Page Walkthrough......................................................................................... 106
Actions Framework.......................................................................................................... 116
Introduction...............................................................................................................116
Update UI Action Walkthrough................................................................................ 116
Actions Framework Reference................................................................................ 120
Dialog Framework............................................................................................................123
Introduction...............................................................................................................123

4 Alfresco Enterprise Edition Version 3.2

Custom Dialog Howto..............................................................................................123


Further Information.................................................................................................. 128
Dialog Framework Reference.................................................................................. 130
Wizard Framework...........................................................................................................132
Introduction...............................................................................................................132
Custom Wizard Howto.............................................................................................132
Further Reading....................................................................................................... 137
Wizard Framework Reference................................................................................. 139
Packaging Extensions.......................................................................................................... 142
Alfresco Module Packages.............................................................................................. 143
Introduction...............................................................................................................143
Basic AMP Howto....................................................................................................143
Further Reading....................................................................................................... 149
Alfresco Module Package Reference...................................................................... 150

API Development Course 5

Introduction

Introduction

6 Alfresco Enterprise Edition Version 3.2

Introduction

Welcome
Welcome to Alfresco API development. This course has been designed to provide you with the
knowledge and skills necessary to develop against the Alfresco Repository APIs and to develop
extensions for the Alfresco enterprise content management system.

How this course is delivered


This course is delivered in 17 modules, each module focusing on a specific subject. All modules
include a combination of introductory materials, an outline of objectives followed by instructorled discussions and walkthroughs. Each module will close with one or more exercises which will
allow students to use and demonstrate what they have learned in each module.

Course objectives
At the conclusion of this course you should be comfortable with all the concepts and tasks
required to competently:
Set up your own development environment and use the Alfresco SDK
Develop against the Alfresco APIs (Foundation Services, JCR, Web Services)
Develop extensions for the Alfresco Repository
Develop extensions for the Alfresco Web Client
Package and deploy extensions

Certification track objectives


At the conclusion of this course, you should have also acquired the requisite knowledge and
abilities to pass the Alfresco API developer exam in preparation for the Certified Alfresco ECM
professional certificate.

Course Schedule
Day 1
Getting Started
Alfresco SDK
Best Practices
Alfresco Repository Architecture
Developing against the Alfresco Repository
The Spring Framework
Foundation Services API
Java Content Repository (JCR) API
Web Services API

Day 2
Developing against the Alfresco Repository
Separating Concerns using AOP
Extending the Alfresco Repository
Repository Policies
Repository Actions

API Development Course 7

Introduction

Content Transformers
Metadata Extractors

Day 3
Extending the Web Client
JavaServer Faces
Actions Framework
Dialog Framework
Wizard Framework
Packaging Extensions
Alfresco Module Packages

How Prepared are You?


Can you program in Java?
Do you have a basic understanding of XML?
Have you followed the Alfresco administration course?
Have you followed the Alfresco customisation course?
Are you familiar with the Eclipse IDE?

Topics not Covered


Installing Alfresco
Repository configuration
Web Client configuration
Customising the Data Dictionary
Developing JavaScript extensions
Developing Freemarker templates
Developing Web Scripts

Introductions
Name
Company
Title, function, job responsibility
Alfresco experience (API development experience)
Courses already taken
Reasons for taking this course
Expectations

8 Alfresco Enterprise Edition Version 3.2

Getting Started

Getting Started

API Development Course 9

Getting Started

Alfresco SDK
Introduction
The Alfresco SDK provides support for developers who wish to extend or customise the Alfresco
platform. It has been designed for the developer to get developing with minimal fuss for the
following development scenarios:
Developing extensions for the Alfresco Repository and Web Client.
Embedding Alfresco into applications via Alfresco's Java Foundation Services API or
standards-compliant JCR API.
Developing applications against a remote Alfresco Repository via Alfresco's Web Services
API.
Typically, the SDK is used stand-alone, but an Alfresco installation is also required if performing
any of the following:
Customising the Alfresco Web Client
Deploying a custom module to a remote Alfresco repository
Testing a custom application that connects to a remote Alfresco repository
The SDK is not designed for re-building Alfresco since it does not provide full build scripts
and artifacts. If you wish to develop bug fixes or extend the core functionality of the
Alfresco platform, you should use the full Alfresco development environment provided in
the Alfresco SVN Repository. For more information, see SVN Repository on page 19.

Downloading and Unpacking the Alfresco SDK


You will need to install the following software and development tools in order to use the SDK
correctly:
the Java SE Development Kit (JDK) version 1.5 (5.0) or above;
a supported database of your choice (MySQL is recommended for development
purposes);
the Eclipse IDE 3.x (highly recommended).
The Alfresco SDK bundle is provided with each release of Alfresco, for both the Enterprise and
Community Networks.
1. Downloading the Alfresco SDK
The Enterprise SDK is only available to Enterprise clients and partners. The Community
SDK is freely available and can be downloaded from the Download Alfresco Community
Network page on the Alfresco Developer web site. The SDK is provided in both ZIP and
TAR (tar.gz) formats.
2. Unpacking the Alfresco SDK
To install the SDK, simply unpack the downloaded ZIP or TAR bundle to a directory of your
choice.

10 Alfresco Enterprise Edition Version 3.2

Getting Started

For a description of the contents of the Alfresco SDK, see SDK Contents on page 20.

Importing the Alfresco SDK projects into Eclipse


When using the Alfresco SDK, the Eclipse IDE is highly recommended. The SDK contains
several pre-configured Eclipse projects that you can import directly into Eclipse with the following
procedure.
1. Setting the Eclipse Compiler Compliance Level
Alfresco uses Java 1.5 (5.0) language features, therefore Eclipse must be configured
appropriately for the Java SE Development Kit (JDK) version 1.5 (5.0) or above.
a.

From the Eclipse main menu, select Window # Preferences...

b.

In the Preferences dialog, select Java # Compiler in the tree view.

c.

In the JDK Compliance panel, set the Compiler compliance level to 5.0 or above:

API Development Course 11

Getting Started

d.

Click OK

2. Importing the Alfresco SDK projects


a.

From the Eclipse main menu, select File # Import...

b.

In the Import dialog, select General # Existing Projects into Workspace import
source and click Next >

c.

Choose Select root directory option and click Browse...

d.

Navigate to the file system directory where you unpacked the Alfresco SDK and click
OK. The Alfresco SDK projects are now listed under Projects.
Do not navigate to the samples sub-directory, otherwise you will not see the SDK
AlfrescoEmbedded and SDK AlfrescoRemote projects in the list.

12 Alfresco Enterprise Edition Version 3.2

Getting Started

In order to run the samples or to develop your own extension modules, you must
import at least the SDK AlfrescoEmbedded and SDK AlfrescoRemote projects. The
other SDK projects are samples for common development scenarios that you can
study and run to learn more about developing Alfresco extension modules.
For more information about the available projects, see SDK Eclipse Projects on page
20.
e.

Once you have selected the projects you wish to import, click Finish.

The imported projects are displayed in the Package Explorer:

API Development Course 13

Getting Started

Associating Source Code and Javadocs with the Alfresco Libraries


Once the Alfresco SDK projects have been imported into Eclipse, it is useful to have access to
Alfresco's source code and Java documentation. The following procedure explains how to do this
by associating the source code and Javadocs with the Alfresco libraries within Eclipse.
1. Expand the SDK AlfrescoEmbedded project in the Project Explorer.
2. Right click on the alfresco-repository.jar and select Properties from the popup
menu.
The JAR files may not be in alphabetical order.
3. Associating source code
a.

In the Properties for alfresco-repository.jar dialog, select Java Source


Attachment in the tree view.

b.

In the Java Source Attachment panel, click External File...

c.

Navigate to src directory within your unpacked Alfresco SDK.

d.

Select repository-src.zip and click Open.

14 Alfresco Enterprise Edition Version 3.2

Getting Started

4. Associating Javadocs
a.

In the Properties for alfresco-repository.jar dialog, select Javadoc Location in the


tree view.

b.

In the Javadoc Location panel, select Javadoc in archive and click Browse...

c.

Navigate to doc/api directory within your unpacked Alfresco SDK.

d.

Select repository-doc.zip and click Open.

e.

Click Validate... to validate the Javadoc location, then click either OK to view the
Javadocs in a web browser or Cancel if not.

API Development Course 15

Getting Started

5. Click OK, to close the Properties dialog.


Once the source code and Javadocs have been attached, the alfresco-repository.jar icon
changes to include a small document:

The above steps need to be repeated for the following JARs:

alfresco-core.jar

alfresco-remote-api.jar

alfresco-web-client.jar

alfresco-web-service-client.jar (only Java source code is available)

SDK samples
FirstFoundationClient

and JCR Samples

The SDK FirstFoundationClient, SDK FirstJCRClient and SDK JCRSamples sample projects
demonstrate how to access an embedded Alfresco repository via the Foundation Services API
and the standards-compliant JCR API. These samples can be tested directly from within Eclipse
and will automatically start an Alfresco repository in embedded mode.
Before starting, the embedded repository needs to be configured. By default, the sample projects
are configured to use a MySQL database named alfresco and a data directory with a relative
path of ./alf_data. These parameters are defined in the custom-repository.properties
file in the source/alfresco/extension directory of each project. It is good practice to define
an absolute path for the data directory (dir.root parameter) and to configure all of the SDK
projects to share the same database and the same dir.root.
Example custom-repository.properties file:
dir.root=C:/alf_data

16 Alfresco Enterprise Edition Version 3.2

Getting Started

#db.username=alfresco
#db.password=alfresco
#
# MySQL connection (This is default and requires mysql-connector-java-3.1.12bin.jar, which ships with the Alfresco server)
#
#db.driver=org.gjt.mm.mysql.Driver
#db.url=jdbc:mysql://localhost/alfresco

The alfresco MySQL database also needs to be created using the scripts provided in the
extras/databases/mysql directory of the unpacked Alfresco SDK. To create the database from
the command line:
C:\alfresco-enterprise-sdk\extras\databases\mysql>mysql -u root -p <
db_setup.sql
Enter password: ********

The samples can now be tested by running the main Java classes from within Eclipse. In the
following example, the FirstFoundationClient class is run as a Java Application:

The Alfresco repository is automatically started in embedded mode. Because this is the first time
the repository has been started, the initial bootstrap is executed to create the database tables. As
you can see from the console messages below, the embedded repository uses C:\alf_data as
it's data directory (dir.root).

API Development Course 17

Getting Started

The other embedded repository samples (SDK FirstJCRClient and SDK JCRSamples) can be
run in the same way.

Web Services Samples


The SDK FirstWebServiceClient and SDK WebServiceSamples sample projects demonstrate
how to access a remote Alfresco repository via the Web Services API. A remote Alfresco
repository needs to be installed and running before testing one of these samples.
Before running one of the Web Services samples, a remote repository needs to be installed
and configured. The easiest solution for development purposes is to install Alfresco on the local
machine and configure it to use the same alfresco MySQL database and the same C:/alf_dir
data directory as the embedded repository used for the other samples.
The location of the remote repository is configured in the webserviceclient.properties
file in the source/alfresco/extension directory of each Web Services project. If the remote
repository is installed on the same machine and configured to use the default 8080 port, you will
not have to modify the default value.
Example webserviceclient.properties file:
#
# Set the following property to reference the Alfresco server that you would
like web service client
# to communicate with
repository.location=http://localhost:8080/alfresco/api

Once the remote repository has been installed and started, the Web Clients samples can be
tested by running the main Java classes from within Eclipse. In the following example, the
FirstWebServiceClient class is run as a Java Application:

Custom Repository Samples


The SDK CustomAction and SDK CustomAspect sample projects demonstrate how to develop
custom modules that may be deployed to an Alfresco repository. Initially, custom repository

18 Alfresco Enterprise Edition Version 3.2

Getting Started

modules can be developed and tested using unit tests and an embedded Alfresco repository. The
SDK CustomAspect project has a sample unit test that does this.
An Ant build.xml file is provided for packaging the repository samples. The package target
packages the compiled classes and extension files into a JAR file. To deploy the samples,
copy the JAR file to the WEB-INF/lib folder of an existing Alfresco installation and restart the
application server.
For deployment in a production environment, a custom repository module should be packaged as
an Alfresco Module Package (AMP).
For more information on creating a custom repository action, see the Repository Action Howto
on page 79. For a detailed presentation of the SDK CustomAspect sample, see the Custom
Aspect with Behaviour Howto on page 71.

Custom Web Client Samples


The SDK CustomDialog, SDK CustomJSP, SDK CustomLogin, SDK CustomWizard, and SDK
TaggingSample sample projects demonstrate how to develop custom modules for the Alfresco
Web Client. Custom Web Client modules have to be deployed to an existing Alfresco installation
for testing.
An Ant build.xml file is provided for packaging the Web Client samples. The packagejar target packages the compiled classes and extension files into a JAR file. The packageextension target then packages the JAR file along with the JSPs into a ZIP file. The optional
integrate-extension target can be used to integrate the packaged ZIP file into an Alfresco
Web Client WAR file. The alfresco.war file must be copied to the same directory as the
build.xml file before running the Ant build and then re-deployed to the application server.
For deployment in a production environment, a custom Web Client module should be packaged
as an Alfresco Module Package (AMP).
The SDK CustomDialog and SDK CustomWizard are presented in detail in the Custom Dialog
Howto on page 123 and the Custom Wizard Howto on page 132. The SDK TaggingSample
sample is presented in detail in the Repository Action Howto on page 79.

Basic AMP Sample


The SDK Basic AMP sample project demonstrates how to structure a project and how to arrange
classes and configuration files to generate an Alfresco Module Package (AMP). For more
information see Basic AMP Howto on page 143.

SVN Repository
The Alfresco Subversion repository gives you access to all of the Alfresco source code and build
artifacts. It provides the latest work-in-progress developments. It should only be used if you wish
to extend the Alfresco core framework or work on Alfresco bug fixes as it allows you to perform
full re-builds of Alfresco itself. For most requirements, it is best to use the Alfresco SDK.
Public read-only access to the Alfresco Subversion repository is available from the Alfresco web
site. To checkout the source code, use the following procedure:
1. Install Subversion and ensure that svn is on the path.
2. Checkout the HEAD of the code stream:
svn co svn://svn.alfresco.com/alfresco/HEAD

or
svn co http://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/HEAD

3. Keep up to date by issuing the command:


svn update

API Development Course 19

Getting Started

SDK Reference
SDK Contents
bin

Supporting dll's, exe's.


doc

Zipped Javadoc's for all pre-built libraries.


extras

Additional files - database setup and migration scripts.


lib

Alfresco pre-built libraries (JAR files).


lib/deployment

Alfresco libraries required for WCM deployment to a remote server.


lib/remote

Alfresco libraries required for access to a remote Alfresco repository via web services.
lib/server

Alfresco libraries required for embedding an Alfresco repository.


licenses

License files.
samples

Sample Eclipse projects for common development scenarios (see SDK Eclipse Projects on
page 20).
src

Zipped source code for all pre-built libraries.


license.txt

Alfresco licence file.


notice.txt

Notices
readme.txt

Alfresco SDK readme.

SDK Eclipse Projects


SDK AlfrescoEmbedded

Project containing all of the Alfresco libraries needed to build a custom module that will be
embedded into the Alfresco repository or Web Client.
SDK AlfrescoRemote

Project containing all of the Alfresco libraries needed to build a custom Web Services client.
SDK Basic AMP

Sample project demonstrating how to build an AMP (Alfresco Module Package) file.
SDK CustomAction

Sample project demonstrating how to develop a custom Action that may be deployed to an
Alfresco repository.
SDK CustomAspect

Sample project demonstrating how to develop a custom Aspect with behaviour that may be
deployed to an Alfresco repository.

20 Alfresco Enterprise Edition Version 3.2

Getting Started

SDK CustomDialog

Sample project demonstrating how to develop and configure a custom Dialog for the Alfresco
Web Client.
SDK CustomJSP

Sample project demonstrating how to develop and configure a custom JSP for the Alfresco
Web Client.
SDK CustomLogin

Sample project demonstrating how to override the Login page of the Alfresco Web Client.
SDK CustomWizard

Sample project demonstrating how to develop and configure a custom Wizard for the Alfresco
Web Client.
SDK FirstFoundationClient

Sample project demonstrating how to access an Alfresco (embedded) repository via the
Foundation Services API.
SDK FirstJCRClient

Sample project demonstrating how to access an Alfresco (embedded) repository via the
standards-compliant JCR API.
SDK FirstWebServiceClient

Sample project demonstrating how to access a remote Alfresco repository via the Web
Services API.
SDK JCRSamples

More sample projects demonstrating how to access an Alfresco (embedded) repository via the
standards-compliant JCR API.
SDK TaggingSample

Advanced sample project demonstrating how to develop a custom Action that takes
parameters.
SDK WebServiceSamples

More sample projects demonstrating how to access a remote Alfresco repository via the Web
Services API.

API Development Course 21

Getting Started

Best Practices
Coding Standards
Coding Standards - Formatting
The core coding standards are the standard Java Code Conventions.
Braces are on new lines.
4 space for tabbing, except for Web Client project that uses 3 spaces.
120 characters on a line is fine.
Import declarations are managed by Eclipse's standard ordering rules (CTRL-SHIFT-O).
This helps prevent code merge conflicts.
XML documents use 3 space tabbing. The Eclipse plug-in, XMLBuddy, is generally used.

Coding Standards - Exceptions


When generating a new exception, always attach the cause to it.
Don't log exceptions unless you are adding the logic to absorb the exception.
Put as much context and formatting into the error message as possible.
Use RuntimeException derived exceptions, unless there is a really good reason to bother
the client code with a checked exception.
Pay attention to the Javadoc specification on unchecked exceptions. Don't declare them
on the interface, just in the Javadocs.

Coding Standards - Logging


Use the Apache Commons Logging API so that all logging output is uniform.
Use the class hierarchy categories, but where deviations are made, add comments to the
Javadocs.
INFO messages are only added at the request of Alfresco users. All other informative
messages are DEBUG.
Put as much context and formatting into the message as time will allow.
Wrap all calls to logger.debug and logger.info, and only log messages if
logger.isDebugEnabled and logger.isInfoEnabled respectively.

Coding Standards - File Formats


UTF-8 encoding of all text files
Windows line endings (CR-LF)

Alfresco Module Packages (AMP)


An Alfresco Module Package (AMP) is a collection of code, XML, images, CSS, etc. that
collectively extend the functionality or data provided by the standard Alfresco Repository. An AMP
file can contain as little as a set of custom templates or a new category. It can contain a custom
model and associated UI customisations. It could contain a complete new set of functionality, for
example records management. As a general rule of thumb, anything that is considered to be an
installable extension to the Alfresco repository should be called a module and packaged as an
AMP file.
AMP files can be installed into the Alfresco WAR using the Module Management Tool. An AMP
file has a standard format that can be customised if required.

22 Alfresco Enterprise Edition Version 3.2

Getting Started

Once the contents of the AMP file has been mapped into an Alfresco WAR using the Module
Management Tool, the WAR can be deployed to the application server. When the repository
is next started, the installed module configuration will be detected, and the repository will be
bootstrapped to include the new module functionality and data.

AMP Project Structure


An Alfresco Module project can be structured in any way that suits the developer environment. As
long as the resulting AMP file is packaged correctly and the required property and context files
are present, the module will install successfully.
The recommended project structure is as follows:
\
|-- source
|
|-- java
|-- <module package structure starts here>
|
|-- web
|-- css
|-- images
|-- jsp
|-- scripts
|
|-- config
|-- <resource package structure starts here>
|
|-- build
|-- dist
|-- lib
|
|-- build.xml
source/java/

Contains the Java source for the Alfresco Module.


source/web/

Contains any web UI resources (JSPs, images, CSS, JavaScript).


config/

Contains configuration files and resources used by the module.


build/

Build directory for compiled class files.


build/dist/

Build directory for AMP files.


build/lib/

Build directory for JAR files.


The recommended package structure for Java source (source/java), configuration files and
resources (config) is org.alfresco.module.<moduleid>, where moduleid is the unique
module id of the module.
Alfresco Module Packages are presented in more detail later on in the course. For more details,
see Introduction on page 143.

API Development Course 23

Getting Started

Alfresco Repository Architecture


Out of the Box
Out-of-the-box, Alfresco's simple installation procedure provides a pre-configured deployment
aimed at reaching a complete and working Content Management application as quickly and easily
as possible. The deployment is as follows:

This is typical of a web architecture, where an application server houses the logic for both the
user interface and domain. Storage of data and content is provided by persistent back-ends such
as a database or file system. Any number of web browsers can connect to the application without
prior client installation costs.
In this particular case, the application server houses both the Alfresco Application and the
Alfresco Repository. An Alfresco Application provides a complete solution tailored for a specific
area of Content Management such as Document Management (DM), Web Content Management
(WCM) and Records Management (RM). The Alfresco Repository provides a set of reusable
cross-cutting Content Management services such as content storage, query, versioning and
transformation which may be utilised by one or more applications.
Although this is the default installed deployment, it is only one of many ways of utilising the
capabilities and components of Alfresco. When we first set out to design Alfresco, we wanted to
break away from the mould of typical Content Management architectures which are monolithic
and closed. The result is that Alfresco can neatly fit into existing environments and each of its

24 Alfresco Enterprise Edition Version 3.2

Getting Started

components may be used in isolation or together to form the basis of many differing Content
Management solutions.
The remainder of this module explores the anatomy of the Alfresco Repository which will give a
good understanding of the concepts and capabilities and how it achieves openness, scalability
and flexibility.

Service & Component Architecture


Every part of the Alfresco Repository is either a component or a service. A component is
an implementation black box that provides a specific feature or capability. A service is an
interface entry point for a client to bind to and use. This fundamental approach allows for existing
components to be switched with new implementations, new components to be added with ease
and for clients to connect and use services without knowledge of how they're implemented.
If there's a feature of Alfresco you don't need, you can take it out, providing a lighter and possibly
faster Alfresco. If there's a feature you wish to re-implement, you can replace it, either by
providing a better implementation, or integrating with your existing environment.
Implementation of this approach is simplified by using the open source project Spring Framework
which Alfresco has taken to heart and has made a core foundation of its architecture. With
Spring, Alfresco components are declaratively configured and bound together. Aspect-oriented
programming allows the weaving of infrastructure concerns such as Transactions and Security
into components without polluting their implementation. Environment touch points are abstracted
such as resource (e.g. database) connections.
The Alfresco Repository structure looks like this:

API Development Course 25

Getting Started

The public interface point is the Alfresco Repository Foundation Services. Each service is
exposed as a Java Interface to which a Repository client can bind and invoke without knowledge
of its underlying implementation. A Service Registry lists the available services. Behind services
are the implementation black boxes i.e. components. Each service and component is configured
via the Spring framework in XML context files.
The Spring context file public-service-context.xml provides the configuration and
binding of the Alfresco Repository Foundation Services.
The Repository Foundation Services are the lowest level of public interface providing access
to all Repository capabilities. Binding to this interface is possible via the Repository Service
Registry, or via Spring dependency injection if the client is also Spring aware. Access to
Foundation Services is limited to Repository clients who reside in the same process as the
Repository. That is, the Foundation Services are an excellent API for clients who wish to embed
the Repository.
An important point to note is that the Foundation Services are where transaction and security
policies are enforced. The policies themselves are declaratively specified and enforced via the
injection of a transaction and security implementation into each service. Every service of Alfresco
is transactional and secure.
Other forms of API are provided too, however, all public entry points eventually go through this
layer.
Alfresco supports a common scheme for making extensions to the Repository i.e. configuring
a component, adding a new component or service, or removing capabilities. Extensions are

26 Alfresco Enterprise Edition Version 3.2

Getting Started

encapsulated outside of the core Repository and plugged-in automatically. This means the core
Repository can be upgraded to a newer version and extensions remain intact.

Repository Foundation Services API


The heart of the Alfresco Repository is responsible for the storage and retrieval of content. This
is split into nodes, content and index information. Nodes provide meta-data and structure to
content. A node may support properties (e.g. author) and relate to other nodes (e.g. represent
folder hierarchies or annotations). Content is the actual information being recorded e.g. a Word
document or XML fragment. Meta-data and content may be structured according to the rules
defined in a Content Model. For example, the Alfresco Document Management application relies
on a model that describes Folders and Files. Indexing information allows the retrieval of metadata and content via many different lookup options.
Repository storage and retrieval is provided by the following Foundation Services:
Node Service for managing meta-data i.e. nodes
Content Service for managing content
Search Service for performing queries
By default, Alfresco has chosen to store meta-data in a database and content in a file system.
Using a database immediately brings in the benefits of databases that have been developed over
many years such as transaction support, scaling & administration capabilities. Content is stored in
the file system to allow for very large content, random access, streaming and options for different
storage devices.
The Alfresco out-of-the-box implementations of the above services are built upon strong
open source projects that already have many man-years of development effort and strong
communities: Hibernate and Lucene.

API Development Course 27

Getting Started

Apart from the strong Object/Relational mapping that Hibernate provides, it also brings pluggable
caching support and SQL dialects. The first allows for tuning of the Alfresco meta-data store to
provide optimum read and write performance in both single and clustered environments. The
second allows for nearly any SQL database back-end by configuring just two properties; the
Alfresco community has already confirmed working support for MySQL, Oracle, DB2, Sybase,
SQL Server.
By externalising the indexing of meta-data and content and using the Lucene engine as a basis,
it is possible to perform complex queries which combine property, location, classification and
full-text predicates in a single query against any content type. Multiple query languages are
supported including Lucene's native language as well as XPath and a SQL-like language in the
future. To ensure reliable operation, transactional support has been added to both Lucene and
the content file store providing ACID operations across the complete store. Security is woven into
each of the service layer ensuring illegal modifications are not permissible and hidden meta-data
and content are not returned.
Nearly all other Foundation services and clients rely upon these three core building blocks.

Repository APIs
The Alfresco Repository actually provides three APIs. We've already seen one - the Repository
Foundation Services - a set of local Java Interfaces covering all capabilities which are ideal for
clients who wish to embed the Repository.
The two other APIs are:
JCR
Web Services
JCR (Content Repository API for Java Technologies) is a standard Java API (as defined by
JSR-170) for accessing Content Repositories. Alfresco provides support for level 1 and level 2
giving standardised read and write access. Supporting this API provides the following benefits:
No risk: The Alfresco Repository can be trialled and developed against, but swapped out
with another JCR Repository if it does not fit requirements.
Familiarity: Developers who know JCR, know Alfresco.
Tools: Tools, Clients and other 3rd Party JCR solutions are immediately available to the
Alfresco community.
Alfresco JCR is implemented as a light facade on top of the Repository Foundation Services.
So, although a familiar API is provided, it sits upon a fully transactional, secure and scalable
Repository which supports many deployment options. Alfresco will continue investment in JCR by
both broadening the compliance of the full specification as well driving forward JSR-283, the next
version of the JCR.
Web Services is the final API provided by the Alfresco Repository. This API supports remote
access and bindings to any client environment, not just Java. For example, the Alfresco
community is already using PHP, Ruby and Microsoft .NET. Numerous standards and integration
efforts are focused around Web Services - SOA is now recognised as a way forward for
integrating disparate systems including Content Management and building new enterprise-wide
solutions. BPEL plays an important role in orchestrating all of these services. Alfresco fits neatly
into this way of thinking.

28 Alfresco Enterprise Edition Version 3.2

Getting Started

Once again, the Repository Foundation Services serve as the base. Both the JCR and Web
Services API eventually go through this layer meaning that all encapsulated content model logic
and rules are honoured.

Repository Server Protocols


A Repository is useless if the content it manages cannot be accessed. To provide the widest
possible range of access points, the Alfresco Repository supports a variety of communication
protocols. These are:
CIFS (Common Internet File System)
WebDAV
FTP
NFS
All of these protocols essentially expose Folders of Files and as such the Alfresco
implementations map neatly onto Folder and File nodes held in the Repository as described by
the Alfresco Document Management content model.
WebDAV, FTP and NFS are well known protocols, but CIFS deserves some more attention.
CIFS transforms the Alfresco Repository into a standard file system. Any tool that understands
how to read and write to a file system, also knows how to directly read and write to the Alfresco
Repository. On the surface, it would seem that a drive mapping to WebDAV provides an
equivalent capability, but this isn't the case. CIFS projects an actual file system giving extra
compatibility with the hosting operating system. For example, in Windows, it's possible to use
Offline Synchronisation and Briefcase features against the Alfresco Repository providing native
and well-known tools for offline Repository working. Many commercial CMS offerings do not
provide this feature. In fact, Alfresco may have the only server-side Java implementation of the

API Development Course 29

Getting Started

CIFS protocol having brought on board the engineers who spent 7 years developing such a
capability.

Like every other feature of the Repository, the protocol components are Spring configured and
as with all other components may or may not be included in a deployment. Typically, they are
enabled when the Repository is deployed as a server to provide access points for remote clients.
The various Repository deployment options are explored later in this document.
Protocol components are implemented against the Repository Foundation Services. This is
important to note, as each protocol will honour the behaviour and content logic encapsulated
behind the Foundation Services. In fact, it cannot be bypassed.

Terminology
Store Reference (StoreRef)
A StoreRef is made up of a store protocol and a store id.

30 Alfresco Enterprise Edition Version 3.2

Getting Started

public static final String PROTOCOL_WORKSPACE = "workspace";


public static final String PROTOCOL_AVM = "avm";
public static final String URI_FILLER = "://";

The standard store used by the Web Client has the following StoreRef:
workspace://SpacesStore

Node Reference (NodeRef)


A NodeRef is made up a store reference and a node id.

private static final String URI_FILLER = "/";

The node id is a 16 byte (128 bit) A Universally Unique Identifier or UUID.


Example NodeRef:
workspace://SpacesStore/808b2b34-a99f-11db-b572-8337f65f7e0d

Qualified Name (QName)


A QName represents the qualified name of a Repository item. Each QName consists of a local
name qualified by a namespace.

API Development Course 31

Getting Started

Namespace scoped Name Format {URI}localname or prefix:localName


Examples
{http://www.alfresco.org/model/content/1.0}auditable

or
cm:auditable

Node Browser
The Node Browser is your friend!

32 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

Developing against the Alfresco Repository

API Development Course 33

Developing against the Alfresco Repository

Spring Framework
Introduction
The Spring Framework is a full-stack Java/JEE application framework. Spring's main aim is to
make J2EE easier to use and promote good programming practise. It does this by enabling a
POJO-based programming model remaining faithful to the fundamental ideas of Expert One-onOne J2EE Design and Development. Spring is portable between application servers.

Bean Factory
A Spring BeanFactory is a generic factory that enables objects to be retrieved by name, and
which can manage relationships between objects. Bean factories support two modes of object:
Singleton: in this case, there's one shared instance of the object with a particular name.
Prototype or non-singleton: in this case, each retrieval will result in the creation of an
independent object.
Beans are defined in an XML bean definition file that is loaded when a new Bean Factory is
created.

Bean Factory Example


1. Writing a simple JavaBean class
Here's the simplest Java Bean you can get!
package ex01_simplebean;
public class Bean1
{
}

2. Defining the Spring beans


Two Spring beans are defined for the same class. bean1 is a singleton bean and
multibean1 is a prototype bean (singleton="false"):
<beans>
<bean id="bean1"
class="ex01_simplebean.Bean1" />
<bean id="multibean1"
class="ex01_simplebean.Bean1"
singleton="false" />
</beans>

3. Testing with a Main program


a.

Creating a new Spring Bean Factory


The bean definition file is loaded into the Spring Bean Factory:
ClassPathResource res = new ClassPathResource(
"ex01_simplebean/ApplicationContext.xml");
XmlBeanFactory factory = new XmlBeanFactory(res);

The following messages are written to the console:


15:13:26,515 INFO [XmlBeanDefinitionReader] Loading XML
bean definitions from class path resource [ex01_simplebean/
ApplicationContext.xml]
15:13:26,593 INFO [XmlBeanFactory] Creating shared instance of
singleton bean 'bean1'

b.

Getting bean1 from the Bean Factory

34 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

We retrieve bean1 from the Bean Factory using the getBean() method:
Bean1 bean1a = (Bean1) factory.getBean("bean1");
System.out.println("Retrieved Bean1: " + bean1a.toString());
Bean1 bean1b = (Bean1) factory.getBean("bean1");
System.out.println("Retrieved Bean1: " + bean1b.toString());

Each call retrieves the same bean (there is only one instance):
Retrieved Bean1: ex01_simplebean.Bean1@471e30
Retrieved Bean1: ex01_simplebean.Bean1@471e30

c.

Getting multibean1 from the Bean Factory


Now we retrieve multibean1 from the Bean Factory.
Bean1 bean1a = (Bean1) factory.getBean("multibean1");
System.out.println("Retrieved MultiBean1: " + bean1a.toString());
Bean1 bean1b = (Bean1) factory.getBean("multibean1");
System.out.println("Retrieved MultiBean1: " + bean1b.toString());

This time each call retrieves a new instance of the bean:


Retrieved MultiBean1: ex01_simplebean.Bean1@10ef90c
Retrieved MultiBean1: ex01_simplebean.Bean1@a32b

Inversion of Control (IoC)


Through its bean factory concept, Spring is an Inversion of Control container. Spring is most
closely identified with a flavour of Inversion of Control known as Dependency Injection. The
concept behind Inversion of Control is often expressed in the Hollywood Principle: Don't call me,
I'll call you. IoC moves the responsibility for making things happen into the framework, and away
from application code. Whereas your code calls a traditional class library, an IoC framework calls
your code.

Dependency Injection
Dependency Injection is a form of IoC that removes explicit dependencies on container APIs.
Ordinary Java methods are used to inject dependencies such as collaborating objects or
configuration values into application object instances. The two major flavors of Dependency
Injection are:
Setter Injection (injection via JavaBean setters)
Constructor Injection (injection via constructor arguments).
Spring provides sophisticated support for both, and even allows you to mix the two when
configuring the one object.

Setter Injection Example


1. Writing a simple JavaBean class
This simple JavaBean supports properties. The values of the properties are set by the
Spring Bean Factory when the bean is instantiated.
package ex02_setter;
public class Bean1
{
public void setString(String val) { m_strVal = val; }
public String getString() { return m_strVal; }
public void setInt(int val) { m_intVal = val; }
public int getInt() { return m_intVal; }

API Development Course 35

Developing against the Alfresco Repository

public void setList(List strings) { m_strings = strings; }


public List getList() { return m_strings; }
private String m_strVal;
private int m_intVal;
private List m_strings;
}

2. Defining the Spring beans


A single bean is defined with initial values for the properties that will be initialised via the
setter methods on the JavaBean:
<beans>
<bean id="bean1" class="ex02_setter.Bean1">
<property name="string">
<value>a string</value>
</property>
<property name="int">
<value>125</value>
</property>
<property name="list">
<list>
<value>item1</value>
<value>item2</value>
<value>item3</value>
</list>
</property>
</bean>
</beans>

3. Testing with a Main program


We retrieve bean1 from the Bean Factory and print out the values of the properties:
Bean1 bean1a = (Bean1) factory.getBean("bean1");
System.out.println("Retrieved Bean1: " + bean1a.toString());
String strVal = bean1a.getString();
System.out.println("String property: " + strVal);
int intVal = bean1a.getInt();
System.out.println("Int property: " + intVal);
List strings = bean1a.getList();
System.out.println("List property: " + strings);

The properties have automatically been initialised by the Bean Factory:


Retrieved Bean1: ex02_setter.Bean1@21b6d
String property: a string
Int property: 125
List property: [item1, item2, item3]

Constructor Injection Example


1. Writing a simple JavaBean class
This time the JavaBean has a constructor and some properties.
The String property is passed to the constructor and the int property has a setter
method.
package ex03_constructor;
public class Bean1
{
public Bean1(String val)
{
m_strVal = val;
}
public String getString() { return m_strVal; }

36 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

public void setInt(int val) { m_intVal = val; }


public int getInt() { return m_intVal; }
private String m_strVal;
private int m_intVal;
}

2. Defining the Spring beans


This time a bean is defined with a constructor argument.
The String property will be initialised via the constructor and the int property via the
setter method on the JavaBean:
<beans>
<bean id="bean1" class="ex03_constructor.Bean1">
<constructor-arg index="0">
<value>a string</value>
</constructor-arg>
<property name="int">
<value>125</value>
</property>
</bean>
</beans>

3. Testing with a Main program


We retrieve bean1 from the Bean Factory and print out the values of the properties:
Bean1 bean1a = (Bean1) factory.getBean("bean1");
System.out.println("Retrieved Bean1: " + bean1a.toString());
String strVal = bean1a.getString();
System.out.println("String property: " + strVal);
int intVal = bean1a.getInt();
System.out.println("Int property: " + intVal);

The properties have automatically been initialised by the Bean Factory:


Retrieved Bean1: ex03_constructor.Bean1@12152e6
String property: a string
Int property: 125

Dependency Injection Example


1. Writing the JavaBean classes
a.

Two more simple JavaBean classes


These two JavaBeans will be used as dependencies:
package ex04_dependency;
public class Bean2
{
}
package ex04_dependency;
public class Bean3
{
}

b.

Establish a constructor dependency


The Bean1 class depends on the Bean2 class via the constructor:
package ex04_dependency;

API Development Course 37

Developing against the Alfresco Repository

public abstract class Bean1


{
public Bean1(Bean2 bean2)
{
m_bean2 = bean2;
}
...
public Bean2 getBean2()
{
return m_bean2;
}
...
private Bean2 m_bean2;
}

c.

Establish a setter dependency


The Bean1 class depends on the Bean3 class via a property setter:
public void setBean3(Bean3 bean3)
{
m_bean3 = bean3;
}
public Bean3 getBean3()
{
return m_bean3;
}
private Bean3 m_bean3;

2. Defining the Spring beans


bean1 depends on bean2 and bean3. The Bean Factory will instantiate the bean2 and
bean3 objects before injecting them into bean1 via the constructor property setter

respectively:
<beans>
<bean id="bean1" class="ex04_dependency.Bean1">
<constructor-arg index="0">
<ref bean="bean2"/>
</constructor-arg>
<property name="bean3">
<ref bean="bean3"/>
</property>
</bean>
<bean id="bean2" class="ex04_dependency.Bean2"/>
<bean id="bean3" class="ex04_dependency.Bean3"/>
</beans>

3. Testing with a Main program


We retrieve bean1 from the Bean Factory and print out the dependencies:
Bean1 bean1 = (Bean1) factory.getBean("bean1");
System.out.println("Retrieved Bean1: " + bean1.toString());
Bean2 bean2 = bean1.getBean2();
Bean3 bean3 = bean1.getBean3();
System.out.println("Retrieved Bean2 Dependency: " + bean2.toString());
System.out.println("Retrieved Bean3 Dependency: " + bean3.toString());

The dependencies have automatically been injected by the Bean Factory:


Retrieved Bean1: ex04_dependency.Bean1$$EnhancerByCGLIB$$d8a83b@1cb25f1
Retrieved Bean2 Dependency: ex04_dependency.Bean2@2808b3
Retrieved Bean3 Dependency: ex04_dependency.Bean3@535b58

38 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

Application Context
To start using Spring you either instantiate a Spring BeanFactory or an ApplicationContext.
ApplicationContext is derived from BeanFactory and provides all of the BeanFactory
funtionality and more including:
MessageSource, providing access to messages in, i18n-style
Access to resources, such as URLs and files
Event propagation to beans implementing the ApplicationListener interface
Loading of multiple (hierarchical) contexts, allowing each to be focused on one particular
layer, for example the web layer of an application

Web Application Context


A Spring WebApplicationContext is just an ordinary ApplicationContext that has some extra
features necessary for web applications. It is bound in the ServletContext and is created by a
ContextLoaderListener.

Context Loader Listener


The Spring ContextLoaderListener is declared in WEB-INF/web.xml:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

Spring contextConfigLocation
Use the contextConfigLocation <context-param> to set which context files to load:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:alfresco/web-client-application-context.xml
classpath:web-services-application-context.xml
classpath:alfresco/application-context.xml
</param-value>
<description>Spring config file locations</description>
</context-param>

API Development Course 39

Developing against the Alfresco Repository

Foundation Services API


Introduction
The Foundation Services API is a set of services providing full access to the capabilities of the
Alfresco Repository. It is an in-process API meaning that the client must run within the same
process as the Repository. For example, the Alfresco Web Client uses this API and is packaged
together with the Repository in a single WAR file for deployment to an application server.

Access to Repository Foundation Services


The Foundation Services API is comprised of a set of interfaces; each interface represents a
function of the Repository. A Spring Framework Bean is provided as the implementation for each
interface.
The list of available public services (i.e. Spring beans) can be found in:
the Spring configuration file public-services-context.xml
the Service Registry interface org.alfresco.service.ServiceRegistry
There are three ways to access Foundation Services:
1. use Spring IoC to directly inject services into your code (If your layer is also Spring
enabled);
2. use the Alfresco Service Registry;
3. manually access services via the Spring getBean() method.
FirstFoundationClient

walkthrough

Before getting started, you should be familiar with the Introduction on page 34.
The sample uses several of the key foundation services, including the ServiceRegistry,
TransactionService, AuthenticationService, SearchService, NodeService and
ContentService. After initialising the Spring Application Context and starting the repository in
embedded mode, we will use the Spring getBean() method to access the ServiceRegistry.
We will then use the ServiceRegistry to access the other foundation services.
After authenticating to the repository using the AuthenticationService, we will search for
the Company Home node using the SearchService. We will then create a new node with
properties and add an aspect using the NodeService. Finally, we will write some content to the
new node using the ContentService. The sample will be wrapped in a single user transaction
with the help of the TransactionService.
1. Getting the ServiceRegistry
The Service Registry maintains a list of available foundation services and some meta-data
about each. In particular, the Service Registry provides access to each service interface.
The registry is a service itself and is therefore accessed using either Spring IoC or the
Spring getBean() method. The static variable SERVICE_REGISTRY found on the interface
org.alfresco.service.ServiceRegistry provides the Spring Bean name to lookup by.
The FirstFoundationClient sample uses the getBean() method on the Spring
Application Context to retrieve the ServiceRegistry:
ApplicationContext ctx =
ApplicationContextHelper.getApplicationContext();
final ServiceRegistry serviceRegistry = (ServiceRegistry)
ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);

40 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

2. Using the TransactionService to run the example in a user transaction


By default, all repository foundation services are transactional and each invocation of
a service method is wrapped in its own transaction. These transactions are defined
declaratively in Spring configuration files and not in your Java code. In most cases,
declarative transactions are preferred to user transactions since they are less invasive.
There are situations, however, when user transactions do need to be used explicitly in your
code.
The FirstFoundationClient uses a RetryingTransactionHelper to run the example
as a unit of work inside a user transaction. The work is defined as an instance of the
RetryingTransactionCallback class. The doInTransaction() method is then called to
run the unit of work in a transaction.
The RetryingTransactionHelper is obtained via the
getRetryingTransactionHelper() method on the TransactionService.

For clarity, not all of the available methods are shown. For a complete description,
please consult the Javadocs: Interface TransactionService
The TransactionService and RetryingTransactionHelper are presented in more
detail in the section on Transactions.
The following example runs the example work in a user transaction via the
RetryingTransactionHelper:
TransactionService transactionService =
serviceRegistry.getTransactionService();
RetryingTransactionCallback<Object> exampleWork =
new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
doExample(serviceRegistry);
return null;
}
};
transactionService.
getRetryingTransactionHelper().doInTransaction(exampleWork);

3. Using the AuthenticationService for authentication

API Development Course 41

Developing against the Alfresco Repository

Before making any call to the repository through the public Foundation Services API,
a user must first be authenticated. This is done via the AuthenticationService by
using the authenticate() method and providing a username and password. Once
authenticated, a ticket can be requested using either the getNewTicket() or the
getCurrentTicket() method. The ticket can then be used to re-validate the user using
the validate() method.
The AuthenticationService defines the API for managing authentication information
against a username.

For clarity, not all of the available methods are shown. For a complete description,
please consult the Javadocs: Interface AuthenticationService
In the example, the authenticate() method is used to authenticate as the admin user.
The password must be passed as a character array.
AuthenticationService authenticationService =
serviceRegistry.getAuthenticationService();
authenticationService.authenticate("admin", "admin".toCharArray());

4. Using the SearchService to locate the Company Home node


The SearchService provides many methods for searching the Alfresco repository using
any of the available query languages: Lucene, XPath or JCR-XPath. The Lucene query
language allows you to run powerful searches, including full text searches on the content
and node properties.
To run a Lucene search using the query() method, you must specify the StoreRef of the
store you wish to search, the query language (using one of the static attributes defined on
the SearchService interface - LANGUAGE_LUCENE for example) and the query as a String.
The parameters can either be passed directly to the query() method, or be defined on a
new SearchParameters object which is then passed to the query() method. The query
results are returned as a ResultSet object.

42 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

For clarity, not all of the available methods are shown. For a complete description,
please consult the Javadocs: Interface SearchService
The following example runs a Lucene query using the PATH syntax to locate the
Company Home by it's absolute path. The getNodeRef(0) call is used to retrieve the first
NodeRef from the ResultSet. In theory, the query should only return one result.
SearchService searchService = serviceRegistry.getSearchService();
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE,
"SpacesStore");
ResultSet resultSet = searchService.query(
storeRef,
SearchService.LANGUAGE_LUCENE,
"PATH:\"/app:company_home\"");
NodeRef companyHome = resultSet.getNodeRef(0);

For more information on using the SearchService and on query string syntax, see the
Search page on the Alfresco Wiki.
5. Using the NodeService to create a node
The NodeService provides methods for operations on nodes and stores.
Stores are created with createStore().
Nodes are created with createNode() and deleted with deleteNode(). Properties
are set with either setProperty() or setProperties(). Properties are removed
with removeProperty(). Aspects are applied with addAspect() and removed with
removeAspect().
Associations are created and removed with createAssociation() and
removeAssociation(). Associations can be navigated with getSourceAssocs() or
getTargetAssocs(). Child associations can be navigated with getChildAssocs() and
getParentAssoc().
Almost all NodeService methods take a NodeRef as an argument. A NodeRef is obtained
either by navigation or from the results of a search. Otherwise a new NodeRef object can
be created from the node's unique UUID.

API Development Course 43

Developing against the Alfresco Repository

For clarity, not all of the available methods are shown. For a complete description,
please consult the Javadocs: Interface NodeService
a.

Setting properties
Properties are set on nodes using either the setProperty() or setProperties()
methods. setProperty() allows a single property to be set, whilst setProperties()
takes a Map of properties and sets all of the node's properties at once. Each property
is identified by it's QName and it's value must be Serializable.
public void setProperty(
NodeRef nodeRef,
QName qname,
Serializable value)
public void setProperties(
NodeRef nodeRef,
Map<QName, Serializable> properties)
nodeRef
NodeRef of the node to set the property on.

44 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

qname
QName of the property to set.
value

Value of the property. The value must be Serializable.


properties
Map of all the properties of the node keyed by QName.

The property QNames are usually defined as a static constants on the dictionary
model interfaces. For example, the cm:name property is defined by the static constant
ContentModel.PROP_NAME.
The following example creates a Map containing the cm:name property that will be
used in the next step:
String name = "Foundation API sample (" + System.currentTimeMillis() +
")";
Map<QName, Serializable> contentProps = new HashMap<QName,
Serializable>();
contentProps.put(ContentModel.PROP_NAME, name);

b.

Creating a node
Nodes are created using the createNode() method. A node is created as a child
of a parent node. The child association name and child association type have to be
supplied as QName objects, as well as the QName of the type of node to create and
optionally a Map of properties to set on the newly created node.
public ChildAssociationRef createNode(
NodeRef parentRef,
QName assocTypeQName,
QName assocQName,
QName nodeTypeQName,
Map<QName, Serializable> properties)
parentRef
NodeRef of the parent node. The created node will be one of it's children.
assocTypeQName
QName of the type of association to create. This is used for verification against the

data dictionary.
assocQName
QName of the association.
nodeTypeQName
QName of the node type.
properties

Optional Map of properties to set keyed by QName.


The association and node type QName objects are usually defined as a static constants
on the dictionary model interfaces. For example, the cm:contains association type is
defined by the static constant ContentModel.ASSOC_CONTAINS and the cm:content
node type is defined by the static constant ContentModel.TYPE_CONTENT. If a
constant does not exist, a QName can be created using the QName.createQName()
static method as in the example below.
The createNode() method returns a ChildAssociationRef to the newly created
child association. The NodeRef of the newly created node is obtained by calling the
getChildRef() on the ChildAssociationRef object.

API Development Course 45

Developing against the Alfresco Repository

The following example creates a new node of type cm:content, using the standard
cm:contains child association. The Map created in the previous step sets the
cm:name property on the newly created node.
NodeService nodeService = serviceRegistry.getNodeService();
ChildAssociationRef association = nodeService.createNode(companyHome,
ContentModel.ASSOC_CONTAINS,
QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX,
name),
ContentModel.TYPE_CONTENT,
contentProps);
NodeRef content = association.getChildRef();

c.

Adding an aspect
Aspects are applied to nodes using the addAspect() method. The node is identified
by it's NodeRef and the aspect by it's QName. The property values are provided as a
Map.
public void addAspect(
NodeRef nodeRef,
QName aspectTypeQName,
Map<QName, Serializable> aspectProperties)
nodeRef
NodeRef of the node to apply the aspect to.
aspectTypeQName

QName of the aspect to apply.


aspectProperties
Map containing a minimum of the mandatory properties required for the aspect.

The aspect QNames are usually defined as a static constants on the dictionary model
interfaces. For example, the cm:titled aspect is defined by the static constant
ContentModel.ASPECT_TITLED.
The following example applies the cm:titled aspect and sets the cm:title and
cm:description properties:
Map<QName, Serializable> titledProps = new HashMap<QName,
Serializable>();
titledProps.put(ContentModel.PROP_TITLE, name);
titledProps.put(ContentModel.PROP_DESCRIPTION, name);
nodeService.addAspect(content, ContentModel.ASPECT_TITLED,
titledProps);

6. Using the ContentService to write content


The ContentService provides methods for reading, writing and transforming content.
In order to read or write content from and to a node, you must first obtain a
ContentReader or a ContentWriter via the getReader() and getWriter() methods
respectively. Methods can then be used on the ContentReader or ContentWriter to
read and write content. Both the ContentReader and the ContentWriter implement the
methods defined by the ContentAccessor interface. These methods allow you to get and
set information about the content, for example to set the mime type, the encoding or to get
the size.
The actual content is stored on the cm:content property (ContentModel.PROP_CONTENT)
of each node. When requesting a ContentReader or a ContentWriter, the NodeRef
needs to be supplied along with the ContentModel.PROP_CONTENT QName.
The ContentService is also used for transforming content. A suitable transformer can
be obtained for a given transformation (defined by a source and target mime type) using
46 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

the getTransformer() and getImageTransformer() methods. The transformation can


then be performed by calling transform directly on the content transformer. Otherwise,
a transformation can be attempted from a source ContentReader object to target
ContentWriter object by calling the transform() method on the ContentService.
For more information, see Introduction on page 91.

For clarity, not all of the available methods are shown. For a complete description,
please consult the Javadocs:

Interface ContentService

Interface ContentReader

Interface ContentWriter

Interface ContentAccessor

The following example gets a ContentWriter to the newly created node. The property
to be updated is defined by the QName ContentModel.PROP_CONTENT. The boolean true
value is to request that the content is updated atomically when the content write stream
is closed. The content mime type and encoding are set before writing the content with the
putContent() method.
ContentService contentService = serviceRegistry.getContentService();
ContentWriter writer = contentService.getWriter(content,
ContentModel.PROP_CONTENT, true);
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setEncoding("UTF-8");
String text = "The quick brown fox jumps over the lazy dog";
writer.putContent(text);

API Development Course 47

Developing against the Alfresco Repository

Once completed, the newly created node may be viewed via the Web client.

The web client will need to be re-started after executing the sample to see the changes in
effect.

Other Foundation Services


FileFolderService
The FileFolderService provides methods specific to manipulating Alfresco defined content
files and folders. The methods can be more convenient and easier to use than the equivalent
NodeService and ContentService methods. Certain methods, such as create(), return a
FileInfo object. A NodeRef can then be retrieved using the FileInfo.getNodeRef() method.

48 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs:

Interface FileFolderService

Interface FileInfo

API Development Course 49

Developing against the Alfresco Repository

JCR API
Introduction
The JCR API (Java Content Repository) specifies a standard, implementation independent API to
access content repositories in Java. It is defined by the Java Specification Request (JSR) 170 as
part of the Java Community Process (JCP). The official JSR-170 Specification can be found on
the JCP web site at the following address: http://jcp.org/en/jsr/detail?id=170
Alfresco implements the JCR API against its own scalable repository and is actively contributing
to the next version of JCR defined by the Java Specification Request (JSR) 283.

JCR Compliance Levels


The JSR 170 specification defines two compliance levels and a set of additional optional features
which repositories of either level may support.
Level 1 provides for read functions and includes:
Retrieval and traversal of nodes and properties
Reading the values of properties
Transient namespace remapping
Export to XML/SAX
Query facility with XPath syntax
Discovery of available node types
Discovery of access control permissions
Level 2 adds additional write functions:
Adding and removing nodes and properties
Writing the values of properties
Persistent namespace changes
Import from XML/SAX
Assigning node types to nodes
Optionally, any combination of the following features may be added to an implementation of either
level:
Transactions
Versioning
Observation (Events)
Locking
SQL syntax for query

JCR Repository Model


Like the Alfresco repository, a JCR repository consists of one or more workspaces, each of
which contains a tree of items. An item is either a node or a property. Each node may have
zero or more child nodes and zero or more child properties. There is a single root node per
workspace, which has no parent. Unlike the Alfresco repository, all other nodes have only one
parent. Properties have one parent (a node) and cannot have children; they are the leaves of the
tree. All of the actual content in the repository is stored within the values of the properties.

50 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

Any item in the tree hierarchy can be identified by either an absolute or a relative path using
a Unix style path syntax. The path / refers to the root node of a workspace and the path /
app:company_home refers to the Company Home space in the standard Alfresco SpacesStore
workspace. The special paths . and .. (meaning respectively, this and parent) are also
supported.
FirstJCRClient

Walkthrough

Before getting started, you should be familiar with the Introduction on page 34.
The sample uses several of the basic JCR APIs, including Repository, Session and Node. After
initialising the Spring Application Context and starting the repository in embedded mode, we will
use the Spring getBean() method to access the JCR.Repository.
After logging into the JCR repository, we will navigate to the Company Home node. We will then
create a new node with properties, add an aspect and write some content. Finally, we will mix the
JCR API calls with the Alfresco Foundation Services API calls to set the content mime type. The
sample is wrapped by an implicit JCR transaction.
1. Getting the JCR.Repository
The JCR repository as a whole is represented by a Repository object. JSR-170 does not
dictate how to obtain the Repository object. In Alfresco, the JSR repository is a Spring
bean called JCR.Repository.
The JCR.Repository bean is defined in jcr-api-context.xml. It has one configuration
parameter; the default workspace name. This is used when a JCR client performs a login
without specifying the workspace. The default workspace is SpacesStore, the same one
used by the Alfresco Web Client.
<bean id="JCR.Repository"
class="org.alfresco.jcr.repository.RepositoryImpl"
init-method="init">
<property name="serviceRegistry">
<ref bean="ServiceRegistry"/>
</property>
<property name="importerComponent">
<ref bean="importerComponent"/>
</property>
<property name="defaultWorkspace">
<value>SpacesStore</value>
</property>
</bean>

The following example uses the Spring getBean() method to access the JCR Repository:
ApplicationContext context = new
ClassPathXmlApplicationContext("classpath:alfresco/applicationcontext.xml");
Repository repository = (Repository)context.getBean("JCR.Repository");

2. Logging into the repository (creating a Session)


A client connects to the repository by calling the login() method on the Repository
object. The client must supply a Credentials object and optionally a workspace name.
Behind the scenes, Alfresco uses the authentication system of the Alfresco repository
which by default is Alfresco's own, but it could also be NTLM, LDAP or your own
depending on how the repository has been configured.
If a workspace is not provided, the default as defined earlier will be used. The Session
returned from login is tied to the workspace and allows read and write operations upon
that workspace. By default, the Session is also backed by a transaction. Work performed

API Development Course 51

Developing against the Alfresco Repository

within the session will not be committed until Session.save() is called. For more
information, see JCR Transactions on page 53.
Session session = repository.login(new SimpleCredentials("admin",
"admin".toCharArray()));

3. Accessing the Company Home


JSR-170 provides two methods for accessing nodes: traversal access and direct access.
Traversal access involves walking the content tree using relative paths, while direct
access allows you to jump directly to a node with either an absolute path or, if the node is
referenceable, with a UUID.
Traversal access is available from any Node object via the Node.getNode() and
Node.getProperty() methods. The root node of a workspace can be obtained by using
the getRootNode() method on the session object.
Direct access is available to any item in the repository (node or property) by passing an
absolute path to the Session.getItem() method. Alternatively direct access to a node is
available by passing the UUID of the node to the Session.getNodeByUUID() method. The
UUID of a node can be retrieved using the Node.getUUID() method.
The following example gets the workspace root node, then uses traversal access with a
relative path to walk to the Company Home node:
Node rootNode = session.getRootNode();
Node companyHome = rootNode.getNode("app:company_home");

4. Creating a new Node


Child nodes are created using the Node.addNode() method. Properties are added to
nodes using the Node.setProperty() method. The addNode() method takes the qualified
relative path of the new node and optionally the node type. The setProperty() methods
takes the property name and it's value. The value arguments accepts most types.
For a complete description of the Node interface, see the official Javadocs: Interface Node.
String name = "JCR sample (" + System.currentTimeMillis() + ")";
Node content = companyHome.addNode("cm:" + name, "cm:content");
content.setProperty("cm:name", name);

5. Adding an aspect
Aspects are added to nodes using the Node.addMixin() method. The aspect properties
are set using the Node.setProperty() as above.
The following example adds the cm:titled aspect:
content.addMixin("cm:titled");
content.setProperty("cm:title", name);
content.setProperty("cm:description", name);

6. Writing some content


The actual content in a JCR repository is stored within the values of the properties. In the
case of Alfresco the content is stored on the cm:content property.
The following example writes The quick brown fox jumps over the lazy dog as the node's
content:
content.setProperty("cm:content", "The quick brown fox jumps over the
lazy dog");

7. Mixing JCR and Alfresco APIs


The JCR API covers a wide variety of repository functions, however, there will always
be scenarios where custom Alfresco capabilities are required. For these scenarios, it is

52 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

possible to mix JCR API calls with Alfresco Foundation Services API calls and have them
controlled in the same transaction.
In the FirstJCRClient sample, the mime type is set using the Alfresco NodeService:
ServiceRegistry serviceRegistry = (ServiceRegistry)
context.getBean(ServiceRegistry.SERVICE_REGISTRY);
NodeService nodeService = serviceRegistry.getNodeService();
NodeRef nodeRef = JCRNodeRef.getNodeRef(node);
ContentData content = (ContentData)nodeService.getProperty(nodeRef,
ContentModel.PROP_CONTENT);
content = ContentData.setMimetype(content, mimeType);
nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, content);

The JCRNodeRef.getNodeRef() method converts a JCR Node to an Alfresco


NodeRef.
8. Saving the session
Work performed on the session is not committed until Session.save() is called. This also
commits the transaction.
session.save();

9. Logging out from the session


This logs out of the session, any uncommitted changes will be lost
session.logout();

JCR Transactions
Alfresco provides Transaction capabilities across all of its persistence based services. The JCR
API is built upon these services and as such Alfresco provides a fully transactional JCR interface.

Implicit Transactions
By default, an Alfresco JCR Session is backed by a transaction. Work performed within the
session will not be committed until Session.save() is called.
A typical interaction sequence is as follows...
Login and establish a session (this starts a transaction):
Session session = repository.login(credentials);

Perform work on the session:


Node node = session.getRootNode();
Node childNode = node.addNode(....);
childNode.setProperty(...);

Save the session (this commits the transaction):


session.save();

Continue to perform more work on the session and save again:


session.save();

Logout from the session (this rolls back the transaction - unsaved items are lost):
session.logout();

It is not just the JCR calls that are bound to the transaction. Calls to the native Alfresco
Foundation Services API are also included. So, if you happen to mix JCR and Alfresco
calls between Session.login() and Session.logout(), they are all bound to the same
transaction and controlled by the JCR session.

API Development Course 53

Developing against the Alfresco Repository

Explicit Transactions
It is also possible to explicitly control transaction boundaries using Alfresco's
TransactionService. Using this technique, you can explicitly control when JCR updates are
committed.
An example of explicit transaction management follows...
Get a user transaction from the Alfresco TransactionService and start the transaction:
UserTransaction trx =
serviceRegistry.getTransactionService().getUserTransaction;
trx.begin();

Login and establish a session (the explicit transaction started above is inherited):
Session session = repository.login(credentials);

Perform work on the session and save the session (do not commit just yet):
session.save();

Perform more work on the session and save again (still no commit):
session.save();

Now commit the transaction:


trx.commit();

In case of an error, rollback:


trx.rollback();

For more information on Alfresco user transactions, see Alfresco User Transactions on page
65.

54 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

Web Services API


Introduction
The Web Services API is an easy to understand and develop against API. Accessible from many
client languages, it is designed for remote repository access. Web Services are particularly
suitable for composite applications and business processes.

Available Web Services


The following Alfresco Web Services are available:
Authentication
login and logout
Repository
query and model manipulation
Content
content manipulation
Authoring
collaborative content creation
Classification
apply classifications and categories
Access Control
roles, permissions & ownership
Action
manage actions and rules
Administration
user management, export & import
Dictionary
model descriptions

Access to Web Services in Java


The WebServiceFactory provides access to the repository services stubs. Each available Web
Service has it's own get method. For example, to access the Repository Web Service, use the
WebServiceFactory.getRepositoryService() static method.

API Development Course 55

Developing against the Alfresco Repository

Static attributes on the WebServiceFactory define the default endpoint address, the location
of the Web Services configuration property file and the name of the property that defines the
repository location:
private static final String DEFAULT_ENDPOINT_ADDRESS = "http://localhost:8080/
alfresco/api";
private static final String PROPERTY_FILE_NAME = "alfresco/
webserviceclient.properties";
private static final String REPO_LOCATION = "repository.location";

The AuthenticationUtils and Utils classes provide common utility methods useful when
using the Web Services API.

Web Services Data Types


Each Web Service method relies upon data types for input and output messages.
The formal definition of the available data types is defined in the Web Service Data Types XML
Schema.
The Web Service Data Types XML Schema can also be found in the wsdl/types.xsd file.

Example Data Types

Content Manipulation Language (CML)


CML (Content Manipulation Language) provides a set of simple statements for updating a
Repository.
The statements are described in the Web Service CML XML Schema and as such can either
be represented as XML documents or mapped to other representations such as Java objects or
appropriate client language binding.
Multiple statements may be collected together into a single document.

56 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

FirstWebServiceClient

Walkthrough

1. Logging into the repository (start a session)


To start a session, the static startSession() method can be used on the
AuthenticationUtils class.
public static void main(String[] args) throws Exception
{
// Start the session
AuthenticationUtils.startSession("admin", "admin");

API Development Course 57

Developing against the Alfresco Repository

try
{
...
}
catch(Throwable e)
{
System.out.println(e.toString());
}
finally
{
// End the session
AuthenticationUtils.endSession();
System.exit(0);
}
}

2. Creating a reference to Company Home


A ParentReference data type is made up of a Reference and a ChildAssociation:

To create a ParentReference to Company Home:


Store storeRef = new Store(Constants.WORKSPACE_STORE, "SpacesStore");
ParentReference companyHomeParent = new ParentReference(
storeRef,
null,
"/app:company_home",
Constants.ASSOC_CONTAINS,
null);

We can set the childName on the ChildAssociation as follows:


String name = "Web Services sample (" + System.currentTimeMillis() + ")";
companyHomeParent.setChildName("cm:" + name);

3. Creating a new node


The create CML statement is used to create a new node.

58 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

The create CML statement is constructed with a new CMLCreate object.


NamedValue[] contentProps = new NamedValue[1];
contentProps[0] = Utils.createNamedValue(Constants.PROP_NAME, name);
CMLCreate create = new CMLCreate(
"1",
companyHomeParent,
null,
null,
null,
Constants.TYPE_CONTENT,
contentProps);

Assign "1" as a local id, so we can refer to it in subsequent CML statements within
the same CML block.
4. Adding an aspect
The addAspect CML statement is used to add an aspect.

The addAspect CML statement is constructed with a new CMLAddAspect object:


NamedValue[] titledProps = new NamedValue[2];
titledProps[0] = Utils.createNamedValue(Constants.PROP_TITLE, name);
titledProps[1] = Utils.createNamedValue(Constants.PROP_DESCRIPTION,
name);
CMLAddAspect addAspect = new CMLAddAspect(
Constants.ASPECT_TITLED,
titledProps,
null,
"1");

API Development Course 59

Developing against the Alfresco Repository

5. Constructing a CML Block


Construct CML Block:
CML cml = new CML();
cml.setCreate(new CMLCreate[] {create});
cml.setAddAspect(new CMLAddAspect[] {addAspect});

6. Issuing a CML update


Issue CML statement via Repository Web Service and retrieve result:
Batching of multiple statements into a single web call
UpdateResult[] result =
WebServiceFactory.getRepositoryService().update(cml);
Reference content = result[0].getDestination();

7. Writing some content


ContentServiceSoapBindingStub contentService =
WebServiceFactory.getContentService();
String text = "The quick brown fox jumps over the lazy dog";
ContentFormat contentFormat = new ContentFormat("text/plain", "UTF-8");
Content contentRef = contentService.write(
content,
Constants.PROP_CONTENT,
text.getBytes(),
contentFormat);
System.out.println("Content Length: " + contentRef.getLength());

8. Logging out (ending the session)


End the session:
finally
{
AuthenticationUtils.endSession();
System.exit(0);
}

60 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

Separating Concerns using AOP


Public Services and AOP proxies
The public services defined in public-services-context.xml are in fact Spring AOP proxies.
For example, the public ContentService bean defined in public-services-context.xml is
an AOP proxy to the target service defined by the contentService bean found in contentservices-context.xml. The AOP proxy allows cross cutting concerns such as security and
transaction management to be implemented in a non-invasive, declarative way. All service
method calls via the ContentService bean defined in public-services-context.xml are
subject to security checks and transaction management whilst those via the contentService
bean defined in content-services-context.xml are subject to none!
All public service Spring beans have ids that begin with an uppercase letter. Public
services are subject to security checks and transaction management. Beans with
equivalent names that begin with a lowercase letter are subject to none.
All public services AOP proxies are defined using a Spring ProxyFactoryBean. The
ContentService example below is taken from public-services-context.xml:
<bean id="ContentService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>org.alfresco.service.cmr.repository.ContentService</value>
</property>
<property name="target">
<ref bean="contentService"/>
</property>
<property name="interceptorNames">
<list>
<idref local="ContentService_transaction"/>
<idref local="AuditMethodInterceptor"/>
<idref local="exceptionTranslator"/>
<idref bean="mlContentInterceptor"/>
<idref bean="ContentService_security"/>
</list>
</property>

The ProxyFactoryBean introduces a level of indirection so that objects referencing the


ContentService bean do not see the ProxyFactoryBean but the object defined by the target
property, in our case, the contentService bean. The proxyInterfaces property, defines an
array of interfaces implemented by the target class and the interceptorNames property a list of
advisor, interceptor or other advice names to apply. Ordering is significant, the first interceptor in
the list will be the first to be able to intercept the method call.
The ContentService_transaction interceptor is a reference to a local bean in publicservices-context.xml that manages transactions for calls to the ContentService. The
ContentService_security is a reference to a bean in public-services-securitycontext.xml that enforces security checks for calls to the ContentService.

Security Enforcement
When any call is made to the repository through the public foundation services API, the caller
must first be authenticated. This can be done by logging in using a username and password
or using a ticket. A ticket can be requested after logging in and can be used, under certain
conditions, to re-validate a user.
Once authenticated, access control allows or denies a user from calling public service
methods on a particular object by checking if the authenticated user, or any of the authorities
granted to that user, has a particular permission or permission group. For example, to call the
readProperties() method on the NodeService, the authenticated user must have read access

API Development Course 61

Developing against the Alfresco Repository

to the properties of the node. On the SearchService, the results from queries are restricted to
return only the nodes for which a user has read permission. The public services are the only
services to have access restrictions.
Security is enforced around every public service method call and can be based on:
the method called,
the objects provided as arguments to the call,
the objects returned by the call.
Since the Web Client, JCR API, Web services API, CIFS, WebDav, FTP etc. all use public
services behind the scenes, the same security enforcement always applies.

Acegi Security Configuration


Security is enforced using the Acegi AOP Alliance (method invocation) security interceptor.
Security interceptors are defined for each public service as Spring beans in public-servicessecurity-context.xml.
The following example is the security interceptor configuration for the ContentService:
<bean id="ContentService_security"
class="net.sf.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref local="accessDecisionManager"/>
</property>
<property name="afterInvocationManager">
<ref local="afterInvocationManager"/>
</property>
<property name="objectDefinitionSource">
<value>
...ContentService.getRawReader=ACL_METHOD.ROLE_ADMINISTRATOR
...ContentService.getReader=ACL_NODE.0.sys:base.ReadContent
...ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent
...ContentService.isTransformable=ACL_ALLOW
...
</value>
</property>
</bean>

Each security interceptor bean has to be configured with an authenticationManager,


accessDecisionManager and afterInvocationManager, which can all be reused. The
authenticationManager is a reference to the Acegi authentication manager bean defined in
authentication-services-context.xml. The accessDecisionManager asks a list of voters
if access should be allowed or not. Voters allow access based on the authorities that the current
authorised user has or based on node access control. The afterInvocationManager enforces
security on objects returned by methods.
The objectDefinitionSource defines security attributes associated with each method. A list of
attributes can be defined as a comma separated list. Pre-conditions are enforced before method
invocation and post-conditions on objects returned by methods.
In the followng, ? represents an authority (user name or group), # a method argument index and *
a string representation of a permission.
Pre-conditions take one of the following forms:
ACL_METHOD.?
Access to the method is restricted to those with the given authority in alfresco. This could be a
user name or group. Dynamic authorities are not supported.

62 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

ACL_NODE.#.*
Access control is restricted to users who have the the specified permission for the node on the
identified argument. If the argument is a NodeRef it will be used; if it is a StoreRef then the
root node for the store will be used; if it is a ChildAssociationRef then the child node will be
used.
ACL_PARENT.#.*
Access control is restricted to users who have the the specified permission for the parent of
the node on the identified argument. If the argument is a NodeRef the parent of the node will
be used; if it is a ChildAssociationRef then the parent node will be used.
ROLE_...
Check for an Acegi authority starting with ROLE_
GROUP_...
Check for an Acegi authority starting with GROUP_
If more than one ACL_NODE.#.* or ACL_PARENT.#.* entry is present then all the permissions
must be available for the method to execute. ACL_ALLOW can be used to give access to all.
ROLE_ADMINISTRATOR can be used to grant access to administrator related methods.
Post-conditions take the forms:
AFTER_ACL_NODE.*
Similar to ACL_NODE.#.* but the restriction applies to the return argument.
AFTER_ACL_PARENT.*
Similar to ACL_PARENT.#.* but the restriction applies to the return argument. The supported
return types are ChildAssociationRef, FileInfo, NodeRef, StoreRef, ResultSet;
Collections and arrays of StoreRef, NodeRef, ChildAssociationRef, and FileInfo.
The post-conditions will create access denied exceptions for return types like NodeRef,
StoreRef, ChildAssociationRef. For collections and arrays, the members will be filtered based
on the access conditions.
NodeService

Security Examples

The following examples are taken from the NodeService_security method security interceptor
bean.
Only administrators can create stores:
NodeService.createStore=ACL_METHOD.ROLE_ADMINISTRATOR

Read permission is required on the node specified as the first argument to the exist() method
call:
NodeService.exists=ACL_NODE.0.sys:base.Read

Creating a new node requires the create children permission on the parent node:
NodeService.createNode=ACL_NODE.0.sys:base.CreateChildren

Adding an aspect to a node requires write permission on the node:


NodeService.addAspect=ACL_NODE.0.sys:base.Write

Deleting a node requires the delete permission on the node:


NodeService.deleteNode=ACL_NODE.0.sys:base.Delete

Accessing the properties of a node requires the permission to read properties:


NodeService.getProperties=ACL_NODE.0.sys:base.ReadProperties

Setting all properties on a node required the permission to write properties:


NodeService.setProperties=ACL_NODE.0.sys:base.WriteProperties

API Development Course 63

Developing against the Alfresco Repository

Reading child associations requires the read children permission on the parent node and the read
permission on the children nodes:
NodeService.getChildAssocs=ACL_NODE.0.sys:base.ReadChildren,
AFTER_ACL_NODE.sys:base.Read

Transaction Management
All repository foundation services are transactional. Transactions are controlled either:
implicitly via Spring declarative transaction demarcation or
explicitly via Alfresco user transactions.

Spring Declarative Transaction Demarcation


Spring declarative transaction demarcation wraps the invocation of every public service method
call in its own transaction. The transactions are defined declaratively in Spring configuration
files and not in your Java code. In most cases, declarative transactions are preferred to user
transactions since they are less invasive. Declarative Transaction Demarcation uses AOP and a
Spring transaction interceptor.
Example TransactionInterceptor configuration for the NodeService taken from publicservices-context.xml:
<bean id="NodeService_transaction"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="exist*">${server.transaction.mode.readOnly}</prop>
<prop key="get*">${server.transaction.mode.readOnly}</prop>
<prop key="has*">${server.transaction.mode.readOnly}</prop>
<prop key="*">${server.transaction.mode.default}</prop>
</props>
</property>
</bean>

The TransactionInterceptor allows any checked application exception to be thrown. By


default, it will only trigger a rollback if an unchecked exception is thrown, or if the transaction has
been marked rollback-only by the application. Specific rollback policies can be configured per
method call and per exception.

Transaction Attributes
The transaction attributes are defined on the TransactionInterceptor as Properties. The
transaction attributes are defined on a per method basis. For each property, the method name is
the key and the transaction attribute descriptor is the value.
For the NodeService, the transaction attributes are as follows:
<property name="transactionAttributes">
<props>
<prop key="exist*">${server.transaction.mode.readOnly}</prop>
<prop key="get*">${server.transaction.mode.readOnly}</prop>
<prop key="has*">${server.transaction.mode.readOnly}</prop>
<prop key="*">${server.transaction.mode.default}</prop>
</props>
</property>

In this example, the method names contain the * wildcard character, to match all methods starting
with a specific keyword.

64 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

The transaction attribute descriptors are defined as properties in the domain/


transaction.properties file:
#
# Server read-only or read-write modes
#
server.transaction.mode.readOnly=PROPAGATION_REQUIRED, readOnly
# the properties below should change in tandem
#server.transaction.mode.default=PROPAGATION_REQUIRED, readOnly
#server.transaction.allow-writes=false
server.transaction.mode.default=PROPAGATION_REQUIRED
server.transaction.allow-writes=true
server.transaction.max-retries=20

A transaction attribute descriptor has the following syntax:


PROPAGATION_NAME, ISOLATION_NAME, readOnly, timeout_NNNN, +Exception1, Exception2

The tokens can be in any order. Only the propagation code is required. The propagation and
isolation codes must use the names of the constants defined in the TransactionDefinition class
(see related link below). Timeout values are in seconds. If no timeout is specified, the transaction
manager will apply a default timeout specific to the particular transaction manager. A + before
an exception name substring indicates that transactions should commit even if this exception is
thrown; a - that they should roll back.
Propagation and isolation codes used in Alfresco:
PROPAGATION_REQUIRED
Support a current transaction; create a new one if none exists.
PROPAGATION_REQUIRES_NEW
Create a new transaction, suspending the current transaction if one exists.
PROPAGATION_NOT_SUPPORTED
Do not support a current transaction; rather always execute non-transactionally.
ISOLATION_DEFAULT
Use the default isolation level of the underlying datastore.
TIMEOUT_DEFAULT
Use the default timeout of the underlying transaction system, or none if timeouts are not
supported.

Transaction Manager
The TransactionInterceptor delegates the actual transaction handling to a
PlatformTransactionManager instance, defined by the transactionManager property. In the
case of Alfresco, the transaction manager is a HibernateTransactionManager.
The transactionManager bean is defined in the hibernate-context.xml file:
<!-- create a transaction manager -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="transactionSynchronizationName">
<value>SYNCHRONIZATION_ALWAYS</value>
</property>
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>

Alfresco User Transactions


By default, all repository foundation services are transactional and each invocation of a service
method is wrapped in its own transaction. These transactions are defined declaratively in

API Development Course 65

Developing against the Alfresco Repository

Spring configuration files and not in your Java code. In most cases, declarative transactions are
preferred to user transactions since they are less invasive. There are situations, however, when
user transactions do need to be used explicitly in your code.
An Alfresco user transaction is accessed via the TransactionService. An example using the
ServiceRegistry is as follows:
UserTransaction trx =
serviceRegistry.getTransactionService().getUserTransaction();

With a UserTransaction in hand, it is possible to mark the beginning and end of a transaction.
Any service calls within the begin and end are thus forced to be included in the same transaction.
For example, the following two NodeService calls are wrapped in the same transaction. Without
the user transaction, the default behaviour would be for each NodeService call to be in its own
transaction.
NodeService nodeService = serviceRegistry.getNodeService();
try
{
trx.begin()
nodeService.createNode(...);
nodeService.createNode(...);
trx.commit();
}
catch(Throwable e)
{
if (trx.getStatus() == Status.ACTIVE)
{
try
{
trx.rollback();
}
catch(Throwable ee)
{
e.printStackTrace();
}
}
}

Although the example shows the usage of one service, any mixture of Alfresco's public services
can be pulled into the same transaction. It is important to note that a UserTransaction cannot be
re-used. That is, once a commit or rollback has been issued, a new UserTransaction has to be
retrieved (via getUserTransaction()) to begin another.

TransactionService
As we have already seen, an Alfresco user transaction is accessed via the
TransactionService.

66 Alfresco Enterprise Edition Version 3.2

Developing against the Alfresco Repository

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs: Interface TransactionService
The interface defines four methods for accessing a user transaction:
getUserTransaction();
This method retrieves a UserTransaction with the PROPAGATION_REQUIRED attribute that
supports transaction propagation.
getUserTransaction(boolean readOnly);
Used to request a read only transaction that supports transaction propagation.
getNonPropagatingUserTransaction();
This method retrieves a UserTransaction with the PROPAGATION_REQUIRES_NEW attribute that
ensures a new transaction is created. Any enclosing transaction is not propagated. When the
transaction is started, the current transaction will be suspended and a new one started.
getNonPropagatingUserTransaction(boolean readOnly);
Used to request a read only transaction, also ensuring that a new transaction is created.

RetryingTransactionHelper
The RetryingTransactionHelper is a helper that runs a unit of work inside a
UserTransaction. If the unit of work fails due to an optimistic locking failure, or a deadlock loser
failure, it will transparently retry the unit of work until it succeeds, or until a maximum number of
retries have been attempted.
The exceptions that trigger retries are:
ConcurrencyFailureException
DeadlockLoserDataAccessException
StaleObjectStateException
LockAcquisitionException
BatchUpdateException
The RetryingTransactionHelper is retrieved from the TransactionService via the
getRetryingTransactionHelper() method.

API Development Course 67

Developing against the Alfresco Repository

A unit of work is defined as an instance of the RetryingTransactionCallback inner class.


The doInTransaction() method is called to run the unit of work in a transaction. The optional
boolean readOnly argument can be used to request a read only transaction. The optional
boolean requiresNew argument can be used to force a new transaction (will retrieve a user
transaction from the TransactionService via the getNonPropagatingUserTransaction()
method.
Example taken from the FirstFoundationClient SDK sample:
TransactionService transactionService =
serviceRegistry.getTransactionService();
RetryingTransactionCallback<Object> exampleWork =
new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
doExample(serviceRegistry);
return null;
}
};
transactionService.
getRetryingTransactionHelper().doInTransaction(exampleWork);

68 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

Extending the Alfresco Repository

API Development Course 69

Extending the Alfresco Repository

Repository Policies
Introduction
Repository policies are similar to events. Each service defines its own set of policies. For
example the Content Service defines two policies:
OnContentUpdatePolicy is fired when the content is updated on a node,
OnContentReadPolicy is fired when the content is read on a node.
A custom method or behaviour can be registered against a policy and will be called
automatically when the policy is fired. This enables tasks such as maintaining the last modified
date on a node or creating a new version and incrementing the version number on a node to be
automated.
The event-driven processing paradigm is similar to that used in traditional user interfaces.

Available Policies
The available policies are defined as interfaces for each service. The Node Service defines more
than twenty policies, including BeforeCreateNodePolicy, OnUpdatePropertiesPolicy and
OnAddAspectPolicy, the other services each define their own specific policies. The policies are
usually defined on an interface named after the service. For example, the Node Service policies
are defined on the NodeServicePolicies interface and the Content Service policies are defined
on the ContentServicePolicies interface:

Classes interested in certain policies must implement the corresponding interfaces and methods.
Each policy method (or behaviour) defines its own specific list of arguments. For example:
A behaviour registered against the BeforeCreateNodePolicy is called before a node
is created. The beforeCreateNode() method receives the NodeRef of the parent of the
node being created, the QName of the association type (usually cm:contains), the QName of
the new association and the node type of the node being created.
A behaviour registered against the OnCreateNodePolicy is called when a node is created.
The onCreateNode() method receives the ChildAssociationRef to the node created.

70 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

Policy Types
Each policy extends one of three policy types:
Class policy
Property policy
Association policy

Class policies are for events related to content types or aspects, Property policies are for events
related to properties and Association policies for events related to associations. Most policies
extend the Class policy interface, a few policies extend the Association policy interface and, at
the time of writing, none extend the Property policy.

Custom Aspect with Behaviour Howto


Before starting the tutorial, you should be familiar with Alfresco Content Models and the
Introduction on page 34.
In the tutorial we are going to define a new Content Hits aspect with behaviour that will
automatically keep a running count of the number of times the content on a node has been
updated and read. The example demonstrates how the counters can be incremented after the
content update or read transactions have been done.
The examples in this tutorial are taken from the SDK CustomAspect sample.
1. Defining the Content Hits model
The Content Hits aspect is defined in a custom model contentHitsModel.xml:
<aspect name="ch:contentHits">
<title>Content Hits</title>

API Development Course 71

Extending the Alfresco Repository

<properties>
<property name="ch:countStartedDate">
<type>d:date</type>
<mandatory>true</mandatory>
</property>
<property name="ch:updateCount">
<type>d:int</type>
<default>0</default>
</property>
<property name="ch:readCount">
<type>d:int</type>
<default>0</default>
</property>
</properties>
</aspect>

The ch:countStartedDate will be set when the aspect is added to a node. The
ch:updateCount and ch:readCount properties will be incremented for each content
update or read respectively.
2. Implementing the aspect behaviour class
This ContentHitsAspect class contains the behaviour behind the ch:contentHits
aspect.
a.

Creating the aspect behaviour class


The Content Hits aspect behaviour class is interested in the following three events:
when the Content Hits aspect is added to a Node;
when the content of a Node with the Content Hits aspect is updated (written);
when the content of a Node with the Content Hits aspect is read.
The class needs to implement the three corresponding polices:
public class ContentHitsAspect implements
ContentServicePolicies.OnContentReadPolicy,
ContentServicePolicies.OnContentUpdatePolicy,
NodeServicePolicies.OnAddAspectPolicy

b.

Writing the constructor


The Content Hits example uses a Transaction Listener to increment the counters after
the content update or read transactions have been committed.
The Transaction Listener is instantiated by the constructor:
private TransactionListener transactionListener;
/**
* Default constructor for bean construction
*/
public ContentHitsAspect()
{
this.transactionListener = new ContentHitsTransactionListener();
}

c.

Bind the behaviours


A behaviour is an encapsulated piece of logic that may be bound to a policy. The logic
may be expressed in either Java or JavaScript.

72 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

When creating a new JavaBehaviour instance, you must provide the following:
public JavaBehaviour(Object instance,
String method,
NotificationFrequency frequency)

instance
the object instance holding the method
method
the method name
frequency
one of three possible values defining when the behaviour should be notified:
EVERY_EVENT, FIRST_EVENT or TRANSACTION_COMMIT
The supplied method implements the behaviour logic and may have dependencies on
Foundation Services that can be resolved using Spring dependency injection.
A behaviour is bound to a policy using the bindClassBehaviour() method on the
Policy Component:
bindClassBehaviour(QName policy,
QName classRef,
Behaviour behaviour);

policy
the policy name
classRef
QName of type or aspect concerned by the policy
behaviour
a Behaviour object (instance of JavaBehaviour or ScriptBehaviour)
A behaviour is bound to a specific content type or aspect using the classRef
argument.
The Policy Component is also used by services to register policies and to invoke
policy behaviours.

API Development Course 73

Extending the Alfresco Repository

In the Content Hits example, the Spring initialise() method is used to bind the
behaviours to policies:
public void initialise()
{
this.policyComponent.bindClassBehaviour(
QName.createQName(NamespaceService.ALFRESCO_URI,
"onAddAspect"),
ASPECT_CONTENT_HITS,
new JavaBehaviour(this, "onAddAspect",
NotificationFrequency.FIRST_EVENT));
this.policyComponent.bindClassBehaviour(
ContentServicePolicies.ON_CONTENT_READ,
ASPECT_CONTENT_HITS,
new JavaBehaviour(this, "onContentRead",
NotificationFrequency.TRANSACTION_COMMIT));
this.policyComponent.bindClassBehaviour(
ContentServicePolicies.ON_CONTENT_UPDATE,
ASPECT_CONTENT_HITS,
new JavaBehaviour(this, "onContentUpdate",
NotificationFrequency.TRANSACTION_COMMIT));
}

d.

Writing the onAddAspect policy behaviour


The onAddAspect policy behaviour will be called when the ch:contentHits aspect
is added to a node. It receives the NodeRef of the node concerned and the QName of
the added aspect. In the example, the added aspect will always be ch:contentHits
since the behaviour has only been registered for that specific aspect.
The onAddAspect() behaviour sets the count start date/time when the
ch:contentHits aspect is added:
public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName)
{
this.nodeService.setProperty(
nodeRef,
PROP_COUNT_STARTED_DATE,
new Date());
}

e.

Writing the onContentRead policy behaviour


The onContentRead policy behaviour will be called when the content property of a
node with the ch:contentHits aspect is read. It receives the NodeRef of the node
being read.
As we have seen, the example uses a Transaction Listener to increment the counters
after the content update or read transactions have been committed. The Transaction
Listener is bound to the current transaction using the static bindListener() method
on the AlfrescoTransactionSupport class and passing the Transaction Listener
created earlier.
The list of nodes read is stored as a resource against the current transaction using
the static bindResource() method on the AlfrescoTransactionSupport class. The
nodes are stored as a Set<NodeRef> using the KEY_CONTENT_HITS_READS key.
The onContentRead() behaviour adds the NodeRef of the node being read to the list
of nodes that require read count increments after the transaction completes:
public void onContentRead(NodeRef nodeRef)
{
// Bind the listener to the transaction
AlfrescoTransactionSupport.bindListener(transactionListener);

74 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

// Get the set of nodes read


@SuppressWarnings("unchecked")
Set<NodeRef> readNodeRefs = (Set<NodeRef>)
AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_READS);
if (readNodeRefs == null)
{
readNodeRefs = new HashSet<NodeRef>(5);
AlfrescoTransactionSupport.bindResource(KEY_CONTENT_HITS_READS,
readNodeRefs);
}
readNodeRefs.add(nodeRef);
}

f.

Writing the onContentUpdate policy behaviour.


The onContentUpdate policy behaviour will be called when the content property of a
node with the ch:contentHits aspect is updates. It receives the NodeRef of the node
being updated and a boolean to indicate if the content is new content or not.
The list of nodes updated is stored as a resource against the current transaction using
the the KEY_CONTENT_HITS_WRITES key.
The onContentUpdate() behaviour adds the NodeRef of the node being updated to
the list of nodes that require write count increments after the transaction completes:
public void onContentUpdate(NodeRef nodeRef, boolean newContent)
{
// Bind the listener to the transaction
AlfrescoTransactionSupport.bindListener(transactionListener);
// Get the set of nodes written
@SuppressWarnings("unchecked")
Set<NodeRef> writeNodeRefs = (Set<NodeRef>)
AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_WRITES);
if (writeNodeRefs == null)
{
writeNodeRefs = new HashSet<NodeRef>(5);
AlfrescoTransactionSupport.bindResource(KEY_CONTENT_HITS_WRITES,
writeNodeRefs);
}
writeNodeRefs.add(nodeRef);
}

g.

Writing the Transaction Listener


The Transaction Listener is bound the the current transaction by the
onContentRead() or onContentUpdate() behaviour methods. The afterCommit()
method is called on the Transaction Listener after the transaction has committed.
In the example, the Transaction Listener is defined as an inner class. It implements
the afterCommit() method.
private class ContentHitsTransactionListener extends
TransactionListenerAdapter
{
public void afterCommit()
{
...
}
}

The afterCommit() method retrieves the list of nodes stored as a resource on the
transaction by the onContentRead() behaviour:
public void afterCommit()
{

API Development Course 75

Extending the Alfresco Repository

Set<NodeRef> readNodeRefs = (Set<NodeRef>)


AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_READS);
if (readNodeRefs != null)
{
for (NodeRef nodeRef : readNodeRefs)
{
Runnable runnable = new
ContentHitsReadCountIncrementer(nodeRef);
threadExecuter.execute(runnable);
}
}
...
}

The Content Hits read count (ch:readCount) is incremented for each node in the list.
The afterCommit() method retrieves the list of nodes stored as a resource on the
transaction by the onContentUpdate() behaviour:
public void afterCommit()
{
....
Set<NodeRef> writeNodeRefs = (Set<NodeRef>)
AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_WRITES);
if (writeNodeRefs != null)
{
for (NodeRef nodeRef : readNodeRefs)
{
Runnable runnable = new
ContentHitsWriteCountIncrementer(nodeRef);
threadExecuter.execute(runnable);
}
}
}

The Content Hits update count (ch:updateCount) is incremented for each node in the
list.
h.

Writing the Spring dependency injection setter methods


The ContentHitsAspect class uses several services that can be injected via Spring:
private
private
private
private
private

PolicyComponent policyComponent;
BehaviourFilter policyFilter;
NodeService nodeService;
TransactionService transactionService;
ThreadPoolExecutor threadExecuter;

public void setPolicyComponent(PolicyComponent policyComponent) {


this.policyComponent = policyComponent;
}
public void setPolicyFilter(BehaviourFilter policyFilter) {
this.policyFilter = policyFilter;
}
public void setNodeService(NodeService nodeService) {
this.nodeService = nodeService;
}
public void setTransactionService(TransactionService
transactionService) {
this.transactionService = transactionService;
}
public void setThreadExecuter(ThreadPoolExecutor threadExecuter) {
this.threadExecuter = threadExecuter;
}

3. Registering the content hits model

76 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

The Content Hits model is registered with the following Spring bean definition taken from
contents-hits-context.xml:
<bean id="contentHits.dictionaryBootstrap"
parent="dictionaryModelBootstrap"
depends-on="dictionaryBootstrap">
<property name="models">
<list>
<value>org/alfresco/sample/contentHitsModel.xml</value>
</list>
</property>
</bean>

4. Registering the aspect behaviour


The ContentHitsAspect behaviour class is registered with the following Spring bean
definition taken from contents-hits-context.xml:
<bean id="contentHitsAspect"
class="org.alfresco.sample.ContentHitsAspect"
init-method="initialise">
<property name="nodeService">
<ref bean="nodeService"/>
</property>
<property name="policyComponent">
<ref bean="policyComponent"/>
</property>
<property name="policyFilter">
<ref bean="policyBehaviourFilter"/>
</property>
<property name="transactionService">
<ref bean="transactionService"/>
</property>
<property name="threadExecuter">
<ref bean="threadPoolExecutor"/>
</property>
</bean>

The initialise() method will be called once the bean has been instantiated and the
properties set.
5. Configuring the property sheet
In order to see the Content Hits aspect properties in the Web Client, the property sheet
has to be configured as in the example web-client-config-custom.xml:
<config evaluator="aspect-name" condition="ch:contentHits">
<property-sheet>
<show-property name="ch:countStartedDate"
read-only="true"
show-in-edit-mode="false"/>
<show-property name="ch:updateCount"
read-only="true"
show-in-edit-mode="false"/>
<show-property name="ch:readCount"
read-only="true"
show-in-edit-mode="false" />
</property-sheet>
</config>

6. Packaging and deploying


In order to deploy a custom aspect with behaviour to the Alfresco repository, the following
files need to be packaged:
the compiled custom aspect behaviour class;
the Spring configuration file (contents-hits-context.xml);
if required, the custom dictionary model (contentsHitModel.xml);

API Development Course 77

Extending the Alfresco Repository

the Web Client custom configuration file containing the aspect property sheet
definition (web-client-config-custom.xml).
The compiled class needs to be exported to a JAR file. The files then need to be deployed
to the following directories in the Alfresco repository:
the JAR file to the WEB-INF/lib directory;
all other files to the WEB-INF/classes/alfresco/extension directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.

78 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

Repository Actions
Introduction
An action is a unit of work that is performed against a node. For example, moving a node,
copying a node, checking a node in, transforming the contents of a node, etc. Many actions
already exist, however it is possible to add you own custom actions in a few easy steps.
An action has to implement the org.alfresco.repo.action.executer.ActionExecuter
interface. The ActionExecuter interface can be implemented directly, however it is best to
extend the abstract class ActionExecuterAbstractBase that has been written to provide
basic services for action executer implementations. Only two methods need implementing when
deriving from the abstract superclass. ActionExecuterAbstractBase is presented in detail in
the Repository Action Howto on page 79.

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs:

Interface ActionExecuter

Class ActionExecuterAbstractBase

Repository Action Howto


Before starting the tutorial, you should be familiar with Alfresco Content Models and the
Introduction on page 34.
This is the first part of a two part tutorial. The first part shows you how to create a no-parameter
action and incorporate it into the web client. The second part (Repository Action with Parameters
Howto on page 85) shows you how to add a parameter to the action.
In the first part of the tutorial we are going to add a Web 2.0 style tagging feature to the web
client. We will define a taggable aspect which will be applied via the custom tag action in the
repository. We will then configure the web client to show the taggable aspect in its property
sheet. The examples in this tutorial are taken from the Alfresco SDK TaggingSample.
1. Creating a tags custom model
A custom model is created to hold the new aspect and user assigned tags. The model
uses a prefix of tag, the aspect is called tag:taggable and the property is called
tag:tags.

API Development Course 79

Extending the Alfresco Repository

In the example, the tags model is defined in the tagsModel.xml file.


<!-- Definition of new Taggable Aspect -->
<aspect name="tag:taggable">
<title>Taggable</title>
<properties>
<property name="tag:tags">
<title>Tags</title>
<type>d:text</type>
<multiple>true</multiple>
</property>
</properties>
</aspect>

2. Implementing the action executer class


a.

Creating the TagActionExecuter class


Action executers generally extend an abstract class called
ActionExecuterAbstractBase. The abstract superclass provides basic services
for ActionExecuter implementations. It also introduces a new abstract method
executeImpl() that is called from execute() to do the actual work. The only two
methods that require implementing when deriving from the abstract superclass are
addParameterDefinitions() and executeImpl().

The name of the action executer is defined by the static NAME attribute. In our case the
action executer name is tag.
public class TagActionExecuter extends ActionExecuterAbstractBase
{
public static final String NAME = "tag";
...
}

b.

Implementing the addParameterDefinitions() method

80 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

In the first part of the tutorial, the action does not take any parameters. The action
executer, however, still has to implement the addParameterDefinitions() method,
even if it is empty.
protected void addParameterDefinitions(List<ParameterDefinition>
paramList)
{
// there are no parameters
}

c.

Implementing the executeImpl() method


The executeImpl() method does the actual work. It receives an Action object and
the NodeRef of the node to do the work on (actionedUponNodeRef). Action executers
generally check that the actionedUponNode exists using the NodeService.exists()
method.
In the example, executeImpl() adds the taggable aspect to the
actionedUponNodeRef using the addAspect() method on the NodeService:
protected void executeImpl(Action action, NodeRef actionedUponNodeRef)
{
if (nodeService.exists(actionedUponNodeRef) == true)
{
// add the aspect if it is not already present on the node
QName tagAspect = QName.createQName("extension.tags",
"taggable");
if (nodeService.hasAspect(actionedUponNodeRef, tagAspect) ==
false)
{
nodeService.addAspect(actionedUponNodeRef, tagAspect,
null);
}
}
}

d.

Writing the Spring dependency injection setter methods


Because the executeImpl() method uses the NodeService, we will need to inject
the NodeService using Spring dependency injection.
In the example, Spring will inject the NodeService using the setNodeService()
method:
private NodeService nodeService;
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}

3. Defining the action I18N messages


An action has an associated title and description. The abstract superclass
ActionExecuterAbstractBase looks for messages in the I18N resource bundles using an
id of the form:
<action-name>.title
<action-name>.description

where <action-name> is the name of the action defined by the static NAME attribute on the
action executer class.
In the example, the I18N messages are defined in the tag-actionmessages.properties file.
# Action title and description

API Development Course 81

Extending the Alfresco Repository

tag.title=Add tags to item


tag.description=This action adds tags to the matched item

4. Registering the action


An action is registered as a bean in a Spring configuration file.
In the example, the tag action executer bean is defined in the tagging-context.xml file.
<!-- Tag Action Bean -->
<bean id="tag" class="org.alfresco.sample.TagActionExecuter"
parent="action-executer" >
<property name="nodeService">
<ref bean="nodeService" />
</property>
</bean>

The bean must define the action-executer bean as its parent bean.
The action-executer parent bean is defined in the action-services-context.xml
Spring configuration file:
<bean id="action-executer" abstract="true" init-method="init">
<property name="runtimeActionService">
<ref bean="actionService" />
</property>
</bean>

The init() method is defined as an initialisation method and is called automatically


by Spring once the action executer has been instantiated. The init() method is
implemented by the ActionExecuterAbstractBase class. If the action is public, it will
register the action executer with the RuntimeActionService.
5. Registering the action I18N message bundle
An I18N message bundle is registered as a bean in a Spring configuration file. The
resourceBundles property contains the list of message bundles to register and the class
attribute must be defined as org.alfresco.i18n.ResourceBundleBootstrapComponent.
In the example, the tag-action-messages.properties file is registered as an I18N
resource bundle in the tagging-context.xml file:
<!-- Load the Tag Action Messages -->
<bean id="tag-action-messages"
class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
<property name="resourceBundles">
<list>
<value>org.alfresco.sample.tag-action-messages</value>
</list>
</property>
</bean>

6. Registering the tags model


A dictionary model is registered as a bean in a Spring configuration file. The bean must
define the dictionaryModelBootstrap bean as its parent bean and that it depends
on the dictionaryBootstrap bean. The models property contains the list of models to
register.
In the example, the tagsModel.xml file is registered as a dictionary model in the
tagging-context.xml file:
<!-- Tag Model Registration -->
<bean id="tags.dictionaryBootstrap" parent="dictionaryModelBootstrap"
depends-on="dictionaryBootstrap">
<property name="models">
<list>
<value>alfresco/extension/tagsModel.xml</value>
</list>

82 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

</property>
</bean>

7. Configuring the property sheet


In order to view and modify the tags property in the Web Client, the property sheet needs
to be configured for the taggable aspect.
The following example, taken from web-client-config-custom.xml, will display the
tags property on the property sheet:
<config evaluator="aspect-name" condition="tag:taggable">
<property-sheet>
<show-property name="tag:tags" />
</property-sheet>
</config>

8. Packaging and deploying


In order to deploy a custom action to the Alfresco repository, the following files need to be
packaged:
the compiled action executer class (TagActionExecuter.class);
the I18N message bundle file (tag-action-messages.properties);
the Spring configuration file containing the bean definitions for the action executer
and the message bundle (tagging-context.xml);
if required, the custom dictionary model (tagsModel.xml);
if required, the Web Client custom configuration file (web-client-configcustom.xml).
The compiled class and message bundle can be exported to the same JAR file. The files
then need to be deployed to the following directories in the Alfresco repository:
the JAR file to the WEB-INF/lib directory;
all other files to the WEB-INF/classes/alfresco/extension directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.
After deployment, the Add tags to item action is available from the action wizards:

API Development Course 83

Extending the Alfresco Repository

The action will automatically add the taggable aspect, however the tag values will have to be
added manually via the property sheet:

84 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

Repository Action with Parameters Howto


This is the second part of a two part tutorial. The first part shows you how to create a noparameter action and incorporate it into the web client.
The second part of the tutorial will now show you how to add a parameter to the repository tag
action and how to configure the web client to add a custom JSP that will prompt the user for the
default tags. The examples in this tutorial are taken from the Alfresco SDK TaggingSample.
1. Adding a parameter to the action
Parameters are added to an action in the addParameterDefinitions() method. They
can be used in the executeImpl() method.
a.

Modifying the addParameterDefinitions() method


The addParameterDefinitions() method is passed a List of
ParameterDefinition objects. To register a new parameter, simply create a new
ParameterDefinition object and add it to the list.
public ParameterDefinitionImpl(
String name,
QName type,
boolean isMandatory,
String displayLabel)
name

The name of the parameter.


type

The QName type of the parameter.


isMandatory
true if the parameter is mandatory, otherwise false.
displayLabel

The display label: usually a call to the getParamDisplayLabel() method.


In the example, the name of the parameter is tags, it takes a string value and is
mandatory:
public static final String PARAM_TAGS = "tags";
protected void addParameterDefinitions(List<ParameterDefinition>
paramList)
{
paramList.add(new ParameterDefinitionImpl(
PARAM_TAGS,
DataTypeDefinition.TEXT,
true,
getParamDisplayLabel(PARAM_TAGS)));
}

b.

Modifying the executeImpl() method


The executeImpl() method can now be modified to retrieve the tags parameter
value and set the tags property on the node being actioned upon. Parameter values
are retrieved from the Action object using the getParameterValue() method.
In the example, executeImpl() retrieves the value of the PARAM_TAGS parameter
from the Action object, converts it to a List and then uses the list to set the tags

API Development Course 85

Extending the Alfresco Repository

property on the actionedUponNodeRef using the setProperty() method on the


NodeService:
protected void executeImpl(Action action, NodeRef actionedUponNodeRef)
{
if (this.nodeService.exists(actionedUponNodeRef) == true)
{
...
// create the tags as a list
String tags = (String)action.getParameterValue(PARAM_TAGS);
List<String> tagsList = new ArrayList<String>();
if (tags != null && tags.length() > 0)
{
StringTokenizer tokenizer = new StringTokenizer(tags, ",");
while (tokenizer.hasMoreTokens())
{
tagsList.add(tokenizer.nextToken().trim());
}
}
// set the tags property
QName tagsProp = QName.createQName("extension.tags", "tags");
this.nodeService.setProperty(actionedUponNodeRef, tagsProp,
(Serializable)tagsList);
}
}

2. Defining the parameter I18N messages


Each action parameter has an associated display label. The getParamDisplayLabel()
method on the abstract superclass ParameterizedItemAbstractBase looks for a
message in the I18N resource bundles using an id of the form:
<action-name>.<param-name>.display-label

where <action-name> is the name of the action defined by the static NAME attribute
on the action executer class and <param-name> is the parameter value passed to the
getParamDisplayLabel() method.
In the example, the I18N display label for the param_tags parameter has been added to
the tag-action-messages.properties file:
# Action title and description
tag.title=Add tags to item
tag.description=This action adds tags to the matched item
# Action parameter display labels
tag.param_tags.display-label=Tags

3. Creating the action JSP


Although the action wizards use the Wizard Framework, the JSPs that collect parameters
for actions and conditions are complete JSPs as they are not displayed within the wizard
container. This means you have to have the whole page structure in your action JSP. The
easiest solution, is to copy an existing one and modify it to your needs.
In the example, copying jsp/actions/add-features.jsp is a good start as we only need
to replace the drop down list with a text field and change a few labels such as the page
title. The modified parts of the page are as follows:
...
<r:page titleId="title_action_tag">
<f:view>
<%-- load a bundle of properties with I18N strings --%>
<f:loadBundle basename="alfresco.messages.webclient" var="msg"/>

86 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

<f:loadBundle basename="alfresco.extension.webclient" var="customMsg"/


>
<h:form acceptcharset="UTF-8" id="tag-action">
...
<tr>
<td><nobr><h:outputText value="#{customMsg.tags}:"/></nobr></td>
<td width="95%">
<h:inputText
value="#{WizardManager.bean.actionProperties.tags}"
size="50" maxlength="1024" />
</td>
</tr>
...

4. Implementing the action handler


To integrate the action into the action based wizards such as the Run Action, Create
Rule and Edit Rule Wizards, an action handler is required.
a.

Creating the TagActionHandler class


An action handler typically extends an abstract BaseActionHandler class and
is responsible for directing the wizard to the page to collect the parameters and
marshalling the parameters between the wizard and the repository.
If the page collecting the parameters requires some default setup the
setupUIDefaults() method can be overridden.

b.

Implementing the getJSPPath() method


The getJSPPath() method should return the path to the action's JSP, for example: /
jsp/extension/tag.jsp.
public String getJSPPath()
{
return "/jsp/extension/tag.jsp";
}

c.

Implementing the prepareForSave() method

API Development Course 87

Extending the Alfresco Repository

The prepareForSave() method places the tags the user entered into the repository
properties map passed in.
public static final String PROP_TAGS = "tags";
public void prepareForSave(Map<String, Serializable> actionProps,
Map<String, Serializable> repoProps)
{
repoProps.put(TagActionExecuter.PARAM_TAGS,
(String)actionProps.get(PROP_TAGS));
}

d.

Implementing the prepareForEdit() method


The prepareForEdit() method does the opposite of prepareForSave() and takes
the tags stored in the action and places them in the properties map for the wizard.
public static final String PROP_TAGS = "tags";
public void prepareForEdit(Map<String, Serializable> actionProps,
Map<String, Serializable> repoProps)
{
actionProps.put(PROP_TAGS,
(String)repoProps.get(TagActionExecuter.PARAM_TAGS));
}

e.

Implementing the generateSummary() method


Finally, the generateSummary() method is used to generate a summary string for the
action, this typically includes the parameters added by the user.
public String generateSummary(FacesContext context, IWizardBean
wizard,
Map<String, Serializable> actionProps)
{
String tags = (String)actionProps.get(PROP_TAGS);
if (tags == null)
{
tags = "";
}
return MessageFormat.format(Application.getMessage(context,
"add_tags"),
new Object[] {tags});
}

5. Defining the action handler messages


The generateSummary() method in the action handler uses a new add_tags message
id. Custom messages are defined in the webclient.properties file in the extension
directory.
Example taken from extension/webclient.properties.
add_tags=Add tags ''{0}''

6. Registering the action handler


The action handler is registered using an Action Wizards entry in a web client
configuration file.
Example taken from web-client-config-custom.xml.
<config evaluator="string-compare" condition="Action Wizards">
<action-handlers>
<handler name="tag"
class="org.alfresco.sample.TagActionHandler" />
</action-handlers>
</config>

88 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

7. Packaging and deploying


In order to deploy a custom action (with parameters) to the Alfresco repository, the
following files need to be packaged:
the compiled ActionExecuter and ActionHandler classes
(TagActionExecuter.class and TagActionHandler.class);
the I18N message bundle file (tag-action-messages.properties);
the Spring configuration file containing the bean definitions for the action executer
and the message bundle (tagging-context.xml);
the action JSP (tag.jsp);
the custom webclient.properties file;
if required, the custom dictionary model (tagsModel.xml);
the Web Client custom configuration file containing the action handler definition
(web-client-config-custom.xml).
The compiled classes and message bundle can be exported to the same JAR file. The files
then need to be deployed to the following directories in the Alfresco repository:
the JAR file to the WEB-INF/lib directory;
the JSP file to the jsp/extension directory;
all other files to the WEB-INF/classes/alfresco/extension directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.
After deployment, the Add tags to item action will have an action JSP that can be used to define
default tag values:

The summary page show the tags that will automatically be added:

API Development Course 89

Extending the Alfresco Repository

When the action is run against a content item, the taggable aspect will be added and
automatically initialised with the default tag values defined on the action:

90 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

Content Transformers
Introduction
A content transformer is a Java class that can transform content from one mime type to another.
Many content transformers already exist, however it is possible to add you own custom content
transformer in a few easy steps.
A content transformer has to implement the
org.alfresco.repo.content.transform.ContentTransformer interface.
ContentTransformer extends the org.alfresco.repo.content.ContentWorker interface that

is a common marker interface for specific worker interfaces such as content transformers and
metadata extractors.
An abstract base class called AbstractContentTransformer has been written to provides basic
services for ContentTransformer implementations. Only two methods need implementing when
deriving from the abstract superclass. AbstractContentTransformer is presented in detail in
the tutorial Content Transformer Howto on page 91.

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs:

Interface ContentTransformer

Class AbstractContentTransformer

Content Transformer Howto


Before starting the tutorial, you should be familiar with the Introduction on page 34.
The examples in this tutorial are taken from the PdfBoxContentTransformer
(org.alfresco.repo.content.transform.PdfBoxContentTransformer).
1. Implementing the content transformer class
Content transformers generally extend an abstract class called
AbstractContentTransformer. The abstract class provides basic services for
ContentTransformer implementations. It also introduces a new abstract method
transformInternal() that is called from transform() to do the actual transformation
work. The only two methods that require implementing when deriving from the abstract
superclass are getReliability() and transformInternal().
a.

Creating the PdfBoxContentTransformer class

API Development Course 91

Extending the Alfresco Repository

b.

Implementing the getReliability() method


The getReliability() method provides the approximate accuracy with which the
transformer can transform from one mime type to another. It is used to determine
which of a set of transformers will be used to perform a specific transformation. The
method returns a score between 0.0 and 1.0:
0.0 indicates that the transformation cannot be performed at all;
1.0 indicates that the transformation can be performed perfectly.
Before calling transformInternal(), the AbstractContentTransformer checks
that the transformation is allowed by calling the getReliability() method on the
subclass. If the subclass method returns 0.0, an AlfrescoRuntimeException is
thrown.
The example below is taken from PdfBoxContentTransformer. The
getReliability() method returns 1.0 if the source and target mime types are
application/pdf and text/plain respectively, otherwise it returns 0.0.
public double getReliability(String sourceMimetype,
String targetMimetype)
{
if (!MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) ||
!MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype))
{
// only support PDF -> Text
return 0.0;
}
else
{
return 1.0;
}
}

c.

Implementing the transformInternal() method

92 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

The transformInternal() method does the actual transformation work. The


reader (ContentReader) and writer (ContentWriter) objects, passed as arguments,
allow direct access to the source and target content. The source is available as an
InputStream via the getContentInputStream() method on the reader. The target
is available as an OutputStream via the getContentOutputStream() method on the
writer. Options may be passed as a Map, however they are transformer dependent and
may be null.
Both reader and writer will be closed after the transformation completes, however
when accessing the content via a stream, failure to close the stream, regardless of
success of failure, will result in an error message being generated to the log output
and the stream will be held open indefinitely.
If it is necessary to work against physical files during the transformation, use the
TempFileProvider to ensure that all temporary files will be cleaned up appropriately.
For more information, see Working with Temporary Files on page 97.
transformInternal() need only be concerned with performing the transformation.

There is no need to handle any exceptions generated during the transformation, either
runtime or otherwise, the AbstractContentTransformer superclass will handle and
report these as required.
AbstractContentTransformer also calculates and maintains the average time taken

to perform a transformation. If a transformation fails, the average time is set to 10


seconds to penalise a transformer that has failed.
The example below is taken from PdfBoxContentTransformer. The source
InputStream and PDDocument are closed once the transformation is complete. The
method may throw an exception.
protected void transformInternal(
ContentReader reader,
ContentWriter writer,
Map<String, Object> options) throws Exception
{
PDDocument pdf = null;
InputStream is = null;
try
{
is = reader.getContentInputStream();
// stream the document in
pdf = PDDocument.load(is);
// strip the text out
PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(pdf);
// dump it all to the writer
writer.putContent(text);
}
finally
{
if (pdf != null)
{
try { pdf.close(); }
catch (Throwable e) {e.printStackTrace(); }
}
if (is != null)
{
try { is.close(); }
catch (Throwable e) {e.printStackTrace(); }
}
}
}

API Development Course 93

Extending the Alfresco Repository

2. Defining the content transformer Spring bean


A content transformer is registered as a bean in a Spring configuration file. By
convention, the bean id has a transformer. prefix. The bean must define
baseContentTransformer as it's parent bean.
The PdfBoxContentTransformer bean is defined in content-services-context.xml:
<bean id="transformer.PdfBox"
class="org.alfresco.repo.content.transform.PdfBoxContentTransformer"
parent="baseContentTransformer" >
<property name="explicitTransformations">
<list>
<bean class="...ContentTransformerRegistry$TransformationKey" >
<constructor-arg>
<value>application/pdf</value>
</constructor-arg>
<constructor-arg>
<value>text/plain</value>
</constructor-arg>
</bean>
</list>
</property>
</bean>

The bean may also define a list of explicit transformations that the content transformer
can perform regardless of what it returns via the getReliability() check. The
explicit transformations are defined on the explicitTransformations property as
a list of ContentTransformerRegistry.TransformationKey objects. The source
and target mime types for the explicit transformation are passed as arguments to the
TransformationKey constructor.
The baseContentTransformer parent bean is defined in content-servicecontext.xml:
<!-- Abstract bean definition defining base definition for all
transformers -->
<bean id="baseContentTransformer"
class="org.alfresco.repo.content.transform.AbstractContentTransformer"
abstract="true"
init-method="register">
<property name="mimetypeService">
<ref bean="mimetypeService" />
</property>
<property name="registry">
<ref bean="contentTransformerRegistry" />
</property>
</bean>

The register() method is defined as a Spring bean initialisation method on the parent
bean and is called automatically by Spring once the content transformer has been
instantiated. It registers the content transformer with the ContentTransformerRegistry
for each explicit transformation provided.

94 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

3. Packaging and deploying


In order to deploy a custom content transformer to the Alfresco repository, the following
files need to be packaged:
the compiled custom content transformer class;
the Spring configuration file containing the bean definition for the custom content
transformer.
The compiled class needs to be exported to a JAR file. The files then need to be deployed
to the following directories in the Alfresco repository:
the JAR file to the WEB-INF/lib directory;
the Spring configuration file to the WEB-INF/classes/alfresco/extension
directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.
ContentTransformerRegistry

The ContentTransformerRegistry holds a list of available content transformers and


provides the most appropriate content transformer for a particular source and target mime type
transformation request.
Upon initialisation, content transformers register themselves with the
ContentTransformerRegistry via the addTransformer() method. Content transformers
also register themselves for each explicit transformation they can perform (as defined in their
Spring bean definition) via the addExplicitTransformer() method. An explicit transformation is
defined as an instance of the ContentTransformerRegistry.TransformationKey inner class.

API Development Course 95

Extending the Alfresco Repository

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs: Class ContentTransformerRegistryr
The getTransformer() method is used by clients to get the best transformer available for a
given transformation. If two transformers perform the same transformation, the most reliable one
will always be chosen. If two or more transformers exist with the same reliability, then they will
be cycled until the fastest one is determined. The timing code is automatically provided by the
AbstractContentTransformer superclass.

The ContentTransformerRegistry bean is defined in the content-service-context.xml


Spring configuration file:
<!-- Content Transformation Regisitry -->
<bean id="contentTransformerRegistry"
class="org.alfresco.repo.content.transform.ContentTransformerRegistry" />

96 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

Further Reading
Working with Temporary Files
If it is necessary to work against physical files during the transformation, use the
org.alfresco.util.TempFileProvider#createTempFile to ensure that all temporary files will
be cleaned up appropriately. Failure to do this will mean that temporary files are not cleaned up
while the system is running. Do not use deleteOnExit - the Alfresco repository is designed to
run under load indefinitely, i.e. until the next upgrade. Give your temp files meaningful prefixes as
it will help during debugging.
Example taken from
org.alfresco.repo.content.transform.OpenOfficeContentTransformer:
String sourceMimetype = getMimetype(reader);
String targetMimetype = getMimetype(writer);
MimetypeService mimetypeService = getMimetypeService();
String sourceExtension = mimetypeService.getExtension(sourceMimetype);
String targetExtension = mimetypeService.getExtension(targetMimetype);
// create temporary files to convert from and to
File tempFromFile = TempFileProvider.createTempFile(
"OpenOfficeContentTransformer-source-",
"." + sourceExtension);
File tempToFile = TempFileProvider.createTempFile(
"OpenOfficeContentTransformer-target-",
"." + targetExtension);

API Development Course 97

Extending the Alfresco Repository

Metadata Extractors
Introduction
A metadata extractor is a Java class that can extract metadata from content of a particular mime
type. Many metadata extractors already exist, however it is possible to add you own custom
metadata extractor in a few easy steps.
A metadata extractor has to implement the
org.alfresco.repo.content.metadata.MetadataExtracter interface. MetadataExtracter
extends the org.alfresco.repo.content.ContentWorker interface that is a common marker

interface for specific worker interfaces such as metadata extractors and content transformers.
An abstract class called AbstractMappingMetadataExtracter has been written to provides
basic services for MetadataExtracter implementations. Only one method needs implementing
when deriving from the abstract superclass. AbstractMappingMetadataExtracter is presented
in detail in the tutorial Metadata Extractor Howto on page 99.

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs:

Interface MetadataExtracter

Class AbstractMappingMetadataExtracter

MetadataExtracterRegistry

The MetadataExtracterRegistry holds a list of available metadata extractors and provides the
most appropriate extractor for a particular mime type extraction request.
Upon initialisation, metadata extractors register themselves with the
MetadataExtracterRegistry via the register() method.

98 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

For clarity, not all of the available methods are shown. For a complete description, please
consult the Javadocs: Class MetadataExtracterRegistry
The getExtracter() method is used by clients to get the most appropriate metadata extractor
for a particular mime type.
The MetadataExtracterRegistry bean is defined in the content-service-context.xml
Spring configuration file:
<!-- Metadata Extraction Regisitry -->
<bean id="metadataExtracterRegistry"
class="org.alfresco.repo.content.metadata.MetadataExtracterRegistry" />

Metadata Extractor Howto


Before starting the tutorial, you should be familiar with the Introduction on page 34.
The examples in this tutorial are taken from PdfBoxMetadataExtracter
(org.alfresco.repo.content.metadata.PdfBoxMetadataExtracter).
1. Implementing the metadata extractor class
Metadata extractors generally extend an abstract class called
AbstractMappingMetadataExtracter. The abstract superclass provides basic services
for MetadataExtracter implementations. The only method that requires implementing
when deriving from the abstract superclass is extractRaw().
a.

Creating the PdfBoxMetadataExtracter class

API Development Course 99

Extending the Alfresco Repository

b.

Writing the constructor


A set of supported mime types has to be passed to the abstract superclass, either
as an argument to the constructor or via a call to the setSupportedMimetypes()
method.
Before calling extractRaw(), the abstract superclass checks that the extraction
is allowed for the content mime type by calling the isSupported() method. The
mime type has to be one of the supported mime types. Subclasses can override
isSupported() to provide a custom implementation.
The PdfBoxMetadataExtracter only supports the application/pdf mime type.
public static String[] SUPPORTED_MIMETYPES = new String[]
{MimetypeMap.MIMETYPE_PDF };
public PdfBoxMetadataExtracter()
{
super(new HashSet<String>(
Arrays.asList(SUPPORTED_MIMETYPES)));
}

c.

Implementing the extractRaw() method


The extractRaw() method does the actual extraction work. The reader
(ContentReader) object, passed as an argument, allows direct access to

100 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

the source content. The source is available as an InputStream via the


getContentInputStream() method on the reader.
The method returns the extracted metadata as a Map. The map is initialised using the
newRawMap() method on the abstract superclass:
Map<String, Serializable> rawProperties = newRawMap();

Extracted metadata values are added to the rawProperties map using the
putRawValue() method on the abstract superclass. Keys are the same as those
used in the default mapping properties file and the value has to be Serializable.
putRawValue() will only add a value if it is non-trivial (not null, not an empty String
or Collection or Array).
private static final String KEY_AUTHOR = "author";
private static final String KEY_TITLE = "title";
private static final String KEY_SUBJECT = "subject";
putRawValue(KEY_AUTHOR, docInfo.getAuthor(), rawProperties);
putRawValue(KEY_TITLE, docInfo.getTitle(), rawProperties);
putRawValue(KEY_SUBJECT, docInfo.getSubject(), rawProperties);
extractRaw() need only be concerned with performing the extraction. There is no

need to handle any exceptions generated during the extraction, either runtime or
otherwise, the abstract superclass will handle and report these as required.
The reader will be closed after the extraction completes, however when accessing the
content via a stream, failure to close the stream, regardless of success of failure, will
result in an error message being generated to the log output and the stream will be
held open indefinitely.
Example from PdfBoxMetadataExtracter. The newRawMap() and putRawValue()
methods on the abstract superclass are used to initialise and populate the result Map
with the extracted metadata. The source InputStream and PDDocument are closed
once the extraction is complete. The method may throw an exception.
public Map<String, Serializable> extractRaw(ContentReader reader)
throws Throwable
{
Map<String, Serializable> rawProperties = newRawMap();
PDDocument pdf = null;
InputStream is = null;
try
{
is = reader.getContentInputStream();
// stream the document in
pdf = PDDocument.load(is);
if (!pdf.isEncrypted())
{
// Scoop out the metadata
PDDocumentInformation docInfo =
pdf.getDocumentInformation();
putRawValue(KEY_AUTHOR, docInfo.getAuthor(),
rawProperties);
putRawValue(KEY_TITLE, docInfo.getTitle(), rawProperties);
putRawValue(KEY_SUBJECT, docInfo.getSubject(),
rawProperties);
Calendar created = docInfo.getCreationDate();
if (created != null)
{
putRawValue(KEY_CREATED, created.getTime(),
rawProperties);
}

API Development Course 101

Extending the Alfresco Repository

}
}
finally
{
if (is != null)
{
try { is.close(); } catch (IOException e) {}
}
if (pdf != null)
{
try { pdf.close(); } catch (Throwable e)
{ e.printStackTrace(); }
}
}
// Done
return rawProperties;
}

2. Defining the metadata default mapping in a properties file


The extracted metadata returned by extractRaw() is mapped to node properties using a
mapping. The default mapping is supplied as a properties file.
Namespaces used in the mapping have to be declared. The property keys are the same as
those used in the raw properties map returned by the extractRaw() method. The property
value is a valid node property with a shorthand namespace prefix. A single raw property
can be mapped to several node properties provided as a comma separated list.
The default implementation looks for a properties file with the same name and in the same
location as the metadata extractor class. For example, the PdfBoxMetadataExtracter
default mapping properties file is called PdfBoxMetadataExtracter.properties and is
located in the org.alfresco.repo.content.metadata package or in the org/alfresco/
repo/content/metadata directory on the classpath.
Example PdfBoxMetadataExtracter.properties:
#
# PdfBoxMetadataExtracter - default mapping
#
# author: Derek Hulley
# Namespaces
namespace.prefix.cm=http://www.alfresco.org/model/content/1.0
# Mappings
author=cm:author
title=cm:title
subject=cm:description
created=cm:created

3. Registering the metadata extractor


A metadata extractor is registered as a bean in a Spring configuration file. By
convention, the bean id has an extracter. prefix. The bean must also define
baseMetadataExtracter as it's parent bean.
The baseMetadataExtracter bean is defined in the content-service-context.xml
Spring configuration file:
<!-- Abstract bean definition defining base definition for all metadata
extracters -->
<bean id="baseMetadataExtracter"
class="org.alfresco.repo.content.metadata.AbstractMetadataExtracter"
abstract="true"
init-method="register">
<property name="registry">

102 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Repository

<ref bean="metadataExtracterRegistry" />


</property>
<property name="mimetypeService">
<ref bean="mimetypeService" />
</property>
</bean>

The register() method is defined as a Spring bean initialisation method and is called
automatically by Spring once the metadata extractor has been instantiated. It registers the
metadata extractor with the MetadataExtracterRegistry.
The standard metadata extractor class names and Spring bean names are all written
er (extracter), they are not written with the correct or spelling (extractor)!
PdfBoxMetadataExtracter example taken from content-services-context.xml.
<!-- Content Metadata Extracters -->
<bean id="extracter.PDFBox"
class="org.alfresco.repo.content.metadata.PdfBoxMetadataExtracter"
parent="baseMetadataExtracter" />

4. Packaging and deploying


In order to deploy a custom metadata extractor to the Alfresco repository, the following
files need to be packaged:
the compiled custom metadata extractor class;
the metadata mapping properties file;
the Spring configuration file containing the bean definition for the custom metadata
extractor.
The compiled class and properties file can be exported to the same JAR file. The files then
need to be deployed to the following directories in the Alfresco repository:
the JAR file to the WEB-INF/lib directory;
the Spring configuration file to the WEB-INF/classes/alfresco/extension
directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.

Further Reading
Metadata Mapping
The extracted metadata returned by extractRaw() is mapped to node properties using a
mapping. The default mapping is supplied as a properties file in a format similar to the following:
# Namespaces
namespace.prefix.cm=http://www.alfresco.org/model/content/1.0
namespace.prefix.my=http://www.mycompany.com/model/mymodel/1.0
# Mappings
editor=cm:author, my:editor
title=cm:title
summary=cm:summary
subject=cm:description

Namespaces used in the mapping have to be declared. The property keys are the same as those
used in the raw properties map returned by the extractRaw() method. The property value is a
valid node property with a shorthand namespace prefix. A single raw property can be mapped to
several node properties provided as a comma separated list.

API Development Course 103

Extending the Alfresco Repository

The raw property values are converted to the corresponding node property types using the
org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter default
converter.
The default mapping is returned by the getDefaultMapping() method. The default
implementation looks for a properties file with the same name and in the same location as
the metadata extractor class. For example, the PdfBoxMetadataExtracter default mapping
properties file is called PdfBoxMetadataExtracter.properties and is located in the
org.alfresco.repo.content.metadata package or in the org/alfresco/repo/content/
metadata directory on the classpath.
The default implementation can be specialised by overriding the getDefaultMapping() method
in the subclass. If the default mapping is defined in a properties file other than the one named
after the class, then the readMappingProperties(String propertiesUrl) method can be
used to read the mapping from an alternate location:
protected Map<<String, Set<QName>> getDefaultMapping()
{
return readMappingProperties(PROPERTIES_URL);
}

Implementations can also dynamically modify the default mapping using either the setMapping()
method, if the mapping is supplied as a Map, or the setMappingProperties() method, if the
mapping is supplied as a Properties object. By default, the supplied mapping will replace the
default mapping returned by getDefaultMapping(). If the supplied mapping should augment the
default mapping, the setInheritDefaultMapping(boolean inheritDefaultMapping) method
should first be called with a value of true. Finally, the init() method needs to be called to reinitialise the extractor.

Overwrite Policies
The overwrite policy determines whether extracted values are written to the destination property
map or not. Three overwrite policies have been defined:
EAGER

If the extracted value is not null, always write the extracted value to the destination property
map.
PRAGMATIC

Only write the extracted value if:


the extracted value is not null
the key does not exist in the destination property map
the key exists but the destination property value is null or an empty string
CAUTIOUS

Only write the extracted value if:


the extracted value is not null
the key does not exist in the destination property map
The default overwrite policy is PRAGMATIC. The setOverWritePolicy() method can be called on
the abstract superclass to change the overwrite policy. The overwrite policy is specified as either
an OverwritePolicy or as a String.

104 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

Extending the Alfresco Web Client

API Development Course 105

Extending the Alfresco Web Client

JavaServer Faces
Introduction
JavaServer Faces (JSF) is a user interface framework for Java web applications. JSF defines an
event-driven, component-based model for web application development, similar to the model that
has been used successfully for standalone GUI applications for years. JSFs core architecture is
designed to be independent of specific protocols and markup. However it is also aimed directly at
solving many of the common problems encountered when writing applications for HTML clients
that communicate via HTTP to a Java application server that supports servlets and JavaServer
Pages (JSP) based applications.
JSF is a specification (JSR-252) that has been developed as part of the Java Community
Process. It is a standard, vendor independent specification. Several implementations exist,
including the GlassFish JavaServer Faces reference implementation and Apache MyFaces.
Alfresco uses the Apache Myfaces implementation.
The specification defines a set of standard user interface components and an API for extending
the standard components or developing new ones.
As well as UI components, JSF also defines artifacts like converters, validators, events listeners,
and renderers:
converters perform type conversion between server-side Java objects and their
representation in the user interface. A good example is a date;
validators can be associated with a JSF component to perform input validation checks on
local values before they are processed;
event listeners are registered against events that are triggered when a user clicks a button
or a link, changes a value in a field, or makes a selection in a list. The outcome of the
event processing controls which page is displayed next;
renderers generate the actual markup for the user interface. JSF is not limited to HTML or
any other markup language and the same JSF component can be coupled with different
renderers to produce different output, for example, either HTML or WML;

Login Page Walkthrough


1. JSF components on a JSP page
In the following screenshot, the User Name and Password input text fields, the Language
drop-down list and the Login button are all implemented using JSF components on the
login JSP page. The text labels themselves are also implemented using JSF components.

106 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

When used with JSP as a presentation layer, the JSF components are represented by JSP
custom actions (or custom tags) on the JSP page.
Some examples of JSF components on the login page are as follows...
Enter Login details: text label:
<h:outputText value="#{msg.login_details}" />:

User Name text label and input text field:


<h:outputText value="#{msg.username}"/>:
<h:inputText id="user-name"
value="#{LoginBean.username}"
validator="#{LoginBean.validateUsername}"
style="width:150px" />

Password text label and input text field:


<h:outputText value="#{msg.password}"/>:
<h:inputSecret id="user-password"
value="#{LoginBean.password}"
validator="#{LoginBean.validatePassword}"
style="width:150px" />

Language text label and drop-down list:


<h:outputText value="#{msg.language}"/>:
<h:selectOneMenu id="language"
value="#{UserPreferencesBean.language}"
style="width:150px"
onchange="document.forms['loginForm'].submit(); return
true;">
<f:selectItems value="#{UserPreferencesBean.languages}" />
</h:selectOneMenu>

Login command button:


<h:commandButton id="submit"
action="#{LoginBean.login}"
value="#{msg.login}" />

API Development Course 107

Extending the Alfresco Web Client

2. JSF tag librairies


The JSP custom actions (or custom tags) representing the JSF components are defined in
one or more custom JSF tag libraries. The tag libraries are declared on a JSP page using
a taglib directive. Each taglib directive contains two attributes: the uri attribute defines
the unique identifier for the library and the prefix attribute defines the namespace prefix.
The login page declares two standard JSF tag libraries:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

a standard JSP tag library:


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

and two Alfresco JSF tag libraries:


<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>

The standard JSF tag libraries:


uri="http://java.sun.com/jsf/html" prefix="h"
HTML tag library containing tags that represent JSF components that are rendered as
HTML elements.
uri="http://java.sun.com/jsf/core" prefix="f"
Core tag library containing tags that represent JSF artifacts that are independent of the
page markup language.
The Alfresco JSF tag libraries:
uri="/WEB-INF/alfresco.tld" prefix="a"
Alfresco tag library containing custom tags that represent JSF components that can be
used in non Alfresco projects.
uri="/WEB-INF/repo.tld" prefix="r"
Alfresco Repository tag library containing custom tags that represent JSF components
that can only be used in Alfresco based projects.
3. Structure of an Alfresco JSP page
Below is the overall structure of the Alfresco login JSP page:
<r:page titleId="title_login">
...
<f:view>
...
<h:form acceptcharset="UTF-8" id="loginForm" >
...
</h:form>
...
</f:view>
...
</r:page>

All Alfresco JSP pages have a similar structure.


An Alfresco JSP page always starts with a <r:page> custom tag. The <r:page> tag
renders the main HTML begin and end tags (<html><head>...</head><body>...</
body></html>).
The set of components that make up a user interface is called a view in JSF. The
components on a page form a tree and the <f:view> custom tag represents the
component at the root of the tree. The <f:view> tag must contain all other JSF tags on the
page.
108 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

The <h:form> custom tag represents an HTML form component. It is a container for input
components that hold values that should be processed together. All JSF input components
must be nested within a form component.
4. Output Text and Message bundles
The first component we will look at in detail is represented by the <h:outputText> custom
tag.
The following tags output text to the browser:
<h:outputText value="#{msg.login_details}" />:
<h:outputText value="#{msg.username}"/>:
<h:outputText value="#{msg.password}"/>:
<h:outputText value="#{msg.language}"/>:

Attribute values #{ ... } are written using a Unified Expression Language (Unified EL,
or just EL). In the above example, the values beginning with msg correspond to properties
defined in a message bundle.
A message bundle is loaded using the <f:loadBundle> custom tag:
<%-- load a bundle of properties I18N strings here --%>
<f:loadBundle basename="alfresco.messages.webclient" var="msg"/>

The above example loads the default Web Client message bundle
webclient.properties from the alfresco/messages directory on the classpath.
All of the Web Client I18N strings are stored in the webclient.properties default
message bundle. Message translations can be provided in separate files, named
with an extra suffix corresponding to the standard ISO language and country codes.
For example, the French translations of the Web Client messages are found in the
webclient_fr_FR.properties file and the French Canadian translations are found in the
webclient_fr_CA.properties file. The messages in the base bundle (without a suffix)
are in the en_US locale.
Login page messages from the default webclient.properties bundle:
login_details=Enter Login details
username=User Name
password=Password
language=Language
login=Login

The same login page messages from the French webclient_fr_FR.properties bundle:
login_details=Entrez les informations de connexion
username=Nom d'utilisateur
password=Mot de passe
language=Langue
login=Connexion

The login page in the French fr_FR locale:

API Development Course 109

Extending the Alfresco Web Client

Custom message bundles can be loaded using the <f:loadBundle> custom tag.
In the following example, a custom webclient.properties message bundle is loaded
from the alfresco/extension directory and stored under the mymsg key:
<f:loadBundle basename="alfresco.extension.webclient" var="mymsg"/>

Custom messages can then be displayed using the <h:outputText> custom tag:
<h:outputText value="#{mymsg.lost_password}" />:

5. Select Menus and Value binding expressions


The next component we will look at is represented by the <h:selectOneMenu> custom tag.
It displays the drop-down language selection menu.

110 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

It is also an example of nested tags. The component represented by the


<f:selectItems> tag is a child component of <h:selectOneMenu>. The
<h:selectOneMenu> component is rendered as an HTML drop-down select menu. The
items to display in the menu are provided by the <f:selectItems> child component.
<h:selectOneMenu id="language"
value="#{UserPreferencesBean.language}"
style="width:150px"
onchange="document.forms['loginForm'].submit(); return
true;">
<f:selectItems value="#{UserPreferencesBean.languages}" />
</h:selectOneMenu>

The expressions #{UserPreferencesBean.language} and


#{UserPreferencesBean.languages} are know as value binding expressions.
UserPreferencesBean is the name of a JSF managed bean that is a simple JavaBean

managed by the JSF framework. Value binding expressions are bound to getters and
setters on managed beans that are automatically called by the JSF framework to retrieve
values to display in the UI (via getter methods) and to update values on the managed bean
(via setter methods) with new values selected or input by the user.
The items to display in the drop-down language selection menu on the login page are
retrieved by calling the getLanguages() method on the UserPreferencesBean:
public SelectItem[] getLanguages()
{
SelectItem[] items = getLanguageItems();
// Change the current language
if (this.language == null)
{
// first try to get the language that the current user is using
Locale lastLocale =
Application.getLanguage(FacesContext.getCurrentInstance());
if (lastLocale != null)
{
this.language = lastLocale.toString();

API Development Course 111

Extending the Alfresco Web Client

}
...
}
return items;
}

The default language for the current user is retrieved by calling the getLanguage()
method on the UserPreferencesBean:
public String getLanguage()
{
return this.language;
}

If a user selects a different language, the form will be submitted:


onchange="document.forms['loginForm'].submit(); return true;"

and the setLanguage() method will be called on the UserPreferencesBean to update the
current language:
public void setLanguage(String language)
{
this.language = language;
Application.setLanguage(FacesContext.getCurrentInstance(), language);
...
}

6. FacesContext
During request processing for a JSF page, a FacesContext object is used to represent
request specific information as well as provide access to services for the application.
The static FacesContext.getCurrentInstance() method is used to obtain the current
FacesContext instance:
FacesContext context = FacesContext.getCurrentInstance();

Many Alfresco methods require (or receive) a FacesContext object as an argument.


Application.getLanguage(FacesContext.getCurrentInstance());
...
Application.setLanguage(FacesContext.getCurrentInstance(), language);

7. Input Text and Validators


The next component we will look at is an input text field represented by the
<h:inputText/> custom tag.
Input Text components are used for the user name and password fields:
<h:inputText id="user-name"
value="#{LoginBean.username}"
validator="#{LoginBean.validateUsername}"
style="width:150px" />
<h:inputSecret id="user-password"
value="#{LoginBean.password}"
validator="#{LoginBean.validatePassword}"
style="width:150px" />

The values of the text fields are bound to the managed bean LoginBean via value binding
expressions.
Both components have an optional validator associated with them to perform input
validation checks on the user name and password values as part of the login process.
JSF calls the validateUsername() method on the LoginBean, to validate the value of the
user name:
public void validateUsername(FacesContext context, UIComponent component,
Object value)

112 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

throws ValidatorException
{
...
if (name.length() < minUsernameLength || name.length() > 256)
{
...
throw new ValidatorException(new FacesMessage(err));
}
}

The method throws a ValidatorException if the value is not valid.


8. Command Buttons and Method binding expressions
Once the user has typed his user name and password, he clicks the Login button to log in.
The login button is a JSF component represented by the <h:commandButton> custom tag.
<h:commandButton id="submit"
action="#{LoginBean.login}"
value="#{msg.login}" />

The expression #{LoginBean.login} is know as a method binding expression.


LoginBean is the name of another JSF managed bean. Method binding expressions bind

actions to methods on managed beans that are automatically called by the JSF framework
when an action event is triggered.
When the Login button is clicked, JSF will call the login() method on the LoginBean:
public String login()
{
String outcome = null;
if (this.username != null && this.username.length() != 0 &&
this.password != null && this.password.length() != 0)
{
...
this.authenticationService.authenticate(this.username,
this.password.toCharArray());
...
if
(NavigationBean.LOCATION_MYALFRESCO.equals(this.preferences.getStartLocation()))
{
return "myalfresco";
}
else
{
// generally this will navigate to the generic browse screen
return "success";
}
...
}
return outcome;
}

9. Outcomes and Navigation


The login() method returns a String value called an outcome. The JSF Navigation
Handler uses the outcome along with predefined navigation rules to determine which page
(or view) to display next. A null outcome means stay on the same page.
The navigation rules are defined by <navigation-rule/> elements in a <faces-config/
> file.
In Alfresco, the navigation rules are defined in the WEB-INF/faces-confignavigation.xml file:
<navigation-rule>

API Development Course 113

Extending the Alfresco Web Client

<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/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>

An outcome of success from the login page will navigate to the standard browse page
(browse.jsp).
<navigation-rule>
<from-view-id>/jsp/*</from-view-id>
<navigation-case>
<from-outcome>browse</from-outcome>
<to-view-id>/jsp/browse/browse.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>myalfresco</from-outcome>
<to-view-id>/jsp/dashboards/container.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>about</from-outcome>
<to-view-id>/jsp/dialog/about.jsp</to-view-id>
</navigation-case>
</navigation-rule>

An outcome of myalfresco from any page will navigate to the dashboard container page
(dashboards/container.jsp).
The standard JSF Navigation Handler can be extended and overridden to customise
navigation handling. A custom navigation handler is defined using a <navigationhandler/> element in a <faces-config/> file.
In Alfresco, the custom navigation handler is defined in the WEB-INF/faces-configapp.xml file:
<application>
<navigation-handler>
org.alfresco.web.app.AlfrescoNavigationHandler
</navigation-handler>
...
</application>

The custom AlfrescoNavigationHandler is used by the Dialog and Wizard frameworks


to handle navigation using outcomes that begin with special dialog: or wizard: prefixes.
For more information, see Introduction on page 123 and Introduction on page 132.
10.

Managed Beans and Variable Resolvers


The JSF Managed Beans are defined by <managed-bean/> elements in a <facesconfig/> file.
In Alfresco, the Web Client managed beans are defined in the WEB-INF/faces-configbeans.xml file:
<managed-bean>
<description>

114 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

The bean that backs up the Login screen


</description>
<managed-bean-name>LoginBean</managed-bean-name>
<managed-bean-class>
org.alfresco.web.bean.LoginBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>nodeService</property-name>
<value>#{NodeService}</value>
</managed-property>
<managed-property>
<property-name>authenticationService</property-name>
<value>#{AuthenticationService}</value>
</managed-property>
...
</managed-bean>

The managed bean name is not always the same as the implementing class name.
When required, JSF automatically instantiates and initialises managed beans. The
managed beans are then stored in the scope defined by the value of the <managed-beanscope/> element. The managed bean scope takes one of the following values:
none
Does not store the managed bean in any scope.
request
The managed bean is stored and kept for a single request.
session
The managed bean is stored and kept for a single user session.
application
The managed bean is stored and shared between all users.
The properties defined by <managed-property/> elements are automatically set on
the managed beans once they have been instantiated via setter methods. The #{...}
property values are resolved by one or more variable resolvers. Custom variable resolvers
can extend the standard JSF VariableResolver.
In Alfresco, a custom variable resolver is defined in the WEB-INF/faces-config-app.xml
file:
<application>
...
<variable-resolver>
org.alfresco.web.app.AlfrescoVariableResolver
</variable-resolver>
...
</application>

The custom AlfrescoVariableResolver delegates to the Spring


DelegatingVariableResolver the resolution of Spring beans:
<managed-property>
<property-name>nodeService</property-name>
<value>#{NodeService}</value>
</managed-property>

In the above example #{NodeService} corresponds to the Spring NodeService public


bean.

API Development Course 115

Extending the Alfresco Web Client

Actions Framework
Introduction
The Alfresco Web Client UI actions are configured using the Actions Framework. Actions such
as
Edit,
View Details,
Update,
Copy are all examples of UI Actions. Actions are
grouped into action groups. Individual actions can be reused between groups and the action
groups reused across pages. Action Groups define an ordered list of actions that are displayed
together, either as a serial list (for example, as a strip of icons) or grouped together in a dropdown menu.
The More Actions menu is an example of an action group.

UI actions and actions groups are configured in XML. The standard UI actions and action groups
are defined in the web-client-config-actions.xml configuration file. You can define your own
custom UI actions and action groups in a Web Client configuration extension file. You can also
extend existing action groups to add your own custom UI actions to existing menus in the Web
Client.

Update UI Action Walkthrough


Before starting, you should be familiar with Introduction on page 106.
1. UI Action and Action Group definitions
A UI action is defined by an <action/> element in a Web Client configuration file. The
mandatory id attribute defines the unique ID by which the action is referenced for use in
action groups. The same action can be referenced and reused by any number of action
groups.
An action group is defined by an <action-group/> element. The mandatory id attribute
is used to identify the action group when displayed on a JSP page by a JSF <r:actions/
> custom tag. The actions in an action group are defined by child <action/> elements
with either an idref attribute, to reference an existing action definition, or an id attribute,

116 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

to define an inline action specific to the current action group. The order of the child
<action/> elements defines the order in which the actions will be displayed on the page.
All <action/> and <action-group/> elements must be contained within an <actions/>
element.
Custom actions and action groups should be defined in the standard web-clientconfig-custom.xml extension file. Defining an action or an action group with the same ID
as an existing one effectively overrides the original definitions. In this way, action groups
may be overridden to add custom actions to existing action groups or to redefine display
attributes for a consistent look and feel. Existing actions can also be hidden in an action
group by using the hide attribute in conjunction with idref.
Below is the Update action definition from the web-client-config-actions.xml
configuration file:
<action id="update_doc">
<permissions>
<permission allow="true">Write</permission>
</permissions>
<evaluator>
org.alfresco.web.action.evaluator.UpdateDocEvaluator
</evaluator>
<label-id>update</label-id>
<image>/images/icons/update.gif</image>
<action-listener>
#{CheckinCheckoutBean.setupContentAction}
</action-listener>
<action>dialog:updateFile</action>
<params>
<param name="id">#{actionContext.id}</param>
</params>
</action>

The update_doc action is referenced in the document_browse_menu and


doc_details_actions action groups:
<!-- Actions Menu for a document in the Browse screen -->
<action-group id="document_browse_menu">
<action idref="preview_doc" />
<action idref="update_doc" />
<action idref="cancelcheckout_doc" />
<action idref="approve_doc" />
<action idref="reject_doc" />
<action idref="cut_node" />
<action idref="copy_node" />
</action-group>
<action-group id="doc_details_actions">
...
<action idref="update_doc" />
...
</action-group>

The document_browse_menu action group corresponds to the More Actions drop down
menu on documents on the main browse page:

API Development Course 117

Extending the Alfresco Web Client

Most standard UI actions and action groups are defined in a global <config/> section (i.e.
there is no evaluator or condition). Actions and action groups can, however, be configured
by node type. It is thus possible to add new actions or hide actions for a specific folder or
document type.
The following action group configuration hides the Cut and Copy actions from WCM
WebProject type folders:
<config evaluator="node-type" condition="wca:webfolder">
<actions>
<action-group id="space_browse">
<show-link>false</show-link>
<action idref="cut_node" hide="true" />
<action idref="copy_node" hide="true" />
</action-group>
</actions>
</config>

Only node-type conditions are supported, aspects are not supported.


2. Action Evaluators
The <evaluator/> element defines the name of a custom class implementing the
org.alfresco.web.action.ActionEvaluator interface that is used to determine if the
action should be displayed or not for the given Node. The evaluate(Node) method is
called on the ActionEvaluator for each Node. The action will only be displayed if the
method returns a value of true.
The update_doc action will only be displayed on a Node if the
UpdateDocEvaluator.evaluate(Node) method returns true:
<!-- Update document -->
<action id="update_doc">
...
<evaluator>
org.alfresco.web.action.evaluator.UpdateDocEvaluator
</evaluator>
...
</action>

118 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

The UpdateDocEvaluator.evaluate(Node) method returns true if the node is a subtype of cm:content and the node is a Working Copy owned by the current user or the
node is not locked and the node is not a Working Copy:
public class UpdateDocEvaluator implements ActionEvaluator
{
public boolean evaluate(Node node)
{
DictionaryService dd = Repository.getServiceRegistry(
FacesContext.getCurrentInstance()).getDictionaryService();
return dd.isSubClass(node.getType(), ContentModel.TYPE_CONTENT) &&
((node.isWorkingCopyOwner() == true ||
(node.isLocked() == false &&
node.hasAspect(ContentModel.ASPECT_WORKING_COPY) ==
false)));
}
}

3. Action Listeners and Action Context objects


The <action-listener/> element defines a JSF action listener method that will be called
when a user selects the action. The action listener method is passed an ActionEvent
object that can be used to retrieve the parameters defined on the action as <param/>
elements.
The actionContext object is the context object for the action. Most of the time, an
actionContext object is a Node object, however it may not always be the case. The
context object can be referenced in JSF value binding expressions. It is often used in a
<param/> element to pass the id of the current Node to the action listener.
The update_doc action defines an action listener as a JSF method binding expression.
The setupContentAction(ActionEvent) method will be called on the
CheckinCheckoutBean when a user selects the action:
<!-- Update document -->
<action id="update_doc">
...
<action-listener>#{CheckinCheckoutBean.setupContentAction}</actionlistener>
...
<params>
<param name="id">#{actionContext.id}</param>
</params>
</action>

The id parameter is defined as the id of the actionContext object (id of the Node).
The setupContentAction(ActionEvent) method retrieves the node id from the list of
parameters:
public void setupContentAction(ActionEvent event)
{
UIActionLink link = (UIActionLink)event.getComponent();
Map<String, String> params = link.getParameterMap();
String id = params.get("id");
if (id != null && id.length() != 0)
{
setupContentDocument(id);
}
...
}

4. Action Outcome

API Development Course 119

Extending the Alfresco Web Client

The <action/> element defines a JSF navigation outcome to use when a user selects the
action.
The dialog:updateFile outcome will be interpreted by the Alfresco Navigation Handler
to call the updateFile dialog:
<!-- Update document -->
<action id="update_doc">
...
<action>dialog:updateFile</action>
...
</action>

5. JSF <r:actions/> custom tag


The <r:actions/> JSF custom tag is used to display an action group on a JSP page. This
value attribute is used to reference an action group by ID. If the showLink attribute is set
to false, a list of icons will be displayed with no textual links. For a complete description of
<r:actions/> custom tag attributes, see Tag actions.
In the browse.jsp page, the document_browse action group is displayed inline, and the
document_browse_menu action group is displayed in a drop-down menu:
<r:actions id="col18-acts1"
value="document_browse"
context="#{r}" showLink="false" styleClass="inlineAction" />
<%-- More actions menu --%>
<a:menu id="content-more-menu"
itemSpacing="4" image="/images/icons/more.gif"
tooltip="#{msg.more_actions}" menuStyleClass="moreActionsMenu">
<r:actions id="col18-acts2"
value="document_browse_menu"
context="#{r}" />
</a:menu>

The mandatory context attribute defines the object to use as the actionContext.
In the above example, r is the current node in the list.

Actions Framework Reference


<action/> element
Each <action/> element defines a single action definition. The mandatory id attribute defines
the unique ID by which the action is referenced for use in action group elements (see <actiongroup/> element on page 121). Defining another action with the same ID as an existing action
effectively overrides the original action definition. All of the following elements must be contained
within the <action/> element.
permissions
Contains 1 or more <permission/> elements.
permission
Contained within <permissions/> element. Defines the permission values that must be
checked against the current Node action context to allow the action to be displayed to
the current user. The allow attribute takes a value true or false to define whether the
permission check is ALLOW or DENY.
evaluator
The name of a custom class implementing the
org.alfresco.web.action.ActionEvaluator interface. The action will only be displayed if
the ActionEvaluator.evaluate() method returns true.

120 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

label-id
The id of an I18N string to be used as the label for the action.
label
A literal string to be used in place of label-id if you do not want an I18N string.
tooltip-id
The id of an I18N string to be used as the tooltip for the action.
tooltip
A literal string to be used in place of tooltip-id if you do not want an I18N string.
show-link
A presentation attribute. Valid values are true or false. If set to true, the action will be
displayed as an icon and a textual link, if set to false, the action will only be displayed as an
icon with the label as the tooltip for the icon image.
style
A presentation attribute. The CSS style to apply to the action.
style-class
A presentation attribute. The CSS class to apply to the action.
image
The icon image to display.
action-listener
The JSF action listener method to call upon user selection of the action.
action
The JSF action navigation outcome to execute upon user selection of the action.
script
The Alfresco JavaScript file to execute upon user selection of the action. The JavaScript file is
specified by either Path or Node Reference.
href
The href to navigate to upon user selection of the action.
target
The associated href target.
onclick
The JavaScript onclick handler to execute upon user selection of the action.
params
Contains 1 or more <param/> elements.
param
Contained within the <params/> element. Defines the JSF <f:param/> custom tags to be
generated as children of the action component. Each <param/> element has a mandatory
name attribute as the name of the parameter and the mandatory value of the element contains
the value to passed as the parameter. Parameters are available from the UIActionLink
component passed to the JSF action listener method upon user selection of the action.
Parameters are also passed directly as href and script URL arguments.

<action-group/> element
Each <action-group/> element defines a single action group. The mandatory id attribute is
used to identify the action group in a JSF <r:actions/> custom tag. Defining another action
group with the same ID as an existing action group effectively overrides the original action group
definition. All other elements must be contained within the <action-group/> element.

API Development Course 121

Extending the Alfresco Web Client

action
References or defines an action definition for the group. The order of the attributes defines
the order in which the actions are displayed. Action elements may be defined using an idref
attribute to reference an existing action definition or inline to define an action specific to the
action group. Existing actions can also be hidden by using the hide attribute in conjunction
with idref.
show-link
Overrides any similarly named attribute set on individual action definitions.
style
Overrides any similarly named attribute set on individual action definitions.
style-class
Overrides any similarly named attribute set on individual action definitions.

122 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

Dialog Framework
Introduction
The dialog framework manages the user interface dialogs in the Alfresco Web Client. Most of the
standard Web Client dialog are managed by the framework and it is possible to create your own
custom dialogs using the framework.
Each dialog has three main components:
A <dialog/> configuration element in a web-client-config.xml file.
A dialog JSP that only contains the HTML and JSF components to define the body of the
dialog.
A JSF managed bean that is used as a backing bean for the dialog.
At the centre of the dialog framework is the Dialog Manager. When a dialog is opened, the
Alfresco Navigation Handler looks up the <dialog/> configuration then calls the Dialog Manager
to initialise the dialog and instantiate the managed bean before navigating to the dialog JSP
page.
See The Dialog Manager on page 130 for more details.

Custom Dialog Howto


Before starting the tutorial, you should be familiar with Introduction on page 106. You may also
wish to complete the Update UI Action Walkthrough on page 116 tutorial.
The tutorial builds upon the Configuring a Custom UI Action tutorial to give the custom UI action
something to call. A dialog will be defined which will allow the user to select an aspect to add to
the space. The examples are taken from the CustomDialog SDK sample.
1. Defining the dialog
An individual dialog is defined by a <dialog/> element in a Web Client configuration file.
All <dialog/> elements must be contained within a <dialogs/> element.
Default dialogs are defined in the global <config/> section (i.e. there is no evaluator or
condition) of the web-client-config-dialogs.xml file. These may be overridden using a
more specific <config/> section, for example, a dialog definition could be overridden for a
particular node type.
All new dialogs or customisations to default dialogs should be defined in the standard
web-client-config-custom.xml extension file. Combining is supported for dialog
configuration, so the overridden definition need only define what needs changing.
Example taken from web-client-config-custom.xml:
<dialogs>
<dialog name="addAspect"
page="/jsp/extension/add-aspect.jsp"
managed-bean="AddAspectDialog"
icon="/images/icons/add_content_large.gif"
title="Add Aspect"
description="Adds an aspect to the selected node" />
</dialogs>

name
Defines the unique name (or id) of the dialog. The above dialog can be referenced in an
action definition as dialog:addAspect.
page
Defines the path to the JSP page to be used for the dialog.

API Development Course 123

Extending the Alfresco Web Client

managed-bean
Defines the name of the JSF managed bean to be used as the backing bean for the
dialog.
See <dialog/> attributes on page 130 for a complete description of all dialog attributes.
2. Defining an action
The custom UI action definition from the Configuring a Custom UI Action tutorial can now
be updated to call the custom addAspect dialog.
Example taken from web-client-config-custom.xml:
<!-- Launch Add Aspect Dialog -->
<action id="add_aspect">
<label>Add Aspect</label>
<image>/images/icons/add.gif</image>
<action>dialog:addAspect</action>
<action-listener>#{BrowseBean.setupSpaceAction}</action-listener>
<params>
<param name="id">#{actionContext.id}</param>
</params>
</action>
<!-- Add action to more actions menu for each space -->
<action-group id="space_browse_menu">
<action idref="add_aspect" />
</action-group>

3. Writing a JSP for the dialog


The JSP referenced by the page attribute should only contain the HTML and JSF
components to define the body of the dialog. It will be included into the dialog container
page, defined by the <dialog-container/> element in the web-client-configdialogs.xml file:
<dialog-container>/jsp/dialog/container.jsp</dialog-container>

The add-aspect.jsp example below displays a simple label and a drop-down list of
aspects for the user to choose from.
<%@
<%@
<%@
<%@

taglib
taglib
taglib
taglib

uri="http://java.sun.com/jsf/html" prefix="h" %>


uri="http://java.sun.com/jsf/core" prefix="f" %>
uri="/WEB-INF/alfresco.tld" prefix="a" %>
uri="/WEB-INF/repo.tld" prefix="r" %>

<h:outputText value="#{msg.aspect}: " />


<h:selectOneMenu value="#{DialogManager.bean.aspect}">
<f:selectItems value="#{RunActionWizard.testableAspects}" />
</h:selectOneMenu>

The first 4 lines simply include the JSF and Alfresco tag libraries.
The <h:outputText/> element will output some text, in this example, the string to display
is read from the webclient.properties file.
The <h:selectOneMenu/> element will display the drop-down list of aspects. The list of
aspects (<f:selectItems/>) is supplied by the getTestableAspects() method on the
RunActionWizard class.
Once the user has selected an aspect and clicked on the OK button, the setAspect()
method will be called on the dialog's JSF managed bean to set the value of the selected
aspect. The expression DialogManager.bean refers to the JSF managed bean being
used by the current dialog and configured using the managed-bean attribute in the dialog's
configuration. Because an explicit bean is not being referenced, the same JSP page could
be re-used for multiple dialogs, each dialog defining it's own specific bean implementation.

124 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

The JSF value binding expressions #{RunActionWizard.testableAspects}


and #{DialogManager.bean.aspect} will actually call
the RunActionWizard.getTestableAspects() and
DialogManager.getBean().setAspect() methods respectively.
4. Localising dialog messages
All of the Web Client I18N strings are stored in a default message bundle called
webclient.properties located in the alfresco/messages directory. Message
translations can be provided in separate files, named with an extra suffix corresponding to
the standard ISO language and country codes. For example, the French translations of the
Web Client messages are found in the webclient_fr_FR.properties file and the French
Canadian translations are found in the webclient_fr_CA.properties file. The messages
in the base bundle (without a suffix) are in the en_US locale.
Custom messages need to be provided in a custom message bundle in the alfresco/
extension directory. The base extension file is also named webclient.properties.
Translations can be provided in separate files in the usual manner.
The custom Add Aspect dialog does not define any custom messages and simply re-uses
an existing message from the standard webclient.properties file:
...
aspect=Aspect
...

5. Implementing a JSF Managed Bean for the dialog


Dialog JSF managed beans referenced by the managed-bean attribute must
implement the IDialogBean interface. Generally, however, dialog beans just extend
the org.alfresco.web.bean.dialog.BaseDialogBean abstract base class.
BaseDialogBean provides the default implementation for the IDialogBean interface
but introduces one abstract method finishImpl() that subclasses have to implement.
Subclasses can override other BaseDialogBean methods to customise dialog behaviour.
See Dialog Beans on page 128 for more detail.
a.

Creating the AddAspectDialog class


The AddAspectDialog class extends the BaseDialogBean and implements the
finishImpl() and getFinishButtonDisabled() methods.
public class AddAspectDialog extends BaseDialogBean

b.

Implementing the getter and setter methods for the aspect property
The aspect property stores the value of the aspect selected by the user from the
drop-down list. Once the user has selected an aspect and clicked on the OK button,
the setAspect() method will be called to set the value of the selected aspect. If
the user returns to the dialog, the value of the aspect property will be read using
the getAspect() method to initialise the drop-down list to the aspect that the user
selected the last time.
protected String aspect;
public String getAspect()
{
return aspect;
}
public void setAspect(String aspect)
{
this.aspect = aspect;
}

API Development Course 125

Extending the Alfresco Web Client

c.

Overriding the getFinishButtonDisabled()method.


The AddAspectDialog bean overrides the getFinishButtonDisabled() method
returning a value of false.
The finish (OK) button will not be disabled (will be visible) in the dialog.
public boolean getFinishButtonDisabled()
{
return false;
}

d.

Implementing the finishImpl() method


The finishImpl() will do the actual work of the dialog and add the selected aspect
to the Space.
protected String finishImpl(FacesContext context, String outcome)
throws Exception
{
// get the space the action will apply to
NodeRef nodeRef = this.browseBean.getActionSpace().getNodeRef();
// resolve the fully qualified aspect name
QName aspectToAdd = Repository.resolveToQName(this.aspect);
// add the aspect to the space
this.nodeService.addAspect(nodeRef, aspectToAdd, null);
// return the default outcome
return outcome;
}

Once the user has selected an aspect from the dialog JSP and clicked on the OK
button, JSF will automatically call the setAspect() method on the AddAspectDialog
bean passing the value of the aspect selected by the user. The setAspect() method
stores the selection in the this.aspect field. The finish() method on the
BaseDialogBean will then be called which will in turn call the finishImpl() method
on the AddAspectDialog bean. The finishImpl() will do the actual work of the
dialog and add the selected aspect to the Space.
6. Registering the JSF Managed Bean
The new AddAspectDialog managed bean now has to be registered with JSF in a facesconfig.xml file. The faces-config.xml file will be packaged in the META-INF directory
for deployment.
Example taken from META-INF/faces-config.xml:
<managed-bean>
<managed-bean-name>AddAspectDialog</managed-bean-name>
<managed-bean-class>
org.alfresco.sample.AddAspectDialog
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>nodeService</property-name>
<value>#{NodeService}</value>
</managed-property>
<managed-property>
<property-name>browseBean</property-name>
<value>#{BrowseBean}</value>
</managed-property>
</managed-bean>

The AddAspectDialog bean uses the nodeService and browseBean. JSF will inject
references to the public NodeService and BrowseBean defined as managed properties

126 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

in the above example. The setter methods are implemented by the BaseDialogBean
superclass.
See Introduction on page 106 for more information on registering managed beans.
7. Packaging and deploying
In order to deploy a custom dialog to the Alfresco Web Client, the following files need to be
packaged:
the web-client-config-custom.xml file containing the dialog and action
configuration;
the dialog JSP containing the HTML and JSF components to define the body of
the dialog;
the custom webclient.properties file containing custom dialog messages;
the compiled JSF managed bean class;
the custom JSF faces-config.xml file.
The compiled class and META-INF/faces-config.xml file needs to be exported to a JAR
file. The files then need to be deployed to the following directories in the Alfresco Web
Client:
the JAR file to the WEB-INF/lib directory;
the dialog JSP to the jsp/extension directory;
the web-client-config-custom.xml and webclient.properties to the WEBINF/classes/alfresco/extension directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.
After deployment, the custom Add Aspect action is available from the More Actions menu on
Spaces.

Once selected, the custom Add Aspect dialog is displayed as in the screenshot below:

API Development Course 127

Extending the Alfresco Web Client

Further Information
Dialog Beans
Dialog beans are JSF managed beans referenced by the managed-bean attribute in the <dialog/
> configuration. Dialog beans must implement the IDialogBean interface.

There is a base class for all dialog managed beans


(org.alfresco.web.bean.dialog.BaseDialogBean). The base class provides the default
implementation for the IDialogBean interface but introduces one abstract method finishImpl()
that subclasses have to implement.
By default, the cancel() method will return an outcome of dialog:close, the
getCancelButtonLabel() method returns Cancel, the getFinishButtonLabel() method
returns OK and the getFinishButtonDisabled() method returns true which will remove the
finish (OK) button from the dialog.
The finish() method takes care of the transaction and error handling, it passes off the
processing to the abstract finishImpl() method. After the transaction has been successfully

128 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

committed the BaseDialogBean will also call a doPostCommitProcessing() method that


subclasses can override.
Both the finishImpl() and doPostCommitProcessing() methods are passed the current
outcome, and both methods are expected to return an outcome. Usually the methods will just
return the given outcome but there may be some scenarios where the outcome needs to be
overridden. The default outcome is dialog:close.
If an error occurs during finish() the transaction is rolled back and the formatErrotMessage()
method is called. BaseDialogBean does provide a default implementation that produces a
generic error message, but subclasses can override this to provide a suitable message for
the type of exception. Finally, a getErrorOutcome() method is called to determine what
outcome to return in the event of an error, by default, null is returned as that will re-display
the page the error occurred on with the error message displayed, there are however, some
scenarios where an outcome needs to be returned, if this is the case subclasses can override the
getErrorOutcome() method too.
The restored() method is a lifecycle method. This is called when the DialogManager restores
a dialog from the view stack i.e. when a nested dialog is closed. This method gives the dialog a
chance to reset any state that may need refreshing.
getContainerTitle() and getContainerDescription() allow the bean implementation to

override the title and description provided by configuration. This in essence allows the bean to
provide titles and descriptions that are not known until runtime, for example if the title needs to
include the title of the node it is acting upon.
The getAdditionalButtons() method returns a list of DialogButtonConfig objects
representing additional buttons to render. This method essentially allows bean implementations
to dynamically add buttons at runtime based on the context of the dialog, see Dialog Buttons on
page 129 for more details.

Dialog Buttons
The getAdditionalButtons() method returns a list of DialogButtonConfig objects
representing additional buttons to render. This method essentially allows bean implementations
to dynamically add buttons at runtime based on the context of the dialog. The DialogManager
combines the button definitions returned by the method and those configured at design time to
build a list of extra buttons to render. As an example, this technique is used to render the buttons
required for each transition defined in a workflow task.
The internals of the DialogButtonConfig class are shown below.
public class DialogButtonConfig
{
private String id;
private String label;
private String labelId;
private String action;
private String disabled;
private String onclick;
}

Implementing Dialog Buttons in Java


The example implementation of the getAdditionalButtons() method shown below will add an
extra button with the My Dialog Button label to the dialog. When the user clicks on the button,
the myButtonSelected() method will be called on the dialog's managed bean.
public List<DialogButtonConfig> getAdditionalButtons()
{
List<DialogButtonConfig> buttons =
new ArrayList<DialogButtonConfig>(1);

API Development Course 129

Extending the Alfresco Web Client

buttons.add(new DialogButtonConfig(
"my-button",
"My Dialog Button",
null,
"#{DialogManager.bean.myButtonSelected}",
"false",
"javascript:method()"));
return buttons;
}

Defining Dialog Buttons in XML


Extra buttons can also be defined with the dialog configuration in a web-client-configcustom.xml file. The equivalent of the above example is shown below.
<dialog name="... >
<buttons>
<button id="my-button"
label="My Dialog Button"
action="#{DialogManager.bean.myButtonSelected}"
disabled="false"
onclick="javascript:method()" />
</buttons>
</dialog>

The Dialog Manager


The Dialog Manager is at the centre of the dialog framework.
When the Alfresco navigation handler gets a request to open a dialog it examines the current
page. If it detects that it is a dialog, the state of the dialog is retrieved and stored on the view
stack. If the current page is not a dialog the current page is added to the view stack.
The navigation handler then looks up the configuration for the dialog and passes the configuration
object to the DialogManager via the setCurrentDialog() method. The DialogManager then
proceeds to instantiate the dialog's managed-bean and set up any parameters passed into the
dialog via the setupParameters() action listener method.
The navigation handler then navigates to the dialog container page. The dialog container page
also uses the #{DialogManager...} syntax to resolve the title, description and icon for the
header area and the page to show as the body of the dialog.
The DialogManager either returns the requested information from the dialog config object it was
passed, or passes the request on to the underlying managed bean. For example, when the user
presses the OK button, the DialogManager calls finish() on the managed bean.
When a dialog:close request is received, the navigation handler examines the view stack to
see what the item at the top of the stack is. If it is a dialog, the state object is retrieved from the
stack and restored, and the dialog container page is then navigated to. If the top of the stack
is a normal page, the page is retrieved and navigated to. If the close request is overridden by
an outcome of dialog:close:browse, the view stack is emptied and the overridden outcome
processed.

Dialog Framework Reference


<dialog/>

attributes

name
The unique name (id) of the dialog.
page
The path to the JSP page to be used for the dialog.

130 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

managed-bean
The name of the JSF managed bean to be used as the backing bean for the dialog.
icon
The path to the icon displayed in the header area of the dialog.
title-id
The id of an I18N string to be used as the title of the dialog.
title
A literal string to be used in place of title-id if you do not want an I18N string.
description-id
The id of an I18N string to be used as the description of the dialog.
description
A literal string to be used in place of description-id if you do not want an I18N string.
error-message-id
The id of an I18N string to be used in the case of an error.
actions-config-id
The id of a configured action group. The actions are displayed in the header area of the
dialog.
show-ok-button
Flag to determine whether the OK button should be displayed. This allows dialogs to display
just the Cancel button.

Dialog<button/> attributes
id
The unique id of the button.
label-id
The id of a string to be used as the label of the button.
label
A label attribute can be used in place of label-id if you want to use a literal string instead of
an I18N string.
action
The action method binding to call when the button is clicked.
disabled
Flag to determine whether the button should be rendered as disabled, this can be a JSF
binding expression.
onclick
The JavaScript onclick handler code to execute when the button is clicked.

API Development Course 131

Extending the Alfresco Web Client

Wizard Framework
Introduction
The wizard framework manages the user interface wizards in the Alfresco Web Client. Most of the
standard Web Client wizards are managed by the framework and it is possible to create your own
custom wizards using the framework.
Each wizard has three main components:
A <wizard/> configuration element in a web-client-config.xml file.performed in exactly
the same
A JSP for each step in the wizard that only contains the HTML and JSF components to
define the body of the step.
A JSF managed bean that is used as a backing bean for the wizard.
At the centre of the wizard framework is the Wizard Manager. When a wizard is opened, the
Alfresco Navigation Handler looks up the <wizard/> configuration then calls the Wizard Manager
to initialise the wizard and instantiate the managed bean before navigating to the JSP page for
the first step.
See The Wizard Manager on page 138 for more details.

Custom Wizard Howto


Before starting the tutorial, you should be familiar with Introduction on page 106.
The tutorial customises the Create Content wizard to add an extra step allowing a user to add an
aspect to the created content. The examples are taken from the CustomWizard SDK sample.
1. Defining the wizard
An individual wizard is defined by a <wizard/> element in a Web Client configuration file.
All <wizard/> elements must be contained within a <wizards/> element.
Default wizards are defined in the global <config/> section (i.e. there is no evaluator or
condition) of the web-client-config-wizards.xml file. These may be overridden using a
more specific <config/> section, for example, a wizard definition could be overridden for a
particular node type.
All new wizards or customisations to default wizards should be defined in the standard
web-client-config-custom.xml extension file.
Combining of configuration is NOT supported for wizard definitions. This means
the whole wizard definition has to be copied to the extension file and adjusted
appropriately.
Example taken from web-client-config-custom.xml.
<wizards>
<wizard name="createContent"
managed-bean="CustomCreateContentWizard"
title-id="custom_create_content_wizard_title"
description-id="create_content_desc"
icon="/images/icons/new_content_large.gif">
...
<step name="aspect"
title-id="select_aspect"
description-id="create_content_step3_desc">
<page path="/jsp/extension/select-aspect.jsp"
title-id="create_content_step3_title"
description-id="create_content_step3_desc"
instruction-id="default_instruction" />

132 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

</step>
...
</wizard>
</wizards>

name
Defines the unique name (or id) of the wizard. The above wizard can be referenced in
an action definition as wizard:createContent.
managed-bean
Defines the name of the JSF managed bean to be used as the backing bean for the
wizard.
path
Defines the path to the JSP page to be used for each step.
See <wizard/> attributes, <step/> attributes on page 140 and <page/> attributes on page
140 for a complete description of all wizard attributes.
2. Defining an action
The createContent wizard is already called from the standard Create Content action. The
action is part of the browse_create_menu action group (Create menu on the Web Client
browse page):

Example taken from web-client-config-actions.xml:


<!-- Create content -->
<action id="create_content">
<permissions>
<permission allow="true">CreateChildren</permission>
</permissions>
<label-id>create_content</label-id>
<image>/images/icons/new_content.gif</image>
<action>wizard:createContent</action>
</action>
<!-- Actions Menu for Create in Browse screen -->
<action-group id="browse_create_menu">
<action idref="create_content" />
<action idref="create_form" />
<action idref="create_website_wizard" />
<action idref="create_space" />
<action idref="create_space_wizard" />
</action-group>

3. Writing the JSPs for the wizard


For each <page/>, the JSP referenced by the path attribute should only contain the
HTML and JSF components to define the body of the wizard. It will be included into the
wizard container page, defined by the <wizard-container/> element in the web-clientconfig-wizards.xml file:
<wizard-container>/jsp/wizard/container.jsp</wizard-container>

API Development Course 133

Extending the Alfresco Web Client

The select-aspect.jsp example below displays a simple label and a drop-down list of
aspects for the user to choose from.
<%@
<%@
<%@
<%@

taglib
taglib
taglib
taglib

uri="http://java.sun.com/jsf/html" prefix="h" %>


uri="http://java.sun.com/jsf/core" prefix="f" %>
uri="/WEB-INF/alfresco.tld" prefix="a" %>
uri="/WEB-INF/repo.tld" prefix="r" %>

<h:outputText value="#{msg.aspect}: " />


<h:selectOneMenu value="#{WizardManager.bean.aspect}">
<f:selectItems value="#{WizardManager.bean.aspects}" />
</h:selectOneMenu>

The first 4 lines simply include the JSF and Alfresco tag libraries.
The <h:outputText/> element will output some text, in this example, the string to display
is read from the webclient.properties file.
The <h:selectOneMenu/> element will display the drop-down list of aspects. The list of
aspects (<f:selectItems/>) is supplied by the getAspects() method on the wizard's
JSF managed bean.
Once the user has selected an aspect and clicked on the Next button, the setAspect()
method will be called on the wizard's JSF managed bean to set the value of the selected
aspect. The expression WizardManager.bean refers to the JSF managed bean being
used by the current wizard and configured using the managed-bean attribute in the
wizard's configuration. Because an explicit bean is not being referenced, the same JSP
page could be re-used for multiple wizards, each wizard defining it's own specific bean
implementation.
The JSF value binding expressions #{WizardManager.bean.aspect}
and #{WizardManager.bean.aspects} will actually
call the WizardManager.getBean().setAspect() and
WizardManager.getBean().getAspects() methods respectively.
4. Localising wizard messages
All of the Web Client I18N strings are stored in a default message bundle called
webclient.properties located in the alfresco/messages directory. Message
translations can be provided in separate files, named with an extra suffix corresponding to
the standard ISO language and country codes. For example, the French translations of the
Web Client messages are found in the webclient_fr_FR.properties file and the French
Canadian translations are found in the webclient_fr_CA.properties file. The messages
in the base bundle (without a suffix) are in the en_US locale.
Custom messages need to be provided in a custom message bundle in the alfresco/
extension directory. The base extension file is also named webclient.properties.
Translations can be provided in separate files in the usual manner.
The custom Create Content wizard messages are stored in a webclient.properties
extension file:
custom_create_content_wizard_title=Custom Create Content Wizard
select_aspect=Select Aspect
create_content_step3_title=Step Three - Select Aspect
create_content_step3_desc=Select the aspect to apply to the content.

5. Implementing a JSF Managed Bean for the wizard


Wizard JSF managed beans referenced by the managed-bean attribute must
implement the IWizardBean interface. Generally, however, wizard beans just extend
the org.alfresco.web.bean.wizard.BaseWizardBean abstract base class.

134 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

BaseWizardBean provides the default implementation for the IWizardBean interface


but introduces one abstract method finishImpl() that subclasses have to implement.
Subclasses can override other BaseWizardBean methods to customise wizard behaviour.

See Wizard Beans on page 137 for more detail.


Wizard JSF managed beans:
are referenced in the managed-bean attribute,
must implement the IWizardBean interface,
generally extend the BaseWizardBean abstract base class.
BaseWizardBean:
provides the default implementation for the IWizardBean interface,
introduces one abstract method finishImpl() that subclasses have to
implement.
Subclasses can override BaseWizardBean methods to customise wizard behaviour.
a.

Creating the CustomCreateContentWizard class


The aim of the tutorial is to customise the Create Content wizard to add
an extra step allowing a user to add an aspect to the created content. The
CustomCreateContentWizard managed bean, therefore, just needs to extend the
existing CreateContentWizard bean.
public class CustomCreateContentWizard
extends CreateContentWizard

b.

Implementing the getter method for the aspects property


The aspects property stores a list of aspects that the use can choose from. The list
of aspects is read from the aspects element in the Content Wizards section of
a Web Client configuration file. Once initialised, the list is no longer read from the
configuration file on future calls.
protected List<SelectItem> aspects;
public List<SelectItem> getAspects()
{
if (this.aspects == null)
{
ConfigService svc = Application.getConfigService(
FacesContext.getCurrentInstance());
Config wizardCfg = svc.getConfig("Content Wizards");
if (wizardCfg != null)
{
ConfigElement aspectsCfg =
wizardCfg.getConfigElement("aspects");
if (aspectsCfg != null)
{
...
}
}
}
return this.aspects;
}

c.

Implementing the getter and setter methods for the aspect property
The aspect property stores the value of the aspect selected by the user from the
drop-down list. Once the user has selected an aspect and clicked on the Next button,
the setAspect() method will be called to set the value of the selected aspect. If the
user returns to the same step, the value of the aspect property will be read using the
API Development Course 135

Extending the Alfresco Web Client

getAspect() method to initialise the drop-down list to the aspect that the user has

already selected.
protected String aspect;
public String getAspect()
{
return aspect;
}
public void setAspect(String aspect)
{
this.aspect = aspect;
}

d.

Implementing the finishImpl() method


The finishImpl() is called at the end of the wizard to do the actual work. In the case
of our customised implementation, we just need to call the standard finishImpl()
method on the CreateContentWizard superclass before adding the selected aspect
to the created content.
protected String finishImpl(FacesContext context, String outcome)
throws Exception
{
super.finishImpl(context, outcome);
// add the selected aspect (the properties page after the
// wizard will allow us to set the properties)
QName aspectToAdd = Repository.resolveToQName(this.aspect);
this.nodeService.addAspect(this.createdNode,
aspectToAdd,
null);
return outcome;
}

6. Registering the JSF Managed Bean


The customised CustomCreateContentWizard managed bean now has to be registered
with JSF in a faces-config.xml file. The faces-config.xml file will be packaged in the
META-INF directory for deployment.
Example taken from META-INF/faces-config.xml:
<managed-bean>
<managed-bean-name>CustomCreateContentWizard</managed-bean-name>
<managed-bean-class>
org.alfresco.sample.CustomCreateContentWizard
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>nodeService</property-name>
<value>#{NodeService}</value>
</managed-property>
...
</managed-bean>

The CustomCreateContentWizard bean uses several public services such as


nodeService . JSF will inject references to the public services defined as managed
properties in the above example. The setter methods are implemented by the
superclasses.
See Introduction on page 106 for more information on registering managed beans.
7. Packaging and deploying

136 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

In order to deploy a custom wizard to the Alfresco Web Client, the following files need to
be packaged:
the web-client-config-custom.xml file containing the wizard and action
configuration;
the wizard JSPs containing the HTML and JSF components to define the body of
each wizard step;
the custom webclient.properties file containing custom wizard messages;
the compiled JSF managed bean class;
the custom JSF faces-config.xml file.
The compiled class and META-INF/faces-config.xml file needs to be exported to a JAR
file. The files then need to be deployed to the following directories in the Alfresco Web
Client:
the JAR file to the WEB-INF/lib directory;
the wizard JSPs to the jsp/extension directory;
the web-client-config-custom.xml and webclient.properties to the WEBINF/classes/alfresco/extension directory.
For deployment in a production environment, the files should be packaged and deployed
as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.
After deployment, the Create Content wizard now has en extra Select Aspect step:

Further Reading
Wizard Beans
Wizard beans are JSF managed beans referenced by the managed-bean attribute in the
<wizard/> configuration. Wizard beans must implement the IWizardBean interface that extends
the IDialogBean interface from the Dialog Framework.

API Development Course 137

Extending the Alfresco Web Client

There is a base class for all wizard managed beans


(org.alfresco.web.bean.wizard.BaseWizardBean). The base class extends the
BaseDialogBean class used for dialogs and provides the default implementation for the
IWizardBean interface.
As BaseWizardBean extends BaseDialogBean most of the processing is performed in exactly
the same way as for dialogs. For more information about BaseDialogBean,see Dialog Beans on
page 128. The only difference is the default return values for some methods.
The cancel() method returns an outcome of wizard:close, the getCancelButtonLabel()
method returns Cancel, the getFinishButtonLabel() method returns Finish, the
getNextButtonLabel() method returns Next, the getBackButtonLabel() method returns Back
and the getNextButtonDisabled() method returns false. Finally, the default outcome returned
from finish() is wizard:close.
The next() and back() methods allow the bean implementation to be notified when the user
presses the Next or Back button. This can be used to perform processing as the user progresses
through the wizard steps.
The only other method BaseWizardBean provides is the buildSummary() method, this is used by
subclasses to help build the HTML summary table for use on the last page of all wizards.

The Wizard Manager


The Wizard Manager is at the centre of the wizard framework.

138 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

When the Alfresco navigation handler gets a request to open a wizard it examines the current
page. If it detects that it is a wizard, the state of the wizard is retrieved and stored on the view
stack. If the current page is not a wizard the current page is added to the view stack.
The navigation handler then looks up the configuration for the wizard and passes the
configuration object to the WizardManager via the setCurrentWizard() method. The
WizardManager then proceeds to instantiate the wizard's managed-bean and set up any
parameters passed into the wizard via the setupParameters() action listener method. It
processes the list of steps the wizard has, and determines what the current page is.
The navigation handler then navigates to the wizard container page. The wizard container page
also uses the #{WizardManager...} syntax to resolve the title, description and icon for the
header area, the list of steps on the left and the page to show as the body for the current step of
the wizard.
The WizardManager either returns the requested information from the wizard config object it was
passed, or passes the request on to the underlying managed bean. For example, when the user
presses the Finish button, the WizardManager calls finish() on the managed bean.
When a user presses the Next or Back button, the WizardManager increases or decreases the
current step counter and determines from the wizard config what page should be shown.
When the container page renders the buttons, the Next and Finish buttons disabled state is
controlled via the getNextButtonDisabled() and getFinishButtonDisabled() methods.
These methods can be used to enable or disable the buttons appropriately, for example, if the
wizard already has enough state to complete successfully.
When a wizard:close request is received, the navigation handler examines the view stack to
see what the item at the top of the stack is. If it is a wizard, the state object is retrieved from the
stack and restored, and the wizard container page is then navigated to. If the top of the stack
is a normal page, the page is retrieved and navigated to. If the close request is overridden by
an outcome of wizard:close:browse, the view stack is emptied and the overridden outcome
processed.

Wizard Framework Reference


<wizard/>

attributes

name
The unique name (id) of the wizard.
managed-bean
The name of the JSF managed bean to be used as the backing bean for the wizard.
icon
The path to the icon displayed in the header area of the wizard.
title-id
The id of an I18N string to be used as the title of the wizard.
title
A literal string to be used in place of title-id if you do not want an I18N string.
description-id
The id of an I18N string to be used as the description of the wizard.
description
A literal string to be used in place of description-id if you do not want an I18N string.
error-message-id
The id of an I18N string to be used in the case of an error.

API Development Course 139

Extending the Alfresco Web Client

actions-config-id
The id of a configured action group. The actions are displayed in the header area of the
wizard.
<step/>

attributes

name
The unique name (within the wizard) of the step.
title-id
The id of an I18N string to be used as the title of the step (appears in the list of steps on the
left hand side).
title
A literal string to be used in place of title-id if you do not want an I18N string.
description-id
The id of an I18N string to be used as the description of the step (the tooltip for the step on
the left hand side).
description
A literal string to be used in place of description-id if you do not want an I18N string.
<page/>

attributes

path
The path to the JSP page to be used for the wizard step.
title-id
The id of an I18N string to be used as the title of the page (appears on the first line of the inner
panel).
title
A literal string to be used in place of title-id if you do not want an I18N string.
description-id
The id of an I18N string to be used as the description of the page (appears on the second line
of the inner panel).
description
A literal string to be used in place of description-id if you do not want an I18N string.
instruction-id
The id of an I18N string to be used as the instruction text for the page (appears on the last line
of the inner panel).
instruction
A literal string to be used in place of instruction-id if you do not want an I18N string.
<condition/>

element

A page can also be wrapped with a condition element as shown below:


<condition if="#{CreateSpaceWizard.createFrom == 'scratch'}">
<page path="/jsp/spaces/create-space-wizard/from-scratch.jsp"
title-id="create_space_step2_title"
description-id="create_space_step2_desc"
instruction-id="default_instruction" />
</condition>

if
A value binding expression that must resolve to true or false.

140 Alfresco Enterprise Edition Version 3.2

Extending the Alfresco Web Client

A <step/> can have any number of <condition/> elements. The first condition to return true is
selected as the active page. A page without a condition will be treated as the default page if none
of the conditions within the step match.

API Development Course 141

Packaging Extensions

Packaging Extensions

142 Alfresco Enterprise Edition Version 3.2

Packaging Extensions

Alfresco Module Packages


Introduction
An Alfresco Module Package (AMP) is a collection of code, XML, images, CSS, etc. that
collectively extend the functionality or data provided by the standard Alfresco Repository. An AMP
file can contain as little as a set of custom templates or a new category. It can contain a custom
model and associated UI customisations. It could contain a complete new set of functionality, for
example records management. As a general rule of thumb, anything that is considered to be an
installable extension to the Alfresco repository should be called a module and packaged as an
AMP file.
AMP files can be installed into the Alfresco WAR using the Module Management Tool. An AMP
file has a standard format described below that can be customised if required.
Once the contents of the AMP file has been mapped into an Alfresco WAR using the Module
Management Tool, the WAR can be deployed to the application server. When the repository
is next started, the installed module configuration will be detected, and the repository will be
bootstrapped to include the new module functionality and data.

Basic AMP Howto


The following examples are taken from the Basic AMP SDK sample and assume use of the
Eclipse IDE.
1. Structuring an Alfresco Module project
An Alfresco Module project can be structured in any way that suits the developer
environment. As long as the resulting AMP file is packaged correctly and the required
property and context files are present, the module will install successfully.
The recommended project structure is as follows:
\
|-- source
|
|-- java
|-- <module package structure starts here>
|
|-- web
|-- css
|-- images
|-- jsp
|-- scripts
|
|-- config
|-- <resource package structure starts here>
|
|-- build
|-- dist
|-- lib
|
|-- build.xml
source/java/

Contains the Java source for the Alfresco Module.


source/web/

Contains any web UI resources (JSPs, images, CSS, JavaScript).


config/

Contains configuration files and resources used by the module.

API Development Course 143

Packaging Extensions

build/

Build directory for compiled class files.


build/dist/

Build directory for AMP files.


build/lib/

Build directory for JAR files.


The recommended package structure for Java source (source/java), configuration files
and resources (config) is org.alfresco.module.<moduleid>, where moduleid is the
unique module id of the module (see below).
2. The Module properties file
The module.properties file is required by the module service to identify the module and
its details, when it is installed.
When the AMP file is built the module.properties file must be placed at the root of the
AMP file, but during development it is recommended that it should reside in the package
alfresco.module.<moduleid> as this is the location it will end up in once it is installed
into the WAR.
In this location it allows the developer to run unit tests within Eclipse and the embedded
repository that is started will behave as if the module is installed. This is because the
relevant module.properties file is on the class path in the correct location.
The module.properties file itself contains the module id, version, title and description of
the module.
Example module.properties from the Basic AMP SDK sample:
# SDK Sample module
module.id=sdkDemoAmp
module.title=SDK Demo AMP Project
module.description=Skeleton demo project to build an amp file
module.version=1.0
module.id

The module id specified in this file will act as a unique identifier for this module. It
is important that the module id is globally unique so that it never clashes with other
modules when it is installed. For example: org.alfresco.module.RecordsManagement. It
is possible to rename a module using the alias mechanism. Module IDs may contain az, A-Z, 0-9, space, minus and underscore.
module.version

The module version number specifies the current version of the module. This is taken
into consideration when installing the module into a WAR. It will determine whether it is
a new install or an update to an existing installation of a previous version. The version
number must be made up of numeric values separated by dots. For example '2.1.56' is
a valid version number, '2.3.4a' is not.
module.title

The title of the module.


module.description

The description of the module.


module.aliases (optional)

When a module gets renamed, it is necessary to add the original name to the list of
aliases. This ensures that the module tool and repository startup can correctly match
the new name to one of the old names.

144 Alfresco Enterprise Edition Version 3.2

Packaging Extensions

module.depends.* (optional)

When a module is installed, it may be a requirement that another module is installed.


It may be a requirement that a specific version, set of versions or range of versions
is present. The dependency has been met as long as the installed version of the
dependency matches any of the ranges or versions give. Here are some examples:
# Any version of X must be installed
module.depends.X=*
# Need to have versions 1.0, 1.5 or 2.0 of Y installed
module.depends.Y=1.0, 1.5, 2.0
# Need to have version any version of Z less than 1.0 installed
module.depends.Z=*-0.9.9

3. The Module Context file


A module is initialised when the Alfresco repository loads the root Spring configuration for
that module.
A module's root Spring configuration must be placed in the package
alfresco.module.<moduleId> and should be called module-context.xml.
When the module service is initialised all the module-context.xml configurations found
are loaded, thus initialising the installed modules ready for use.
The module-context.xml file is a standard Spring configuration file and typically new
beans will be defined, custom content models and client configuration specified and data
loaded or patched. In a big module the configuration may be split up into smaller Spring
configurations which are included by module-context.xml.
Example module-context.xml from the Basic AMP SDK sample:
<beans>
<import resource=
"classpath:alfresco/module/sdkDemoAmp/context/service-context.xml"/>
</beans>

The imported context/service-context.xml file:


<beans>
<!-- A simple class that is initialized by Spring -->
<bean id="sdk.demoAmp.exampleBean"
class="org.alfresco.module.sdkdemoamp.Demo"
init-method="init" />
<!-- A simple module component that will be executed once -->
<bean id="sdk.demoAmp.exampleComponent"
class="org.alfresco.module.sdkdemoamp.DemoComponent"
parent="module.baseComponent" >
<property name="moduleId" value="sdkDemoAmp" />
<property name="name" value="exampleComponent" />
<property name="description"
value="A demonstration component" />
<property name="sinceVersion" value="1.0" />
<property name="appliesFromVersion" value="1.0" />
</bean>
</beans>

The Demo class prints a message to the console when it is initialised by Spring:
public class Demo
{
public void init()
{
System.out.println("SDK Demo AMP class has been loaded");
}
}

API Development Course 145

Packaging Extensions

The DemoComponent is an example component that will be executed just once during
module initialisation:
public class DemoComponent extends AbstractModuleComponent
{
@Override
protected void executeInternal() throws Throwable
{
System.out.println("DemoComponent has been executed");
}
}

4. Building the AMP file


a.

The structure of an AMP file


An AMP file is ZIP compressed and has the following default structure:
/
|
|-|
|-|
|-|
|--

/config
/lib
/licenses
/web
|
|-- /jsp
|-- /css
|-- /images
|-- /scripts

|
|-- module.properties
|
|-- file-mapping.properties
config/

Contains the Spring module-context.xml and UI config that generally reside


in the standard package structure (alfresco.module.<moduleId>) within this
directory. Other resources, such as XML import files or ACP's, may also reside
here. The contents are mapped into the /WEB-INF/classes directory in the WAR
file and as such will be on the classpath.
lib/

Contains any module specific JAR files. The contents are mapped into the /WEBINF/lib directory in the WAR file.
licenses/

Contains licence files for any 3rd party JARs in /lib.


web/

Contains any custom or modified JSPs, CSS files, images and JavaScript. The
contents are mapped to the equivalent directories in the WAR file. All sub-directory
structures are preserved. If a file already exists it is overridden in the WAR and a
recoverable backup is taken.
module.properties
The module.properties file is required. It contains meta-data about the module,

including the module id and version number.


file-mapping.properties

A file-mapping.properties file can optionally be provided to customise the way AMP


file are mapped into the WAR file. See Customising the structure of an AMP file on
page 149 for more details.

146 Alfresco Enterprise Edition Version 3.2

Packaging Extensions

b.

Ant build.xml file


The module files are packaged into an AMP using Ant.
Example build.xml from the Basic AMP SDK sample:
<project name="SDK Demo AMP Build File" default="package-amp"
basedir=".">
<property name="project.dir" value="."/>
<property name="build.dir"
value="${project.dir}/build"/>
<property name="config.dir"
value="${project.dir}/config"/>
<property name="jar.file"
value="${build.dir}/lib/alfresco-sdk-custom-service.jar"/
>
<property name="amp.file"
value="${build.dir}/dist/alfresco-sdk-customservice.amp"/>
...
</project>

The compile target compiles the Java source in /source/java to /build/classes:


<path id="class.path">
<dirset dir="${build.dir}" />
<fileset dir="../../lib/server" includes="**/*.jar"/>
</path>
<target name="compile">
<mkdir dir="${build.dir}/classes" />
<javac classpathref="class.path"
srcdir="${project.dir}/source/java"
destdir="${build.dir}/classes" />
</target>

The package-jar target packages the compiled class files into the destination JAR
file:
<target name="package-jar" depends="compile">
<jar destfile="${jar.file}" >
<fileset dir="${build.dir}/classes"
excludes="**/custom*,**/*Test*"
includes="**/*.class" />
</jar>
</target>

The package-amp target packages the JAR files and configuration files into the
destination AMP file:
<target name="package-amp" depends="mkdirs, package-jar"
description="Package the Module" >
<zip destfile="${amp.file}" >
<fileset dir="${project.dir}/build"
includes="lib/*.jar" />
<fileset dir="${project.dir}"
includes="config/**/*.*"
excludes="**/module.properties" />
<fileset dir="${project.dir}/config/alfresco/module/sdkDemoAmp"
includes="module.properties" />
</zip>
</target>

The update-war target uses the Module Management Tool to install the AMP into an
existing WAR file:
<target name="update-war" depends="package-amp"
description="Update the WAR file. Set -Dwar.file=..." >
<echo>Installing SDK Demo AMP into WAR</echo>

API Development Course 147

Packaging Extensions

<java dir="."
fork="true"
classname="org.alfresco.repo.module.tool.ModuleManagementTool">
<classpath refid="class.path" />
<arg line="install ${amp.file} ${war.file} -force -verbose"/>
</java>
</target>

5. Installing the AMP


AMP files are installed and managed using the Module Management Tool (MMT). See
Module Management Tool on page 150 for more details. Once the AMP file has been
packaged, it can be installed into an Alfresco WAR file either using Ant or directly from the
command line.
The Ant build.xml file in the Basic AMP SDK sample is an example of how to run the
Module Management Tool from Ant. To install the AMP file (alfresco-sdk-customservice.amp) in an existing Alfresco WAR file use the following command:
ant -f build.xml -Dwar.file=/path/to/alfresco.war update-war

setting the value of war.file to the correct path to the Alfresco WAR file to update.
Example Ant build from within Eclipse:

Alternatively, the MMT install command can be run directly from the command line. The
following command will do a dry-run (preview) installation:
java -jar alfresco-mmt-2.1.jar install alfresco-sdk-custom-service.amp
alfresco.war -preview

The following example will install the AMP file in verbose mode:
java -jar alfresco-mmt-2.1.jar install alfresco-sdk-custom-service.amp
alfresco.war -verbose

The modified Alfresco WAR can then be re-deployed back into your application server.
On re-starting the application server, the console will show that the custom class has been
initialised during startup:

148 Alfresco Enterprise Edition Version 3.2

Packaging Extensions

Further Reading
Customising the structure of an AMP file
In order to customise the structure of your AMP file, a file-mapping.properties file must be
provided which describes how the structure of your AMP file will be mapped to the Alfresco WAR
when the AMP file is installed by the Module Management Tool.
If no file-mapping.properties file is provided the default mapping will be used.
The structure of file-mapping.properties file is that of a standard Java property file, with the
key of each entry being the directory in the AMP file structure and the value being the location
that the contents for that directory should be copied to in the WAR file.
If the source directory does not exist in the AMP file, then the mapping will be ignored; however,
if the destination directory in the WAR file does not exist then a runtime exception will be raised
when the Module Management Tool tries to install the AMP.
If a mapping provided in the file-mapping.properties file overrides one of the default
mappings, then this will take precedence when the installation into the WAR takes place. If a
mapping is declared that has a folder as the source, then the folder will be recursively copied into
the WAR file.
Below is an example of a file-mapping.properties file:
# Custom AMP to WAR location mappings
#
# The following property can be used to include the standard set of mappings.
# The contents of this file will override any defaults. The default is
# 'true', i.e. the default mappings will be augmented or modified by values in
# this file.
#
include.default=false
#
# Custom mappings.
set.
#
/WEB-INF=/WEB-INF
/web=/

If 'include.default' is false, then this is the complete

Importing Module Data


Module data can be imported from an XML file or an ACP file during module initialisation using
the Importer Module Component.
The XML file or ACP file to be imported needs to be placed somewhere on the module's
classpath and then a Spring bean similar to the following needs to be defined in the module's
module-context.xml file:
<bean id="myModule.bootstrap"
class="org.alfresco.repo.module.ImporterModuleComponent"
parent="module.baseComponent">
<!-- Module Details -->
<property name="moduleId" value="myModule" />
<property name="name" value="myModuleBootstrap" />
<property name="description"
value="My Modules initial data requirements" />
<property name="sinceVersion" value="1.0" />
<property name="appliesFromVersion" value="1.0" />
<!-- Data properties -->
<property name="importer" ref="spacesBootstrap"/>

API Development Course 149

Packaging Extensions

<property name="bootstrapViews">
<list>
<props>
<prop key="path">
/${spaces.company_home.childname}
</prop>
<prop key="location">
alfresco/module/myModule-123/myACP.acp
</prop>
</props>
</list>
</property>
</bean>

The XML and/or ACP files to be imported and the destination location in the repository where the
data should be imported are supplied as a list to the bootstrapViews property.

Alfresco Module Package Reference


Module Management Tool
The Module Management Tool (MMT) has a number of commands. Details of these are outlined
below:

150 Alfresco Enterprise Edition Version 3.2

Packaging Extensions

Install
usage: install <AMPFileLocation> <WARFileLocation> [options]
valid options:
-verbose
: enable verbose output
-directory : indicates that the amp file location specified is a
directory.
All amp files found in the directory and its sub directories
are installed.
-force
: forces installation of AMP regardless of currently installed
module version
-preview
: previews installation of AMP without modifying WAR file
-nobackup : indicates that no backup should be made of the WAR

Installs the files found in the AMP file into the Alfresco WAR, updates if an older version
is already installed. This is done using the standard mapping, unless a custom mapping is
provided.
If the module represented by the AMP is already installed and the installing AMP is of a higher
release version, then the files relating to the older version will be removed from the WAR and
replaced with the newer files.
It is the responsibility of the module developer to provide the appropriate Module components
to bootstrap or patch any data as required when updated WAR is run.
If the installing module version is less than or equal to the version already installed in the WAR
then installation will be aborted unless the -force option is specified. In this case the installing
AMP will always replace the currently installed version. This option is especially useful when
developing an AMP.
Before an AMP is installed into a WAR a copy of the original WAR is taken an placed in the
same directory. Specifying the -nobackup option prevent this from occurring.
It is considered good practice to do a -preview install prior to doing the install for real. This
reports the modifications that will occur on the WAR without making any physical changes.
The changes that are of most importance to note are those that are going to update existing
files.
As a general rule it is considered bad practice to overwrite an existing file in an AMP, however
it is sometimes necessary. In these situations when the AMP is installed a backup of the
updated file is taken and stored in the WAR.
When an update of the module occurs and the old files are removed, this backup will be
restored prior to the installation of the new files. Problems may occur if multiple installed
modules modify the same existing file. In these cases a manual restore may be necessary if
recovery to an existing state is required.
list
usage: list <warFile>

Lists the details about all the modules currently installed in the WAR file specified. The output
is directed to the console.

API Development Course 151

S-ar putea să vă placă și