2140 api developer-student-guide

151
API Development Course

description

dsada

Transcript of 2140 api developer-student-guide

Page 1: 2140 api developer-student-guide

API Development Course

Page 2: 2140 api developer-student-guide

2

Copyright (c) 2007 by Alfresco and others.

Information in this document is subject to change without notice. No part of this document maybe reproduced or transmitted in any form or by any means, electronic or mechanical, for anypurpose, 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 thisdocument does not give you license to these patents, trademarks, copyrights or other intellectualproperty 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 regulationsapply 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 assumesole responsibility for any such unauthorized exportation.

If you need technical support for this product, contact Customer Support by email [email protected]. If you have comments or suggestions about this documentation, contactus at [email protected].

This edition applies to version 2.1 of the licensed program.

Page 3: 2140 api developer-student-guide

API Development Course 3

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

Page 4: 2140 api developer-student-guide

4 Alfresco Enterprise Edition Version 3.2

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

Page 5: 2140 api developer-student-guide

API Development Course 5

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

Page 6: 2140 api developer-student-guide

Introduction

6 Alfresco Enterprise Edition Version 3.2

Introduction

Page 7: 2140 api developer-student-guide

Introduction

API Development Course 7

Welcome

Welcome to Alfresco API development. This course has been designed to provide you with theknowledge and skills necessary to develop against the Alfresco Repository APIs and to developextensions 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 modulesinclude a combination of introductory materials, an outline of objectives followed by instructor-led discussions and walkthroughs. Each module will close with one or more exercises which willallow 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 tasksrequired 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 andabilities to pass the Alfresco API developer exam in preparation for the Certified Alfresco ECMprofessional 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

Page 8: 2140 api developer-student-guide

Introduction

8 Alfresco Enterprise Edition Version 3.2

• 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

Page 9: 2140 api developer-student-guide

Getting Started

API Development Course 9

Getting Started

Page 10: 2140 api developer-student-guide

Getting Started

10 Alfresco Enterprise Edition Version 3.2

Alfresco SDK

Introduction

The Alfresco SDK provides support for developers who wish to extend or customise the Alfrescoplatform. It has been designed for the developer to get developing with minimal fuss for thefollowing development scenarios:

• Developing extensions for the Alfresco Repository and Web Client.

• Embedding Alfresco into applications via Alfresco's Java Foundation Services API orstandards-compliant JCR API.

• Developing applications against a remote Alfresco Repository via Alfresco's Web ServicesAPI.

Typically, the SDK is used stand-alone, but an Alfresco installation is also required if performingany 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 scriptsand artifacts. If you wish to develop bug fixes or extend the core functionality of theAlfresco platform, you should use the full Alfresco development environment provided inthe 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 SDKcorrectly:

• the Java SE Development Kit (JDK) version 1.5 (5.0) or above;

• a supported database of your choice (MySQL is recommended for developmentpurposes);

• the Eclipse IDE 3.x (highly recommended).

The Alfresco SDK bundle is provided with each release of Alfresco, for both the Enterprise andCommunity Networks.

1. Downloading the Alfresco SDK

The Enterprise SDK is only available to Enterprise clients and partners. The CommunitySDK is freely available and can be downloaded from the Download Alfresco CommunityNetwork page on the Alfresco Developer web site. The SDK is provided in both ZIP andTAR (tar.gz) formats.

2. Unpacking the Alfresco SDK

To install the SDK, simply unpack the downloaded ZIP or TAR bundle to a directory of yourchoice.

Page 11: 2140 api developer-student-guide

Getting Started

API Development Course 11

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 containsseveral pre-configured Eclipse projects that you can import directly into Eclipse with the followingprocedure.

1. Setting the Eclipse Compiler Compliance Level

Alfresco uses Java 1.5 (5.0) language features, therefore Eclipse must be configuredappropriately 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:

Page 12: 2140 api developer-student-guide

Getting Started

12 Alfresco Enterprise Edition Version 3.2

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 importsource 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 clickOK. The Alfresco SDK projects are now listed under Projects.

Do not navigate to the samples sub-directory, otherwise you will not see the SDKAlfrescoEmbedded and SDK AlfrescoRemote projects in the list.

Page 13: 2140 api developer-student-guide

Getting Started

API Development Course 13

In order to run the samples or to develop your own extension modules, you mustimport at least the SDK AlfrescoEmbedded and SDK AlfrescoRemote projects. Theother SDK projects are samples for common development scenarios that you canstudy and run to learn more about developing Alfresco extension modules.

For more information about the available projects, see SDK Eclipse Projects on page20.

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

The imported projects are displayed in the Package Explorer:

Page 14: 2140 api developer-student-guide

Getting Started

14 Alfresco Enterprise Edition Version 3.2

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 toAlfresco's source code and Java documentation. The following procedure explains how to do thisby 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 popupmenu.

The JAR files may not be in alphabetical order.

3. Associating source code

a. In the Properties for alfresco-repository.jar dialog, select Java SourceAttachment 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.

Page 15: 2140 api developer-student-guide

Getting Started

API Development Course 15

4. Associating Javadocs

a. In the Properties for alfresco-repository.jar dialog, select Javadoc Location in thetree 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 theJavadocs in a web browser or Cancel if not.

Page 16: 2140 api developer-student-guide

Getting Started

16 Alfresco Enterprise Edition Version 3.2

5. Click OK, to close the Properties dialog.

Once the source code and Javadocs have been attached, the alfresco-repository.jar iconchanges 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 projectsdemonstrate how to access an embedded Alfresco repository via the Foundation Services APIand the standards-compliant JCR API. These samples can be tested directly from within Eclipseand will automatically start an Alfresco repository in embedded mode.

Before starting, the embedded repository needs to be configured. By default, the sample projectsare configured to use a MySQL database named alfresco and a data directory with a relativepath of ./alf_data. These parameters are defined in the custom-repository.propertiesfile in the source/alfresco/extension directory of each project. It is good practice to definean absolute path for the data directory (dir.root parameter) and to configure all of the SDKprojects to share the same database and the same dir.root.

Example custom-repository.properties file:

dir.root=C:/alf_data

Page 17: 2140 api developer-student-guide

Getting Started

API Development Course 17

#db.username=alfresco#db.password=alfresco

## MySQL connection (This is default and requires mysql-connector-java-3.1.12-bin.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 theextras/databases/mysql directory of the unpacked Alfresco SDK. To create the database fromthe command line:

C:\alfresco-enterprise-sdk\extras\databases\mysql>mysql -u root -p < db_setup.sqlEnter password: ********

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

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

Page 18: 2140 api developer-student-guide

Getting Started

18 Alfresco Enterprise Edition Version 3.2

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

Web Services Samples

The SDK FirstWebServiceClient and SDK WebServiceSamples sample projects demonstratehow to access a remote Alfresco repository via the Web Services API. A remote Alfrescorepository 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 installedand configured. The easiest solution for development purposes is to install Alfresco on the localmachine and configure it to use the same alfresco MySQL database and the same C:/alf_dirdata directory as the embedded repository used for the other samples.

The location of the remote repository is configured in the webserviceclient.propertiesfile in the source/alfresco/extension directory of each Web Services project. If the remoterepository is installed on the same machine and configured to use the default 8080 port, you willnot 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 withrepository.location=http://localhost:8080/alfresco/api

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

Custom Repository Samples

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

Page 19: 2140 api developer-student-guide

Getting Started

API Development Course 19

modules can be developed and tested using unit tests and an embedded Alfresco repository. TheSDK CustomAspect project has a sample unit test that does this.

An Ant build.xml file is provided for packaging the repository samples. The package targetpackages 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 theapplication server.

For deployment in a production environment, a custom repository module should be packaged asan Alfresco Module Package (AMP).

For more information on creating a custom repository action, see the Repository Action Howtoon page 79. For a detailed presentation of the SDK CustomAspect sample, see the CustomAspect with Behaviour Howto on page 71.

Custom Web Client Samples

The SDK CustomDialog, SDK CustomJSP, SDK CustomLogin, SDK CustomWizard, and SDKTaggingSample sample projects demonstrate how to develop custom modules for the AlfrescoWeb Client. Custom Web Client modules have to be deployed to an existing Alfresco installationfor testing.

An Ant build.xml file is provided for packaging the Web Client samples. The package-jar target packages the compiled classes and extension files into a JAR file. The package-extension target then packages the JAR file along with the JSPs into a ZIP file. The optionalintegrate-extension target can be used to integrate the packaged ZIP file into an AlfrescoWeb Client WAR file. The alfresco.war file must be copied to the same directory as thebuild.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 packagedas an Alfresco Module Package (AMP).

The SDK CustomDialog and SDK CustomWizard are presented in detail in the Custom DialogHowto on page 123 and the Custom Wizard Howto on page 132. The SDK TaggingSamplesample 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 arrangeclasses and configuration files to generate an Alfresco Module Package (AMP). For moreinformation see Basic AMP Howto on page 143.

SVN Repository

The Alfresco Subversion repository gives you access to all of the Alfresco source code and buildartifacts. It provides the latest work-in-progress developments. It should only be used if you wishto extend the Alfresco core framework or work on Alfresco bug fixes as it allows you to performfull 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 website. 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

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

3. Keep up to date by issuing the command:svn update

Page 20: 2140 api developer-student-guide

Getting Started

20 Alfresco Enterprise Edition Version 3.2

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 onpage 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 beembedded 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 anAlfresco repository.

SDK CustomAspect

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

Page 21: 2140 api developer-student-guide

Getting Started

API Development Course 21

SDK CustomDialog

Sample project demonstrating how to develop and configure a custom Dialog for the AlfrescoWeb Client.

SDK CustomJSP

Sample project demonstrating how to develop and configure a custom JSP for the AlfrescoWeb 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 AlfrescoWeb Client.

SDK FirstFoundationClient

Sample project demonstrating how to access an Alfresco (embedded) repository via theFoundation Services API.

SDK FirstJCRClient

Sample project demonstrating how to access an Alfresco (embedded) repository via thestandards-compliant JCR API.

SDK FirstWebServiceClient

Sample project demonstrating how to access a remote Alfresco repository via the WebServices API.

SDK JCRSamples

More sample projects demonstrating how to access an Alfresco (embedded) repository via thestandards-compliant JCR API.

SDK TaggingSample

Advanced sample project demonstrating how to develop a custom Action that takesparameters.

SDK WebServiceSamples

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

Page 22: 2140 api developer-student-guide

Getting Started

22 Alfresco Enterprise Edition Version 3.2

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 botherthe client code with a checked exception.

• Pay attention to the Javadoc specification on unchecked exceptions. Don't declare themon 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 theJavadocs.

• INFO messages are only added at the request of Alfresco users. All other informativemessages 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 iflogger.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. thatcollectively extend the functionality or data provided by the standard Alfresco Repository. An AMPfile can contain as little as a set of custom templates or a new category. It can contain a custommodel and associated UI customisations. It could contain a complete new set of functionality, forexample 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 anAMP file.

AMP files can be installed into the Alfresco WAR using the Module Management Tool. An AMPfile has a standard format that can be customised if required.

Page 23: 2140 api developer-student-guide

Getting Started

API Development Course 23

Once the contents of the AMP file has been mapped into an Alfresco WAR using the ModuleManagement Tool, the WAR can be deployed to the application server. When the repositoryis next started, the installed module configuration will be detected, and the repository will bebootstrapped 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. Aslong as the resulting AMP file is packaged correctly and the required property and context filesare 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 andresources (config) is org.alfresco.module.<moduleid>, where moduleid is the uniquemodule 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.

Page 24: 2140 api developer-student-guide

Getting Started

24 Alfresco Enterprise Edition Version 3.2

Alfresco Repository Architecture

Out of the Box

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

This is typical of a web architecture, where an application server houses the logic for both theuser interface and domain. Storage of data and content is provided by persistent back-ends suchas a database or file system. Any number of web browsers can connect to the application withoutprior client installation costs.

In this particular case, the application server houses both the Alfresco Application and theAlfresco Repository. An Alfresco Application provides a complete solution tailored for a specificarea of Content Management such as Document Management (DM), Web Content Management(WCM) and Records Management (RM). The Alfresco Repository provides a set of reusablecross-cutting Content Management services such as content storage, query, versioning andtransformation 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 thecapabilities and components of Alfresco. When we first set out to design Alfresco, we wanted tobreak away from the mould of typical Content Management architectures which are monolithicand closed. The result is that Alfresco can neatly fit into existing environments and each of its

Page 25: 2140 api developer-student-guide

Getting Started

API Development Course 25

components may be used in isolation or together to form the basis of many differing ContentManagement solutions.

The remainder of this module explores the anatomy of the Alfresco Repository which will give agood understanding of the concepts and capabilities and how it achieves openness, scalabilityand flexibility.

Service & Component Architecture

Every part of the Alfresco Repository is either a component or a service. A component isan implementation black box that provides a specific feature or capability. A service is aninterface entry point for a client to bind to and use. This fundamental approach allows for existingcomponents to be switched with new implementations, new components to be added with easeand 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 possiblyfaster Alfresco. If there's a feature you wish to re-implement, you can replace it, either byproviding a better implementation, or integrating with your existing environment.

Implementation of this approach is simplified by using the open source project Spring Frameworkwhich Alfresco has taken to heart and has made a core foundation of its architecture. WithSpring, Alfresco components are declaratively configured and bound together. Aspect-orientedprogramming allows the weaving of infrastructure concerns such as Transactions and Securityinto components without polluting their implementation. Environment touch points are abstractedsuch as resource (e.g. database) connections.

The Alfresco Repository structure looks like this:

Page 26: 2140 api developer-student-guide

Getting Started

26 Alfresco Enterprise Edition Version 3.2

The public interface point is the Alfresco Repository Foundation Services. Each service isexposed as a Java Interface to which a Repository client can bind and invoke without knowledgeof its underlying implementation. A Service Registry lists the available services. Behind servicesare the implementation black boxes i.e. components. Each service and component is configuredvia the Spring framework in XML “context” files.

The Spring context file public-service-context.xml provides the configuration andbinding of the Alfresco Repository Foundation Services.

The Repository Foundation Services are the lowest level of public interface providing accessto all Repository capabilities. Binding to this interface is possible via the Repository ServiceRegistry, or via Spring dependency injection if the client is also Spring aware. Access toFoundation Services is limited to Repository clients who reside in the same process as theRepository. That is, the Foundation Services are an excellent API for clients who wish to embedthe Repository.

An important point to note is that the Foundation Services are where transaction and securitypolicies are enforced. The policies themselves are declaratively specified and enforced via theinjection of a transaction and security implementation into each service. Every service of Alfrescois transactional and secure.

Other forms of API are provided too, however, all public entry points eventually go through thislayer.

Alfresco supports a common scheme for making extensions to the Repository i.e. configuringa component, adding a new component or service, or removing capabilities. Extensions are

Page 27: 2140 api developer-student-guide

Getting Started

API Development Course 27

encapsulated outside of the core Repository and plugged-in automatically. This means the coreRepository 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. Thisis split into nodes, content and index information. Nodes provide meta-data and structure tocontent. A node may support properties (e.g. author) and relate to other nodes (e.g. representfolder hierarchies or annotations). Content is the actual information being recorded e.g. a Worddocument or XML fragment. Meta-data and content may be structured according to the rulesdefined in a Content Model. For example, the Alfresco Document Management application relieson a model that describes Folders and Files. Indexing information allows the retrieval of meta-data 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 overmany years such as transaction support, scaling & administration capabilities. Content is stored inthe file system to allow for very large content, random access, streaming and options for differentstorage devices.

The Alfresco out-of-the-box implementations of the above services are built upon strongopen source projects that already have many man-years of development effort and strongcommunities: Hibernate and Lucene.

Page 28: 2140 api developer-student-guide

Getting Started

28 Alfresco Enterprise Edition Version 3.2

Apart from the strong Object/Relational mapping that Hibernate provides, it also brings pluggablecaching support and SQL dialects. The first allows for tuning of the Alfresco meta-data store toprovide optimum read and write performance in both single and clustered environments. Thesecond allows for nearly any SQL database back-end by configuring just two properties; theAlfresco 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 andfull-text predicates in a single query against any content type. Multiple query languages aresupported including Lucene's native language as well as XPath and a SQL-like language in thefuture. To ensure reliable operation, transactional support has been added to both Lucene andthe content file store providing ACID operations across the complete store. Security is woven intoeach of the service layer ensuring illegal modifications are not permissible and hidden meta-dataand 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 RepositoryFoundation Services - a set of local Java Interfaces covering all capabilities which are ideal forclients 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 byJSR-170) for accessing Content Repositories. Alfresco provides support for level 1 and level 2giving 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 outwith 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 theAlfresco 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 scalableRepository which supports many deployment options. Alfresco will continue investment in JCR byboth broadening the compliance of the full specification as well driving forward JSR-283, the nextversion of the JCR.

Web Services is the final API provided by the Alfresco Repository. This API supports remoteaccess and bindings to any client environment, not just Java. For example, the Alfrescocommunity is already using PHP, Ruby and Microsoft .NET. Numerous standards and integrationefforts are focused around Web Services - SOA is now recognised as a way forward forintegrating disparate systems including Content Management and building new enterprise-widesolutions. BPEL plays an important role in orchestrating all of these services. Alfresco fits neatlyinto this way of thinking.

Page 29: 2140 api developer-student-guide

Getting Started

API Development Course 29

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

Repository Server Protocols

A Repository is useless if the content it manages cannot be accessed. To provide the widestpossible range of access points, the Alfresco Repository supports a variety of communicationprotocols. These are:

• CIFS (Common Internet File System)

• WebDAV

• FTP

• NFS

All of these protocols essentially expose Folders of Files and as such the Alfrescoimplementations map neatly onto Folder and File nodes held in the Repository as described bythe 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 understandshow to read and write to a file system, also knows how to directly read and write to the AlfrescoRepository. On the surface, it would seem that a drive mapping to WebDAV provides anequivalent capability, but this isn't the case. CIFS projects an actual file system giving extracompatibility with the hosting operating system. For example, in Windows, it's possible to useOffline Synchronisation and Briefcase features against the Alfresco Repository providing nativeand well-known tools for offline Repository working. Many commercial CMS offerings do notprovide this feature. In fact, Alfresco may have the only server-side Java implementation of the

Page 30: 2140 api developer-student-guide

Getting Started

30 Alfresco Enterprise Edition Version 3.2

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

Like every other feature of the Repository, the protocol components are Spring configured andas with all other components may or may not be included in a deployment. Typically, they areenabled 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 isimportant to note, as each protocol will honour the behaviour and content logic encapsulatedbehind 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.

Page 31: 2140 api developer-student-guide

Getting Started

API Development Course 31

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 localname qualified by a namespace.

Page 32: 2140 api developer-student-guide

Getting Started

32 Alfresco Enterprise Edition Version 3.2

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

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

orcm:auditable

Node Browser

The Node Browser is your friend!

Page 33: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 33

Developing against the Alfresco Repository

Page 34: 2140 api developer-student-guide

Developing against the Alfresco Repository

34 Alfresco Enterprise Edition Version 3.2

Spring Framework

Introduction

The Spring Framework is a full-stack Java/JEE application framework. Spring's main aim is tomake J2EE easier to use and promote good programming practise. It does this by enabling aPOJO-based programming model remaining faithful to the fundamental ideas of Expert One-on-One 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, andwhich 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 anindependent object.

Beans are defined in an XML bean definition file that is loaded when a new Bean Factory iscreated.

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

Page 35: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 35

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@471e30Retrieved 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@10ef90cRetrieved MultiBean1: ex01_simplebean.Bean1@a32b

Inversion of Control (IoC)

Through its bean factory concept, Spring is an Inversion of Control container. Spring is mostclosely identified with a flavour of Inversion of Control known as Dependency Injection. Theconcept 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 awayfrom application code. Whereas your code calls a traditional class library, an IoC framework callsyour 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 orconfiguration values into application object instances. The two major flavors of DependencyInjection 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 whenconfiguring 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 theSpring 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; }

Page 36: 2140 api developer-student-guide

Developing against the Alfresco Repository

36 Alfresco Enterprise Edition Version 3.2

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@21b6dString property: a stringInt property: 125List 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; }

Page 37: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 37

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@12152e6String property: a stringInt 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;

Page 38: 2140 api developer-student-guide

Developing against the Alfresco Repository

38 Alfresco Enterprise Edition Version 3.2

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 andbean3 objects before “injecting” them into bean1 via the constructor property setterrespectively:<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@1cb25f1Retrieved Bean2 Dependency: ex04_dependency.Bean2@2808b3Retrieved Bean3 Dependency: ex04_dependency.Bean3@535b58

Page 39: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 39

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 BeanFactoryfuntionality 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 particularlayer, for example the web layer of an application

Web Application Context

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

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>

Page 40: 2140 api developer-student-guide

Developing against the Alfresco Repository

40 Alfresco Enterprise Edition Version 3.2

Foundation Services API

Introduction

The Foundation Services API is a set of services providing full access to the capabilities of theAlfresco Repository. It is an in-process API meaning that the client must run within the sameprocess as the Repository. For example, the Alfresco Web Client uses this API and is packagedtogether 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 afunction of the Repository. A Spring Framework Bean is provided as the implementation for eachinterface.

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 Springenabled);

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 andContentService. After initialising the Spring Application Context and starting the repository inembedded 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 forthe “Company Home” node using the SearchService. We will then create a new node withproperties and add an aspect using the NodeService. Finally, we will write some content to thenew node using the ContentService. The sample will be wrapped in a single user transactionwith the help of the TransactionService.

1. Getting the ServiceRegistry

The Service Registry maintains a list of available foundation services and some meta-dataabout 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 theSpring getBean() method. The static variable SERVICE_REGISTRY found on the interfaceorg.alfresco.service.ServiceRegistry provides the Spring Bean name to lookup by.

The FirstFoundationClient sample uses the getBean() method on the SpringApplication Context to retrieve the ServiceRegistry:

ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();

final ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);

Page 41: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 41

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

By default, all repository foundation services are transactional and each invocation ofa service method is wrapped in its own transaction. These transactions are defineddeclaratively 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 yourcode.

The FirstFoundationClient uses a RetryingTransactionHelper to run the exampleas a unit of work inside a user transaction. The work is defined as an instance of theRetryingTransactionCallback class. The doInTransaction() method is then called torun the unit of work in a transaction.

The RetryingTransactionHelper is obtained via thegetRetryingTransactionHelper() 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 moredetail in the section on Transactions.

The following example runs the example work in a user transaction via theRetryingTransactionHelper:

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

Page 42: 2140 api developer-student-guide

Developing against the Alfresco Repository

42 Alfresco Enterprise Edition Version 3.2

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 byusing the authenticate() method and providing a username and password. Onceauthenticated, a ticket can be requested using either the getNewTicket() or thegetCurrentTicket() method. The ticket can then be used to re-validate the user usingthe validate() method.

The AuthenticationService defines the API for managing authentication informationagainst 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 usingany of the available query languages: Lucene, XPath or JCR-XPath. The Lucene querylanguage allows you to run powerful searches, including full text searches on the contentand node properties.

To run a Lucene search using the query() method, you must specify the StoreRef of thestore you wish to search, the query language (using one of the static attributes defined onthe 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 anew SearchParameters object which is then passed to the query() method. The queryresults are returned as a ResultSet object.

Page 43: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 43

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 firstNodeRef 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 theSearch 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(). Propertiesare set with either setProperty() or setProperties(). Properties are removedwith removeProperty(). Aspects are applied with addAspect() and removed withremoveAspect().

Associations are created and removed with createAssociation() andremoveAssociation(). Associations can be navigated with getSourceAssocs() orgetTargetAssocs(). Child associations can be navigated with getChildAssocs() andgetParentAssoc().

Almost all NodeService methods take a NodeRef as an argument. A NodeRef is obtainedeither by navigation or from the results of a search. Otherwise a new NodeRef object canbe created from the node's unique UUID.

Page 44: 2140 api developer-student-guide

Developing against the Alfresco Repository

44 Alfresco Enterprise Edition Version 3.2

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 propertyis 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.

Page 45: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 45

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 dictionarymodel interfaces. For example, the cm:name property is defined by the static constantContentModel.PROP_NAME.

The following example creates a Map containing the cm:name property that will beused 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 childof a parent node. The child association name and child association type have to besupplied as QName objects, as well as the QName of the type of node to create andoptionally 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 thedata 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 constantson the dictionary model interfaces. For example, the cm:contains association type isdefined by the static constant ContentModel.ASSOC_CONTAINS and the cm:contentnode type is defined by the static constant ContentModel.TYPE_CONTENT. If aconstant 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 createdchild association. The NodeRef of the newly created node is obtained by calling thegetChildRef() on the ChildAssociationRef object.

Page 46: 2140 api developer-student-guide

Developing against the Alfresco Repository

46 Alfresco Enterprise Edition Version 3.2

The following example creates a new node of type cm:content, using the standardcm:contains child association. The Map created in the previous step sets thecm: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 identifiedby it's NodeRef and the aspect by it's QName. The property values are provided as aMap.

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 modelinterfaces. For example, the cm:titled aspect is defined by the static constantContentModel.ASPECT_TITLED.

The following example applies the cm:titled aspect and sets the cm:title andcm: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 aContentReader or a ContentWriter via the getReader() and getWriter() methodsrespectively. Methods can then be used on the ContentReader or ContentWriter toread and write content. Both the ContentReader and the ContentWriter implement themethods defined by the ContentAccessor interface. These methods allow you to get andset information about the content, for example to set the mime type, the encoding or to getthe 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 NodeRefneeds to be supplied along with the ContentModel.PROP_CONTENT QName.

The ContentService is also used for transforming content. A suitable transformer canbe obtained for a given transformation (defined by a source and target mime type) using

Page 47: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 47

the getTransformer() and getImageTransformer() methods. The transformation canthen be performed by calling transform directly on the content transformer. Otherwise,a transformation can be attempted from a source ContentReader object to targetContentWriter 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 propertyto be updated is defined by the QName ContentModel.PROP_CONTENT. The boolean truevalue is to request that the content is updated atomically when the content write streamis closed. The content mime type and encoding are set before writing the content with theputContent() 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);

Page 48: 2140 api developer-student-guide

Developing against the Alfresco Repository

48 Alfresco Enterprise Edition Version 3.2

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 ineffect.

Other Foundation Services

FileFolderService

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

Page 49: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 49

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

• Interface FileFolderService

• Interface FileInfo

Page 50: 2140 api developer-student-guide

Developing against the Alfresco Repository

50 Alfresco Enterprise Edition Version 3.2

JCR API

Introduction

The JCR API (Java Content Repository) specifies a standard, implementation independent API toaccess content repositories in Java. It is defined by the Java Specification Request (JSR) 170 aspart of the Java Community Process (JCP). The official JSR-170 Specification can be found onthe 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 contributingto 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 featureswhich 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 eitherlevel:

• 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 ofwhich contains a tree of items. An item is either a node or a property. Each node may havezero or more child nodes and zero or more child properties. There is a single root node perworkspace, which has no parent. Unlike the Alfresco repository, all other nodes have only oneparent. Properties have one parent (a node) and cannot have children; they are the leaves of thetree. All of the actual content in the repository is stored within the values of the properties.

Page 51: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 51

Any item in the tree hierarchy can be identified by either an absolute or a relative path usinga 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 SpacesStoreworkspace. The special paths “.” and “..” (meaning respectively, “this” and “parent”) are alsosupported.

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. Afterinitialising the Spring Application Context and starting the repository in embedded mode, we willuse 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 thencreate a new node with properties, add an aspect and write some content. Finally, we will mix theJCR API calls with the Alfresco Foundation Services API calls to set the content mime type. Thesample 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 notdictate how to obtain the Repository object. In Alfresco, the JSR repository is a Springbean called JCR.Repository.

The JCR.Repository bean is defined in jcr-api-context.xml. It has one configurationparameter; the default workspace name. This is used when a JCR client performs a loginwithout specifying the workspace. The default workspace is SpacesStore, the same oneused 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/application-context.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 Repositoryobject. The client must supply a Credentials object and optionally a workspace name.Behind the scenes, Alfresco uses the authentication system of the Alfresco repositorywhich by default is Alfresco's own, but it could also be NTLM, LDAP or your owndepending on how the repository has been configured.

If a workspace is not provided, the default as defined earlier will be used. The Sessionreturned from login is tied to the workspace and allows read and write operations uponthat workspace. By default, the Session is also backed by a transaction. Work performed

Page 52: 2140 api developer-student-guide

Developing against the Alfresco Repository

52 Alfresco Enterprise Edition Version 3.2

within the session will not be committed until Session.save() is called. For moreinformation, 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 directaccess allows you to jump directly to a node with either an absolute path or, if the node isreferenceable, with a UUID.

Traversal access is available from any Node object via the Node.getNode() andNode.getProperty() methods. The root node of a workspace can be obtained by usingthe getRootNode() method on the session object.

Direct access is available to any item in the repository (node or property) by passing anabsolute path to the Session.getItem() method. Alternatively direct access to a node isavailable by passing the UUID of the node to the Session.getNodeByUUID() method. TheUUID of a node can be retrieved using the Node.getUUID() method.

The following example gets the workspace root node, then uses traversal access with arelative 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 tonodes using the Node.setProperty() method. The addNode() method takes the qualifiedrelative path of the new node and optionally the node type. The setProperty() methodstakes 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 propertiesare 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 thecase 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'scontent: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 alwaysbe scenarios where custom Alfresco capabilities are required. For these scenarios, it is

Page 53: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 53

possible to mix JCR API calls with Alfresco Foundation Services API calls and have themcontrolled 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 AlfrescoNodeRef.

8. Saving the session

Work performed on the session is not committed until Session.save() is called. This alsocommits 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 JCRAPI 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 thesession 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 AlfrescoFoundation Services API are also included. So, if you happen to mix JCR and Alfrescocalls between Session.login() and Session.logout(), they are all bound to the sametransaction and controlled by the JCR session.

Page 54: 2140 api developer-student-guide

Developing against the Alfresco Repository

54 Alfresco Enterprise Edition Version 3.2

Explicit Transactions

It is also possible to explicitly control transaction boundaries using Alfresco'sTransactionService. Using this technique, you can explicitly control when JCR updates arecommitted.

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 page65.

Page 55: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 55

Web Services API

Introduction

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

Available Web Services

The following Alfresco Web Services are available:

Authenticationlogin and logout

Repositoryquery and model manipulation

Contentcontent manipulation

Authoringcollaborative content creation

Classificationapply classifications and categories

Access Controlroles, permissions & ownership

Actionmanage actions and rules

Administrationuser management, export & import

Dictionarymodel descriptions

Access to Web Services in Java

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

Page 56: 2140 api developer-student-guide

Developing against the Alfresco Repository

56 Alfresco Enterprise Edition Version 3.2

Static attributes on the WebServiceFactory define the default endpoint address, the locationof the Web Services configuration property file and the name of the property that defines therepository 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 whenusing 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 XMLSchema.

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 aRepository.

The statements are described in the Web Service CML XML Schema and as such can eitherbe represented as XML documents or mapped to other representations such as Java objects orappropriate client language binding.

Multiple statements may be collected together into a single document.

Page 57: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 57

FirstWebServiceClient Walkthrough

1. Logging into the repository (start a session)

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

Page 58: 2140 api developer-student-guide

Developing against the Alfresco Repository

58 Alfresco Enterprise Edition Version 3.2

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.

Page 59: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 59

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 withinthe 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");

Page 60: 2140 api developer-student-guide

Developing against the Alfresco Repository

60 Alfresco Enterprise Edition Version 3.2

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

Page 61: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 61

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 isan AOP proxy to the “target” service defined by the contentService bean found in content-services-context.xml. The AOP proxy allows cross cutting concerns such as security andtransaction management to be implemented in a non-invasive, declarative way. All servicemethod calls via the ContentService bean defined in public-services-context.xml aresubject to security checks and transaction management whilst those via the contentServicebean defined in content-services-context.xml are subject to none!

All public service Spring beans have ids that begin with an uppercase letter. Publicservices are subject to security checks and transaction management. Beans withequivalent names that begin with a lowercase letter are subject to none.

All public services AOP proxies are defined using a Spring ProxyFactoryBean. TheContentService 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 theContentService bean do not see the ProxyFactoryBean but the object defined by the targetproperty, in our case, the contentService bean. The proxyInterfaces property, defines anarray of interfaces implemented by the target class and the interceptorNames property a list ofadvisor, interceptor or other advice names to apply. Ordering is significant, the first interceptor inthe 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 public-services-context.xml that manages transactions for calls to the ContentService. TheContentService_security is a reference to a bean in public-services-security-context.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 callermust first be authenticated. This can be done by logging in using a username and passwordor using a ticket. A ticket can be requested after logging in and can be used, under certainconditions, to re-validate a user.

Once authenticated, access control allows or denies a user from calling public servicemethods on a particular object by checking if the authenticated user, or any of the authoritiesgranted to that user, has a particular permission or permission group. For example, to call thereadProperties() method on the NodeService, the authenticated user must have read access

Page 62: 2140 api developer-student-guide

Developing against the Alfresco Repository

62 Alfresco Enterprise Edition Version 3.2

to the properties of the node. On the SearchService, the results from queries are restricted toreturn only the nodes for which a user has read permission. The public services are the onlyservices 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 publicservices 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-services-security-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. TheauthenticationManager is a reference to the Acegi authentication manager bean defined inauthentication-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 currentauthorised user has or based on node access control. The afterInvocationManager enforcessecurity on objects returned by methods.

The objectDefinitionSource defines security attributes associated with each method. A list ofattributes can be defined as a comma separated list. Pre-conditions are enforced before methodinvocation 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 auser name or group. Dynamic authorities are not supported.

Page 63: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 63

ACL_NODE.#.*Access control is restricted to users who have the the specified permission for the node on theidentified argument. If the argument is a NodeRef it will be used; if it is a StoreRef then theroot node for the store will be used; if it is a ChildAssociationRef then the child node will beused.

ACL_PARENT.#.*Access control is restricted to users who have the the specified permission for the parent ofthe node on the identified argument. If the argument is a NodeRef the parent of the node willbe 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 permissionsmust 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 supportedreturn 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 basedon the access conditions.

NodeService Security Examples

The following examples are taken from the NodeService_security method security interceptorbean.

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() methodcall: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

Page 64: 2140 api developer-student-guide

Developing against the Alfresco Repository

64 Alfresco Enterprise Edition Version 3.2

Reading child associations requires the read children permission on the parent node and the readpermission 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 methodcall in its own transaction. The transactions are defined declaratively in Spring configurationfiles and not in your Java code. In most cases, declarative transactions are preferred to usertransactions since they are less invasive. Declarative Transaction Demarcation uses AOP and aSpring transaction interceptor.

Example TransactionInterceptor configuration for the NodeService taken from public-services-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. Bydefault, it will only trigger a rollback if an unchecked exception is thrown, or if the transaction hasbeen marked rollback-only by the application. Specific rollback policies can be configured permethod call and per exception.

Transaction Attributes

The transaction attributes are defined on the TransactionInterceptor as Properties. Thetransaction attributes are defined on a per method basis. For each property, the method name isthe 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 startingwith a specific keyword.

Page 65: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 65

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=falseserver.transaction.mode.default=PROPAGATION_REQUIREDserver.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 andisolation 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 transactionmanager will apply a default timeout specific to the particular transaction manager. A “+” beforean exception name substring indicates that transactions should commit even if this exception isthrown; a “-” that they should roll back.

Propagation and isolation codes used in Alfresco:

PROPAGATION_REQUIREDSupport a current transaction; create a new one if none exists.

PROPAGATION_REQUIRES_NEWCreate a new transaction, suspending the current transaction if one exists.

PROPAGATION_NOT_SUPPORTEDDo not support a current transaction; rather always execute non-transactionally.

ISOLATION_DEFAULTUse the default isolation level of the underlying datastore.

TIMEOUT_DEFAULTUse the default timeout of the underlying transaction system, or none if timeouts are notsupported.

Transaction Manager

The TransactionInterceptor delegates the actual transaction handling to aPlatformTransactionManager instance, defined by the transactionManager property. In thecase 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 servicemethod is wrapped in its own transaction. These transactions are defined declaratively in

Page 66: 2140 api developer-student-guide

Developing against the Alfresco Repository

66 Alfresco Enterprise Edition Version 3.2

Spring configuration files and not in your Java code. In most cases, declarative transactions arepreferred to user transactions since they are less invasive. There are situations, however, whenuser transactions do need to be used explicitly in your code.

An Alfresco user transaction is accessed via the TransactionService. An example using theServiceRegistry 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. Withoutthe user transaction, the default behaviour would be for each NodeService call to be in its owntransaction.

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 servicescan be pulled into the same transaction. It is important to note that a UserTransaction cannot bere-used. That is, once a commit or rollback has been issued, a new UserTransaction has to beretrieved (via getUserTransaction()) to begin another.

TransactionService

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

Page 67: 2140 api developer-student-guide

Developing against the Alfresco Repository

API Development Course 67

For clarity, not all of the available methods are shown. For a complete description, pleaseconsult 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 thatsupports 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 thatensures a new transaction is created. Any enclosing transaction is not propagated. When thetransaction 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 aUserTransaction. If the unit of work fails due to an optimistic locking failure, or a deadlock loserfailure, it will transparently retry the unit of work until it succeeds, or until a maximum number ofretries have been attempted.

The exceptions that trigger retries are:

• ConcurrencyFailureException

• DeadlockLoserDataAccessException

• StaleObjectStateException

• LockAcquisitionException

• BatchUpdateException

The RetryingTransactionHelper is retrieved from the TransactionService via thegetRetryingTransactionHelper() method.

Page 68: 2140 api developer-student-guide

Developing against the Alfresco Repository

68 Alfresco Enterprise Edition Version 3.2

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 optionalboolean readOnly argument can be used to request a read only transaction. The optionalboolean requiresNew argument can be used to force a new transaction (will retrieve a usertransaction 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);

Page 69: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 69

Extending the Alfresco Repository

Page 70: 2140 api developer-student-guide

Extending the Alfresco Repository

70 Alfresco Enterprise Edition Version 3.2

Repository Policies

Introduction

Repository policies are similar to events. Each service defines its own set of policies. Forexample 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 calledautomatically when the policy is fired. This enables tasks such as maintaining the last modifieddate on a node or creating a new version and incrementing the version number on a node to beautomated.

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 morethan twenty policies, including BeforeCreateNodePolicy, OnUpdatePropertiesPolicy andOnAddAspectPolicy, the other services each define their own specific policies. The policies areusually defined on an interface named after the service. For example, the Node Service policiesare defined on the NodeServicePolicies interface and the Content Service policies are definedon 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 nodeis created. The beforeCreateNode() method receives the NodeRef of the parent of thenode being created, the QName of the association type (usually cm:contains), the QName ofthe 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.

Page 71: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 71

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 eventsrelated to properties and Association policies for events related to associations. Most policiesextend the Class policy interface, a few policies extend the Association policy interface and, atthe 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 theIntroduction on page 34.

In the tutorial we are going to define a new Content Hits aspect with behaviour that willautomatically keep a running count of the number of times the content on a node has beenupdated and read. The example demonstrates how the counters can be incremented after thecontent 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>

Page 72: 2140 api developer-student-guide

Extending the Alfresco Repository

72 Alfresco Enterprise Edition Version 3.2

<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. Thech:updateCount and ch:readCount properties will be incremented for each contentupdate or read respectively.

2. Implementing the aspect behaviour class

This ContentHitsAspect class contains the behaviour behind the ch:contentHitsaspect.

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 afterthe 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 logicmay be expressed in either Java or JavaScript.

Page 73: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 73

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

instancethe object instance holding the method

methodthe method name

frequencyone 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 onFoundation Services that can be resolved using Spring dependency injection.

A behaviour is bound to a policy using the bindClassBehaviour() method on thePolicy Component:bindClassBehaviour(QName policy, QName classRef, Behaviour behaviour);

policythe policy name

classRefQName of type or aspect concerned by the policy

behavioura Behaviour object (instance of JavaBehaviour or ScriptBehaviour)

A behaviour is bound to a specific content type or aspect using the classRefargument.

The Policy Component is also used by services to register policies and to invokepolicy behaviours.

Page 74: 2140 api developer-student-guide

Extending the Alfresco Repository

74 Alfresco Enterprise Edition Version 3.2

In the Content Hits example, the Spring initialise() method is used to bind thebehaviours 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 aspectis added to a node. It receives the NodeRef of the node concerned and the QName ofthe added aspect. In the example, the added aspect will always be ch:contentHitssince the behaviour has only been registered for that specific aspect.

The onAddAspect() behaviour sets the count start date/time when thech: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 anode with the ch:contentHits aspect is read. It receives the NodeRef of the nodebeing read.

As we have seen, the example uses a Transaction Listener to increment the countersafter the content update or read transactions have been committed. The TransactionListener is bound to the current transaction using the static bindListener() methodon the AlfrescoTransactionSupport class and passing the Transaction Listenercreated earlier.

The list of nodes read is stored as a resource against the current transaction usingthe static bindResource() method on the AlfrescoTransactionSupport class. Thenodes 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 listof nodes that require read count increments after the transaction completes:public void onContentRead(NodeRef nodeRef){ // Bind the listener to the transaction AlfrescoTransactionSupport.bindListener(transactionListener);

Page 75: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 75

// 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 anode with the ch:contentHits aspect is updates. It receives the NodeRef of the nodebeing 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 usingthe the KEY_CONTENT_HITS_WRITES key.

The onContentUpdate() behaviour adds the NodeRef of the node being updated tothe 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 theonContentRead() 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 implementsthe afterCommit() method.private class ContentHitsTransactionListener extends TransactionListenerAdapter{ public void afterCommit() { ... }}

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

Page 76: 2140 api developer-student-guide

Extending the Alfresco Repository

76 Alfresco Enterprise Edition Version 3.2

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 thetransaction 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 thelist.

h. Writing the Spring dependency injection setter methods

The ContentHitsAspect class uses several services that can be injected via Spring:private PolicyComponent policyComponent;private BehaviourFilter policyFilter;private NodeService nodeService;private TransactionService transactionService;private 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

Page 77: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 77

The Content Hits model is registered with the following Spring bean definition taken fromcontents-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 beandefinition 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 theproperties set.

5. Configuring the property sheet

In order to see the Content Hits aspect properties in the Web Client, the property sheethas 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 followingfiles 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);

Page 78: 2140 api developer-student-guide

Extending the Alfresco Repository

78 Alfresco Enterprise Edition Version 3.2

• the Web Client custom configuration file containing the aspect property sheetdefinition (web-client-config-custom.xml).

The compiled class needs to be exported to a JAR file. The files then need to be deployedto 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 deployedas an Alfresco Module Package (AMP). For more details, see Introduction on page 143.

Page 79: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 79

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 actionsalready 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.ActionExecuterinterface. The ActionExecuter interface can be implemented directly, however it is best toextend the abstract class ActionExecuterAbstractBase that has been written to providebasic services for action executer implementations. Only two methods need implementing whenderiving from the abstract superclass. ActionExecuterAbstractBase is presented in detail inthe Repository Action Howto on page 79.

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

• Interface ActionExecuter

• Class ActionExecuterAbstractBase

Repository Action Howto

Before starting the tutorial, you should be familiar with Alfresco Content Models and theIntroduction on page 34.

This is the first part of a two part tutorial. The first part shows you how to create a no-parameteraction and incorporate it into the web client. The second part (Repository Action with ParametersHowto 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 webclient. We will define a taggable aspect which will be applied via the custom tag action in therepository. We will then configure the web client to show the taggable aspect in its propertysheet. 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 modeluses a prefix of tag, the aspect is called tag:taggable and the property is calledtag:tags.

Page 80: 2140 api developer-student-guide

Extending the Alfresco Repository

80 Alfresco Enterprise Edition Version 3.2

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 calledActionExecuterAbstractBase. The abstract superclass provides basic servicesfor ActionExecuter implementations. It also introduces a new abstract methodexecuteImpl() that is called from execute() to do the actual work. The only twomethods that require implementing when deriving from the abstract superclass areaddParameterDefinitions() and executeImpl().

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

b. Implementing the addParameterDefinitions() method

Page 81: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 81

In the first part of the tutorial, the action does not take any parameters. The actionexecuter, 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 andthe NodeRef of the node to do the work on (actionedUponNodeRef). Action executersgenerally check that the actionedUponNode exists using the NodeService.exists()method.

In the example, executeImpl() adds the taggable aspect to theactionedUponNodeRef 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 injectthe 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 superclassActionExecuterAbstractBase looks for messages in the I18N resource bundles using anid 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 theaction executer class.

In the example, the I18N messages are defined in the tag-action-messages.properties file.# Action title and description

Page 82: 2140 api developer-student-guide

Extending the Alfresco Repository

82 Alfresco Enterprise Edition Version 3.2

tag.title=Add tags to itemtag.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.xmlSpring 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 automaticallyby Spring once the action executer has been instantiated. The init() method isimplemented by the ActionExecuterAbstractBase class. If the action is public, it willregister 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. TheresourceBundles property contains the list of message bundles to register and the classattribute must be defined as org.alfresco.i18n.ResourceBundleBootstrapComponent.

In the example, the tag-action-messages.properties file is registered as an I18Nresource 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 mustdefine the dictionaryModelBootstrap bean as its parent bean and that it dependson the dictionaryBootstrap bean. The models property contains the list of models toregister.

In the example, the tagsModel.xml file is registered as a dictionary model in thetagging-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>

Page 83: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 83

</property></bean>

7. Configuring the property sheet

In order to view and modify the tags property in the Web Client, the property sheet needsto be configured for the taggable aspect.

The following example, taken from web-client-config-custom.xml, will display thetags 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 bepackaged:

• 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 executerand the message bundle (tagging-context.xml);

• if required, the custom dictionary model (tagsModel.xml);

• if required, the Web Client custom configuration file (web-client-config-custom.xml).

The compiled class and message bundle can be exported to the same JAR file. The filesthen 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 deployedas 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:

Page 84: 2140 api developer-student-guide

Extending the Alfresco Repository

84 Alfresco Enterprise Edition Version 3.2

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

Page 85: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 85

Repository Action with Parameters Howto

This is the second 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 of the tutorial will now show you how to add a parameter to the repository tagaction and how to configure the web client to add a custom JSP that will prompt the user for thedefault 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. Theycan be used in the executeImpl() method.

a. Modifying the addParameterDefinitions() method

The addParameterDefinitions() method is passed a List ofParameterDefinition objects. To register a new parameter, simply create a newParameterDefinition 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 ismandatory: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” parametervalue and set the tags property on the node being actioned upon. Parameter valuesare retrieved from the Action object using the getParameterValue() method.

In the example, executeImpl() retrieves the value of the PARAM_TAGS parameterfrom the Action object, converts it to a List and then uses the list to set the tags

Page 86: 2140 api developer-student-guide

Extending the Alfresco Repository

86 Alfresco Enterprise Edition Version 3.2

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

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 amessage 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 attributeon the action executer class and <param-name> is the parameter value passed to thegetParamDisplayLabel() method.

In the example, the I18N display label for the param_tags parameter has been added tothe tag-action-messages.properties file:# Action title and descriptiontag.title=Add tags to itemtag.description=This action adds tags to the matched item

# Action parameter display labelstag.param_tags.display-label=Tags

3. Creating the action JSP

Although the action wizards use the Wizard Framework, the JSPs that collect parametersfor actions and conditions are complete JSPs as they are not displayed within the wizardcontainer. This means you have to have the whole page structure in your action JSP. Theeasiest 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 needto replace the drop down list with a text field and change a few labels such as the pagetitle. 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"/>

Page 87: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 87

<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, CreateRule and Edit Rule Wizards, an action handler is required.

a. Creating the TagActionHandler class

An action handler typically extends an abstract BaseActionHandler class andis responsible for directing the wizard to the page to collect the parameters andmarshalling the parameters between the wizard and the repository.

If the page collecting the parameters requires some default setup thesetupUIDefaults() 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

Page 88: 2140 api developer-student-guide

Extending the Alfresco Repository

88 Alfresco Enterprise Edition Version 3.2

The prepareForSave() method places the tags the user entered into the repositoryproperties 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 takesthe 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 theaction, 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 messageid. Custom messages are defined in the webclient.properties file in the extensiondirectory.

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

Page 89: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 89

7. Packaging and deploying

In order to deploy a custom action (with parameters) to the Alfresco repository, thefollowing 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 executerand 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 filesthen 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 deployedas 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 definedefault tag values:

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

Page 90: 2140 api developer-student-guide

Extending the Alfresco Repository

90 Alfresco Enterprise Edition Version 3.2

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

Page 91: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 91

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 contenttransformer in a few easy steps.

A content transformer has to implement theorg.alfresco.repo.content.transform.ContentTransformer interface.ContentTransformer extends the org.alfresco.repo.content.ContentWorker interface thatis a common marker interface for specific “worker” interfaces such as content transformers andmetadata extractors.

An abstract base class called AbstractContentTransformer has been written to provides basicservices for ContentTransformer implementations. Only two methods need implementing whenderiving from the abstract superclass. AbstractContentTransformer is presented in detail inthe tutorial Content Transformer Howto on page 91.

For clarity, not all of the available methods are shown. For a complete description, pleaseconsult 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 calledAbstractContentTransformer. The abstract class provides basic services forContentTransformer implementations. It also introduces a new abstract methodtransformInternal() that is called from transform() to do the actual transformationwork. The only two methods that require implementing when deriving from the abstractsuperclass are getReliability() and transformInternal().

a. Creating the PdfBoxContentTransformer class

Page 92: 2140 api developer-student-guide

Extending the Alfresco Repository

92 Alfresco Enterprise Edition Version 3.2

b. Implementing the getReliability() method

The getReliability() method provides the approximate accuracy with which thetransformer can transform from one mime type to another. It is used to determinewhich of a set of transformers will be used to perform a specific transformation. Themethod 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 checksthat the transformation is allowed by calling the getReliability() method on thesubclass. If the subclass method returns 0.0, an AlfrescoRuntimeException isthrown.

The example below is taken from PdfBoxContentTransformer. ThegetReliability() 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

Page 93: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 93

The transformInternal() method does the actual transformation work. Thereader (ContentReader) and writer (ContentWriter) objects, passed as arguments,allow direct access to the source and target content. The source is available as anInputStream via the getContentInputStream() method on the reader. The targetis available as an OutputStream via the getContentOutputStream() method on thewriter. Options may be passed as a Map, however they are transformer dependent andmay be null.

Both reader and writer will be closed after the transformation completes, howeverwhen accessing the content via a stream, failure to close the stream, regardless ofsuccess of failure, will result in an error message being generated to the log outputand the stream will be held open indefinitely.

If it is necessary to work against physical files during the transformation, use theTempFileProvider 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, eitherruntime or otherwise, the AbstractContentTransformer superclass will handle andreport these as required.

AbstractContentTransformer also calculates and maintains the average time takento perform a transformation. If a transformation fails, the average time is set to 10seconds to penalise a transformer that has failed.

The example below is taken from PdfBoxContentTransformer. The sourceInputStream and PDDocument are closed once the transformation is complete. Themethod 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(); } } }}

Page 94: 2140 api developer-student-guide

Extending the Alfresco Repository

94 Alfresco Enterprise Edition Version 3.2

2. Defining the content transformer Spring bean

A content transformer is registered as a bean in a Spring configuration file. Byconvention, the bean id has a “transformer.” prefix. The bean must definebaseContentTransformer 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 transformercan perform regardless of what it returns via the getReliability() check. Theexplicit transformations are defined on the explicitTransformations property asa list of ContentTransformerRegistry.TransformationKey objects. The sourceand target mime types for the explicit transformation are passed as arguments to theTransformationKey constructor.

The baseContentTransformer parent bean is defined in content-service-context.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 parentbean and is called automatically by Spring once the content transformer has beeninstantiated. It registers the content transformer with the ContentTransformerRegistryfor each explicit transformation provided.

Page 95: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 95

3. Packaging and deploying

In order to deploy a custom content transformer to the Alfresco repository, the followingfiles need to be packaged:

• the compiled custom content transformer class;

• the Spring configuration file containing the bean definition for the custom contenttransformer.

The compiled class needs to be exported to a JAR file. The files then need to be deployedto 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/extensiondirectory.

For deployment in a production environment, the files should be packaged and deployedas an Alfresco Module Package (AMP). For more details, see Introduction on page 143.

ContentTransformerRegistry

The ContentTransformerRegistry holds a list of available content transformers andprovides the most appropriate content transformer for a particular source and target mime typetransformation request.

Upon initialisation, content transformers register themselves with theContentTransformerRegistry via the addTransformer() method. Content transformersalso register themselves for each explicit transformation they can perform (as defined in theirSpring bean definition) via the addExplicitTransformer() method. An explicit transformation isdefined as an instance of the ContentTransformerRegistry.TransformationKey inner class.

Page 96: 2140 api developer-student-guide

Extending the Alfresco Repository

96 Alfresco Enterprise Edition Version 3.2

For clarity, not all of the available methods are shown. For a complete description, pleaseconsult the Javadocs: Class ContentTransformerRegistryr

The getTransformer() method is used by clients to get the best transformer available for agiven transformation. If two transformers perform the same transformation, the most reliable onewill always be chosen. If two or more transformers exist with the same reliability, then they willbe cycled until the fastest one is determined. The timing code is automatically provided by theAbstractContentTransformer superclass.

The ContentTransformerRegistry bean is defined in the content-service-context.xmlSpring configuration file:<!-- Content Transformation Regisitry --><bean id="contentTransformerRegistry" class="org.alfresco.repo.content.transform.ContentTransformerRegistry" />

Page 97: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 97

Further Reading

Working with Temporary Files

If it is necessary to work against physical files during the transformation, use theorg.alfresco.util.TempFileProvider#createTempFile to ensure that all temporary files willbe cleaned up appropriately. Failure to do this will mean that temporary files are not cleaned upwhile the system is running. Do not use deleteOnExit - the Alfresco repository is designed torun under load indefinitely, i.e. until the next upgrade. Give your temp files meaningful prefixes asit will help during debugging.

Example taken fromorg.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 toFile tempFromFile = TempFileProvider.createTempFile( "OpenOfficeContentTransformer-source-", "." + sourceExtension);File tempToFile = TempFileProvider.createTempFile( "OpenOfficeContentTransformer-target-", "." + targetExtension);

Page 98: 2140 api developer-student-guide

Extending the Alfresco Repository

98 Alfresco Enterprise Edition Version 3.2

Metadata Extractors

Introduction

A metadata extractor is a Java class that can extract metadata from content of a particular mimetype. Many metadata extractors already exist, however it is possible to add you own custommetadata extractor in a few easy steps.

A metadata extractor has to implement theorg.alfresco.repo.content.metadata.MetadataExtracter interface. MetadataExtracterextends the org.alfresco.repo.content.ContentWorker interface that is a common markerinterface for specific “worker” interfaces such as metadata extractors and content transformers.

An abstract class called AbstractMappingMetadataExtracter has been written to providesbasic services for MetadataExtracter implementations. Only one method needs implementingwhen deriving from the abstract superclass. AbstractMappingMetadataExtracter is presentedin detail in the tutorial Metadata Extractor Howto on page 99.

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

• Interface MetadataExtracter

• Class AbstractMappingMetadataExtracter

MetadataExtracterRegistry

The MetadataExtracterRegistry holds a list of available metadata extractors and provides themost appropriate extractor for a particular mime type extraction request.

Upon initialisation, metadata extractors register themselves with theMetadataExtracterRegistry via the register() method.

Page 99: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 99

For clarity, not all of the available methods are shown. For a complete description, pleaseconsult the Javadocs: Class MetadataExtracterRegistry

The getExtracter() method is used by clients to get the most appropriate metadata extractorfor a particular mime type.

The MetadataExtracterRegistry bean is defined in the content-service-context.xmlSpring 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 calledAbstractMappingMetadataExtracter. The abstract superclass provides basic servicesfor MetadataExtracter implementations. The only method that requires implementingwhen deriving from the abstract superclass is extractRaw().

a. Creating the PdfBoxMetadataExtracter class

Page 100: 2140 api developer-student-guide

Extending the Alfresco Repository

100 Alfresco Enterprise Edition Version 3.2

b. Writing the constructor

A set of supported mime types has to be passed to the abstract superclass, eitheras an argument to the constructor or via a call to the setSupportedMimetypes()method.

Before calling extractRaw(), the abstract superclass checks that the extractionis allowed for the content mime type by calling the isSupported() method. Themime type has to be one of the supported mime types. Subclasses can overrideisSupported() 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

Page 101: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 101

the source content. The source is available as an InputStream via thegetContentInputStream() method on the reader.

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

Extracted metadata values are added to the rawProperties map using theputRawValue() method on the abstract superclass. Keys are the same as thoseused 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 Stringor 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 noneed to handle any exceptions generated during the extraction, either runtime orotherwise, the abstract superclass will handle and report these as required.

The reader will be closed after the extraction completes, however when accessing thecontent via a stream, failure to close the stream, regardless of success of failure, willresult in an error message being generated to the log output and the stream will beheld open indefinitely.

Example from PdfBoxMetadataExtracter. The newRawMap() and putRawValue()methods on the abstract superclass are used to initialise and populate the result Mapwith the extracted metadata. The source InputStream and PDDocument are closedonce 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); }

Page 102: 2140 api developer-student-guide

Extending the Alfresco Repository

102 Alfresco Enterprise Edition Version 3.2

} } 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 amapping. The default mapping is supplied as a properties file.

Namespaces used in the mapping have to be declared. The property keys are the same asthose used in the raw properties map returned by the extractRaw() method. The propertyvalue is a valid node property with a shorthand namespace prefix. A single raw propertycan 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 samelocation as the metadata extractor class. For example, the PdfBoxMetadataExtracterdefault mapping properties file is called PdfBoxMetadataExtracter.properties and islocated 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

# Namespacesnamespace.prefix.cm=http://www.alfresco.org/model/content/1.0

# Mappingsauthor=cm:authortitle=cm:titlesubject=cm:descriptioncreated=cm:created

3. Registering the metadata extractor

A metadata extractor is registered as a bean in a Spring configuration file. Byconvention, the bean id has an “extracter.” prefix. The bean must also definebaseMetadataExtracter as it's parent bean.

The baseMetadataExtracter bean is defined in the content-service-context.xmlSpring 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">

Page 103: 2140 api developer-student-guide

Extending the Alfresco Repository

API Development Course 103

<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 calledautomatically by Spring once the metadata extractor has been instantiated. It registers themetadata 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 followingfiles 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 metadataextractor.

The compiled class and properties file can be exported to the same JAR file. The files thenneed 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/extensiondirectory.

For deployment in a production environment, the files should be packaged and deployedas 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 amapping. The default mapping is supplied as a properties file in a format similar to the following:

# Namespacesnamespace.prefix.cm=http://www.alfresco.org/model/content/1.0namespace.prefix.my=http://www.mycompany.com/model/mymodel/1.0

# Mappingseditor=cm:author, my:editortitle=cm:titlesummary=cm:summarysubject=cm:description

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

Page 104: 2140 api developer-student-guide

Extending the Alfresco Repository

104 Alfresco Enterprise Edition Version 3.2

The raw property values are converted to the corresponding node property types using theorg.alfresco.service.cmr.repository.datatype.DefaultTypeConverter defaultconverter.

The default mapping is returned by the getDefaultMapping() method. The defaultimplementation looks for a properties file with the same name and in the same location asthe metadata extractor class. For example, the PdfBoxMetadataExtracter default mappingproperties file is called PdfBoxMetadataExtracter.properties and is located in theorg.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() methodin the subclass. If the default mapping is defined in a properties file other than the one namedafter the class, then the readMappingProperties(String propertiesUrl) method can beused 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 themapping is supplied as a Properties object. By default, the supplied mapping will replace thedefault mapping returned by getDefaultMapping(). If the supplied mapping should augment thedefault mapping, the setInheritDefaultMapping(boolean inheritDefaultMapping) methodshould first be called with a value of true. Finally, the init() method needs to be called to re-initialise the extractor.

Overwrite Policies

The overwrite policy determines whether extracted values are written to the destination propertymap or not. Three overwrite policies have been defined:

EAGER

If the extracted value is not null, always write the extracted value to the destination propertymap.

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 onthe abstract superclass to change the overwrite policy. The overwrite policy is specified as eitheran OverwritePolicy or as a String.

Page 105: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 105

Extending the Alfresco Web Client

Page 106: 2140 api developer-student-guide

Extending the Alfresco Web Client

106 Alfresco Enterprise Edition Version 3.2

JavaServer Faces

Introduction

JavaServer Faces (JSF) is a user interface framework for Java web applications. JSF defines anevent-driven, component-based model for web application development, similar to the model thathas been used successfully for standalone GUI applications for years. JSF’s core architecture isdesigned to be independent of specific protocols and markup. However it is also aimed directly atsolving many of the common problems encountered when writing applications for HTML clientsthat communicate via HTTP to a Java application server that supports servlets and JavaServerPages (JSP) based applications.

JSF is a specification (JSR-252) that has been developed as part of the Java CommunityProcess. 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 extendingthe 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 theirrepresentation in the user interface. A good example is a date;

• validators can be associated with a JSF component to perform input validation checks onlocal values before they are processed;

• event listeners are registered against events that are triggered when a user clicks a buttonor a link, changes a value in a field, or makes a selection in a list. The outcome of theevent processing controls which page is displayed next;

• renderers generate the actual markup for the user interface. JSF is not limited to HTML orany other markup language and the same JSF component can be coupled with differentrenderers 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 Languagedrop-down list and the Login button are all implemented using JSF components on thelogin JSP page. The text labels themselves are also implemented using JSF components.

Page 107: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 107

When used with JSP as a presentation layer, the JSF components are represented by JSPcustom 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}" />

Page 108: 2140 api developer-student-guide

Extending the Alfresco Web Client

108 Alfresco Enterprise Edition Version 3.2

2. JSF tag librairies

The JSP custom actions (or custom tags) representing the JSF components are defined inone or more custom JSF tag libraries. The tag libraries are declared on a JSP page usinga taglib directive. Each taglib directive contains two attributes: the uri attribute definesthe 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 asHTML elements.

uri="http://java.sun.com/jsf/core" prefix="f"Core tag library containing tags that represent JSF artifacts that are independent of thepage 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 beused in non Alfresco projects.

uri="/WEB-INF/repo.tld" prefix="r"Alfresco Repository tag library containing custom tags that represent JSF componentsthat 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> tagrenders 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. Thecomponents on a page form a tree and the <f:view> custom tag represents thecomponent at the root of the tree. The <f:view> tag must contain all other JSF tags on thepage.

Page 109: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 109

The <h:form> custom tag represents an HTML form component. It is a container for inputcomponents that hold values that should be processed together. All JSF input componentsmust 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> customtag.

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 propertiesdefined 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 bundlewebclient.properties from the alfresco/messages directory on the classpath.

All of the Web Client I18N strings are stored in the webclient.properties defaultmessage bundle. Message translations can be provided in separate files, namedwith 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 thewebclient_fr_FR.properties file and the French Canadian translations are found in thewebclient_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 detailsusername=User Namepassword=Passwordlanguage=Languagelogin=Login

The same login page messages from the French webclient_fr_FR.properties bundle:login_details=Entrez les informations de connexionusername=Nom d'utilisateurpassword=Mot de passelanguage=Languelogin=Connexion

The login page in the French fr_FR locale:

Page 110: 2140 api developer-student-guide

Extending the Alfresco Web Client

110 Alfresco Enterprise Edition Version 3.2

Custom message bundles can be loaded using the <f:loadBundle> custom tag.

In the following example, a custom webclient.properties message bundle is loadedfrom 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.

Page 111: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 111

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. Theitems 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 JavaBeanmanaged 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 retrievevalues 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 areretrieved 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();

Page 112: 2140 api developer-student-guide

Extending the Alfresco Web Client

112 Alfresco Enterprise Edition Version 3.2

} ... } 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 thecurrent 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 representrequest specific information as well as provide access to services for the application.

The static FacesContext.getCurrentInstance() method is used to obtain the currentFacesContext 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 bindingexpressions.

Both components have an optional validator associated with them to perform inputvalidation 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 theuser name:public void validateUsername(FacesContext context, UIComponent component, Object value)

Page 113: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 113

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 bindactions to methods on managed beans that are automatically called by the JSF frameworkwhen 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 NavigationHandler 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-config-navigation.xml file:<navigation-rule>

Page 114: 2140 api developer-student-guide

Extending the Alfresco Web Client

114 Alfresco Enterprise Edition Version 3.2

<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 customisenavigation handling. A custom navigation handler is defined using a <navigation-handler/> element in a <faces-config/> file.

In Alfresco, the custom navigation handler is defined in the WEB-INF/faces-config-app.xml file:<application> <navigation-handler> org.alfresco.web.app.AlfrescoNavigationHandler </navigation-handler> ...</application>

The custom AlfrescoNavigationHandler is used by the Dialog and Wizard frameworksto 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 <faces-config/> file.

In Alfresco, the Web Client managed beans are defined in the WEB-INF/faces-config-beans.xml file:<managed-bean> <description>

Page 115: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 115

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. Themanaged beans are then stored in the scope defined by the value of the <managed-bean-scope/> element. The managed bean scope takes one of the following values:

noneDoes not store the managed bean in any scope.

requestThe managed bean is stored and kept for a single request.

sessionThe managed bean is stored and kept for a single user session.

applicationThe managed bean is stored and shared between all users.

The properties defined by <managed-property/> elements are automatically set onthe managed beans once they have been instantiated via “setter” methods. The #{...}property values are resolved by one or more variable resolvers. Custom variable resolverscan extend the standard JSF VariableResolver.

In Alfresco, a custom variable resolver is defined in the WEB-INF/faces-config-app.xmlfile:<application> ... <variable-resolver> org.alfresco.web.app.AlfrescoVariableResolver </variable-resolver> ...</application>

The custom AlfrescoVariableResolver delegates to the SpringDelegatingVariableResolver 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 publicbean.

Page 116: 2140 api developer-student-guide

Extending the Alfresco Web Client

116 Alfresco Enterprise Edition Version 3.2

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 aregrouped into action groups. Individual actions can be reused between groups and the actiongroups reused across pages. Action Groups define an ordered list of actions that are displayedtogether, either as a serial list (for example, as a strip of icons) or grouped together in a drop-down 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 groupsare defined in the web-client-config-actions.xml configuration file. You can define your owncustom UI actions and action groups in a Web Client configuration extension file. You can alsoextend existing action groups to add your own custom UI actions to existing menus in the WebClient.

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. Themandatory id attribute defines the unique ID by which the action is referenced for use inaction groups. The same action can be referenced and reused by any number of actiongroups.

An action group is defined by an <action-group/> element. The mandatory id attributeis 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/> elementswith either an idref attribute, to reference an existing action definition, or an id attribute,

Page 117: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 117

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-client-config-custom.xml extension file. Defining an action or an action group with the same IDas an existing one effectively overrides the original definitions. In this way, action groupsmay be overridden to add custom actions to existing action groups or to redefine displayattributes for a consistent “look and feel”. Existing actions can also be hidden in an actiongroup by using the hide attribute in conjunction with idref.

Below is the Update action definition from the web-client-config-actions.xmlconfiguration 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 anddoc_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 downmenu on documents on the main browse page:

Page 118: 2140 api developer-student-guide

Extending the Alfresco Web Client

118 Alfresco Enterprise Edition Version 3.2

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 configuredby node type. It is thus possible to add new actions or hide actions for a specific folder ordocument type.

The following action group configuration hides the Cut and Copy actions from WCMWebProject 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 theorg.alfresco.web.action.ActionEvaluator interface that is used to determine if theaction should be displayed or not for the given Node. The evaluate(Node) method iscalled on the ActionEvaluator for each Node. The action will only be displayed if themethod returns a value of true.

The update_doc action will only be displayed on a Node if theUpdateDocEvaluator.evaluate(Node) method returns true:<!-- Update document --><action id="update_doc"> ... <evaluator> org.alfresco.web.action.evaluator.UpdateDocEvaluator </evaluator> ...</action>

Page 119: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 119

The UpdateDocEvaluator.evaluate(Node) method returns true if the node is a sub-type of cm:content and the node is a Working Copy owned by the current user or thenode 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 calledwhen a user selects the action. The action listener method is passed an ActionEventobject 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, anactionContext object is a Node object, however it may not always be the case. Thecontext 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 theCheckinCheckoutBean when a user selects the action:<!-- Update document --><action id="update_doc"> ... <action-listener>#{CheckinCheckoutBean.setupContentAction}</action-listener> ... <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 ofparameters:

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

Page 120: 2140 api developer-student-guide

Extending the Alfresco Web Client

120 Alfresco Enterprise Edition Version 3.2

The <action/> element defines a JSF navigation outcome to use when a user selects theaction.

The dialog:updateFile outcome will be interpreted by the Alfresco Navigation Handlerto 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. Thisvalue attribute is used to reference an action group by ID. If the showLink attribute is setto 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 thedocument_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 definesthe unique ID by which the action is referenced for use in action group elements (see <action-group/> element on page 121). Defining another action with the same ID as an existing actioneffectively overrides the original action definition. All of the following elements must be containedwithin the <action/> element.

permissionsContains 1 or more <permission/> elements.

permissionContained within <permissions/> element. Defines the permission values that must bechecked against the current Node action context to allow the action to be displayed tothe current user. The allow attribute takes a value true or false to define whether thepermission check is ALLOW or DENY.

evaluatorThe name of a custom class implementing theorg.alfresco.web.action.ActionEvaluator interface. The action will only be displayed ifthe ActionEvaluator.evaluate() method returns true.

Page 121: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 121

label-idThe id of an I18N string to be used as the label for the action.

labelA literal string to be used in place of label-id if you do not want an I18N string.

tooltip-idThe id of an I18N string to be used as the tooltip for the action.

tooltipA literal string to be used in place of tooltip-id if you do not want an I18N string.

show-linkA presentation attribute. Valid values are true or false. If set to true, the action will bedisplayed as an icon and a textual link, if set to false, the action will only be displayed as anicon with the label as the tooltip for the icon image.

styleA presentation attribute. The CSS style to apply to the action.

style-classA presentation attribute. The CSS class to apply to the action.

imageThe icon image to display.

action-listenerThe JSF action listener method to call upon user selection of the action.

actionThe JSF action navigation outcome to execute upon user selection of the action.

scriptThe Alfresco JavaScript file to execute upon user selection of the action. The JavaScript file isspecified by either Path or Node Reference.

hrefThe href to navigate to upon user selection of the action.

targetThe associated href target.

onclickThe JavaScript onclick handler to execute upon user selection of the action.

paramsContains 1 or more <param/> elements.

paramContained within the <params/> element. Defines the JSF <f:param/> custom tags to begenerated as children of the action component. Each <param/> element has a mandatoryname attribute as the name of the parameter and the mandatory value of the element containsthe value to passed as the parameter. Parameters are available from the UIActionLinkcomponent 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 isused to identify the action group in a JSF <r:actions/> custom tag. Defining another actiongroup with the same ID as an existing action group effectively overrides the original action groupdefinition. All other elements must be contained within the <action-group/> element.

Page 122: 2140 api developer-student-guide

Extending the Alfresco Web Client

122 Alfresco Enterprise Edition Version 3.2

actionReferences or defines an action definition for the group. The order of the attributes definesthe order in which the actions are displayed. Action elements may be defined using an idrefattribute to reference an existing action definition or “inline” to define an action specific to theaction group. Existing actions can also be hidden by using the hide attribute in conjunctionwith idref.

show-linkOverrides any similarly named attribute set on individual action definitions.

styleOverrides any similarly named attribute set on individual action definitions.

style-classOverrides any similarly named attribute set on individual action definitions.

Page 123: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 123

Dialog Framework

Introduction

The dialog framework manages the user interface dialogs in the Alfresco Web Client. Most of thestandard Web Client dialog are managed by the framework and it is possible to create your owncustom 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 thedialog.

• 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, theAlfresco Navigation Handler looks up the <dialog/> configuration then calls the Dialog Managerto initialise the dialog and instantiate the managed bean before navigating to the dialog JSPpage.

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 alsowish 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 actionsomething to call. A dialog will be defined which will allow the user to select an aspect to add tothe 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 orcondition) of the web-client-config-dialogs.xml file. These may be overridden using amore specific <config/> section, for example, a dialog definition could be overridden for aparticular node type.

All new dialogs or customisations to default dialogs should be defined in the standardweb-client-config-custom.xml extension file. Combining is supported for dialogconfiguration, 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>

nameDefines the unique name (or id) of the dialog. The above dialog can be referenced in anaction definition as dialog:addAspect.

pageDefines the path to the JSP page to be used for the dialog.

Page 124: 2140 api developer-student-guide

Extending the Alfresco Web Client

124 Alfresco Enterprise Edition Version 3.2

managed-beanDefines the name of the JSF managed bean to be used as the backing bean for thedialog.

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 nowbe 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 JSFcomponents to define the “body” of the dialog. It will be included into the dialog containerpage, defined by the <dialog-container/> element in the web-client-config-dialogs.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 ofaspects for the user to choose from.<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %><%@ taglib 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 displayis read from the webclient.properties file.

The <h:selectOneMenu/> element will display the drop-down list of aspects. The list ofaspects (<f:selectItems/>) is supplied by the getTestableAspects() method on theRunActionWizard 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 selectedaspect. The expression DialogManager.bean refers to the JSF managed bean beingused by the current dialog and configured using the managed-bean attribute in the dialog'sconfiguration. Because an explicit bean is not being referenced, the same JSP page couldbe re-used for multiple dialogs, each dialog defining it's own specific bean implementation.

Page 125: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 125

The JSF “value binding” expressions #{RunActionWizard.testableAspects}and #{DialogManager.bean.aspect} will actually callthe RunActionWizard.getTestableAspects() andDialogManager.getBean().setAspect() methods respectively.

4. Localising dialog messages

All of the Web Client I18N strings are stored in a default message bundle calledwebclient.properties located in the alfresco/messages directory. Messagetranslations can be provided in separate files, named with an extra suffix corresponding tothe standard ISO language and country codes. For example, the French translations of theWeb Client messages are found in the webclient_fr_FR.properties file and the FrenchCanadian translations are found in the webclient_fr_CA.properties file. The messagesin 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-usesan 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 mustimplement the IDialogBean interface. Generally, however, dialog beans just extendthe org.alfresco.web.bean.dialog.BaseDialogBean abstract base class.BaseDialogBean provides the default implementation for the IDialogBean interfacebut 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 thefinishImpl() 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 thedrop-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. Ifthe user returns to the dialog, the value of the aspect property will be read usingthe getAspect() method to initialise the drop-down list to the aspect that the userselected the last time.

protected String aspect;

public String getAspect(){ return aspect;}

public void setAspect(String aspect){ this.aspect = aspect;}

Page 126: 2140 api developer-student-guide

Extending the Alfresco Web Client

126 Alfresco Enterprise Edition Version 3.2

c. Overriding the getFinishButtonDisabled()method.

The AddAspectDialog bean overrides the getFinishButtonDisabled() methodreturning 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 aspectto 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 OKbutton, JSF will automatically call the setAspect() method on the AddAspectDialogbean passing the value of the aspect selected by the user. The setAspect() methodstores the selection in the this.aspect field. The finish() method on theBaseDialogBean will then be called which will in turn call the finishImpl() methodon the AddAspectDialog bean. The finishImpl() will do the actual work of thedialog 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 faces-config.xml file. The faces-config.xml file will be packaged in the META-INF directoryfor 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

Page 127: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 127

in the above example. The “setter” methods are implemented by the BaseDialogBeansuperclass.

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 bepackaged:

• the web-client-config-custom.xml file containing the dialog and actionconfiguration;

• the dialog JSP containing the HTML and JSF components to define the “body” ofthe 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 JARfile. The files then need to be deployed to the following directories in the Alfresco WebClient:

• 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 WEB-INF/classes/alfresco/extension directory.

For deployment in a production environment, the files should be packaged and deployedas 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 onSpaces.

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

Page 128: 2140 api developer-student-guide

Extending the Alfresco Web Client

128 Alfresco Enterprise Edition Version 3.2

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 defaultimplementation 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, thegetCancelButtonLabel() method returns Cancel, the getFinishButtonLabel() methodreturns OK and the getFinishButtonDisabled() method returns true which will remove thefinish (OK) button from the dialog.

The finish() method takes care of the transaction and error handling, it passes off theprocessing to the abstract finishImpl() method. After the transaction has been successfully

Page 129: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 129

committed the BaseDialogBean will also call a doPostCommitProcessing() method thatsubclasses can override.

Both the finishImpl() and doPostCommitProcessing() methods are passed the currentoutcome, and both methods are expected to return an outcome. Usually the methods will justreturn the given outcome but there may be some scenarios where the outcome needs to beoverridden. 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 ageneric error message, but subclasses can override this to provide a suitable message forthe type of exception. Finally, a getErrorOutcome() method is called to determine whatoutcome to return in the event of an error, by default, null is returned as that will re-displaythe page the error occurred on with the error message displayed, there are however, somescenarios where an outcome needs to be returned, if this is the case subclasses can override thegetErrorOutcome() method too.

The restored() method is a lifecycle method. This is called when the DialogManager restoresa dialog from the view stack i.e. when a nested dialog is closed. This method gives the dialog achance to reset any state that may need refreshing.

getContainerTitle() and getContainerDescription() allow the bean implementation tooverride the title and description provided by configuration. This in essence allows the bean toprovide titles and descriptions that are not known until runtime, for example if the title needs toinclude the title of the node it is acting upon.

The getAdditionalButtons() method returns a list of DialogButtonConfig objectsrepresenting additional buttons to render. This method essentially allows bean implementationsto dynamically add buttons at runtime based on the context of the dialog, see Dialog Buttons onpage 129 for more details.

Dialog Buttons

The getAdditionalButtons() method returns a list of DialogButtonConfig objectsrepresenting additional buttons to render. This method essentially allows bean implementationsto dynamically add buttons at runtime based on the context of the dialog. The DialogManagercombines the button definitions returned by the method and those configured at design time tobuild a list of extra buttons to render. As an example, this technique is used to render the buttonsrequired 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 anextra 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);

Page 130: 2140 api developer-student-guide

Extending the Alfresco Web Client

130 Alfresco Enterprise Edition Version 3.2

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-config-custom.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 currentpage. If it detects that it is a dialog, the state of the dialog is retrieved and stored on the viewstack. 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 configurationobject to the DialogManager via the setCurrentDialog() method. The DialogManager thenproceeds to instantiate the dialog's managed-bean and set up any parameters passed into thedialog via the setupParameters() action listener method.

The navigation handler then navigates to the dialog container page. The dialog container pagealso uses the #{DialogManager...} syntax to resolve the title, description and icon for theheader 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 waspassed, or passes the request on to the underlying managed bean. For example, when the userpresses 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 tosee what the item at the top of the stack is. If it is a dialog, the state object is retrieved from thestack and restored, and the dialog container page is then navigated to. If the top of the stackis a normal page, the page is retrieved and navigated to. If the close request is overridden byan outcome of dialog:close:browse, the view stack is emptied and the overridden outcomeprocessed.

Dialog Framework Reference

<dialog/> attributes

nameThe unique name (id) of the dialog.

pageThe path to the JSP page to be used for the dialog.

Page 131: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 131

managed-beanThe name of the JSF managed bean to be used as the backing bean for the dialog.

iconThe path to the icon displayed in the header area of the dialog.

title-idThe id of an I18N string to be used as the title of the dialog.

titleA literal string to be used in place of title-id if you do not want an I18N string.

description-idThe id of an I18N string to be used as the description of the dialog.

descriptionA literal string to be used in place of description-id if you do not want an I18N string.

error-message-idThe id of an I18N string to be used in the case of an error.

actions-config-idThe id of a configured action group. The actions are displayed in the header area of thedialog.

show-ok-buttonFlag to determine whether the OK button should be displayed. This allows dialogs to displayjust the Cancel button.

Dialog<button/> attributes

idThe unique id of the button.

label-idThe id of a string to be used as the label of the button.

labelA label attribute can be used in place of label-id if you want to use a literal string instead ofan I18N string.

actionThe action method binding to call when the button is clicked.

disabledFlag to determine whether the button should be rendered as disabled, this can be a JSFbinding expression.

onclickThe JavaScript onclick handler code to execute when the button is clicked.

Page 132: 2140 api developer-student-guide

Extending the Alfresco Web Client

132 Alfresco Enterprise Edition Version 3.2

Wizard Framework

IntroductionThe wizard framework manages the user interface wizards in the Alfresco Web Client. Most of thestandard Web Client wizards are managed by the framework and it is possible to create your owncustom wizards using the framework.

Each wizard has three main components:

• A <wizard/> configuration element in a web-client-config.xml file.performed in exactlythe same

• A JSP for each step in the wizard that only contains the HTML and JSF components todefine 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, theAlfresco Navigation Handler looks up the <wizard/> configuration then calls the Wizard Managerto initialise the wizard and instantiate the managed bean before navigating to the JSP page forthe first step.

See The Wizard Manager on page 138 for more details.

Custom Wizard HowtoBefore 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 anaspect 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 orcondition) of the web-client-config-wizards.xml file. These may be overridden using amore specific <config/> section, for example, a wizard definition could be overridden for aparticular node type.

All new wizards or customisations to default wizards should be defined in the standardweb-client-config-custom.xml extension file.

Combining of configuration is NOT supported for wizard definitions. This meansthe whole wizard definition has to be copied to the extension file and adjustedappropriately.

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

Page 133: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 133

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

nameDefines the unique name (or id) of the wizard. The above wizard can be referenced inan action definition as wizard:createContent.

managed-beanDefines the name of the JSF managed bean to be used as the backing bean for thewizard.

pathDefines the path to the JSP page to be used for each step.

See <wizard/> attributes, <step/> attributes on page 140 and <page/> attributes on page140 for a complete description of all wizard attributes.

2. Defining an action

The createContent wizard is already called from the standard Create Content action. Theaction is part of the browse_create_menu action group (Create menu on the Web Clientbrowse 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 theHTML and JSF components to define the “body” of the wizard. It will be included into thewizard container page, defined by the <wizard-container/> element in the web-client-config-wizards.xml file:

<wizard-container>/jsp/wizard/container.jsp</wizard-container>

Page 134: 2140 api developer-student-guide

Extending the Alfresco Web Client

134 Alfresco Enterprise Edition Version 3.2

The select-aspect.jsp example below displays a simple label and a drop-down list ofaspects for the user to choose from.

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %><%@ taglib 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 displayis read from the webclient.properties file.

The <h:selectOneMenu/> element will display the drop-down list of aspects. The list ofaspects (<f:selectItems/>) is supplied by the getAspects() method on the wizard'sJSF 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 selectedaspect. The expression WizardManager.bean refers to the JSF managed bean beingused by the current wizard and configured using the managed-bean attribute in thewizard's configuration. Because an explicit bean is not being referenced, the same JSPpage could be re-used for multiple wizards, each wizard defining it's own specific beanimplementation.

The JSF “value binding” expressions #{WizardManager.bean.aspect}and #{WizardManager.bean.aspects} will actuallycall the WizardManager.getBean().setAspect() andWizardManager.getBean().getAspects() methods respectively.

4. Localising wizard messages

All of the Web Client I18N strings are stored in a default message bundle calledwebclient.properties located in the alfresco/messages directory. Messagetranslations can be provided in separate files, named with an extra suffix corresponding tothe standard ISO language and country codes. For example, the French translations of theWeb Client messages are found in the webclient_fr_FR.properties file and the FrenchCanadian translations are found in the webclient_fr_CA.properties file. The messagesin 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.propertiesextension file:custom_create_content_wizard_title=Custom Create Content Wizardselect_aspect=Select Aspect

create_content_step3_title=Step Three - Select Aspectcreate_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 mustimplement the IWizardBean interface. Generally, however, wizard beans just extendthe org.alfresco.web.bean.wizard.BaseWizardBean abstract base class.

Page 135: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 135

BaseWizardBean provides the default implementation for the IWizardBean interfacebut 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 toimplement.

• 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 addan extra step allowing a user to add an aspect to the created content. TheCustomCreateContentWizard managed bean, therefore, just needs to extend theexisting 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 listof aspects is read from the aspects element in the Content Wizards section ofa Web Client configuration file. Once initialised, the list is no longer read from theconfiguration 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 thedrop-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 theuser returns to the same step, the value of the aspect property will be read using the

Page 136: 2140 api developer-student-guide

Extending the Alfresco Web Client

136 Alfresco Enterprise Edition Version 3.2

getAspect() method to initialise the drop-down list to the aspect that the user hasalready 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 caseof our customised implementation, we just need to call the standard finishImpl()method on the CreateContentWizard superclass before adding the selected aspectto 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 registeredwith JSF in a faces-config.xml file. The faces-config.xml file will be packaged in theMETA-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 asnodeService . JSF will “inject” references to the public services defined as managedproperties in the above example. The “setter” methods are implemented by thesuperclasses.

See Introduction on page 106 for more information on registering managed beans.

7. Packaging and deploying

Page 137: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 137

In order to deploy a custom wizard to the Alfresco Web Client, the following files need tobe packaged:

• the web-client-config-custom.xml file containing the wizard and actionconfiguration;

• the wizard JSPs containing the HTML and JSF components to define the “body” ofeach 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 JARfile. The files then need to be deployed to the following directories in the Alfresco WebClient:

• 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 WEB-INF/classes/alfresco/extension directory.

For deployment in a production environment, the files should be packaged and deployedas 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 extendsthe IDialogBean interface from the Dialog Framework.

Page 138: 2140 api developer-student-guide

Extending the Alfresco Web Client

138 Alfresco Enterprise Edition Version 3.2

There is a base class for all wizard managed beans(org.alfresco.web.bean.wizard.BaseWizardBean). The base class extends theBaseDialogBean class used for dialogs and provides the default implementation for theIWizardBean interface.

As BaseWizardBean extends BaseDialogBean most of the processing is performed in exactlythe same way as for dialogs. For more information about BaseDialogBean,see Dialog Beans onpage 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, thegetNextButtonLabel() method returns Next, the getBackButtonLabel() method returns Backand the getNextButtonDisabled() method returns false. Finally, the default outcome returnedfrom finish() is wizard:close.

The next() and back() methods allow the bean implementation to be notified when the userpresses the Next or Back button. This can be used to perform processing as the user progressesthrough the wizard steps.

The only other method BaseWizardBean provides is the buildSummary() method, this is used bysubclasses 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.

Page 139: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 139

When the Alfresco navigation handler gets a request to open a wizard it examines the currentpage. If it detects that it is a wizard, the state of the wizard is retrieved and stored on the viewstack. 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 theconfiguration object to the WizardManager via the setCurrentWizard() method. TheWizardManager then proceeds to instantiate the wizard's managed-bean and set up anyparameters passed into the wizard via the setupParameters() action listener method. Itprocesses 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 pagealso uses the #{WizardManager...} syntax to resolve the title, description and icon for theheader area, the list of steps on the left and the page to show as the body for the current step ofthe wizard.

The WizardManager either returns the requested information from the wizard config object it waspassed, or passes the request on to the underlying managed bean. For example, when the userpresses 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 thecurrent 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 iscontrolled via the getNextButtonDisabled() and getFinishButtonDisabled() methods.These methods can be used to enable or disable the buttons appropriately, for example, if thewizard already has enough state to complete successfully.

When a wizard:close request is received, the navigation handler examines the view stack tosee what the item at the top of the stack is. If it is a wizard, the state object is retrieved from thestack and restored, and the wizard container page is then navigated to. If the top of the stackis a normal page, the page is retrieved and navigated to. If the close request is overridden byan outcome of wizard:close:browse, the view stack is emptied and the overridden outcomeprocessed.

Wizard Framework Reference

<wizard/> attributes

nameThe unique name (id) of the wizard.

managed-beanThe name of the JSF managed bean to be used as the backing bean for the wizard.

iconThe path to the icon displayed in the header area of the wizard.

title-idThe id of an I18N string to be used as the title of the wizard.

titleA literal string to be used in place of title-id if you do not want an I18N string.

description-idThe id of an I18N string to be used as the description of the wizard.

descriptionA literal string to be used in place of description-id if you do not want an I18N string.

error-message-idThe id of an I18N string to be used in the case of an error.

Page 140: 2140 api developer-student-guide

Extending the Alfresco Web Client

140 Alfresco Enterprise Edition Version 3.2

actions-config-idThe id of a configured action group. The actions are displayed in the header area of thewizard.

<step/> attributes

nameThe unique name (within the wizard) of the step.

title-idThe id of an I18N string to be used as the title of the step (appears in the list of steps on theleft hand side).

titleA literal string to be used in place of title-id if you do not want an I18N string.

description-idThe id of an I18N string to be used as the description of the step (the “tooltip” for the step onthe left hand side).

descriptionA literal string to be used in place of description-id if you do not want an I18N string.

<page/> attributes

pathThe path to the JSP page to be used for the wizard step.

title-idThe id of an I18N string to be used as the title of the page (appears on the first line of the innerpanel).

titleA literal string to be used in place of title-id if you do not want an I18N string.

description-idThe id of an I18N string to be used as the description of the page (appears on the second lineof the inner panel).

descriptionA literal string to be used in place of description-id if you do not want an I18N string.

instruction-idThe id of an I18N string to be used as the instruction text for the page (appears on the last lineof the inner panel).

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

ifA value binding expression that must resolve to true or false.

Page 141: 2140 api developer-student-guide

Extending the Alfresco Web Client

API Development Course 141

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

Page 142: 2140 api developer-student-guide

Packaging Extensions

142 Alfresco Enterprise Edition Version 3.2

Packaging Extensions

Page 143: 2140 api developer-student-guide

Packaging Extensions

API Development Course 143

Alfresco Module Packages

Introduction

An Alfresco Module Package (AMP) is a collection of code, XML, images, CSS, etc. thatcollectively extend the functionality or data provided by the standard Alfresco Repository. An AMPfile can contain as little as a set of custom templates or a new category. It can contain a custommodel and associated UI customisations. It could contain a complete new set of functionality, forexample 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 anAMP file.

AMP files can be installed into the Alfresco WAR using the Module Management Tool. An AMPfile 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 ModuleManagement Tool, the WAR can be deployed to the application server. When the repositoryis next started, the installed module configuration will be detected, and the repository will bebootstrapped 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 theEclipse IDE.

1. Structuring an Alfresco Module project

An Alfresco Module project can be structured in any way that suits the developerenvironment. As long as the resulting AMP file is packaged correctly and the requiredproperty 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.

Page 144: 2140 api developer-student-guide

Packaging Extensions

144 Alfresco Enterprise Edition Version 3.2

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 filesand resources (config) is org.alfresco.module.<moduleid>, where moduleid is theunique 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 andits details, when it is installed.

When the AMP file is built the module.properties file must be placed at the root of theAMP file, but during development it is recommended that it should reside in the packagealfresco.module.<moduleid> as this is the location it will end up in once it is installedinto the WAR.

In this location it allows the developer to run unit tests within Eclipse and the embeddedrepository that is started will behave as if the module is installed. This is because therelevant 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 ofthe module.

Example module.properties from the Basic AMP SDK sample:# SDK Sample modulemodule.id=sdkDemoAmpmodule.title=SDK Demo AMP Projectmodule.description=Skeleton demo project to build an amp filemodule.version=1.0

module.id

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

module.version

The module version number specifies the current version of the module. This is takeninto consideration when installing the module into a WAR. It will determine whether it isa new install or an update to an existing installation of a previous version. The versionnumber must be made up of numeric values separated by dots. For example '2.1.56' isa 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 ofaliases. This ensures that the module tool and repository startup can correctly matchthe new name to one of the old names.

Page 145: 2140 api developer-student-guide

Packaging Extensions

API Development Course 145

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 versionsis present. The dependency has been met as long as the installed version of thedependency 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 forthat module.

A module's root Spring configuration must be placed in the packagealfresco.module.<moduleId> and should be called module-context.xml.

When the module service is initialised all the module-context.xml configurations foundare loaded, thus initialising the installed modules ready for use.

The module-context.xml file is a standard Spring configuration file and typically newbeans will be defined, custom content models and client configuration specified and dataloaded or patched. In a big module the configuration may be split up into smaller Springconfigurations 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"); }}

Page 146: 2140 api developer-student-guide

Packaging Extensions

146 Alfresco Enterprise Edition Version 3.2

The DemoComponent is an example component that will be executed just once duringmodule 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 residein the standard package structure (alfresco.module.<moduleId>) within thisdirectory. Other resources, such as XML import files or ACP's, may also residehere. The contents are mapped into the /WEB-INF/classes directory in the WARfile and as such will be on the classpath.

lib/

Contains any module specific JAR files. The contents are mapped into the /WEB-INF/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. Thecontents are mapped to the equivalent directories in the WAR file. All sub-directorystructures are preserved. If a file already exists it is overridden in the WAR and arecoverable 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 AMPfile are mapped into the WAR file. See Customising the structure of an AMP file onpage 149 for more details.

Page 147: 2140 api developer-student-guide

Packaging Extensions

API Development Course 147

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-custom-service.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 JARfile:<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 thedestination 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 anexisting 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>

Page 148: 2140 api developer-student-guide

Packaging Extensions

148 Alfresco Enterprise Edition Version 3.2

<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). SeeModule Management Tool on page 150 for more details. Once the AMP file has beenpackaged, it can be installed into an Alfresco WAR file either using Ant or directly from thecommand line.

The Ant build.xml file in the Basic AMP SDK sample is an example of how to run theModule Management Tool from Ant. To install the AMP file (alfresco-sdk-custom-service.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. Thefollowing 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 beeninitialised during startup:

Page 149: 2140 api developer-student-guide

Packaging Extensions

API Development Course 149

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 beprovided which describes how the structure of your AMP file will be mapped to the Alfresco WARwhen 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 locationthat 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 raisedwhen the Module Management Tool tries to install the AMP.

If a mapping provided in the file-mapping.properties file overrides one of the defaultmappings, then this will take precedence when the installation into the WAR takes place. If amapping is declared that has a folder as the source, then the folder will be recursively copied intothe 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. If 'include.default' is false, then this is the complete set.#/WEB-INF=/WEB-INF/web=/

Importing Module Data

Module data can be imported from an XML file or an ACP file during module initialisation usingthe Importer Module Component.

The XML file or ACP file to be imported needs to be placed somewhere on the module'sclasspath and then a Spring bean similar to the following needs to be defined in the module'smodule-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"/>

Page 150: 2140 api developer-student-guide

Packaging Extensions

150 Alfresco Enterprise Edition Version 3.2

<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 thedata 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 outlinedbelow:

Page 151: 2140 api developer-student-guide

Packaging Extensions

API Development Course 151

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 versionis already installed. This is done using the standard mapping, unless a custom mapping isprovided.

If the module represented by the AMP is already installed and the installing AMP is of a higherrelease version, then the files relating to the older version will be removed from the WAR andreplaced with the newer files.

It is the responsibility of the module developer to provide the appropriate Module componentsto 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 WARthen installation will be aborted unless the -force option is specified. In this case the installingAMP will always replace the currently installed version. This option is especially useful whendeveloping an AMP.

Before an AMP is installed into a WAR a copy of the original WAR is taken an placed in thesame 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. Thisreports 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 existingfiles.

As a general rule it is considered bad practice to overwrite an existing file in an AMP, howeverit is sometimes necessary. In these situations when the AMP is installed a backup of theupdated file is taken and stored in the WAR.

When an update of the module occurs and the old files are removed, this backup will berestored prior to the installation of the new files. Problems may occur if multiple installedmodules modify the same existing file. In these cases a manual restore may be necessary ifrecovery 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 outputis directed to the console.