• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
2140 api developer-student-guide
 

2140 api developer-student-guide

on

  • 1,112 views

dsada

dsada

Statistics

Views

Total Views
1,112
Views on SlideShare
1,112
Embed Views
0

Actions

Likes
0
Downloads
58
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    2140 api developer-student-guide 2140 api developer-student-guide Document Transcript

    • API Development Course
    • Copyright (c) 2007 by Alfresco and others. Information in this document is subject to change without notice. No part of this document may be reproduced or transmitted in any form or by any means, electronic or mechanical, for any purpose, without the express written permission of Alfresco. The trademarks, service marks, logos or other intellectual property rights of Alfresco and others used in this documentation (“Trademarks”) are the property of Alfresco and their respective owners. The furnishing of this document does not give you license to these patents, trademarks, copyrights or other intellectual property except as expressly provided in any written agreement from Alfresco. The United States export control laws and regulations, including the Export Administration Regulations of the U.S. Department of Commerce, and other applicable laws and regulations apply to this documentation which prohibit the export or re-export of content, products, services, and technology to certain countries and persons. You agree to comply with all export laws, regulations and restrictions of the United States and any foreign agency or authority and assume sole responsibility for any such unauthorized exportation. If you need technical support for this product, contact Customer Support by email at support@alfresco.com. If you have comments or suggestions about this documentation, contact us at documentation@alfresco.com. This edition applies to version 2.1 of the licensed program.2
    • Contents Introduction................................................................................................................................ 6 Welcome.............................................................................................................................. 7 How this course is delivered....................................................................................... 7 Course Schedule......................................................................................................... 7 How Prepared are You?..............................................................................................8 Topics not Covered..................................................................................................... 8 Introductions.................................................................................................................8 Getting Started...........................................................................................................................9 Alfresco SDK..................................................................................................................... 10 Introduction.................................................................................................................10 Downloading and Unpacking the Alfresco SDK........................................................ 10 Importing the Alfresco SDK projects into Eclipse......................................................11 Associating Source Code and Javadocs with the Alfresco Libraries......................... 14 SDK samples............................................................................................................. 16 SVN Repository......................................................................................................... 19 SDK Reference.......................................................................................................... 20 Best Practices....................................................................................................................22 Coding Standards...................................................................................................... 22 Alfresco Module Packages (AMP).............................................................................22 Alfresco Repository Architecture....................................................................................... 24 Out of the Box........................................................................................................... 24 Service & Component Architecture........................................................................... 25 Repository Foundation Services API.........................................................................27 Repository APIs......................................................................................................... 28 Repository Server Protocols...................................................................................... 29 Terminology................................................................................................................30 Developing against the Alfresco Repository........................................................................33 Spring Framework............................................................................................................. 34 Introduction.................................................................................................................34 Bean Factory..............................................................................................................34 Inversion of Control (IoC).......................................................................................... 35 Application Context.................................................................................................... 39 Foundation Services API................................................................................................... 40 Introduction.................................................................................................................40 Access to Repository Foundation Services...............................................................40 FirstFoundationClient walkthrough............................................................................ 40 Other Foundation Services........................................................................................ 48 JCR API............................................................................................................................. 50 Introduction.................................................................................................................50 JCR Compliance Levels............................................................................................ 50 JCR Repository Model...............................................................................................50 API Development Course 3
    • FirstJCRClient Walkthrough.......................................................................................51 JCR Transactions...................................................................................................... 53 Web Services API............................................................................................................. 55 Introduction.................................................................................................................55 Available Web Services.............................................................................................55 Access to Web Services in Java...............................................................................55 Web Services Data Types.........................................................................................56 Content Manipulation Language (CML).....................................................................56 FirstWebServiceClient Walkthrough.......................................................................... 57 Separating Concerns using AOP...................................................................................... 61 Public Services and AOP proxies............................................................................. 61 Security Enforcement.................................................................................................61 Transaction Management.......................................................................................... 64 Extending the Alfresco Repository....................................................................................... 69 Repository Policies............................................................................................................ 70 Introduction.................................................................................................................70 Available Policies....................................................................................................... 70 Policy Types...............................................................................................................71 Custom Aspect with Behaviour Howto...................................................................... 71 Repository Actions.............................................................................................................79 Introduction.................................................................................................................79 Repository Action Howto........................................................................................... 79 Repository Action with Parameters Howto................................................................ 85 Content Transformers........................................................................................................91 Introduction.................................................................................................................91 Content Transformer Howto...................................................................................... 91 ContentTransformerRegistry...................................................................................... 95 Further Reading......................................................................................................... 97 Metadata Extractors...........................................................................................................98 Introduction.................................................................................................................98 MetadataExtracterRegistry.........................................................................................98 Metadata Extractor Howto......................................................................................... 99 Further Reading....................................................................................................... 103 Extending the Alfresco Web Client..................................................................................... 105 JavaServer Faces............................................................................................................106 Introduction...............................................................................................................106 Login Page Walkthrough......................................................................................... 106 Actions Framework.......................................................................................................... 116 Introduction...............................................................................................................116 Update UI Action Walkthrough................................................................................ 116 Actions Framework Reference................................................................................ 120 Dialog Framework............................................................................................................123 Introduction...............................................................................................................1234 Alfresco Enterprise Edition Version 3.2
    • Custom Dialog Howto..............................................................................................123 Further Information.................................................................................................. 128 Dialog Framework Reference.................................................................................. 130 Wizard Framework...........................................................................................................132 Introduction...............................................................................................................132 Custom Wizard Howto.............................................................................................132 Further Reading....................................................................................................... 137 Wizard Framework Reference................................................................................. 139Packaging Extensions.......................................................................................................... 142 Alfresco Module Packages.............................................................................................. 143 Introduction...............................................................................................................143 Basic AMP Howto....................................................................................................143 Further Reading....................................................................................................... 149 Alfresco Module Package Reference...................................................................... 150 API Development Course 5
    • IntroductionIntroduction6 Alfresco Enterprise Edition Version 3.2
    • IntroductionWelcome Welcome to Alfresco API development. This course has been designed to provide you with the knowledge and skills necessary to develop against the Alfresco Repository APIs and to develop extensions for the Alfresco enterprise content management system.How this course is delivered This course is delivered in 17 modules, each module focusing on a specific subject. All modules include a combination of introductory materials, an outline of objectives followed by instructor- led discussions and walkthroughs. Each module will close with one or more exercises which will allow students to use and demonstrate what they have learned in each module.Course objectives At the conclusion of this course you should be comfortable with all the concepts and tasks required to competently: • Set up your own development environment and use the Alfresco SDK • Develop against the Alfresco APIs (Foundation Services, JCR, Web Services) • Develop extensions for the Alfresco Repository • Develop extensions for the Alfresco Web Client • Package and deploy extensionsCertification track objectives At the conclusion of this course, you should have also acquired the requisite knowledge and abilities to pass the Alfresco API developer exam in preparation for the Certified Alfresco ECM professional certificate.Course ScheduleDay 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 APIDay 2 • Developing against the Alfresco Repository • Separating Concerns using AOP • Extending the Alfresco Repository • Repository Policies • Repository Actions API Development Course 7
    • Introduction • Content Transformers • Metadata ExtractorsDay 3 • Extending the Web Client • JavaServer Faces • Actions Framework • Dialog Framework • Wizard Framework • Packaging Extensions • Alfresco Module PackagesHow 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 ScriptsIntroductions • Name • Company • Title, function, job responsibility • Alfresco experience (API development experience) • Courses already taken • Reasons for taking this course • Expectations8 Alfresco Enterprise Edition Version 3.2
    • Getting StartedGetting Started API Development Course 9
    • Getting StartedAlfresco SDKIntroduction The Alfresco SDK provides support for developers who wish to extend or customise the Alfresco platform. It has been designed for the developer to get developing with minimal fuss for the following development scenarios: • Developing extensions for the Alfresco Repository and Web Client. • Embedding Alfresco into applications via Alfrescos Java Foundation Services API or standards-compliant JCR API. • Developing applications against a remote Alfresco Repository via Alfrescos Web Services API. Typically, the SDK is used stand-alone, but an Alfresco installation is also required if performing any of the following: • Customising the Alfresco Web Client • Deploying a custom module to a remote Alfresco repository • Testing a custom application that connects to a remote Alfresco repository The SDK is not designed for re-building Alfresco since it does not provide full build scripts and artifacts. If you wish to develop bug fixes or extend the core functionality of the Alfresco platform, you should use the full Alfresco development environment provided in the Alfresco SVN Repository. For more information, see SVN Repository on page 19.Downloading and Unpacking the Alfresco SDK You will need to install the following software and development tools in order to use the SDK correctly: • the Java SE Development Kit (JDK) version 1.5 (5.0) or above; • a supported database of your choice (MySQL is recommended for development purposes); • the Eclipse IDE 3.x (highly recommended). The Alfresco SDK bundle is provided with each release of Alfresco, for both the Enterprise and Community Networks. 1. Downloading the Alfresco SDK The Enterprise SDK is only available to Enterprise clients and partners. The Community SDK is freely available and can be downloaded from the Download Alfresco Community Network page on the Alfresco Developer web site. The SDK is provided in both ZIP and TAR (tar.gz) formats. 2. Unpacking the Alfresco SDK To install the SDK, simply unpack the downloaded ZIP or TAR bundle to a directory of your choice.10 Alfresco Enterprise Edition Version 3.2
    • Getting Started For a description of the contents of the Alfresco SDK, see SDK Contents on page 20.Importing the Alfresco SDK projects into Eclipse When using the Alfresco SDK, the Eclipse IDE is highly recommended. The SDK contains several pre-configured Eclipse projects that you can import directly into Eclipse with the following procedure. 1. Setting the Eclipse Compiler Compliance Level Alfresco uses Java 1.5 (5.0) language features, therefore Eclipse must be configured appropriately for the Java SE Development Kit (JDK) version 1.5 (5.0) or above. a. From the Eclipse main menu, select Window # Preferences... b. In the Preferences dialog, select Java # Compiler in the tree view. c. In the JDK Compliance panel, set the Compiler compliance level to 5.0 or above: API Development Course 11
    • Getting Started d. Click OK 2. Importing the Alfresco SDK projects a. From the Eclipse main menu, select File # Import... b. In the Import dialog, select General # Existing Projects into Workspace import source and click Next > c. Choose Select root directory option and click Browse... d. Navigate to the file system directory where you unpacked the Alfresco SDK and click OK. The Alfresco SDK projects are now listed under Projects. Do not navigate to the samples sub-directory, otherwise you will not see the SDK AlfrescoEmbedded and SDK AlfrescoRemote projects in the list.12 Alfresco Enterprise Edition Version 3.2
    • Getting Started In order to run the samples or to develop your own extension modules, you must import at least the SDK AlfrescoEmbedded and SDK AlfrescoRemote projects. The other SDK projects are samples for common development scenarios that you can study and run to learn more about developing Alfresco extension modules. For more information about the available projects, see SDK Eclipse Projects on page 20. e. Once you have selected the projects you wish to import, click Finish.The imported projects are displayed in the Package Explorer: API Development Course 13
    • Getting StartedAssociating Source Code and Javadocs with the Alfresco Libraries Once the Alfresco SDK projects have been imported into Eclipse, it is useful to have access to Alfrescos source code and Java documentation. The following procedure explains how to do this by associating the source code and Javadocs with the Alfresco libraries within Eclipse. 1. Expand the SDK AlfrescoEmbedded project in the Project Explorer. 2. Right click on the alfresco-repository.jar and select Properties from the popup menu. The JAR files may not be in alphabetical order. 3. Associating source code a. In the Properties for alfresco-repository.jar dialog, select Java Source Attachment in the tree view. b. In the Java Source Attachment panel, click External File... c. Navigate to src directory within your unpacked Alfresco SDK. d. Select repository-src.zip and click Open.14 Alfresco Enterprise Edition Version 3.2
    • Getting Started4. Associating Javadocs a. In the Properties for alfresco-repository.jar dialog, select Javadoc Location in the tree view. b. In the Javadoc Location panel, select Javadoc in archive and click Browse... c. Navigate to doc/api directory within your unpacked Alfresco SDK. d. Select repository-doc.zip and click Open. e. Click Validate... to validate the Javadoc location, then click either OK to view the Javadocs in a web browser or Cancel if not. API Development Course 15
    • Getting Started 5. Click OK, to close the Properties dialog. Once the source code and Javadocs have been attached, the alfresco-repository.jar icon changes to include a small document: The above steps need to be repeated for the following JARs: • alfresco-core.jar • alfresco-remote-api.jar • alfresco-web-client.jar • alfresco-web-service-client.jar (only Java source code is available)SDK samplesFirstFoundationClient and JCR Samples The SDK FirstFoundationClient, SDK FirstJCRClient and SDK JCRSamples sample projects demonstrate how to access an embedded Alfresco repository via the Foundation Services API and the standards-compliant JCR API. These samples can be tested directly from within Eclipse and will automatically start an Alfresco repository in embedded mode. Before starting, the embedded repository needs to be configured. By default, the sample projects are configured to use a MySQL database named alfresco and a data directory with a relative path of ./alf_data. These parameters are defined in the custom-repository.properties file in the source/alfresco/extension directory of each project. It is good practice to define an absolute path for the data directory (dir.root parameter) and to configure all of the SDK projects to share the same database and the same dir.root. Example custom-repository.properties file: dir.root=C:/alf_data16 Alfresco Enterprise Edition Version 3.2
    • Getting Started#db.username=alfresco#db.password=alfresco## MySQL connection (This is default and requires mysql-connector-java-3.1.12-bin.jar, which ships with the Alfresco server)##db.driver=org.gjt.mm.mysql.Driver#db.url=jdbc:mysql://localhost/alfrescoThe 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-sdkextrasdatabasesmysql>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 asits data directory (dir.root). API Development Course 17
    • Getting Started The other embedded repository samples (SDK FirstJCRClient and SDK JCRSamples) can be run in the same way.Web Services Samples The SDK FirstWebServiceClient and SDK WebServiceSamples sample projects demonstrate how to access a remote Alfresco repository via the Web Services API. A remote Alfresco repository needs to be installed and running before testing one of these samples. Before running one of the Web Services samples, a remote repository needs to be installed and configured. The easiest solution for development purposes is to install Alfresco on the local machine and configure it to use the same alfresco MySQL database and the same C:/alf_dir data directory as the embedded repository used for the other samples. The location of the remote repository is configured in the webserviceclient.properties file in the source/alfresco/extension directory of each Web Services project. If the remote repository is installed on the same machine and configured to use the default 8080 port, you will not have to modify the default value. Example webserviceclient.properties file: # # Set the following property to reference the Alfresco server that you would like web service client # to communicate with repository.location=http://localhost:8080/alfresco/api Once the remote repository has been installed and started, the Web Clients samples can be tested by running the main Java classes from within Eclipse. In the following example, the FirstWebServiceClient class is run as a Java Application:Custom Repository Samples The SDK CustomAction and SDK CustomAspect sample projects demonstrate how to develop custom modules that may be deployed to an Alfresco repository. Initially, custom repository18 Alfresco Enterprise Edition Version 3.2
    • Getting Started modules can be developed and tested using unit tests and an embedded Alfresco repository. The SDK CustomAspect project has a sample unit test that does this. An Ant build.xml file is provided for packaging the repository samples. The package target packages the compiled classes and extension files into a JAR file. To deploy the samples, copy the JAR file to the WEB-INF/lib folder of an existing Alfresco installation and restart the application server. For deployment in a production environment, a custom repository module should be packaged as an Alfresco Module Package (AMP). For more information on creating a custom repository action, see the Repository Action Howto on page 79. For a detailed presentation of the SDK CustomAspect sample, see the Custom Aspect with Behaviour Howto on page 71.Custom Web Client Samples The SDK CustomDialog, SDK CustomJSP, SDK CustomLogin, SDK CustomWizard, and SDK TaggingSample sample projects demonstrate how to develop custom modules for the Alfresco Web Client. Custom Web Client modules have to be deployed to an existing Alfresco installation for testing. An Ant build.xml file is provided for packaging the Web Client samples. The 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 optional integrate-extension target can be used to integrate the packaged ZIP file into an Alfresco Web Client WAR file. The alfresco.war file must be copied to the same directory as the build.xml file before running the Ant build and then re-deployed to the application server. For deployment in a production environment, a custom Web Client module should be packaged as an Alfresco Module Package (AMP). The SDK CustomDialog and SDK CustomWizard are presented in detail in the Custom Dialog Howto on page 123 and the Custom Wizard Howto on page 132. The SDK TaggingSample sample is presented in detail in the Repository Action Howto on page 79.Basic AMP Sample The SDK Basic AMP sample project demonstrates how to structure a project and how to arrange classes and configuration files to generate an Alfresco Module Package (AMP). For more information see Basic AMP Howto on page 143.SVN Repository The Alfresco Subversion repository gives you access to all of the Alfresco source code and build artifacts. It provides the latest work-in-progress developments. It should only be used if you wish to extend the Alfresco core framework or work on Alfresco bug fixes as it allows you to perform full re-builds of Alfresco itself. For most requirements, it is best to use the Alfresco SDK. Public read-only access to the Alfresco Subversion repository is available from the Alfresco web site. To checkout the source code, use the following procedure: 1. Install Subversion and ensure that svn is on the path. 2. Checkout the HEAD of the code stream: svn co svn://svn.alfresco.com/alfresco/HEAD or svn co http://svn.alfresco.com/repos/alfresco-open-mirror/alfresco/HEAD 3. Keep up to date by issuing the command: svn update API Development Course 19
    • Getting StartedSDK ReferenceSDK Contents bin Supporting dlls, exes. doc Zipped Javadocs for all pre-built libraries. extras Additional files - database setup and migration scripts. lib Alfresco pre-built libraries (JAR files). lib/deployment Alfresco libraries required for WCM deployment to a remote server. lib/remote Alfresco libraries required for access to a remote Alfresco repository via web services. lib/server Alfresco libraries required for embedding an Alfresco repository. licenses License files. samples Sample Eclipse projects for common development scenarios (see SDK Eclipse Projects on page 20). src Zipped source code for all pre-built libraries. license.txt Alfresco licence file. notice.txt Notices readme.txt Alfresco SDK readme.SDK Eclipse Projects SDK AlfrescoEmbedded Project containing all of the Alfresco libraries needed to build a custom module that will be embedded into the Alfresco repository or Web Client. SDK AlfrescoRemote Project containing all of the Alfresco libraries needed to build a custom Web Services client. SDK Basic AMP Sample project demonstrating how to build an AMP (Alfresco Module Package) file. SDK CustomAction Sample project demonstrating how to develop a custom Action that may be deployed to an Alfresco repository. SDK CustomAspect Sample project demonstrating how to develop a custom Aspect with behaviour that may be deployed to an Alfresco repository.20 Alfresco Enterprise Edition Version 3.2
    • Getting StartedSDK CustomDialog Sample project demonstrating how to develop and configure a custom Dialog for the Alfresco Web Client.SDK CustomJSP Sample project demonstrating how to develop and configure a custom JSP for the Alfresco Web Client.SDK CustomLogin Sample project demonstrating how to override the Login page of the Alfresco Web Client.SDK CustomWizard Sample project demonstrating how to develop and configure a custom Wizard for the Alfresco Web Client.SDK FirstFoundationClient Sample project demonstrating how to access an Alfresco (embedded) repository via the Foundation Services API.SDK FirstJCRClient Sample project demonstrating how to access an Alfresco (embedded) repository via the standards-compliant JCR API.SDK FirstWebServiceClient Sample project demonstrating how to access a remote Alfresco repository via the Web Services API.SDK JCRSamples More sample projects demonstrating how to access an Alfresco (embedded) repository via the standards-compliant JCR API.SDK TaggingSample Advanced sample project demonstrating how to develop a custom Action that takes parameters.SDK WebServiceSamples More sample projects demonstrating how to access a remote Alfresco repository via the Web Services API. API Development Course 21
    • Getting StartedBest PracticesCoding StandardsCoding 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 Eclipses 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. • Dont log exceptions unless you are adding the logic to absorb the exception. • Put as much context and formatting into the error message as possible. • Use RuntimeException derived exceptions, unless there is a really good reason to bother the client code with a checked exception. • Pay attention to the Javadoc specification on unchecked exceptions. Dont declare them on the interface, just in the Javadocs.Coding Standards - Logging • Use the Apache Commons Logging API so that all logging output is uniform. • Use the class hierarchy categories, but where deviations are made, add comments to the Javadocs. • INFO messages are only added at the request of Alfresco users. All other informative messages are DEBUG. • Put as much context and formatting into the message as time will allow. • Wrap all calls to logger.debug and logger.info, and only log messages if logger.isDebugEnabled and logger.isInfoEnabled respectively.Coding Standards - File Formats • UTF-8 encoding of all text files • Windows line endings (CR-LF)Alfresco Module Packages (AMP) An Alfresco Module Package (AMP) is a collection of code, XML, images, CSS, etc. that collectively extend the functionality or data provided by the standard Alfresco Repository. An AMP file can contain as little as a set of custom templates or a new category. It can contain a custom model and associated UI customisations. It could contain a complete new set of functionality, for example records management. As a general rule of thumb, anything that is considered to be an “installable” extension to the Alfresco repository should be called a module and packaged as an AMP file. AMP files can be installed into the Alfresco WAR using the Module Management Tool. An AMP file has a standard format that can be customised if required.22 Alfresco Enterprise Edition Version 3.2
    • Getting Started Once the contents of the AMP file has been mapped into an Alfresco WAR using the Module Management Tool, the WAR can be deployed to the application server. When the repository is next started, the installed module configuration will be detected, and the repository will be bootstrapped to include the new module functionality and data.AMP Project Structure An Alfresco Module project can be structured in any way that suits the developer environment. As long as the resulting AMP file is packaged correctly and the required property and context files are present, the module will install successfully. The recommended project structure is as follows: |-- source | |-- java |-- <module package structure starts here> | |-- web |-- css |-- images |-- jsp |-- scripts | |-- config |-- <resource package structure starts here> | |-- build |-- dist |-- lib | |-- build.xml source/java/ Contains the Java source for the Alfresco Module. source/web/ Contains any web UI resources (JSPs, images, CSS, JavaScript). config/ Contains configuration files and resources used by the module. build/ Build directory for compiled class files. build/dist/ Build directory for AMP files. build/lib/ Build directory for JAR files. The recommended package structure for Java source (source/java), configuration files and resources (config) is org.alfresco.module.<moduleid>, where moduleid is the unique module id of the module. Alfresco Module Packages are presented in more detail later on in the course. For more details, see Introduction on page 143. API Development Course 23
    • Getting StartedAlfresco Repository ArchitectureOut of the Box Out-of-the-box, Alfrescos simple installation procedure provides a pre-configured deployment aimed at reaching a complete and working Content Management application as quickly and easily as possible. The deployment is as follows: This is typical of a web architecture, where an application server houses the logic for both the user interface and domain. Storage of data and content is provided by persistent back-ends such as a database or file system. Any number of web browsers can connect to the application without prior client installation costs. In this particular case, the application server houses both the Alfresco Application and the Alfresco Repository. An Alfresco Application provides a complete solution tailored for a specific area of Content Management such as Document Management (DM), Web Content Management (WCM) and Records Management (RM). The Alfresco Repository provides a set of reusable cross-cutting Content Management services such as content storage, query, versioning and transformation which may be utilised by one or more applications. Although this is the default installed deployment, it is only one of many ways of utilising the capabilities and components of Alfresco. When we first set out to design Alfresco, we wanted to break away from the mould of typical Content Management architectures which are monolithic and closed. The result is that Alfresco can neatly fit into existing environments and each of its24 Alfresco Enterprise Edition Version 3.2
    • Getting Started components may be used in isolation or together to form the basis of many differing Content Management solutions. The remainder of this module explores the anatomy of the Alfresco Repository which will give a good understanding of the concepts and capabilities and how it achieves openness, scalability and flexibility.Service & Component Architecture Every part of the Alfresco Repository is either a component or a service. A component is an implementation black box that provides a specific feature or capability. A service is an interface entry point for a client to bind to and use. This fundamental approach allows for existing components to be switched with new implementations, new components to be added with ease and for clients to connect and use services without knowledge of how theyre implemented. If theres a feature of Alfresco you dont need, you can take it out, providing a lighter and possibly faster Alfresco. If theres a feature you wish to re-implement, you can replace it, either by providing a better implementation, or integrating with your existing environment. Implementation of this approach is simplified by using the open source project Spring Framework which Alfresco has taken to heart and has made a core foundation of its architecture. With Spring, Alfresco components are declaratively configured and bound together. Aspect-oriented programming allows the weaving of infrastructure concerns such as Transactions and Security into components without polluting their implementation. Environment touch points are abstracted such as resource (e.g. database) connections. The Alfresco Repository structure looks like this: API Development Course 25
    • Getting Started The public interface point is the Alfresco Repository Foundation Services. Each service is exposed as a Java Interface to which a Repository client can bind and invoke without knowledge of its underlying implementation. A Service Registry lists the available services. Behind services are the implementation black boxes i.e. components. Each service and component is configured via the Spring framework in XML “context” files. The Spring context file public-service-context.xml provides the configuration and binding of the Alfresco Repository Foundation Services. The Repository Foundation Services are the lowest level of public interface providing access to all Repository capabilities. Binding to this interface is possible via the Repository Service Registry, or via Spring dependency injection if the client is also Spring aware. Access to Foundation Services is limited to Repository clients who reside in the same process as the Repository. That is, the Foundation Services are an excellent API for clients who wish to embed the Repository. An important point to note is that the Foundation Services are where transaction and security policies are enforced. The policies themselves are declaratively specified and enforced via the injection of a transaction and security implementation into each service. Every service of Alfresco is transactional and secure. Other forms of API are provided too, however, all public entry points eventually go through this layer. Alfresco supports a common scheme for making extensions to the Repository i.e. configuring a component, adding a new component or service, or removing capabilities. Extensions are26 Alfresco Enterprise Edition Version 3.2
    • Getting Started encapsulated outside of the core Repository and plugged-in automatically. This means the core Repository can be upgraded to a newer version and extensions remain intact.Repository Foundation Services API The heart of the Alfresco Repository is responsible for the storage and retrieval of content. This is split into nodes, content and index information. Nodes provide meta-data and structure to content. A node may support properties (e.g. author) and relate to other nodes (e.g. represent folder hierarchies or annotations). Content is the actual information being recorded e.g. a Word document or XML fragment. Meta-data and content may be structured according to the rules defined in a Content Model. For example, the Alfresco Document Management application relies on a model that describes Folders and Files. Indexing information allows the retrieval of 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 over many years such as transaction support, scaling & administration capabilities. Content is stored in the file system to allow for very large content, random access, streaming and options for different storage devices. The Alfresco out-of-the-box implementations of the above services are built upon strong open source projects that already have many man-years of development effort and strong communities: Hibernate and Lucene. API Development Course 27
    • Getting Started Apart from the strong Object/Relational mapping that Hibernate provides, it also brings pluggable caching support and SQL dialects. The first allows for tuning of the Alfresco meta-data store to provide optimum read and write performance in both single and clustered environments. The second allows for nearly any SQL database back-end by configuring just two properties; the Alfresco community has already confirmed working support for MySQL, Oracle, DB2, Sybase, SQL Server. By externalising the indexing of meta-data and content and using the Lucene engine as a basis, it is possible to perform complex queries which combine property, location, classification and full-text predicates in a single query against any content type. Multiple query languages are supported including Lucenes native language as well as XPath and a SQL-like language in the future. To ensure reliable operation, transactional support has been added to both Lucene and the content file store providing ACID operations across the complete store. Security is woven into each of the service layer ensuring illegal modifications are not permissible and hidden meta-data and content are not returned. Nearly all other Foundation services and clients rely upon these three core building blocks.Repository APIs The Alfresco Repository actually provides three APIs. Weve already seen one - the Repository Foundation Services - a set of local Java Interfaces covering all capabilities which are ideal for clients who wish to embed the Repository. The two other APIs are: • JCR • Web Services JCR (Content Repository API for Java Technologies) is a standard Java API (as defined by JSR-170) for accessing Content Repositories. Alfresco provides support for level 1 and level 2 giving standardised read and write access. Supporting this API provides the following benefits: • No risk: The Alfresco Repository can be trialled and developed against, but swapped out with another JCR Repository if it does not fit requirements. • Familiarity: Developers who know JCR, know Alfresco. • Tools: Tools, Clients and other 3rd Party JCR solutions are immediately available to the Alfresco community. Alfresco JCR is implemented as a light facade on top of the Repository Foundation Services. So, although a familiar API is provided, it sits upon a fully transactional, secure and scalable Repository which supports many deployment options. Alfresco will continue investment in JCR by both broadening the compliance of the full specification as well driving forward JSR-283, the next version of the JCR. Web Services is the final API provided by the Alfresco Repository. This API supports remote access and bindings to any client environment, not just Java. For example, the Alfresco community is already using PHP, Ruby and Microsoft .NET. Numerous standards and integration efforts are focused around Web Services - SOA is now recognised as a way forward for integrating disparate systems including Content Management and building new enterprise-wide solutions. BPEL plays an important role in orchestrating all of these services. Alfresco fits neatly into this way of thinking.28 Alfresco Enterprise Edition Version 3.2
    • Getting Started Once again, the Repository Foundation Services serve as the base. Both the JCR and Web Services API eventually go through this layer meaning that all encapsulated content model logic and rules are honoured.Repository Server Protocols A Repository is useless if the content it manages cannot be accessed. To provide the widest possible range of access points, the Alfresco Repository supports a variety of communication protocols. These are: • CIFS (Common Internet File System) • WebDAV • FTP • NFS All of these protocols essentially expose Folders of Files and as such the Alfresco implementations map neatly onto Folder and File nodes held in the Repository as described by the Alfresco Document Management content model. WebDAV, FTP and NFS are well known protocols, but CIFS deserves some more attention. CIFS transforms the Alfresco Repository into a standard file system. Any tool that understands how to read and write to a file system, also knows how to directly read and write to the Alfresco Repository. On the surface, it would seem that a drive mapping to WebDAV provides an equivalent capability, but this isnt the case. CIFS projects an actual file system giving extra compatibility with the hosting operating system. For example, in Windows, its possible to use Offline Synchronisation and Briefcase features against the Alfresco Repository providing native and well-known tools for offline Repository working. Many commercial CMS offerings do not provide this feature. In fact, Alfresco may have the only server-side Java implementation of the API Development Course 29
    • Getting Started CIFS protocol having brought on board the engineers who spent 7 years developing such a capability. Like every other feature of the Repository, the protocol components are Spring configured and as with all other components may or may not be included in a deployment. Typically, they are enabled when the Repository is deployed as a server to provide access points for remote clients. The various Repository deployment options are explored later in this document. Protocol components are implemented against the Repository Foundation Services. This is important to note, as each protocol will honour the behaviour and content logic encapsulated behind the Foundation Services. In fact, it cannot be bypassed.TerminologyStore Reference (StoreRef) A StoreRef is made up of a store protocol and a store id.30 Alfresco Enterprise Edition Version 3.2
    • Getting Started public static final String PROTOCOL_WORKSPACE = "workspace"; public static final String PROTOCOL_AVM = "avm"; public static final String URI_FILLER = "://"; The standard store used by the Web Client has the following StoreRef: workspace://SpacesStoreNode 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-8337f65f7e0dQualified Name (QName) A QName represents the qualified name of a Repository item. Each QName consists of a local name qualified by a namespace. API Development Course 31
    • Getting Started Namespace scoped Name Format {URI}localname or prefix:localName Examples {http://www.alfresco.org/model/content/1.0}auditable or cm:auditableNode Browser The Node Browser is your friend!32 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco RepositoryDeveloping against the Alfresco Repository API Development Course 33
    • Developing against the Alfresco RepositorySpring FrameworkIntroduction The Spring Framework is a full-stack Java/JEE application framework. Springs main aim is to make J2EE easier to use and promote good programming practise. It does this by enabling a POJO-based programming model remaining faithful to the fundamental ideas of Expert One-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, and which can manage relationships between objects. Bean factories support two modes of object: • Singleton: in this case, theres one shared instance of the object with a particular name. • Prototype or non-singleton: in this case, each retrieval will result in the creation of an independent object. Beans are defined in an XML bean definition file that is loaded when a new Bean Factory is created.Bean Factory Example 1. Writing a simple JavaBean class Heres the simplest Java Bean you can get! package ex01_simplebean; public class Bean1 { } 2. Defining the Spring beans Two Spring beans are defined for the same class. bean1 is a singleton bean and multibean1 is a prototype bean (singleton="false"): <beans> <bean id="bean1" class="ex01_simplebean.Bean1" /> <bean id="multibean1" class="ex01_simplebean.Bean1" singleton="false" /> </beans> 3. Testing with a Main program a. Creating a new Spring Bean Factory The bean definition file is loaded into the Spring Bean Factory: ClassPathResource res = new ClassPathResource( "ex01_simplebean/ApplicationContext.xml"); XmlBeanFactory factory = new XmlBeanFactory(res); The following messages are written to the console: 15:13:26,515 INFO [XmlBeanDefinitionReader] Loading XML bean definitions from class path resource [ex01_simplebean/ ApplicationContext.xml] 15:13:26,593 INFO [XmlBeanFactory] Creating shared instance of singleton bean bean1 b. Getting bean1 from the Bean Factory34 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository We retrieve bean1 from the Bean Factory using the getBean() method: Bean1 bean1a = (Bean1) factory.getBean("bean1"); System.out.println("Retrieved Bean1: " + bean1a.toString()); Bean1 bean1b = (Bean1) factory.getBean("bean1"); System.out.println("Retrieved Bean1: " + bean1b.toString()); Each call retrieves the same bean (there is only one instance): Retrieved Bean1: ex01_simplebean.Bean1@471e30 Retrieved Bean1: ex01_simplebean.Bean1@471e30 c. Getting multibean1 from the Bean Factory Now we retrieve multibean1 from the Bean Factory. Bean1 bean1a = (Bean1) factory.getBean("multibean1"); System.out.println("Retrieved MultiBean1: " + bean1a.toString()); Bean1 bean1b = (Bean1) factory.getBean("multibean1"); System.out.println("Retrieved MultiBean1: " + bean1b.toString()); This time each call retrieves a new instance of the bean: Retrieved MultiBean1: ex01_simplebean.Bean1@10ef90c Retrieved MultiBean1: ex01_simplebean.Bean1@a32bInversion of Control (IoC) Through its bean factory concept, Spring is an Inversion of Control container. Spring is most closely identified with a flavour of Inversion of Control known as Dependency Injection. The concept behind Inversion of Control is often expressed in the Hollywood Principle: “Dont call me, Ill call you.” IoC moves the responsibility for making things happen into the framework, and away from application code. Whereas your code calls a traditional class library, an IoC framework calls your code.Dependency Injection Dependency Injection is a form of IoC that removes explicit dependencies on container APIs. Ordinary Java methods are used to inject dependencies such as collaborating objects or configuration values into application object instances. The two major flavors of Dependency Injection are: • Setter Injection (injection via JavaBean setters) • Constructor Injection (injection via constructor arguments). Spring provides sophisticated support for both, and even allows you to mix the two when configuring the one object.Setter Injection Example 1. Writing a simple JavaBean class This simple JavaBean supports properties. The values of the properties are set by the Spring Bean Factory when the bean is instantiated. package ex02_setter; public class Bean1 { public void setString(String val) { m_strVal = val; } public String getString() { return m_strVal; } public void setInt(int val) { m_intVal = val; } public int getInt() { return m_intVal; } API Development Course 35
    • Developing against the Alfresco Repository public void setList(List strings) { m_strings = strings; } public List getList() { return m_strings; } private String m_strVal; private int m_intVal; private List m_strings; } 2. Defining the Spring beans A single bean is defined with initial values for the properties that will be initialised via the “setter” methods on the JavaBean: <beans> <bean id="bean1" class="ex02_setter.Bean1"> <property name="string"> <value>a string</value> </property> <property name="int"> <value>125</value> </property> <property name="list"> <list> <value>item1</value> <value>item2</value> <value>item3</value> </list> </property> </bean> </beans> 3. Testing with a Main program We retrieve bean1 from the Bean Factory and print out the values of the properties: Bean1 bean1a = (Bean1) factory.getBean("bean1"); System.out.println("Retrieved Bean1: " + bean1a.toString()); String strVal = bean1a.getString(); System.out.println("String property: " + strVal); int intVal = bean1a.getInt(); System.out.println("Int property: " + intVal); List strings = bean1a.getList(); System.out.println("List property: " + strings); The properties have automatically been initialised by the Bean Factory: Retrieved Bean1: ex02_setter.Bean1@21b6d String property: a string Int property: 125 List property: [item1, item2, item3]Constructor Injection Example 1. Writing a simple JavaBean class This time the JavaBean has a constructor and some properties. The String property is passed to the constructor and the int property has a “setter” method. package ex03_constructor; public class Bean1 { public Bean1(String val) { m_strVal = val; } public String getString() { return m_strVal; }36 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository public void setInt(int val) { m_intVal = val; } public int getInt() { return m_intVal; } private String m_strVal; private int m_intVal; } 2. Defining the Spring beans This time a bean is defined with a constructor argument. The String property will be initialised via the constructor and the int property via the “setter” method on the JavaBean: <beans> <bean id="bean1" class="ex03_constructor.Bean1"> <constructor-arg index="0"> <value>a string</value> </constructor-arg> <property name="int"> <value>125</value> </property> </bean> </beans> 3. Testing with a Main program We retrieve bean1 from the Bean Factory and print out the values of the properties: Bean1 bean1a = (Bean1) factory.getBean("bean1"); System.out.println("Retrieved Bean1: " + bean1a.toString()); String strVal = bean1a.getString(); System.out.println("String property: " + strVal); int intVal = bean1a.getInt(); System.out.println("Int property: " + intVal); The properties have automatically been initialised by the Bean Factory: Retrieved Bean1: ex03_constructor.Bean1@12152e6 String property: a string Int property: 125Dependency Injection Example 1. Writing the JavaBean classes a. Two more simple JavaBean classes These two JavaBeans will be used as dependencies: package ex04_dependency; public class Bean2 { } package ex04_dependency; public class Bean3 { } b. Establish a constructor dependency The Bean1 class depends on the Bean2 class via the constructor: package ex04_dependency; API Development Course 37
    • Developing against the Alfresco Repository public abstract class Bean1 { public Bean1(Bean2 bean2) { m_bean2 = bean2; } ... public Bean2 getBean2() { return m_bean2; } ... private Bean2 m_bean2; } c. Establish a setter dependency The Bean1 class depends on the Bean3 class via a property setter: public void setBean3(Bean3 bean3) { m_bean3 = bean3; } public Bean3 getBean3() { return m_bean3; } private Bean3 m_bean3; 2. Defining the Spring beans bean1 depends on bean2 and bean3. The Bean Factory will instantiate the bean2 and bean3 objects before “injecting” them into bean1 via the constructor property setter respectively: <beans> <bean id="bean1" class="ex04_dependency.Bean1"> <constructor-arg index="0"> <ref bean="bean2"/> </constructor-arg> <property name="bean3"> <ref bean="bean3"/> </property> </bean> <bean id="bean2" class="ex04_dependency.Bean2"/> <bean id="bean3" class="ex04_dependency.Bean3"/> </beans> 3. Testing with a Main program We retrieve bean1 from the Bean Factory and print out the dependencies: Bean1 bean1 = (Bean1) factory.getBean("bean1"); System.out.println("Retrieved Bean1: " + bean1.toString()); Bean2 bean2 = bean1.getBean2(); Bean3 bean3 = bean1.getBean3(); System.out.println("Retrieved Bean2 Dependency: " + bean2.toString()); System.out.println("Retrieved Bean3 Dependency: " + bean3.toString()); The dependencies have automatically been injected by the Bean Factory: Retrieved Bean1: ex04_dependency.Bean1$$EnhancerByCGLIB$$d8a83b@1cb25f1 Retrieved Bean2 Dependency: ex04_dependency.Bean2@2808b3 Retrieved Bean3 Dependency: ex04_dependency.Bean3@535b5838 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco RepositoryApplication Context To start using Spring you either instantiate a Spring BeanFactory or an ApplicationContext. ApplicationContext is derived from BeanFactory and provides all of the BeanFactory funtionality and more including: • MessageSource, providing access to messages in, i18n-style • Access to resources, such as URLs and files • Event propagation to beans implementing the ApplicationListener interface • Loading of multiple (hierarchical) contexts, allowing each to be focused on one particular layer, for example the web layer of an applicationWeb Application Context A Spring WebApplicationContext is just an ordinary ApplicationContext that has some extra features necessary for web applications. It is bound in the ServletContext and is created by a ContextLoaderListener.Context Loader Listener The Spring ContextLoaderListener is declared in WEB-INF/web.xml: <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>Spring contextConfigLocation Use the contextConfigLocation <context-param> to set which context files to load: <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:alfresco/web-client-application-context.xml classpath:web-services-application-context.xml classpath:alfresco/application-context.xml </param-value> <description>Spring config file locations</description> </context-param> API Development Course 39
    • Developing against the Alfresco RepositoryFoundation Services APIIntroduction The Foundation Services API is a set of services providing full access to the capabilities of the Alfresco Repository. It is an in-process API meaning that the client must run within the same process as the Repository. For example, the Alfresco Web Client uses this API and is packaged together with the Repository in a single WAR file for deployment to an application server.Access to Repository Foundation Services The Foundation Services API is comprised of a set of interfaces; each interface represents a function of the Repository. A Spring Framework Bean is provided as the implementation for each interface. The list of available public services (i.e. Spring beans) can be found in: • the Spring configuration file public-services-context.xml • the Service Registry interface org.alfresco.service.ServiceRegistry There are three ways to access Foundation Services: 1. use Spring IoC to directly inject services into your code (If your layer is also Spring enabled); 2. use the Alfresco Service Registry; 3. manually access services via the Spring getBean() method.FirstFoundationClient walkthrough Before getting started, you should be familiar with the Introduction on page 34. The sample uses several of the key foundation services, including the ServiceRegistry, TransactionService, AuthenticationService, SearchService, NodeService and ContentService. After initialising the Spring Application Context and starting the repository in embedded mode, we will use the Spring getBean() method to access the ServiceRegistry. We will then use the ServiceRegistry to access the other foundation services. After authenticating to the repository using the AuthenticationService, we will search for the “Company Home” node using the SearchService. We will then create a new node with properties and add an aspect using the NodeService. Finally, we will write some content to the new node using the ContentService. The sample will be wrapped in a single user transaction with the help of the TransactionService. 1. Getting the ServiceRegistry The Service Registry maintains a list of available foundation services and some meta-data about each. In particular, the Service Registry provides access to each service interface. The registry is a service itself and is therefore accessed using either Spring IoC or the Spring getBean() method. The static variable SERVICE_REGISTRY found on the interface org.alfresco.service.ServiceRegistry provides the Spring Bean name to lookup by. The FirstFoundationClient sample uses the getBean() method on the Spring Application Context to retrieve the ServiceRegistry: ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); final ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);40 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository2. Using the TransactionService to run the example in a user transaction By default, all repository foundation services are transactional and each invocation of a service method is wrapped in its own transaction. These transactions are defined declaratively in Spring configuration files and not in your Java code. In most cases, declarative transactions are preferred to user transactions since they are less invasive. There are situations, however, when user transactions do need to be used explicitly in your code. The FirstFoundationClient uses a RetryingTransactionHelper to run the example as a unit of work inside a user transaction. The work is defined as an instance of the RetryingTransactionCallback class. The doInTransaction() method is then called to run the unit of work in a transaction. The RetryingTransactionHelper is obtained via the getRetryingTransactionHelper() method on the TransactionService. For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Interface TransactionService The TransactionService and RetryingTransactionHelper are presented in more detail in the section on Transactions. The following example runs the example work in a user transaction via the RetryingTransactionHelper: TransactionService transactionService = serviceRegistry.getTransactionService(); RetryingTransactionCallback<Object> exampleWork = new RetryingTransactionCallback<Object>() { public Object execute() throws Exception { doExample(serviceRegistry); return null; } }; transactionService. getRetryingTransactionHelper().doInTransaction(exampleWork);3. Using the AuthenticationService for authentication API Development Course 41
    • Developing against the Alfresco Repository Before making any call to the repository through the public Foundation Services API, a user must first be authenticated. This is done via the AuthenticationService by using the authenticate() method and providing a username and password. Once authenticated, a ticket can be requested using either the getNewTicket() or the getCurrentTicket() method. The ticket can then be used to re-validate the user using the validate() method. The AuthenticationService defines the API for managing authentication information against a username. For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Interface AuthenticationService In the example, the authenticate() method is used to authenticate as the “admin” user. The password must be passed as a character array. AuthenticationService authenticationService = serviceRegistry.getAuthenticationService(); authenticationService.authenticate("admin", "admin".toCharArray()); 4. Using the SearchService to locate the “Company Home” node The SearchService provides many methods for searching the Alfresco repository using any of the available query languages: Lucene, XPath or JCR-XPath. The Lucene query language allows you to run powerful searches, including full text searches on the content and node properties. To run a Lucene search using the query() method, you must specify the StoreRef of the store you wish to search, the query language (using one of the static attributes defined on the SearchService interface - LANGUAGE_LUCENE for example) and the query as a String. The parameters can either be passed directly to the query() method, or be defined on a new SearchParameters object which is then passed to the query() method. The query results are returned as a ResultSet object.42 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Interface SearchService The following example runs a Lucene query using the PATH syntax to locate the “Company Home” by its absolute path. The getNodeRef(0) call is used to retrieve the first NodeRef from the ResultSet. In theory, the query should only return one result. SearchService searchService = serviceRegistry.getSearchService(); StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); ResultSet resultSet = searchService.query( storeRef, SearchService.LANGUAGE_LUCENE, "PATH:"/app:company_home""); NodeRef companyHome = resultSet.getNodeRef(0); For more information on using the SearchService and on query string syntax, see the Search page on the Alfresco Wiki.5. Using the NodeService to create a node The NodeService provides methods for operations on nodes and stores. Stores are created with createStore(). Nodes are created with createNode() and deleted with deleteNode(). Properties are set with either setProperty() or setProperties(). Properties are removed with removeProperty(). Aspects are applied with addAspect() and removed with removeAspect(). Associations are created and removed with createAssociation() and removeAssociation(). Associations can be navigated with getSourceAssocs() or getTargetAssocs(). Child associations can be navigated with getChildAssocs() and getParentAssoc(). Almost all NodeService methods take a NodeRef as an argument. A NodeRef is obtained either by navigation or from the results of a search. Otherwise a new NodeRef object can be created from the nodes unique UUID. API Development Course 43
    • Developing against the Alfresco Repository For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Interface NodeService a. Setting properties Properties are set on nodes using either the setProperty() or setProperties() methods. setProperty() allows a single property to be set, whilst setProperties() takes a Map of properties and sets all of the nodes properties at once. Each property is identified by its QName and its value must be Serializable. public void setProperty( NodeRef nodeRef, QName qname, Serializable value) public void setProperties( NodeRef nodeRef, Map<QName, Serializable> properties) nodeRef NodeRef of the node to set the property on.44 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository qname QName of the property to set. value Value of the property. The value must be Serializable. properties Map of all the properties of the node keyed by QName. The property QNames are usually defined as a static constants on the dictionary model interfaces. For example, the cm:name property is defined by the static constant ContentModel.PROP_NAME. The following example creates a Map containing the cm:name property that will be used in the next step: String name = "Foundation API sample (" + System.currentTimeMillis() + ")"; Map<QName, Serializable> contentProps = new HashMap<QName, Serializable>(); contentProps.put(ContentModel.PROP_NAME, name);b. Creating a node Nodes are created using the createNode() method. A node is created as a child of a parent node. The child association name and child association type have to be supplied as QName objects, as well as the QName of the type of node to create and optionally a Map of properties to set on the newly created node. public ChildAssociationRef createNode( NodeRef parentRef, QName assocTypeQName, QName assocQName, QName nodeTypeQName, Map<QName, Serializable> properties) parentRef NodeRef of the parent node. The created node will be one of its children. assocTypeQName QName of the type of association to create. This is used for verification against the data dictionary. assocQName QName of the association. nodeTypeQName QName of the node type. properties Optional Map of properties to set keyed by QName. The association and node type QName objects are usually defined as a static constants on the dictionary model interfaces. For example, the cm:contains association type is defined by the static constant ContentModel.ASSOC_CONTAINS and the cm:content node type is defined by the static constant ContentModel.TYPE_CONTENT. If a constant does not exist, a QName can be created using the QName.createQName() static method as in the example below. The createNode() method returns a ChildAssociationRef to the newly created child association. The NodeRef of the newly created node is obtained by calling the getChildRef() on the ChildAssociationRef object. API Development Course 45
    • Developing against the Alfresco Repository The following example creates a new node of type cm:content, using the standard cm:contains child association. The Map created in the previous step sets the cm:name property on the newly created node. NodeService nodeService = serviceRegistry.getNodeService(); ChildAssociationRef association = nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, name), ContentModel.TYPE_CONTENT, contentProps); NodeRef content = association.getChildRef(); c. Adding an aspect Aspects are applied to nodes using the addAspect() method. The node is identified by its NodeRef and the aspect by its QName. The property values are provided as a Map. public void addAspect( NodeRef nodeRef, QName aspectTypeQName, Map<QName, Serializable> aspectProperties) nodeRef NodeRef of the node to apply the aspect to. aspectTypeQName QName of the aspect to apply. aspectProperties Map containing a minimum of the mandatory properties required for the aspect. The aspect QNames are usually defined as a static constants on the dictionary model interfaces. For example, the cm:titled aspect is defined by the static constant ContentModel.ASPECT_TITLED. The following example applies the cm:titled aspect and sets the cm:title and cm:description properties: Map<QName, Serializable> titledProps = new HashMap<QName, Serializable>(); titledProps.put(ContentModel.PROP_TITLE, name); titledProps.put(ContentModel.PROP_DESCRIPTION, name); nodeService.addAspect(content, ContentModel.ASPECT_TITLED, titledProps); 6. Using the ContentService to write content The ContentService provides methods for reading, writing and transforming content. In order to read or write content from and to a node, you must first obtain a ContentReader or a ContentWriter via the getReader() and getWriter() methods respectively. Methods can then be used on the ContentReader or ContentWriter to read and write content. Both the ContentReader and the ContentWriter implement the methods defined by the ContentAccessor interface. These methods allow you to get and set information about the content, for example to set the mime type, the encoding or to get the size. The actual content is stored on the cm:content property (ContentModel.PROP_CONTENT) of each node. When requesting a ContentReader or a ContentWriter, the NodeRef needs to be supplied along with the ContentModel.PROP_CONTENT QName. The ContentService is also used for transforming content. A suitable transformer can be obtained for a given transformation (defined by a source and target mime type) using46 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repositorythe 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 ContentAccessorThe 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); API Development Course 47
    • Developing against the Alfresco Repository Once completed, the newly created node may be viewed via the Web client. The web client will need to be re-started after executing the sample to see the changes in effect.Other Foundation ServicesFileFolderService The FileFolderService provides methods specific to manipulating Alfresco defined content files and folders. The methods can be more convenient and easier to use than the equivalent NodeService and ContentService methods. Certain methods, such as create(), return a FileInfo object. A NodeRef can then be retrieved using the FileInfo.getNodeRef() method.48 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco RepositoryFor clarity, not all of the available methods are shown. For a complete description, pleaseconsult the Javadocs: • Interface FileFolderService • Interface FileInfo API Development Course 49
    • Developing against the Alfresco RepositoryJCR APIIntroduction The JCR API (Java Content Repository) specifies a standard, implementation independent API to access content repositories in Java. It is defined by the Java Specification Request (JSR) 170 as part of the Java Community Process (JCP). The official JSR-170 Specification can be found on the JCP web site at the following address: http://jcp.org/en/jsr/detail?id=170 Alfresco implements the JCR API against its own scalable repository and is actively contributing to the next version of JCR defined by the Java Specification Request (JSR) 283.JCR Compliance Levels The JSR 170 specification defines two compliance levels and a set of additional optional features which repositories of either level may support. Level 1 provides for read functions and includes: • Retrieval and traversal of nodes and properties • Reading the values of properties • Transient namespace remapping • Export to XML/SAX • Query facility with XPath syntax • Discovery of available node types • Discovery of access control permissions Level 2 adds additional write functions: • Adding and removing nodes and properties • Writing the values of properties • Persistent namespace changes • Import from XML/SAX • Assigning node types to nodes Optionally, any combination of the following features may be added to an implementation of either level: • Transactions • Versioning • Observation (Events) • Locking • SQL syntax for queryJCR Repository Model Like the Alfresco repository, a JCR repository consists of one or more workspaces, each of which contains a tree of items. An item is either a node or a property. Each node may have zero or more child nodes and zero or more child properties. There is a single root node per workspace, which has no parent. Unlike the Alfresco repository, all other nodes have only one parent. Properties have one parent (a node) and cannot have children; they are the leaves of the tree. All of the actual content in the repository is stored within the values of the properties.50 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository Any item in the tree hierarchy can be identified by either an absolute or a relative path using a Unix style path syntax. The path / refers to the root node of a workspace and the path / app:company_home refers to the “Company Home” space in the standard Alfresco SpacesStore workspace. The special paths “.” and “..” (meaning respectively, “this” and “parent”) are also supported.FirstJCRClient Walkthrough Before getting started, you should be familiar with the Introduction on page 34. The sample uses several of the basic JCR APIs, including Repository, Session and Node. After initialising the Spring Application Context and starting the repository in embedded mode, we will use the Spring getBean() method to access the JCR.Repository. After logging into the JCR repository, we will navigate to the “Company Home” node. We will then create a new node with properties, add an aspect and write some content. Finally, we will mix the JCR API calls with the Alfresco Foundation Services API calls to set the content mime type. The sample is wrapped by an implicit JCR transaction. 1. Getting the JCR.Repository The JCR repository as a whole is represented by a Repository object. JSR-170 does not dictate how to obtain the Repository object. In Alfresco, the JSR repository is a Spring bean called JCR.Repository. The JCR.Repository bean is defined in jcr-api-context.xml. It has one configuration parameter; the default workspace name. This is used when a JCR client performs a login without specifying the workspace. The default workspace is SpacesStore, the same one used by the Alfresco Web Client. <bean id="JCR.Repository" class="org.alfresco.jcr.repository.RepositoryImpl" init-method="init"> <property name="serviceRegistry"> <ref bean="ServiceRegistry"/> </property> <property name="importerComponent"> <ref bean="importerComponent"/> </property> <property name="defaultWorkspace"> <value>SpacesStore</value> </property> </bean> The following example uses the Spring getBean() method to access the JCR Repository: ApplicationContext context = new ClassPathXmlApplicationContext("classpath:alfresco/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 Repository object. The client must supply a Credentials object and optionally a workspace name. Behind the scenes, Alfresco uses the authentication system of the Alfresco repository which by default is Alfrescos own, but it could also be NTLM, LDAP or your own depending on how the repository has been configured. If a workspace is not provided, the default as defined earlier will be used. The Session returned from login is tied to the workspace and allows read and write operations upon that workspace. By default, the Session is also backed by a transaction. Work performed API Development Course 51
    • Developing against the Alfresco Repository within the session will not be committed until Session.save() is called. For more information, see JCR Transactions on page 53. Session session = repository.login(new SimpleCredentials("admin", "admin".toCharArray())); 3. Accessing the Company Home JSR-170 provides two methods for accessing nodes: traversal access and direct access. Traversal access involves walking the content tree using relative paths, while direct access allows you to jump directly to a node with either an absolute path or, if the node is referenceable, with a UUID. Traversal access is available from any Node object via the Node.getNode() and Node.getProperty() methods. The root node of a workspace can be obtained by using the getRootNode() method on the session object. Direct access is available to any item in the repository (node or property) by passing an absolute path to the Session.getItem() method. Alternatively direct access to a node is available by passing the UUID of the node to the Session.getNodeByUUID() method. The UUID of a node can be retrieved using the Node.getUUID() method. The following example gets the workspace root node, then uses traversal access with a relative path to “walk” to the “Company Home” node: Node rootNode = session.getRootNode(); Node companyHome = rootNode.getNode("app:company_home"); 4. Creating a new Node Child nodes are created using the Node.addNode() method. Properties are added to nodes using the Node.setProperty() method. The addNode() method takes the qualified relative path of the new node and optionally the node type. The setProperty() methods takes the property name and its value. The value arguments accepts most types. For a complete description of the Node interface, see the official Javadocs: Interface Node. String name = "JCR sample (" + System.currentTimeMillis() + ")"; Node content = companyHome.addNode("cm:" + name, "cm:content"); content.setProperty("cm:name", name); 5. Adding an aspect Aspects are added to nodes using the Node.addMixin() method. The aspect properties are set using the Node.setProperty() as above. The following example adds the cm:titled aspect: content.addMixin("cm:titled"); content.setProperty("cm:title", name); content.setProperty("cm:description", name); 6. Writing some content The actual content in a JCR repository is stored within the values of the properties. In the case of Alfresco the content is stored on the cm:content property. The following example writes “The quick brown fox jumps over the lazy dog” as the nodes content: content.setProperty("cm:content", "The quick brown fox jumps over the lazy dog"); 7. Mixing JCR and Alfresco APIs The JCR API covers a wide variety of repository functions, however, there will always be scenarios where custom Alfresco capabilities are required. For these scenarios, it is52 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository possible to mix JCR API calls with Alfresco Foundation Services API calls and have them controlled in the same transaction. In the FirstJCRClient sample, the mime type is set using the Alfresco NodeService: ServiceRegistry serviceRegistry = (ServiceRegistry) context.getBean(ServiceRegistry.SERVICE_REGISTRY); NodeService nodeService = serviceRegistry.getNodeService(); NodeRef nodeRef = JCRNodeRef.getNodeRef(node); ContentData content = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); content = ContentData.setMimetype(content, mimeType); nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, content); The JCRNodeRef.getNodeRef() method converts a JCR Node to an Alfresco NodeRef. 8. Saving the session Work performed on the session is not committed until Session.save() is called. This also commits the transaction. session.save(); 9. Logging out from the session This logs out of the session, any uncommitted changes will be lost session.logout();JCR Transactions Alfresco provides Transaction capabilities across all of its persistence based services. The JCR API is built upon these services and as such Alfresco provides a fully transactional JCR interface.Implicit Transactions By default, an Alfresco JCR Session is backed by a transaction. Work performed within the session will not be committed until Session.save() is called. A typical interaction sequence is as follows... Login and establish a session (this starts a transaction): Session session = repository.login(credentials); Perform work on the session: Node node = session.getRootNode(); Node childNode = node.addNode(....); childNode.setProperty(...); Save the session (this commits the transaction): session.save(); Continue to perform more work on the session and save again: session.save(); Logout from the session (this rolls back the transaction - unsaved items are lost): session.logout(); It is not just the JCR calls that are bound to the transaction. Calls to the native Alfresco Foundation Services API are also included. So, if you happen to mix JCR and Alfresco calls between Session.login() and Session.logout(), they are all bound to the same transaction and controlled by the JCR session. API Development Course 53
    • Developing against the Alfresco RepositoryExplicit Transactions It is also possible to explicitly control transaction boundaries using Alfrescos TransactionService. Using this technique, you can explicitly control when JCR updates are committed. An example of explicit transaction management follows... Get a user transaction from the Alfresco TransactionService and start the transaction: UserTransaction trx = serviceRegistry.getTransactionService().getUserTransaction; trx.begin(); Login and establish a session (the explicit transaction started above is inherited): Session session = repository.login(credentials); Perform work on the session and save the session (do not commit just yet): session.save(); Perform more work on the session and save again (still no commit): session.save(); Now commit the transaction: trx.commit(); In case of an error, rollback: trx.rollback(); For more information on Alfresco user transactions, see Alfresco User Transactions on page 65.54 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco RepositoryWeb Services APIIntroduction The Web Services API is an easy to understand and develop against API. Accessible from many client languages, it is designed for remote repository access. Web Services are particularly suitable for composite applications and business processes.Available Web Services The following Alfresco Web Services are available: Authentication login and logout Repository query and model manipulation Content content manipulation Authoring collaborative content creation Classification apply classifications and categories Access Control roles, permissions & ownership Action manage actions and rules Administration user management, export & import Dictionary model descriptionsAccess to Web Services in Java The WebServiceFactory provides access to the repository services stubs. Each available Web Service has its own get method. For example, to access the Repository Web Service, use the WebServiceFactory.getRepositoryService() static method. API Development Course 55
    • Developing against the Alfresco Repository Static attributes on the WebServiceFactory define the default endpoint address, the location of the Web Services configuration property file and the name of the property that defines the repository location: private static final String DEFAULT_ENDPOINT_ADDRESS = "http://localhost:8080/ alfresco/api"; private static final String PROPERTY_FILE_NAME = "alfresco/ webserviceclient.properties"; private static final String REPO_LOCATION = "repository.location"; The AuthenticationUtils and Utils classes provide common utility methods useful when using the Web Services API.Web Services Data Types Each Web Service method relies upon data types for input and output messages. The formal definition of the available data types is defined in the Web Service Data Types XML Schema. The Web Service Data Types XML Schema can also be found in the wsdl/types.xsd file.Example Data TypesContent Manipulation Language (CML) CML (Content Manipulation Language) provides a set of simple statements for updating a Repository. The statements are described in the Web Service CML XML Schema and as such can either be represented as XML documents or mapped to other representations such as Java objects or appropriate client language binding. Multiple statements may be collected together into a single document.56 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco RepositoryFirstWebServiceClient Walkthrough 1. Logging into the repository (start a session) To start a session, the static startSession() method can be used on the AuthenticationUtils class. public static void main(String[] args) throws Exception { // Start the session AuthenticationUtils.startSession("admin", "admin"); API Development Course 57
    • Developing against the Alfresco Repository try { ... } catch(Throwable e) { System.out.println(e.toString()); } finally { // End the session AuthenticationUtils.endSession(); System.exit(0); } } 2. Creating a reference to Company Home A ParentReference data type is made up of a Reference and a ChildAssociation: To create a ParentReference to Company Home: Store storeRef = new Store(Constants.WORKSPACE_STORE, "SpacesStore"); ParentReference companyHomeParent = new ParentReference( storeRef, null, "/app:company_home", Constants.ASSOC_CONTAINS, null); We can set the childName on the ChildAssociation as follows: String name = "Web Services sample (" + System.currentTimeMillis() + ")"; companyHomeParent.setChildName("cm:" + name); 3. Creating a new node The create CML statement is used to create a new node.58 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository The create CML statement is constructed with a new CMLCreate object. NamedValue[] contentProps = new NamedValue[1]; contentProps[0] = Utils.createNamedValue(Constants.PROP_NAME, name); CMLCreate create = new CMLCreate( "1", companyHomeParent, null, null, null, Constants.TYPE_CONTENT, contentProps); Assign "1" as a local id, so we can refer to it in subsequent CML statements within the same CML block.4. Adding an aspect The addAspect CML statement is used to add an aspect. The addAspect CML statement is constructed with a new CMLAddAspect object: NamedValue[] titledProps = new NamedValue[2]; titledProps[0] = Utils.createNamedValue(Constants.PROP_TITLE, name); titledProps[1] = Utils.createNamedValue(Constants.PROP_DESCRIPTION, name); CMLAddAspect addAspect = new CMLAddAspect( Constants.ASPECT_TITLED, titledProps, null, "1"); API Development Course 59
    • Developing against the Alfresco Repository 5. Constructing a CML Block Construct CML Block: CML cml = new CML(); cml.setCreate(new CMLCreate[] {create}); cml.setAddAspect(new CMLAddAspect[] {addAspect}); 6. Issuing a CML update Issue CML statement via Repository Web Service and retrieve result: Batching of multiple statements into a single web call UpdateResult[] result = WebServiceFactory.getRepositoryService().update(cml); Reference content = result[0].getDestination(); 7. Writing some content ContentServiceSoapBindingStub contentService = WebServiceFactory.getContentService(); String text = "The quick brown fox jumps over the lazy dog"; ContentFormat contentFormat = new ContentFormat("text/plain", "UTF-8"); Content contentRef = contentService.write( content, Constants.PROP_CONTENT, text.getBytes(), contentFormat); System.out.println("Content Length: " + contentRef.getLength()); 8. Logging out (ending the session) End the session: finally { AuthenticationUtils.endSession(); System.exit(0); }60 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco RepositorySeparating Concerns using AOPPublic Services and AOP proxies The public services defined in public-services-context.xml are in fact Spring AOP proxies. For example, the public ContentService bean defined in public-services-context.xml is an AOP proxy to the “target” service defined by the contentService bean found in content- services-context.xml. The AOP proxy allows cross cutting concerns such as security and transaction management to be implemented in a non-invasive, declarative way. All service method calls via the ContentService bean defined in public-services-context.xml are subject to security checks and transaction management whilst those via the contentService bean defined in content-services-context.xml are subject to none! All public service Spring beans have ids that begin with an uppercase letter. Public services are subject to security checks and transaction management. Beans with equivalent names that begin with a lowercase letter are subject to none. All public services AOP proxies are defined using a Spring ProxyFactoryBean. The ContentService example below is taken from public-services-context.xml: <bean id="ContentService" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="proxyInterfaces"> <value>org.alfresco.service.cmr.repository.ContentService</value> </property> <property name="target"> <ref bean="contentService"/> </property> <property name="interceptorNames"> <list> <idref local="ContentService_transaction"/> <idref local="AuditMethodInterceptor"/> <idref local="exceptionTranslator"/> <idref bean="mlContentInterceptor"/> <idref bean="ContentService_security"/> </list> </property> The ProxyFactoryBean introduces a level of indirection so that objects referencing the ContentService bean do not see the ProxyFactoryBean but the object defined by the target property, in our case, the contentService bean. The proxyInterfaces property, defines an array of interfaces implemented by the target class and the interceptorNames property a list of advisor, interceptor or other advice names to apply. Ordering is significant, the first interceptor in the list will be the first to be able to intercept the method call. The ContentService_transaction interceptor is a reference to a local bean in public- services-context.xml that manages transactions for calls to the ContentService. The ContentService_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 caller must first be authenticated. This can be done by logging in using a username and password or using a ticket. A ticket can be requested after logging in and can be used, under certain conditions, to re-validate a user. Once authenticated, access control allows or denies a user from calling public service methods on a particular object by checking if the authenticated user, or any of the authorities granted to that user, has a particular permission or permission group. For example, to call the readProperties() method on the NodeService, the authenticated user must have read access API Development Course 61
    • Developing against the Alfresco Repository to the properties of the node. On the SearchService, the results from queries are restricted to return only the nodes for which a user has read permission. The public services are the only services to have access restrictions. Security is enforced around every public service method call and can be based on: • the method called, • the objects provided as arguments to the call, • the objects returned by the call. Since the Web Client, JCR API, Web services API, CIFS, WebDav, FTP etc. all use public services behind the scenes, the same security enforcement always applies.Acegi Security Configuration Security is enforced using the Acegi AOP Alliance (method invocation) security interceptor. Security interceptors are defined for each public service as Spring beans in public-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. The authenticationManager is a reference to the Acegi authentication manager bean defined in authentication-services-context.xml. The accessDecisionManager asks a list of “voters” if access should be allowed or not. Voters allow access based on the authorities that the current authorised user has or based on node access control. The afterInvocationManager enforces security on objects returned by methods. The objectDefinitionSource defines security attributes associated with each method. A list of attributes can be defined as a comma separated list. Pre-conditions are enforced before method invocation and post-conditions on objects returned by methods. In the followng, ? represents an authority (user name or group), # a method argument index and * a string representation of a permission. Pre-conditions take one of the following forms: ACL_METHOD.? Access to the method is restricted to those with the given authority in alfresco. This could be a user name or group. Dynamic authorities are not supported.62 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository ACL_NODE.#.* Access control is restricted to users who have the the specified permission for the node on the identified argument. If the argument is a NodeRef it will be used; if it is a StoreRef then the root node for the store will be used; if it is a ChildAssociationRef then the child node will be used. ACL_PARENT.#.* Access control is restricted to users who have the the specified permission for the parent of the node on the identified argument. If the argument is a NodeRef the parent of the node will be used; if it is a ChildAssociationRef then the parent node will be used. ROLE_... Check for an Acegi authority starting with ROLE_ GROUP_... Check for an Acegi authority starting with GROUP_ If more than one ACL_NODE.#.* or ACL_PARENT.#.* entry is present then all the permissions must be available for the method to execute. ACL_ALLOW can be used to give access to all. ROLE_ADMINISTRATOR can be used to grant access to administrator related methods. Post-conditions take the forms: AFTER_ACL_NODE.* Similar to ACL_NODE.#.* but the restriction applies to the return argument. AFTER_ACL_PARENT.* Similar to ACL_PARENT.#.* but the restriction applies to the return argument. The supported return types are ChildAssociationRef, FileInfo, NodeRef, StoreRef, ResultSet; Collections and arrays of StoreRef, NodeRef, ChildAssociationRef, and FileInfo. The post-conditions will create access denied exceptions for return types like NodeRef, StoreRef, ChildAssociationRef. For collections and arrays, the members will be filtered based on the access conditions.NodeService Security Examples The following examples are taken from the NodeService_security method security interceptor bean. Only administrators can create stores: NodeService.createStore=ACL_METHOD.ROLE_ADMINISTRATOR Read permission is required on the node specified as the first argument to the exist() method call: NodeService.exists=ACL_NODE.0.sys:base.Read Creating a new node requires the create children permission on the parent node: NodeService.createNode=ACL_NODE.0.sys:base.CreateChildren Adding an aspect to a node requires write permission on the node: NodeService.addAspect=ACL_NODE.0.sys:base.Write Deleting a node requires the delete permission on the node: NodeService.deleteNode=ACL_NODE.0.sys:base.Delete Accessing the properties of a node requires the permission to read properties: NodeService.getProperties=ACL_NODE.0.sys:base.ReadProperties Setting all properties on a node required the permission to write properties: NodeService.setProperties=ACL_NODE.0.sys:base.WriteProperties API Development Course 63
    • Developing against the Alfresco Repository Reading child associations requires the read children permission on the parent node and the read permission on the children nodes: NodeService.getChildAssocs=ACL_NODE.0.sys:base.ReadChildren, AFTER_ACL_NODE.sys:base.ReadTransaction Management All repository foundation services are transactional. Transactions are controlled either: • implicitly via Spring declarative transaction demarcation or • explicitly via Alfresco user transactions.Spring Declarative Transaction Demarcation Spring declarative transaction demarcation wraps the invocation of every public service method call in its own transaction. The transactions are defined declaratively in Spring configuration files and not in your Java code. In most cases, declarative transactions are preferred to user transactions since they are less invasive. Declarative Transaction Demarcation uses AOP and a Spring transaction interceptor. Example TransactionInterceptor configuration for the NodeService taken from 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. By default, it will only trigger a rollback if an unchecked exception is thrown, or if the transaction has been marked rollback-only by the application. Specific rollback policies can be configured per method call and per exception. Transaction Attributes The transaction attributes are defined on the TransactionInterceptor as Properties. The transaction attributes are defined on a per method basis. For each property, the method name is the key and the transaction attribute descriptor is the value. For the NodeService, the transaction attributes are as follows: <property name="transactionAttributes"> <props> <prop key="exist*">${server.transaction.mode.readOnly}</prop> <prop key="get*">${server.transaction.mode.readOnly}</prop> <prop key="has*">${server.transaction.mode.readOnly}</prop> <prop key="*">${server.transaction.mode.default}</prop> </props> </property> In this example, the method names contain the * wildcard character, to match all methods starting with a specific keyword.64 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository The transaction attribute descriptors are defined as properties in the domain/ transaction.properties file: # # Server read-only or read-write modes # server.transaction.mode.readOnly=PROPAGATION_REQUIRED, readOnly # the properties below should change in tandem #server.transaction.mode.default=PROPAGATION_REQUIRED, readOnly #server.transaction.allow-writes=false server.transaction.mode.default=PROPAGATION_REQUIRED server.transaction.allow-writes=true server.transaction.max-retries=20 A transaction attribute descriptor has the following syntax: PROPAGATION_NAME, ISOLATION_NAME, readOnly, timeout_NNNN, +Exception1, - Exception2 The tokens can be in any order. Only the propagation code is required. The propagation and isolation codes must use the names of the constants defined in the TransactionDefinition class (see related link below). Timeout values are in seconds. If no timeout is specified, the transaction manager will apply a default timeout specific to the particular transaction manager. A “+” before an exception name substring indicates that transactions should commit even if this exception is thrown; a “-” that they should roll back. Propagation and isolation codes used in Alfresco: PROPAGATION_REQUIRED Support a current transaction; create a new one if none exists. PROPAGATION_REQUIRES_NEW Create a new transaction, suspending the current transaction if one exists. PROPAGATION_NOT_SUPPORTED Do not support a current transaction; rather always execute non-transactionally. ISOLATION_DEFAULT Use the default isolation level of the underlying datastore. TIMEOUT_DEFAULT Use the default timeout of the underlying transaction system, or none if timeouts are not supported. Transaction Manager The TransactionInterceptor delegates the actual transaction handling to a PlatformTransactionManager instance, defined by the transactionManager property. In the case of Alfresco, the transaction manager is a HibernateTransactionManager. The transactionManager bean is defined in the hibernate-context.xml file: <!-- create a transaction manager --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="transactionSynchronizationName"> <value>SYNCHRONIZATION_ALWAYS</value> </property> <property name="sessionFactory"> <ref bean="sessionFactory" /> </property> </bean>Alfresco User Transactions By default, all repository foundation services are transactional and each invocation of a service method is wrapped in its own transaction. These transactions are defined declaratively in API Development Course 65
    • Developing against the Alfresco Repository Spring configuration files and not in your Java code. In most cases, declarative transactions are preferred to user transactions since they are less invasive. There are situations, however, when user transactions do need to be used explicitly in your code. An Alfresco user transaction is accessed via the TransactionService. An example using the ServiceRegistry is as follows: UserTransaction trx = serviceRegistry.getTransactionService().getUserTransaction(); With a UserTransaction in hand, it is possible to mark the beginning and end of a transaction. Any service calls within the begin and end are thus forced to be included in the same transaction. For example, the following two NodeService calls are wrapped in the same transaction. Without the user transaction, the default behaviour would be for each NodeService call to be in its own transaction. NodeService nodeService = serviceRegistry.getNodeService(); try { trx.begin() nodeService.createNode(...); nodeService.createNode(...); trx.commit(); } catch(Throwable e) { if (trx.getStatus() == Status.ACTIVE) { try { trx.rollback(); } catch(Throwable ee) { e.printStackTrace(); } } } Although the example shows the usage of one service, any mixture of Alfrescos public services can be pulled into the same transaction. It is important to note that a UserTransaction cannot be re-used. That is, once a commit or rollback has been issued, a new UserTransaction has to be retrieved (via getUserTransaction()) to begin another. TransactionService As we have already seen, an Alfresco user transaction is accessed via the TransactionService.66 Alfresco Enterprise Edition Version 3.2
    • Developing against the Alfresco Repository For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Interface TransactionServiceThe interface defines four methods for accessing a user transaction:getUserTransaction(); This method retrieves a UserTransaction with the PROPAGATION_REQUIRED attribute that supports transaction propagation.getUserTransaction(boolean readOnly); Used to request a read only transaction that supports transaction propagation.getNonPropagatingUserTransaction(); This method retrieves a UserTransaction with the PROPAGATION_REQUIRES_NEW attribute that ensures a new transaction is created. Any enclosing transaction is not propagated. When the transaction is started, the current transaction will be suspended and a new one started.getNonPropagatingUserTransaction(boolean readOnly); Used to request a read only transaction, also ensuring that a new transaction is created.RetryingTransactionHelperThe 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 • BatchUpdateExceptionThe RetryingTransactionHelper is retrieved from the TransactionService via thegetRetryingTransactionHelper() method. API Development Course 67
    • Developing against the Alfresco Repository A unit of work is defined as an instance of the RetryingTransactionCallback inner class. The doInTransaction() method is called to run the unit of work in a transaction. The optional boolean readOnly argument can be used to request a read only transaction. The optional boolean requiresNew argument can be used to force a new transaction (will retrieve a user transaction from the TransactionService via the getNonPropagatingUserTransaction() method. Example taken from the FirstFoundationClient SDK sample: TransactionService transactionService = serviceRegistry.getTransactionService(); RetryingTransactionCallback<Object> exampleWork = new RetryingTransactionCallback<Object>() { public Object execute() throws Exception { doExample(serviceRegistry); return null; } }; transactionService. getRetryingTransactionHelper().doInTransaction(exampleWork);68 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryExtending the Alfresco Repository API Development Course 69
    • Extending the Alfresco RepositoryRepository PoliciesIntroduction Repository policies are similar to events. Each service defines its own set of policies. For example the Content Service defines two policies: • OnContentUpdatePolicy is fired when the content is updated on a node, • OnContentReadPolicy is fired when the content is read on a node. A custom method or “behaviour” can be registered against a policy and will be called automatically when the policy is fired. This enables tasks such as maintaining the last modified date on a node or creating a new version and incrementing the version number on a node to be automated. The event-driven processing paradigm is similar to that used in traditional user interfaces.Available Policies The available policies are defined as interfaces for each service. The Node Service defines more than twenty policies, including BeforeCreateNodePolicy, OnUpdatePropertiesPolicy and OnAddAspectPolicy, the other services each define their own specific policies. The policies are usually defined on an interface named after the service. For example, the Node Service policies are defined on the NodeServicePolicies interface and the Content Service policies are defined on the ContentServicePolicies interface: Classes interested in certain policies must implement the corresponding interfaces and methods. Each policy method (or behaviour) defines its own specific list of arguments. For example: • A behaviour registered against the BeforeCreateNodePolicy is called before a node is created. The beforeCreateNode() method receives the NodeRef of the parent of the node being created, the QName of the association type (usually cm:contains), the QName of the new association and the node type of the node being created. • A behaviour registered against the OnCreateNodePolicy is called when a node is created. The onCreateNode() method receives the ChildAssociationRef to the node created.70 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryPolicy Types Each policy extends one of three policy types: • Class policy • Property policy • Association policy Class policies are for events related to content types or aspects, Property policies are for events related to properties and Association policies for events related to associations. Most policies extend the Class policy interface, a few policies extend the Association policy interface and, at the time of writing, none extend the Property policy.Custom Aspect with Behaviour Howto Before starting the tutorial, you should be familiar with Alfresco Content Models and the Introduction on page 34. In the tutorial we are going to define a new Content Hits aspect with behaviour that will automatically keep a running count of the number of times the content on a node has been updated and read. The example demonstrates how the counters can be incremented after the content update or read transactions have been done. The examples in this tutorial are taken from the SDK CustomAspect sample. 1. Defining the Content Hits model The Content Hits aspect is defined in a custom model contentHitsModel.xml: <aspect name="ch:contentHits"> <title>Content Hits</title> API Development Course 71
    • Extending the Alfresco Repository <properties> <property name="ch:countStartedDate"> <type>d:date</type> <mandatory>true</mandatory> </property> <property name="ch:updateCount"> <type>d:int</type> <default>0</default> </property> <property name="ch:readCount"> <type>d:int</type> <default>0</default> </property> </properties> </aspect> The ch:countStartedDate will be set when the aspect is added to a node. The ch:updateCount and ch:readCount properties will be incremented for each content update or read respectively. 2. Implementing the aspect behaviour class This ContentHitsAspect class contains the behaviour behind the ch:contentHits aspect. a. Creating the aspect behaviour class The Content Hits aspect behaviour class is interested in the following three events: • when the Content Hits aspect is added to a Node; • when the content of a Node with the Content Hits aspect is updated (written); • when the content of a Node with the Content Hits aspect is read. The class needs to implement the three corresponding polices: public class ContentHitsAspect implements ContentServicePolicies.OnContentReadPolicy, ContentServicePolicies.OnContentUpdatePolicy, NodeServicePolicies.OnAddAspectPolicy b. Writing the constructor The Content Hits example uses a Transaction Listener to increment the counters after the content update or read transactions have been committed. The Transaction Listener is instantiated by the constructor: private TransactionListener transactionListener; /** * Default constructor for bean construction */ public ContentHitsAspect() { this.transactionListener = new ContentHitsTransactionListener(); } c. Bind the behaviours A behaviour is an encapsulated piece of logic that may be bound to a policy. The logic may be expressed in either Java or JavaScript.72 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryWhen creating a new JavaBehaviour instance, you must provide the following:public JavaBehaviour(Object instance, String method, NotificationFrequency frequency)instance the object instance holding the methodmethod the method namefrequency one of three possible values defining when the behaviour should be notified: EVERY_EVENT, FIRST_EVENT or TRANSACTION_COMMITThe 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);policy the policy nameclassRef QName of type or aspect concerned by the policybehaviour a 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. API Development Course 73
    • Extending the Alfresco Repository In the Content Hits example, the Spring initialise() method is used to bind the behaviours to policies: public void initialise() { this.policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onAddAspect"), ASPECT_CONTENT_HITS, new JavaBehaviour(this, "onAddAspect", NotificationFrequency.FIRST_EVENT)); this.policyComponent.bindClassBehaviour( ContentServicePolicies.ON_CONTENT_READ, ASPECT_CONTENT_HITS, new JavaBehaviour(this, "onContentRead", NotificationFrequency.TRANSACTION_COMMIT)); this.policyComponent.bindClassBehaviour( ContentServicePolicies.ON_CONTENT_UPDATE, ASPECT_CONTENT_HITS, new JavaBehaviour(this, "onContentUpdate", NotificationFrequency.TRANSACTION_COMMIT)); } d. Writing the onAddAspect policy behaviour The onAddAspect policy behaviour will be called when the ch:contentHits aspect is added to a node. It receives the NodeRef of the node concerned and the QName of the added aspect. In the example, the added aspect will always be ch:contentHits since the behaviour has only been registered for that specific aspect. The onAddAspect() behaviour sets the count start date/time when the ch:contentHits aspect is added: public void onAddAspect(NodeRef nodeRef, QName aspectTypeQName) { this.nodeService.setProperty( nodeRef, PROP_COUNT_STARTED_DATE, new Date()); } e. Writing the onContentRead policy behaviour The onContentRead policy behaviour will be called when the content property of a node with the ch:contentHits aspect is read. It receives the NodeRef of the node being read. As we have seen, the example uses a Transaction Listener to increment the counters after the content update or read transactions have been committed. The Transaction Listener is bound to the current transaction using the static bindListener() method on the AlfrescoTransactionSupport class and passing the Transaction Listener created earlier. The list of nodes read is stored as a resource against the current transaction using the static bindResource() method on the AlfrescoTransactionSupport class. The nodes are stored as a Set<NodeRef> using the KEY_CONTENT_HITS_READS key. The onContentRead() behaviour adds the NodeRef of the node being read to the list of nodes that require read count increments after the transaction completes: public void onContentRead(NodeRef nodeRef) { // Bind the listener to the transaction AlfrescoTransactionSupport.bindListener(transactionListener);74 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository // Get the set of nodes read @SuppressWarnings("unchecked") Set<NodeRef> readNodeRefs = (Set<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_READS); if (readNodeRefs == null) { readNodeRefs = new HashSet<NodeRef>(5); AlfrescoTransactionSupport.bindResource(KEY_CONTENT_HITS_READS, readNodeRefs); } readNodeRefs.add(nodeRef); }f. Writing the onContentUpdate policy behaviour. The onContentUpdate policy behaviour will be called when the content property of a node with the ch:contentHits aspect is updates. It receives the NodeRef of the node being updated and a boolean to indicate if the content is new content or not. The list of nodes updated is stored as a resource against the current transaction using the the KEY_CONTENT_HITS_WRITES key. The onContentUpdate() behaviour adds the NodeRef of the node being updated to the list of nodes that require write count increments after the transaction completes: public void onContentUpdate(NodeRef nodeRef, boolean newContent) { // Bind the listener to the transaction AlfrescoTransactionSupport.bindListener(transactionListener); // Get the set of nodes written @SuppressWarnings("unchecked") Set<NodeRef> writeNodeRefs = (Set<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_WRITES); if (writeNodeRefs == null) { writeNodeRefs = new HashSet<NodeRef>(5); AlfrescoTransactionSupport.bindResource(KEY_CONTENT_HITS_WRITES, writeNodeRefs); } writeNodeRefs.add(nodeRef); }g. Writing the Transaction Listener The Transaction Listener is bound the the current transaction by the onContentRead() or onContentUpdate() behaviour methods. The afterCommit() method is called on the Transaction Listener after the transaction has committed. In the example, the Transaction Listener is defined as an inner class. It implements the afterCommit() method. private class ContentHitsTransactionListener extends TransactionListenerAdapter { public void afterCommit() { ... } } The afterCommit() method retrieves the list of nodes stored as a resource on the transaction by the onContentRead() behaviour: public void afterCommit() { API Development Course 75
    • Extending the Alfresco Repository Set<NodeRef> readNodeRefs = (Set<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_READS); if (readNodeRefs != null) { for (NodeRef nodeRef : readNodeRefs) { Runnable runnable = new ContentHitsReadCountIncrementer(nodeRef); threadExecuter.execute(runnable); } } ... } The Content Hits read count (ch:readCount) is incremented for each node in the list. The afterCommit() method retrieves the list of nodes stored as a resource on the transaction by the onContentUpdate() behaviour: public void afterCommit() { .... Set<NodeRef> writeNodeRefs = (Set<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_CONTENT_HITS_WRITES); if (writeNodeRefs != null) { for (NodeRef nodeRef : readNodeRefs) { Runnable runnable = new ContentHitsWriteCountIncrementer(nodeRef); threadExecuter.execute(runnable); } } } The Content Hits update count (ch:updateCount) is incremented for each node in the list. h. Writing the Spring dependency injection setter methods The ContentHitsAspect class uses several services that can be injected via Spring: private 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 model76 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository The Content Hits model is registered with the following Spring bean definition taken from contents-hits-context.xml: <bean id="contentHits.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap"> <property name="models"> <list> <value>org/alfresco/sample/contentHitsModel.xml</value> </list> </property> </bean>4. Registering the aspect behaviour The ContentHitsAspect behaviour class is registered with the following Spring bean definition taken from contents-hits-context.xml: <bean id="contentHitsAspect" class="org.alfresco.sample.ContentHitsAspect" init-method="initialise"> <property name="nodeService"> <ref bean="nodeService"/> </property> <property name="policyComponent"> <ref bean="policyComponent"/> </property> <property name="policyFilter"> <ref bean="policyBehaviourFilter"/> </property> <property name="transactionService"> <ref bean="transactionService"/> </property> <property name="threadExecuter"> <ref bean="threadPoolExecutor"/> </property> </bean> The initialise() method will be called once the bean has been instantiated and the properties set.5. Configuring the property sheet In order to see the Content Hits aspect properties in the Web Client, the property sheet has to be configured as in the example web-client-config-custom.xml: <config evaluator="aspect-name" condition="ch:contentHits"> <property-sheet> <show-property name="ch:countStartedDate" read-only="true" show-in-edit-mode="false"/> <show-property name="ch:updateCount" read-only="true" show-in-edit-mode="false"/> <show-property name="ch:readCount" read-only="true" show-in-edit-mode="false" /> </property-sheet> </config>6. Packaging and deploying In order to deploy a custom aspect with behaviour to the Alfresco repository, the following files need to be packaged: • the compiled custom aspect behaviour class; • the Spring configuration file (contents-hits-context.xml); • if required, the custom dictionary model (contentsHitModel.xml); API Development Course 77
    • Extending the Alfresco Repository • the Web Client custom configuration file containing the aspect property sheet definition (web-client-config-custom.xml). The compiled class needs to be exported to a JAR file. The files then need to be deployed to the following directories in the Alfresco repository: • the JAR file to the WEB-INF/lib directory; • all other files to the WEB-INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.78 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryRepository ActionsIntroduction An action is a unit of work that is performed against a node. For example, moving a node, copying a node, checking a node in, transforming the contents of a node, etc. Many actions already exist, however it is possible to add you own custom actions in a few easy steps. An action has to implement the org.alfresco.repo.action.executer.ActionExecuter interface. The ActionExecuter interface can be implemented directly, however it is best to extend the abstract class ActionExecuterAbstractBase that has been written to provide basic services for action executer implementations. Only two methods need implementing when deriving from the abstract superclass. ActionExecuterAbstractBase is presented in detail in the Repository Action Howto on page 79. For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: • Interface ActionExecuter • Class ActionExecuterAbstractBaseRepository Action Howto Before starting the tutorial, you should be familiar with Alfresco Content Models and the Introduction on page 34. This is the first part of a two part tutorial. The first part shows you how to create a no-parameter action and incorporate it into the web client. The second part (Repository Action with Parameters Howto on page 85) shows you how to add a parameter to the action. In the first part of the tutorial we are going to add a Web 2.0 style tagging feature to the web client. We will define a taggable aspect which will be applied via the custom tag action in the repository. We will then configure the web client to show the taggable aspect in its property sheet. The examples in this tutorial are taken from the Alfresco SDK TaggingSample. 1. Creating a “tags” custom model A custom model is created to hold the new aspect and user assigned tags. The model uses a prefix of tag, the aspect is called tag:taggable and the property is called tag:tags. API Development Course 79
    • Extending the Alfresco Repository In the example, the tags model is defined in the tagsModel.xml file. <!-- Definition of new Taggable Aspect --> <aspect name="tag:taggable"> <title>Taggable</title> <properties> <property name="tag:tags"> <title>Tags</title> <type>d:text</type> <multiple>true</multiple> </property> </properties> </aspect> 2. Implementing the action executer class a. Creating the TagActionExecuter class Action executers generally extend an abstract class called ActionExecuterAbstractBase. The abstract superclass provides basic services for ActionExecuter implementations. It also introduces a new abstract method executeImpl() that is called from execute() to do the actual work. The only two methods that require implementing when deriving from the abstract superclass are addParameterDefinitions() and executeImpl(). The name of the action executer is defined by the static NAME attribute. In our case the action executer name is “tag”. public class TagActionExecuter extends ActionExecuterAbstractBase { public static final String NAME = "tag"; ... } b. Implementing the addParameterDefinitions() method80 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository In the first part of the tutorial, the action does not take any parameters. The action executer, however, still has to implement the addParameterDefinitions() method, even if it is empty. protected void addParameterDefinitions(List<ParameterDefinition> paramList) { // there are no parameters } c. Implementing the executeImpl() method The executeImpl() method does the actual work. It receives an Action object and the NodeRef of the node to do the work on (actionedUponNodeRef). Action executers generally check that the actionedUponNode exists using the NodeService.exists() method. In the example, executeImpl() adds the taggable aspect to the actionedUponNodeRef using the addAspect() method on the NodeService: protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { if (nodeService.exists(actionedUponNodeRef) == true) { // add the aspect if it is not already present on the node QName tagAspect = QName.createQName("extension.tags", "taggable"); if (nodeService.hasAspect(actionedUponNodeRef, tagAspect) == false) { nodeService.addAspect(actionedUponNodeRef, tagAspect, null); } } } d. Writing the Spring dependency injection setter methods Because the executeImpl() method uses the NodeService, we will need to inject the NodeService using Spring dependency injection. In the example, Spring will inject the NodeService using the setNodeService() method: private NodeService nodeService; public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; }3. Defining the action I18N messages An action has an associated title and description. The abstract superclass ActionExecuterAbstractBase looks for messages in the I18N resource bundles using an id of the form: <action-name>.title <action-name>.description where <action-name> is the name of the action defined by the static NAME attribute on the action executer class. In the example, the I18N messages are defined in the tag-action- messages.properties file. # Action title and description API Development Course 81
    • Extending the Alfresco Repository tag.title=Add tags to item tag.description=This action adds tags to the matched item 4. Registering the action An action is registered as a bean in a Spring configuration file. In the example, the tag action executer bean is defined in the tagging-context.xml file. <!-- Tag Action Bean --> <bean id="tag" class="org.alfresco.sample.TagActionExecuter" parent="action-executer" > <property name="nodeService"> <ref bean="nodeService" /> </property> </bean> The bean must define the action-executer bean as its parent bean. The action-executer parent bean is defined in the action-services-context.xml Spring configuration file: <bean id="action-executer" abstract="true" init-method="init"> <property name="runtimeActionService"> <ref bean="actionService" /> </property> </bean> The init() method is defined as an initialisation method and is called automatically by Spring once the action executer has been instantiated. The init() method is implemented by the ActionExecuterAbstractBase class. If the action is public, it will register the action executer with the RuntimeActionService. 5. Registering the action I18N message bundle An I18N message bundle is registered as a bean in a Spring configuration file. The resourceBundles property contains the list of message bundles to register and the class attribute must be defined as org.alfresco.i18n.ResourceBundleBootstrapComponent. In the example, the tag-action-messages.properties file is registered as an I18N resource bundle in the tagging-context.xml file: <!-- Load the Tag Action Messages --> <bean id="tag-action-messages" class="org.alfresco.i18n.ResourceBundleBootstrapComponent"> <property name="resourceBundles"> <list> <value>org.alfresco.sample.tag-action-messages</value> </list> </property> </bean> 6. Registering the “tags” model A dictionary model is registered as a bean in a Spring configuration file. The bean must define the dictionaryModelBootstrap bean as its parent bean and that it depends on the dictionaryBootstrap bean. The models property contains the list of models to register. In the example, the tagsModel.xml file is registered as a dictionary model in the tagging-context.xml file: <!-- Tag Model Registration --> <bean id="tags.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap"> <property name="models"> <list> <value>alfresco/extension/tagsModel.xml</value> </list>82 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository </property> </bean> 7. Configuring the property sheet In order to view and modify the tags property in the Web Client, the property sheet needs to be configured for the taggable aspect. The following example, taken from web-client-config-custom.xml, will display the tags property on the property sheet: <config evaluator="aspect-name" condition="tag:taggable"> <property-sheet> <show-property name="tag:tags" /> </property-sheet> </config> 8. Packaging and deploying In order to deploy a custom action to the Alfresco repository, the following files need to be packaged: • the compiled action executer class (TagActionExecuter.class); • the I18N message bundle file (tag-action-messages.properties); • the Spring configuration file containing the bean definitions for the action executer and the message bundle (tagging-context.xml); • if required, the custom dictionary model (tagsModel.xml); • if required, the Web Client custom configuration file (web-client-config- custom.xml). The compiled class and message bundle can be exported to the same JAR file. The files then need to be deployed to the following directories in the Alfresco repository: • the JAR file to the WEB-INF/lib directory; • all other files to the WEB-INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.After deployment, the Add tags to item action is available from the action wizards: API Development Course 83
    • Extending the Alfresco Repository The action will automatically add the taggable aspect, however the tag values will have to be added manually via the property sheet:84 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryRepository 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 tag action and how to configure the web client to add a custom JSP that will prompt the user for the default tags. The examples in this tutorial are taken from the Alfresco SDK TaggingSample. 1. Adding a parameter to the action Parameters are added to an action in the addParameterDefinitions() method. They can be used in the executeImpl() method. a. Modifying the addParameterDefinitions() method The addParameterDefinitions() method is passed a List of ParameterDefinition objects. To register a new parameter, simply create a new ParameterDefinition object and add it to the list. public ParameterDefinitionImpl( String name, QName type, boolean isMandatory, String displayLabel) name The name of the parameter. type The QName type of the parameter. isMandatory true if the parameter is mandatory, otherwise false. displayLabel The display label: usually a call to the getParamDisplayLabel() method. In the example, the name of the parameter is “tags”, it takes a string value and is mandatory: public static final String PARAM_TAGS = "tags"; protected void addParameterDefinitions(List<ParameterDefinition> paramList) { paramList.add(new ParameterDefinitionImpl( PARAM_TAGS, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_TAGS))); } b. Modifying the executeImpl() method The executeImpl() method can now be modified to retrieve the “tags” parameter value and set the tags property on the node being actioned upon. Parameter values are retrieved from the Action object using the getParameterValue() method. In the example, executeImpl() retrieves the value of the PARAM_TAGS parameter from the Action object, converts it to a List and then uses the list to set the tags API Development Course 85
    • Extending the Alfresco Repository property on the actionedUponNodeRef using the setProperty() method on the NodeService: protected void executeImpl(Action action, NodeRef actionedUponNodeRef) { if (this.nodeService.exists(actionedUponNodeRef) == true) { ... // create the tags as a list String tags = (String)action.getParameterValue(PARAM_TAGS); List<String> tagsList = new ArrayList<String>(); if (tags != null && tags.length() > 0) { StringTokenizer tokenizer = new StringTokenizer(tags, ","); while (tokenizer.hasMoreTokens()) { tagsList.add(tokenizer.nextToken().trim()); } } // set the tags property QName tagsProp = QName.createQName("extension.tags", "tags"); this.nodeService.setProperty(actionedUponNodeRef, tagsProp, (Serializable)tagsList); } } 2. Defining the parameter I18N messages Each action parameter has an associated display label. The getParamDisplayLabel() method on the abstract superclass ParameterizedItemAbstractBase looks for a message in the I18N resource bundles using an id of the form: <action-name>.<param-name>.display-label where <action-name> is the name of the action defined by the static NAME attribute on the action executer class and <param-name> is the parameter value passed to the getParamDisplayLabel() method. In the example, the I18N display label for the param_tags parameter has been added to the tag-action-messages.properties file: # Action title and description tag.title=Add tags to item tag.description=This action adds tags to the matched item # Action parameter display labels tag.param_tags.display-label=Tags 3. Creating the action JSP Although the action wizards use the Wizard Framework, the JSPs that collect parameters for actions and conditions are complete JSPs as they are not displayed within the wizard container. This means you have to have the whole page structure in your action JSP. The easiest solution, is to copy an existing one and modify it to your needs. In the example, copying jsp/actions/add-features.jsp is a good start as we only need to replace the drop down list with a text field and change a few labels such as the page title. The modified parts of the page are as follows: ... <r:page titleId="title_action_tag"> <f:view> <%-- load a bundle of properties with I18N strings --%> <f:loadBundle basename="alfresco.messages.webclient" var="msg"/>86 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository <f:loadBundle basename="alfresco.extension.webclient" var="customMsg"/ > <h:form acceptcharset="UTF-8" id="tag-action"> ... <tr> <td><nobr><h:outputText value="#{customMsg.tags}:"/></nobr></td> <td width="95%"> <h:inputText value="#{WizardManager.bean.actionProperties.tags}" size="50" maxlength="1024" /> </td> </tr> ...4. Implementing the action handler To integrate the action into the action based wizards such as the Run Action, Create Rule and Edit Rule Wizards, an action handler is required. a. Creating the TagActionHandler class An action handler typically extends an abstract BaseActionHandler class and is responsible for directing the wizard to the page to collect the parameters and marshalling the parameters between the wizard and the repository. If the page collecting the parameters requires some default setup the setupUIDefaults() method can be overridden. b. Implementing the getJSPPath() method The getJSPPath() method should return the path to the actions JSP, for example: / jsp/extension/tag.jsp. public String getJSPPath() { return "/jsp/extension/tag.jsp"; } c. Implementing the prepareForSave() method API Development Course 87
    • Extending the Alfresco Repository The prepareForSave() method places the tags the user entered into the repository properties map passed in. public static final String PROP_TAGS = "tags"; public void prepareForSave(Map<String, Serializable> actionProps, Map<String, Serializable> repoProps) { repoProps.put(TagActionExecuter.PARAM_TAGS, (String)actionProps.get(PROP_TAGS)); } d. Implementing the prepareForEdit() method The prepareForEdit() method does the opposite of prepareForSave() and takes the tags stored in the action and places them in the properties map for the wizard. public static final String PROP_TAGS = "tags"; public void prepareForEdit(Map<String, Serializable> actionProps, Map<String, Serializable> repoProps) { actionProps.put(PROP_TAGS, (String)repoProps.get(TagActionExecuter.PARAM_TAGS)); } e. Implementing the generateSummary() method Finally, the generateSummary() method is used to generate a summary string for the action, this typically includes the parameters added by the user. public String generateSummary(FacesContext context, IWizardBean wizard, Map<String, Serializable> actionProps) { String tags = (String)actionProps.get(PROP_TAGS); if (tags == null) { tags = ""; } return MessageFormat.format(Application.getMessage(context, "add_tags"), new Object[] {tags}); } 5. Defining the action handler messages The generateSummary() method in the action handler uses a new add_tags message id. Custom messages are defined in the webclient.properties file in the extension directory. Example taken from extension/webclient.properties. add_tags=Add tags {0} 6. Registering the action handler The action handler is registered using an “Action Wizards” entry in a web client configuration file. Example taken from web-client-config-custom.xml. <config evaluator="string-compare" condition="Action Wizards"> <action-handlers> <handler name="tag" class="org.alfresco.sample.TagActionHandler" /> </action-handlers> </config>88 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository 7. Packaging and deploying In order to deploy a custom action (with parameters) to the Alfresco repository, the following files need to be packaged: • the compiled ActionExecuter and ActionHandler classes (TagActionExecuter.class and TagActionHandler.class); • the I18N message bundle file (tag-action-messages.properties); • the Spring configuration file containing the bean definitions for the action executer and the message bundle (tagging-context.xml); • the action JSP (tag.jsp); • the custom webclient.properties file; • if required, the custom dictionary model (tagsModel.xml); • the Web Client custom configuration file containing the action handler definition (web-client-config-custom.xml). The compiled classes and message bundle can be exported to the same JAR file. The files then need to be deployed to the following directories in the Alfresco repository: • the JAR file to the WEB-INF/lib directory; • the JSP file to the jsp/extension directory; • all other files to the WEB-INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.After deployment, the Add tags to item action will have an action JSP that can be used to definedefault tag values:The summary page show the tags that will automatically be added: API Development Course 89
    • Extending the Alfresco Repository When the action is run against a content item, the taggable aspect will be added and automatically initialised with the default tag values defined on the action:90 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryContent TransformersIntroduction A content transformer is a Java class that can transform content from one mime type to another. Many content transformers already exist, however it is possible to add you own custom content transformer in a few easy steps. A content transformer has to implement the org.alfresco.repo.content.transform.ContentTransformer interface. ContentTransformer extends the org.alfresco.repo.content.ContentWorker interface that is a common marker interface for specific “worker” interfaces such as content transformers and metadata extractors. An abstract base class called AbstractContentTransformer has been written to provides basic services for ContentTransformer implementations. Only two methods need implementing when deriving from the abstract superclass. AbstractContentTransformer is presented in detail in the tutorial Content Transformer Howto on page 91. For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: • Interface ContentTransformer • Class AbstractContentTransformerContent Transformer Howto Before starting the tutorial, you should be familiar with the Introduction on page 34. The examples in this tutorial are taken from the PdfBoxContentTransformer (org.alfresco.repo.content.transform.PdfBoxContentTransformer). 1. Implementing the content transformer class Content transformers generally extend an abstract class called AbstractContentTransformer. The abstract class provides basic services for ContentTransformer implementations. It also introduces a new abstract method transformInternal() that is called from transform() to do the actual transformation work. The only two methods that require implementing when deriving from the abstract superclass are getReliability() and transformInternal(). a. Creating the PdfBoxContentTransformer class API Development Course 91
    • Extending the Alfresco Repository b. Implementing the getReliability() method The getReliability() method provides the approximate accuracy with which the transformer can transform from one mime type to another. It is used to determine which of a set of transformers will be used to perform a specific transformation. The method returns a score between 0.0 and 1.0: • 0.0 indicates that the transformation cannot be performed at all; • 1.0 indicates that the transformation can be performed perfectly. Before calling transformInternal(), the AbstractContentTransformer checks that the transformation is allowed by calling the getReliability() method on the subclass. If the subclass method returns 0.0, an AlfrescoRuntimeException is thrown. The example below is taken from PdfBoxContentTransformer. The getReliability() method returns 1.0 if the source and target mime types are “application/pdf” and “text/plain” respectively, otherwise it returns 0.0. public double getReliability(String sourceMimetype, String targetMimetype) { if (!MimetypeMap.MIMETYPE_PDF.equals(sourceMimetype) || !MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype)) { // only support PDF -> Text return 0.0; } else { return 1.0; } } c. Implementing the transformInternal() method92 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryThe 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(); } } }} API Development Course 93
    • Extending the Alfresco Repository 2. Defining the content transformer Spring bean A content transformer is registered as a bean in a Spring configuration file. By convention, the bean id has a “transformer.” prefix. The bean must define baseContentTransformer as its parent bean. The PdfBoxContentTransformer bean is defined in content-services-context.xml: <bean id="transformer.PdfBox" class="org.alfresco.repo.content.transform.PdfBoxContentTransformer" parent="baseContentTransformer" > <property name="explicitTransformations"> <list> <bean class="...ContentTransformerRegistry$TransformationKey" > <constructor-arg> <value>application/pdf</value> </constructor-arg> <constructor-arg> <value>text/plain</value> </constructor-arg> </bean> </list> </property> </bean> The bean may also define a list of explicit transformations that the content transformer can perform regardless of what it returns via the getReliability() check. The explicit transformations are defined on the explicitTransformations property as a list of ContentTransformerRegistry.TransformationKey objects. The source and target mime types for the explicit transformation are passed as arguments to the TransformationKey constructor. The baseContentTransformer parent bean is defined in content-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 parent bean and is called automatically by Spring once the content transformer has been instantiated. It registers the content transformer with the ContentTransformerRegistry for each explicit transformation provided.94 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository 3. Packaging and deploying In order to deploy a custom content transformer to the Alfresco repository, the following files need to be packaged: • the compiled custom content transformer class; • the Spring configuration file containing the bean definition for the custom content transformer. The compiled class needs to be exported to a JAR file. The files then need to be deployed to the following directories in the Alfresco repository: • the JAR file to the WEB-INF/lib directory; • the Spring configuration file to the WEB-INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.ContentTransformerRegistry The ContentTransformerRegistry holds a list of available content transformers and provides the most appropriate content transformer for a particular source and target mime type transformation request. Upon initialisation, content transformers register themselves with the ContentTransformerRegistry via the addTransformer() method. Content transformers also register themselves for each explicit transformation they can perform (as defined in their Spring bean definition) via the addExplicitTransformer() method. An explicit transformation is defined as an instance of the ContentTransformerRegistry.TransformationKey inner class. API Development Course 95
    • Extending the Alfresco Repository For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Class ContentTransformerRegistryr The getTransformer() method is used by clients to get the best transformer available for a given transformation. If two transformers perform the same transformation, the most reliable one will always be chosen. If two or more transformers exist with the same reliability, then they will be cycled until the fastest one is determined. The timing code is automatically provided by the AbstractContentTransformer superclass. The ContentTransformerRegistry bean is defined in the content-service-context.xml Spring configuration file: <!-- Content Transformation Regisitry --> <bean id="contentTransformerRegistry" class="org.alfresco.repo.content.transform.ContentTransformerRegistry" />96 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco RepositoryFurther ReadingWorking with Temporary Files If it is necessary to work against physical files during the transformation, use the org.alfresco.util.TempFileProvider#createTempFile to ensure that all temporary files will be cleaned up appropriately. Failure to do this will mean that temporary files are not cleaned up while the system is running. Do not use deleteOnExit - the Alfresco repository is designed to run under load indefinitely, i.e. until the next upgrade. Give your temp files meaningful prefixes as it will help during debugging. Example taken from org.alfresco.repo.content.transform.OpenOfficeContentTransformer: String sourceMimetype = getMimetype(reader); String targetMimetype = getMimetype(writer); MimetypeService mimetypeService = getMimetypeService(); String sourceExtension = mimetypeService.getExtension(sourceMimetype); String targetExtension = mimetypeService.getExtension(targetMimetype); // create temporary files to convert from and to File tempFromFile = TempFileProvider.createTempFile( "OpenOfficeContentTransformer-source-", "." + sourceExtension); File tempToFile = TempFileProvider.createTempFile( "OpenOfficeContentTransformer-target-", "." + targetExtension); API Development Course 97
    • Extending the Alfresco RepositoryMetadata ExtractorsIntroduction A metadata extractor is a Java class that can extract metadata from content of a particular mime type. Many metadata extractors already exist, however it is possible to add you own custom metadata extractor in a few easy steps. A metadata extractor has to implement the org.alfresco.repo.content.metadata.MetadataExtracter interface. MetadataExtracter extends the org.alfresco.repo.content.ContentWorker interface that is a common marker interface for specific “worker” interfaces such as metadata extractors and content transformers. An abstract class called AbstractMappingMetadataExtracter has been written to provides basic services for MetadataExtracter implementations. Only one method needs implementing when deriving from the abstract superclass. AbstractMappingMetadataExtracter is presented in detail in the tutorial Metadata Extractor Howto on page 99. For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: • Interface MetadataExtracter • Class AbstractMappingMetadataExtracterMetadataExtracterRegistry The MetadataExtracterRegistry holds a list of available metadata extractors and provides the most appropriate extractor for a particular mime type extraction request. Upon initialisation, metadata extractors register themselves with the MetadataExtracterRegistry via the register() method.98 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository For clarity, not all of the available methods are shown. For a complete description, please consult the Javadocs: Class MetadataExtracterRegistry The getExtracter() method is used by clients to get the most appropriate metadata extractor for a particular mime type. The MetadataExtracterRegistry bean is defined in the content-service-context.xml Spring configuration file: <!-- Metadata Extraction Regisitry --> <bean id="metadataExtracterRegistry" class="org.alfresco.repo.content.metadata.MetadataExtracterRegistry" />Metadata Extractor Howto Before starting the tutorial, you should be familiar with the Introduction on page 34. The examples in this tutorial are taken from PdfBoxMetadataExtracter (org.alfresco.repo.content.metadata.PdfBoxMetadataExtracter). 1. Implementing the metadata extractor class Metadata extractors generally extend an abstract class called AbstractMappingMetadataExtracter. The abstract superclass provides basic services for MetadataExtracter implementations. The only method that requires implementing when deriving from the abstract superclass is extractRaw(). a. Creating the PdfBoxMetadataExtracter class API Development Course 99
    • Extending the Alfresco Repository b. Writing the constructor A set of supported mime types has to be passed to the abstract superclass, either as an argument to the constructor or via a call to the setSupportedMimetypes() method. Before calling extractRaw(), the abstract superclass checks that the extraction is allowed for the content mime type by calling the isSupported() method. The mime type has to be one of the supported mime types. Subclasses can override isSupported() to provide a custom implementation. The PdfBoxMetadataExtracter only supports the “application/pdf” mime type. public static String[] SUPPORTED_MIMETYPES = new String[] {MimetypeMap.MIMETYPE_PDF }; public PdfBoxMetadataExtracter() { super(new HashSet<String>( Arrays.asList(SUPPORTED_MIMETYPES))); } c. Implementing the extractRaw() method The extractRaw() method does the actual extraction work. The reader (ContentReader) object, passed as an argument, allows direct access to100 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repositorythe 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); } API Development Course 101
    • Extending the Alfresco Repository } } finally { if (is != null) { try { is.close(); } catch (IOException e) {} } if (pdf != null) { try { pdf.close(); } catch (Throwable e) { e.printStackTrace(); } } } // Done return rawProperties; } 2. Defining the metadata default mapping in a properties file The extracted metadata returned by extractRaw() is mapped to node properties using a mapping. The default mapping is supplied as a properties file. Namespaces used in the mapping have to be declared. The property keys are the same as those used in the raw properties map returned by the extractRaw() method. The property value is a valid node property with a shorthand namespace prefix. A single raw property can be mapped to several node properties provided as a comma separated list. The default implementation looks for a properties file with the same name and in the same location as the metadata extractor class. For example, the PdfBoxMetadataExtracter default mapping properties file is called PdfBoxMetadataExtracter.properties and is located in the org.alfresco.repo.content.metadata package or in the org/alfresco/ repo/content/metadata directory on the classpath. Example PdfBoxMetadataExtracter.properties: # # PdfBoxMetadataExtracter - default mapping # # author: Derek Hulley # Namespaces namespace.prefix.cm=http://www.alfresco.org/model/content/1.0 # Mappings author=cm:author title=cm:title subject=cm:description created=cm:created 3. Registering the metadata extractor A metadata extractor is registered as a bean in a Spring configuration file. By convention, the bean id has an “extracter.” prefix. The bean must also define baseMetadataExtracter as its parent bean. The baseMetadataExtracter bean is defined in the content-service-context.xml Spring configuration file: <!-- Abstract bean definition defining base definition for all metadata extracters --> <bean id="baseMetadataExtracter" class="org.alfresco.repo.content.metadata.AbstractMetadataExtracter" abstract="true" init-method="register"> <property name="registry">102 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Repository <ref bean="metadataExtracterRegistry" /> </property> <property name="mimetypeService"> <ref bean="mimetypeService" /> </property> </bean> The register() method is defined as a Spring bean initialisation method and is called automatically by Spring once the metadata extractor has been instantiated. It registers the metadata extractor with the MetadataExtracterRegistry. The standard metadata extractor class names and Spring bean names are all written “er” (extracter), they are not written with the correct “or” spelling (extractor)! PdfBoxMetadataExtracter example taken from content-services-context.xml. <!-- Content Metadata Extracters --> <bean id="extracter.PDFBox" class="org.alfresco.repo.content.metadata.PdfBoxMetadataExtracter" parent="baseMetadataExtracter" /> 4. Packaging and deploying In order to deploy a custom metadata extractor to the Alfresco repository, the following files need to be packaged: • the compiled custom metadata extractor class; • the metadata mapping properties file; • the Spring configuration file containing the bean definition for the custom metadata extractor. The compiled class and properties file can be exported to the same JAR file. The files then need to be deployed to the following directories in the Alfresco repository: • the JAR file to the WEB-INF/lib directory; • the Spring configuration file to the WEB-INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.Further ReadingMetadata Mapping The extracted metadata returned by extractRaw() is mapped to node properties using a mapping. The default mapping is supplied as a properties file in a format similar to the following: # Namespaces namespace.prefix.cm=http://www.alfresco.org/model/content/1.0 namespace.prefix.my=http://www.mycompany.com/model/mymodel/1.0 # Mappings editor=cm:author, my:editor title=cm:title summary=cm:summary subject=cm:description Namespaces used in the mapping have to be declared. The property keys are the same as those used in the raw properties map returned by the extractRaw() method. The property value is a valid node property with a shorthand namespace prefix. A single raw property can be mapped to several node properties provided as a comma separated list. API Development Course 103
    • Extending the Alfresco Repository The raw property values are converted to the corresponding node property types using the org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter default converter. The default mapping is returned by the getDefaultMapping() method. The default implementation looks for a properties file with the same name and in the same location as the metadata extractor class. For example, the PdfBoxMetadataExtracter default mapping properties file is called PdfBoxMetadataExtracter.properties and is located in the org.alfresco.repo.content.metadata package or in the org/alfresco/repo/content/ metadata directory on the classpath. The default implementation can be specialised by overriding the getDefaultMapping() method in the subclass. If the default mapping is defined in a properties file other than the one named after the class, then the readMappingProperties(String propertiesUrl) method can be used to read the mapping from an alternate location: protected Map<<String, Set<QName>> getDefaultMapping() { return readMappingProperties(PROPERTIES_URL); } Implementations can also dynamically modify the default mapping using either the setMapping() method, if the mapping is supplied as a Map, or the setMappingProperties() method, if the mapping is supplied as a Properties object. By default, the supplied mapping will replace the default mapping returned by getDefaultMapping(). If the supplied mapping should augment the default mapping, the setInheritDefaultMapping(boolean inheritDefaultMapping) method should first be called with a value of true. Finally, the init() method needs to be called to re- initialise the extractor. Overwrite Policies The overwrite policy determines whether extracted values are written to the destination property map or not. Three overwrite policies have been defined: EAGER If the extracted value is not null, always write the extracted value to the destination property map. PRAGMATIC Only write the extracted value if: • the extracted value is not null • the key does not exist in the destination property map • the key exists but the destination property value is null or an empty string CAUTIOUS Only write the extracted value if: • the extracted value is not null • the key does not exist in the destination property map The default overwrite policy is PRAGMATIC. The setOverWritePolicy() method can be called on the abstract superclass to change the overwrite policy. The overwrite policy is specified as either an OverwritePolicy or as a String.104 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web ClientExtending the Alfresco Web Client API Development Course 105
    • Extending the Alfresco Web ClientJavaServer FacesIntroduction JavaServer Faces (JSF) is a user interface framework for Java web applications. JSF defines an event-driven, component-based model for web application development, similar to the model that has been used successfully for standalone GUI applications for years. JSF’s core architecture is designed to be independent of specific protocols and markup. However it is also aimed directly at solving many of the common problems encountered when writing applications for HTML clients that communicate via HTTP to a Java application server that supports servlets and JavaServer Pages (JSP) based applications. JSF is a specification (JSR-252) that has been developed as part of the Java Community Process. It is a standard, vendor independent specification. Several implementations exist, including the GlassFish JavaServer Faces reference implementation and Apache MyFaces. Alfresco uses the Apache Myfaces implementation. The specification defines a set of standard user interface components and an API for extending the standard components or developing new ones. As well as UI components, JSF also defines artifacts like converters, validators, events listeners, and renderers: • converters perform type conversion between server-side Java objects and their representation in the user interface. A good example is a date; • validators can be associated with a JSF component to perform input validation checks on local values before they are processed; • event listeners are registered against events that are triggered when a user clicks a button or a link, changes a value in a field, or makes a selection in a list. The outcome of the event processing controls which page is displayed next; • renderers generate the actual markup for the user interface. JSF is not limited to HTML or any other markup language and the same JSF component can be coupled with different renderers to produce different output, for example, either HTML or WML;Login Page Walkthrough 1. JSF components on a JSP page In the following screenshot, the User Name and Password input text fields, the Language drop-down list and the Login button are all implemented using JSF components on the login JSP page. The text labels themselves are also implemented using JSF components.106 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web ClientWhen 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}" /> API Development Course 107
    • Extending the Alfresco Web Client 2. JSF tag librairies The JSP custom actions (or custom tags) representing the JSF components are defined in one or more custom JSF tag libraries. The tag libraries are declared on a JSP page using a taglib directive. Each taglib directive contains two attributes: the uri attribute defines the unique identifier for the library and the prefix attribute defines the namespace prefix. The login page declares two standard JSF tag libraries: <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> a standard JSP tag library: <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> and two Alfresco JSF tag libraries: <%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %> <%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %> The standard JSF tag libraries: uri="http://java.sun.com/jsf/html" prefix="h" HTML tag library containing tags that represent JSF components that are rendered as HTML elements. uri="http://java.sun.com/jsf/core" prefix="f" Core tag library containing tags that represent JSF artifacts that are independent of the page markup language. The Alfresco JSF tag libraries: uri="/WEB-INF/alfresco.tld" prefix="a" Alfresco tag library containing custom tags that represent JSF components that can be used in non Alfresco projects. uri="/WEB-INF/repo.tld" prefix="r" Alfresco Repository tag library containing custom tags that represent JSF components that can only be used in Alfresco based projects. 3. Structure of an Alfresco JSP page Below is the overall structure of the Alfresco login JSP page: <r:page titleId="title_login"> ... <f:view> ... <h:form acceptcharset="UTF-8" id="loginForm" > ... </h:form> ... </f:view> ... </r:page> All Alfresco JSP pages have a similar structure. An Alfresco JSP page always starts with a <r:page> custom tag. The <r:page> tag renders the main HTML begin and end tags (<html><head>...</head><body>...</ body></html>). The set of components that make up a user interface is called a view in JSF. The components on a page form a tree and the <f:view> custom tag represents the component at the root of the tree. The <f:view> tag must contain all other JSF tags on the page.108 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client The <h:form> custom tag represents an HTML form component. It is a container for input components that hold values that should be processed together. All JSF input components must be nested within a form component.4. Output Text and Message bundles The first component we will look at in detail is represented by the <h:outputText> custom tag. The following tags output text to the browser: <h:outputText value="#{msg.login_details}" />: <h:outputText value="#{msg.username}"/>: <h:outputText value="#{msg.password}"/>: <h:outputText value="#{msg.language}"/>: Attribute values #{ ... } are written using a Unified Expression Language (Unified EL, or just EL). In the above example, the values beginning with msg correspond to properties defined in a message bundle. A message bundle is loaded using the <f:loadBundle> custom tag: <%-- load a bundle of properties I18N strings here --%> <f:loadBundle basename="alfresco.messages.webclient" var="msg"/> The above example loads the default Web Client message bundle webclient.properties from the alfresco/messages directory on the classpath. All of the Web Client I18N strings are stored in the webclient.properties default message bundle. Message translations can be provided in separate files, named with an extra suffix corresponding to the standard ISO language and country codes. For example, the French translations of the Web Client messages are found in the webclient_fr_FR.properties file and the French Canadian translations are found in the webclient_fr_CA.properties file. The messages in the base bundle (without a suffix) are in the en_US locale. Login page messages from the default webclient.properties bundle: login_details=Enter Login details username=User Name password=Password language=Language login=Login The same login page messages from the French webclient_fr_FR.properties bundle: login_details=Entrez les informations de connexion username=Nom dutilisateur password=Mot de passe language=Langue login=Connexion The login page in the French fr_FR locale: API Development Course 109
    • Extending the Alfresco Web Client Custom message bundles can be loaded using the <f:loadBundle> custom tag. In the following example, a custom webclient.properties message bundle is loaded from the alfresco/extension directory and stored under the mymsg key: <f:loadBundle basename="alfresco.extension.webclient" var="mymsg"/> Custom messages can then be displayed using the <h:outputText> custom tag: <h:outputText value="#{mymsg.lost_password}" />: 5. Select Menus and Value binding expressions The next component we will look at is represented by the <h:selectOneMenu> custom tag. It displays the drop-down language selection menu.110 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web ClientIt 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(); API Development Course 111
    • Extending the Alfresco Web Client } ... } return items; } The default language for the current user is retrieved by calling the getLanguage() method on the UserPreferencesBean: public String getLanguage() { return this.language; } If a user selects a different language, the form will be submitted: onchange="document.forms[loginForm].submit(); return true;" and the setLanguage() method will be called on the UserPreferencesBean to update the current language: public void setLanguage(String language) { this.language = language; Application.setLanguage(FacesContext.getCurrentInstance(), language); ... } 6. FacesContext During request processing for a JSF page, a FacesContext object is used to represent request specific information as well as provide access to services for the application. The static FacesContext.getCurrentInstance() method is used to obtain the current FacesContext instance: FacesContext context = FacesContext.getCurrentInstance(); Many Alfresco methods require (or receive) a FacesContext object as an argument. Application.getLanguage(FacesContext.getCurrentInstance()); ... Application.setLanguage(FacesContext.getCurrentInstance(), language); 7. Input Text and Validators The next component we will look at is an input text field represented by the <h:inputText/> custom tag. Input Text components are used for the user name and password fields: <h:inputText id="user-name" value="#{LoginBean.username}" validator="#{LoginBean.validateUsername}" style="width:150px" /> <h:inputSecret id="user-password" value="#{LoginBean.password}" validator="#{LoginBean.validatePassword}" style="width:150px" /> The values of the text fields are bound to the managed bean LoginBean via value binding expressions. Both components have an optional validator associated with them to perform input validation checks on the user name and password values as part of the login process. JSF calls the validateUsername() method on the LoginBean, to validate the value of the user name: public void validateUsername(FacesContext context, UIComponent component, Object value)112 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client throws ValidatorException { ... if (name.length() < minUsernameLength || name.length() > 256) { ... throw new ValidatorException(new FacesMessage(err)); } } The method throws a ValidatorException if the value is not valid.8. Command Buttons and Method binding expressions Once the user has typed his user name and password, he clicks the Login button to log in. The login button is a JSF component represented by the <h:commandButton> custom tag. <h:commandButton id="submit" action="#{LoginBean.login}" value="#{msg.login}" /> The expression #{LoginBean.login} is know as a method binding expression. LoginBean is the name of another JSF managed bean. Method binding expressions bind actions to methods on managed beans that are automatically called by the JSF framework when an action event is triggered. When the Login button is clicked, JSF will call the login() method on the LoginBean: public String login() { String outcome = null; if (this.username != null && this.username.length() != 0 && this.password != null && this.password.length() != 0) { ... this.authenticationService.authenticate(this.username, this.password.toCharArray()); ... if (NavigationBean.LOCATION_MYALFRESCO.equals(this.preferences.getStartLocation())) { return "myalfresco"; } else { // generally this will navigate to the generic browse screen return "success"; } ... } return outcome; }9. Outcomes and Navigation The login() method returns a String value called an outcome. The JSF Navigation Handler uses the outcome along with predefined navigation rules to determine which page (or view) to display next. A null outcome means “stay on the same page”. The navigation rules are defined by <navigation-rule/> elements in a <faces-config/ > file. In Alfresco, the navigation rules are defined in the WEB-INF/faces-config- navigation.xml file: <navigation-rule> API Development Course 113
    • Extending the Alfresco Web Client <description> The decision rule used by the NavigationHandler to determine which view must be displayed after the current view, login.jsp is processed. </description> <from-view-id>/jsp/login.jsp</from-view-id> <navigation-case> <description> Indicates to the NavigationHandler that the browse.jsp view must be displayed if the Action referenced by a UICommand component on the login.jsp view returns the outcome "success". </description> <from-outcome>success</from-outcome> <to-view-id>/jsp/browse/browse.jsp</to-view-id> </navigation-case> </navigation-rule> An outcome of success from the login page will navigate to the standard browse page (browse.jsp). <navigation-rule> <from-view-id>/jsp/*</from-view-id> <navigation-case> <from-outcome>browse</from-outcome> <to-view-id>/jsp/browse/browse.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>myalfresco</from-outcome> <to-view-id>/jsp/dashboards/container.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>about</from-outcome> <to-view-id>/jsp/dialog/about.jsp</to-view-id> </navigation-case> </navigation-rule> An outcome of myalfresco from any page will navigate to the dashboard container page (dashboards/container.jsp). The standard JSF Navigation Handler can be extended and overridden to customise navigation handling. A custom navigation handler is defined using a <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 frameworks to handle navigation using outcomes that begin with special dialog: or wizard: prefixes. For more information, see Introduction on page 123 and Introduction on page 132. 10. Managed Beans and Variable Resolvers The JSF Managed Beans are defined by <managed-bean/> elements in a <faces- config/> file. In Alfresco, the Web Client managed beans are defined in the WEB-INF/faces-config- beans.xml file: <managed-bean> <description>114 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client The bean that backs up the Login screen </description> <managed-bean-name>LoginBean</managed-bean-name> <managed-bean-class> org.alfresco.web.bean.LoginBean </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>nodeService</property-name> <value>#{NodeService}</value> </managed-property> <managed-property> <property-name>authenticationService</property-name> <value>#{AuthenticationService}</value> </managed-property> ...</managed-bean> The managed bean name is not always the same as the implementing class name.When required, JSF automatically instantiates and initialises managed beans. 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:none Does not store the managed bean in any scope.request The managed bean is stored and kept for a single request.session The managed bean is stored and kept for a single user session.application The managed bean is stored and shared between all users.The properties defined by <managed-property/> elements are automatically set 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. API Development Course 115
    • Extending the Alfresco Web ClientActions FrameworkIntroduction The Alfresco Web Client UI actions are configured using the Actions Framework. Actions such as Edit, View Details, Update, Copy are all examples of UI Actions. Actions are grouped into action groups. Individual actions can be reused between groups and the action groups reused across pages. Action Groups define an ordered list of actions that are displayed together, either as a serial list (for example, as a strip of icons) or grouped together in a 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 groups are defined in the web-client-config-actions.xml configuration file. You can define your own custom UI actions and action groups in a Web Client configuration extension file. You can also extend existing action groups to add your own custom UI actions to existing menus in the Web Client.Update UI Action Walkthrough Before starting, you should be familiar with Introduction on page 106. 1. UI Action and Action Group definitions A UI action is defined by an <action/> element in a Web Client configuration file. The mandatory id attribute defines the unique ID by which the action is referenced for use in action groups. The same action can be referenced and reused by any number of action groups. An action group is defined by an <action-group/> element. The mandatory id attribute is used to identify the action group when displayed on a JSP page by a JSF <r:actions/ > custom tag. The actions in an action group are defined by child <action/> elements with either an idref attribute, to reference an existing action definition, or an id attribute,116 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Clientto 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: API Development Course 117
    • Extending the Alfresco Web Client Most standard UI actions and action groups are defined in a global <config/> section (i.e. there is no evaluator or condition). Actions and action groups can, however, be configured by node type. It is thus possible to add new actions or hide actions for a specific folder or document type. The following action group configuration hides the Cut and Copy actions from WCM WebProject type folders: <config evaluator="node-type" condition="wca:webfolder"> <actions> <action-group id="space_browse"> <show-link>false</show-link> <action idref="cut_node" hide="true" /> <action idref="copy_node" hide="true" /> </action-group> </actions> </config> Only node-type conditions are supported, aspects are not supported. 2. Action Evaluators The <evaluator/> element defines the name of a custom class implementing the org.alfresco.web.action.ActionEvaluator interface that is used to determine if the action should be displayed or not for the given Node. The evaluate(Node) method is called on the ActionEvaluator for each Node. The action will only be displayed if the method returns a value of true. The update_doc action will only be displayed on a Node if the UpdateDocEvaluator.evaluate(Node) method returns true: <!-- Update document --> <action id="update_doc"> ... <evaluator> org.alfresco.web.action.evaluator.UpdateDocEvaluator </evaluator> ... </action>118 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client The UpdateDocEvaluator.evaluate(Node) method returns true if the node is a sub- type of cm:content and the node is a Working Copy owned by the current user or the node is not locked and the node is not a Working Copy: public class UpdateDocEvaluator implements ActionEvaluator { public boolean evaluate(Node node) { DictionaryService dd = Repository.getServiceRegistry( FacesContext.getCurrentInstance()).getDictionaryService(); return dd.isSubClass(node.getType(), ContentModel.TYPE_CONTENT) && ((node.isWorkingCopyOwner() == true || (node.isLocked() == false && node.hasAspect(ContentModel.ASPECT_WORKING_COPY) == false))); } }3. Action Listeners and Action Context objects The <action-listener/> element defines a JSF action listener method that will be called when a user selects the action. The action listener method is passed an ActionEvent object that can be used to retrieve the parameters defined on the action as <param/> elements. The actionContext object is the context object for the action. Most of the time, an actionContext object is a Node object, however it may not always be the case. The context object can be referenced in JSF value binding expressions. It is often used in a <param/> element to pass the id of the current Node to the action listener. The update_doc action defines an action listener as a JSF method binding expression. The setupContentAction(ActionEvent) method will be called on the CheckinCheckoutBean when a user selects the action: <!-- Update document --> <action id="update_doc"> ... <action-listener>#{CheckinCheckoutBean.setupContentAction}</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 of parameters: public void setupContentAction(ActionEvent event) { UIActionLink link = (UIActionLink)event.getComponent(); Map<String, String> params = link.getParameterMap(); String id = params.get("id"); if (id != null && id.length() != 0) { setupContentDocument(id); } ... }4. Action Outcome API Development Course 119
    • Extending the Alfresco Web Client The <action/> element defines a JSF navigation outcome to use when a user selects the action. The dialog:updateFile outcome will be interpreted by the Alfresco Navigation Handler to call the updateFile dialog: <!-- Update document --> <action id="update_doc"> ... <action>dialog:updateFile</action> ... </action> 5. JSF <r:actions/> custom tag The <r:actions/> JSF custom tag is used to display an action group on a JSP page. This value attribute is used to reference an action group by ID. If the showLink attribute is set to false, a list of icons will be displayed with no textual links. For a complete description of <r:actions/> custom tag attributes, see Tag actions. In the browse.jsp page, the document_browse action group is displayed inline, and the document_browse_menu action group is displayed in a drop-down menu: <r:actions id="col18-acts1" value="document_browse" context="#{r}" showLink="false" styleClass="inlineAction" /> <%-- More actions menu --%> <a:menu id="content-more-menu" itemSpacing="4" image="/images/icons/more.gif" tooltip="#{msg.more_actions}" menuStyleClass="moreActionsMenu"> <r:actions id="col18-acts2" value="document_browse_menu" context="#{r}" /> </a:menu> The mandatory context attribute defines the object to use as the actionContext. In the above example, r is the current node in the list.Actions Framework Reference<action/> element Each <action/> element defines a single action definition. The mandatory id attribute defines the unique ID by which the action is referenced for use in action group elements (see <action- group/> element on page 121). Defining another action with the same ID as an existing action effectively overrides the original action definition. All of the following elements must be contained within the <action/> element. permissions Contains 1 or more <permission/> elements. permission Contained within <permissions/> element. Defines the permission values that must be checked against the current Node action context to allow the action to be displayed to the current user. The allow attribute takes a value true or false to define whether the permission check is ALLOW or DENY. evaluator The name of a custom class implementing the org.alfresco.web.action.ActionEvaluator interface. The action will only be displayed if the ActionEvaluator.evaluate() method returns true.120 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client label-id The id of an I18N string to be used as the label for the action. label A literal string to be used in place of label-id if you do not want an I18N string. tooltip-id The id of an I18N string to be used as the tooltip for the action. tooltip A literal string to be used in place of tooltip-id if you do not want an I18N string. show-link A presentation attribute. Valid values are true or false. If set to true, the action will be displayed as an icon and a textual link, if set to false, the action will only be displayed as an icon with the label as the tooltip for the icon image. style A presentation attribute. The CSS style to apply to the action. style-class A presentation attribute. The CSS class to apply to the action. image The icon image to display. action-listener The JSF action listener method to call upon user selection of the action. action The JSF action navigation outcome to execute upon user selection of the action. script The Alfresco JavaScript file to execute upon user selection of the action. The JavaScript file is specified by either Path or Node Reference. href The href to navigate to upon user selection of the action. target The associated href target. onclick The JavaScript onclick handler to execute upon user selection of the action. params Contains 1 or more <param/> elements. param Contained within the <params/> element. Defines the JSF <f:param/> custom tags to be generated as children of the action component. Each <param/> element has a mandatory name attribute as the name of the parameter and the mandatory value of the element contains the value to passed as the parameter. Parameters are available from the UIActionLink component passed to the JSF action listener method upon user selection of the action. Parameters are also passed directly as href and script URL arguments.<action-group/> element Each <action-group/> element defines a single action group. The mandatory id attribute is used to identify the action group in a JSF <r:actions/> custom tag. Defining another action group with the same ID as an existing action group effectively overrides the original action group definition. All other elements must be contained within the <action-group/> element. API Development Course 121
    • Extending the Alfresco Web Client action References or defines an action definition for the group. The order of the attributes defines the order in which the actions are displayed. Action elements may be defined using an idref attribute to reference an existing action definition or “inline” to define an action specific to the action group. Existing actions can also be hidden by using the hide attribute in conjunction with idref. show-link Overrides any similarly named attribute set on individual action definitions. style Overrides any similarly named attribute set on individual action definitions. style-class Overrides any similarly named attribute set on individual action definitions.122 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web ClientDialog FrameworkIntroduction The dialog framework manages the user interface dialogs in the Alfresco Web Client. Most of the standard Web Client dialog are managed by the framework and it is possible to create your own custom dialogs using the framework. Each dialog has three main components: • A <dialog/> configuration element in a web-client-config.xml file. • A dialog JSP that only contains the HTML and JSF components to define the “body” of the dialog. • A JSF managed bean that is used as a backing bean for the dialog. At the centre of the dialog framework is the Dialog Manager. When a dialog is opened, the Alfresco Navigation Handler looks up the <dialog/> configuration then calls the Dialog Manager to initialise the dialog and instantiate the managed bean before navigating to the dialog JSP page. See The Dialog Manager on page 130 for more details.Custom Dialog Howto Before starting the tutorial, you should be familiar with Introduction on page 106. You may also wish to complete the Update UI Action Walkthrough on page 116 tutorial. The tutorial builds upon the Configuring a Custom UI Action tutorial to give the custom UI action something to call. A dialog will be defined which will allow the user to select an aspect to add to the space. The examples are taken from the CustomDialog SDK sample. 1. Defining the dialog An individual dialog is defined by a <dialog/> element in a Web Client configuration file. All <dialog/> elements must be contained within a <dialogs/> element. Default dialogs are defined in the global <config/> section (i.e. there is no evaluator or condition) of the web-client-config-dialogs.xml file. These may be overridden using a more specific <config/> section, for example, a dialog definition could be overridden for a particular node type. All new dialogs or customisations to default dialogs should be defined in the standard web-client-config-custom.xml extension file. Combining is supported for dialog configuration, so the overridden definition need only define what needs changing. Example taken from web-client-config-custom.xml: <dialogs> <dialog name="addAspect" page="/jsp/extension/add-aspect.jsp" managed-bean="AddAspectDialog" icon="/images/icons/add_content_large.gif" title="Add Aspect" description="Adds an aspect to the selected node" /> </dialogs> name Defines the unique name (or id) of the dialog. The above dialog can be referenced in an action definition as dialog:addAspect. page Defines the path to the JSP page to be used for the dialog. API Development Course 123
    • Extending the Alfresco Web Client managed-bean Defines the name of the JSF managed bean to be used as the backing bean for the dialog. See <dialog/> attributes on page 130 for a complete description of all dialog attributes. 2. Defining an action The custom UI action definition from the Configuring a Custom UI Action tutorial can now be updated to “call” the custom addAspect dialog. Example taken from web-client-config-custom.xml: <!-- Launch Add Aspect Dialog --> <action id="add_aspect"> <label>Add Aspect</label> <image>/images/icons/add.gif</image> <action>dialog:addAspect</action> <action-listener>#{BrowseBean.setupSpaceAction}</action-listener> <params> <param name="id">#{actionContext.id}</param> </params> </action> <!-- Add action to more actions menu for each space --> <action-group id="space_browse_menu"> <action idref="add_aspect" /> </action-group> 3. Writing a JSP for the dialog The JSP referenced by the page attribute should only contain the HTML and JSF components to define the “body” of the dialog. It will be included into the dialog container page, defined by the <dialog-container/> element in the web-client-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 of aspects 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 display is read from the webclient.properties file. The <h:selectOneMenu/> element will display the drop-down list of aspects. The list of aspects (<f:selectItems/>) is supplied by the getTestableAspects() method on the RunActionWizard class. Once the user has selected an aspect and clicked on the OK button, the setAspect() method will be called on the dialogs JSF managed bean to set the value of the selected aspect. The expression DialogManager.bean refers to the JSF managed bean being used by the current dialog and configured using the managed-bean attribute in the dialogs configuration. Because an explicit bean is not being referenced, the same JSP page could be re-used for multiple dialogs, each dialog defining its own specific bean implementation.124 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client The JSF “value binding” expressions #{RunActionWizard.testableAspects} and #{DialogManager.bean.aspect} will actually call the RunActionWizard.getTestableAspects() and DialogManager.getBean().setAspect() methods respectively.4. Localising dialog messages All of the Web Client I18N strings are stored in a default message bundle called webclient.properties located in the alfresco/messages directory. Message translations can be provided in separate files, named with an extra suffix corresponding to the standard ISO language and country codes. For example, the French translations of the Web Client messages are found in the webclient_fr_FR.properties file and the French Canadian translations are found in the webclient_fr_CA.properties file. The messages in the base bundle (without a suffix) are in the en_US locale. Custom messages need to be provided in a custom message bundle in the alfresco/ extension directory. The base extension file is also named webclient.properties. Translations can be provided in separate files in the usual manner. The custom Add Aspect dialog does not define any custom messages and simply re-uses an existing message from the standard webclient.properties file: ... aspect=Aspect ...5. Implementing a JSF Managed Bean for the dialog Dialog JSF managed beans referenced by the managed-bean attribute must implement the IDialogBean interface. Generally, however, dialog beans just extend the org.alfresco.web.bean.dialog.BaseDialogBean abstract base class. BaseDialogBean provides the default implementation for the IDialogBean interface but introduces one abstract method finishImpl() that subclasses have to implement. Subclasses can override other BaseDialogBean methods to customise dialog behaviour. See Dialog Beans on page 128 for more detail. a. Creating the AddAspectDialog class The AddAspectDialog class extends the BaseDialogBean and implements the finishImpl() and getFinishButtonDisabled() methods. public class AddAspectDialog extends BaseDialogBean b. Implementing the “getter” and “setter” methods for the aspect property The aspect property stores the value of the aspect selected by the user from the drop-down list. Once the user has selected an aspect and clicked on the OK button, the setAspect() method will be called to set the value of the selected aspect. If the user returns to the dialog, the value of the aspect property will be read using the getAspect() method to initialise the drop-down list to the aspect that the user selected the last time. protected String aspect; public String getAspect() { return aspect; } public void setAspect(String aspect) { this.aspect = aspect; } API Development Course 125
    • Extending the Alfresco Web Client c. Overriding the getFinishButtonDisabled()method. The AddAspectDialog bean overrides the getFinishButtonDisabled() method returning a value of false. The finish (OK) button will not be disabled (will be visible) in the dialog. public boolean getFinishButtonDisabled() { return false; } d. Implementing the finishImpl() method The finishImpl() will do the actual work of the dialog and add the selected aspect to the Space. protected String finishImpl(FacesContext context, String outcome) throws Exception { // get the space the action will apply to NodeRef nodeRef = this.browseBean.getActionSpace().getNodeRef(); // resolve the fully qualified aspect name QName aspectToAdd = Repository.resolveToQName(this.aspect); // add the aspect to the space this.nodeService.addAspect(nodeRef, aspectToAdd, null); // return the default outcome return outcome; } Once the user has selected an aspect from the dialog JSP and clicked on the OK button, JSF will automatically call the setAspect() method on the AddAspectDialog bean passing the value of the aspect selected by the user. The setAspect() method stores the selection in the this.aspect field. The finish() method on the BaseDialogBean will then be called which will in turn call the finishImpl() method on the AddAspectDialog bean. The finishImpl() will do the actual work of the dialog and add the selected aspect to the Space. 6. Registering the JSF Managed Bean The new AddAspectDialog managed bean now has to be registered with JSF in a faces- config.xml file. The faces-config.xml file will be packaged in the META-INF directory for deployment. Example taken from META-INF/faces-config.xml: <managed-bean> <managed-bean-name>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 properties126 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client in the above example. The “setter” methods are implemented by the BaseDialogBean superclass. See Introduction on page 106 for more information on registering managed beans. 7. Packaging and deploying In order to deploy a custom dialog to the Alfresco Web Client, the following files need to be packaged: • the web-client-config-custom.xml file containing the dialog and action configuration; • the dialog JSP containing the HTML and JSF components to define the “body” of the dialog; • the custom webclient.properties file containing custom dialog messages; • the compiled JSF managed bean class; • the custom JSF faces-config.xml file. The compiled class and META-INF/faces-config.xml file needs to be exported to a JAR file. The files then need to be deployed to the following directories in the Alfresco Web Client: • the JAR file to the WEB-INF/lib directory; • the dialog JSP to the jsp/extension directory; • the web-client-config-custom.xml and webclient.properties to the WEB- INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143.After deployment, the 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: API Development Course 127
    • Extending the Alfresco Web ClientFurther InformationDialog Beans Dialog beans are JSF managed beans referenced by the managed-bean attribute in the <dialog/ > configuration. Dialog beans must implement the IDialogBean interface. There is a base class for all dialog managed beans (org.alfresco.web.bean.dialog.BaseDialogBean). The base class provides the default implementation for the IDialogBean interface but introduces one abstract method finishImpl() that subclasses have to implement. By default, the cancel() method will return an outcome of dialog:close, the getCancelButtonLabel() method returns Cancel, the getFinishButtonLabel() method returns OK and the getFinishButtonDisabled() method returns true which will remove the finish (OK) button from the dialog. The finish() method takes care of the transaction and error handling, it passes off the processing to the abstract finishImpl() method. After the transaction has been successfully128 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client committed the BaseDialogBean will also call a doPostCommitProcessing() method that subclasses can override. Both the finishImpl() and doPostCommitProcessing() methods are passed the current outcome, and both methods are expected to return an outcome. Usually the methods will just return the given outcome but there may be some scenarios where the outcome needs to be overridden. The default outcome is dialog:close. If an error occurs during finish() the transaction is rolled back and the formatErrotMessage() method is called. BaseDialogBean does provide a default implementation that produces a generic error message, but subclasses can override this to provide a suitable message for the type of exception. Finally, a getErrorOutcome() method is called to determine what outcome to return in the event of an error, by default, null is returned as that will re-display the page the error occurred on with the error message displayed, there are however, some scenarios where an outcome needs to be returned, if this is the case subclasses can override the getErrorOutcome() method too. The restored() method is a lifecycle method. This is called when the DialogManager restores a dialog from the view stack i.e. when a nested dialog is closed. This method gives the dialog a chance to reset any state that may need refreshing. getContainerTitle() and getContainerDescription() allow the bean implementation to override the title and description provided by configuration. This in essence allows the bean to provide titles and descriptions that are not known until runtime, for example if the title needs to include the title of the node it is acting upon. The getAdditionalButtons() method returns a list of DialogButtonConfig objects representing additional buttons to render. This method essentially allows bean implementations to dynamically add buttons at runtime based on the context of the dialog, see Dialog Buttons on page 129 for more details. Dialog Buttons The getAdditionalButtons() method returns a list of DialogButtonConfig objects representing additional buttons to render. This method essentially allows bean implementations to dynamically add buttons at runtime based on the context of the dialog. The DialogManager combines the button definitions returned by the method and those configured at design time to build a list of extra buttons to render. As an example, this technique is used to render the buttons required for each transition defined in a workflow task. The internals of the DialogButtonConfig class are shown below. public class DialogButtonConfig { private String id; private String label; private String labelId; private String action; private String disabled; private String onclick; }Implementing Dialog Buttons in Java The example implementation of the getAdditionalButtons() method shown below will add an extra button with the My Dialog Button label to the dialog. When the user clicks on the button, the myButtonSelected() method will be called on the dialogs managed bean. public List<DialogButtonConfig> getAdditionalButtons() { List<DialogButtonConfig> buttons = new ArrayList<DialogButtonConfig>(1); API Development Course 129
    • Extending the Alfresco Web Client buttons.add(new DialogButtonConfig( "my-button", "My Dialog Button", null, "#{DialogManager.bean.myButtonSelected}", "false", "javascript:method()")); return buttons; }Defining Dialog Buttons in XML Extra buttons can also be defined with the dialog configuration in a web-client-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 current page. If it detects that it is a dialog, the state of the dialog is retrieved and stored on the view stack. If the current page is not a dialog the current page is added to the view stack. The navigation handler then looks up the configuration for the dialog and passes the configuration object to the DialogManager via the setCurrentDialog() method. The DialogManager then proceeds to instantiate the dialogs managed-bean and set up any parameters passed into the dialog via the setupParameters() action listener method. The navigation handler then navigates to the dialog container page. The dialog container page also uses the #{DialogManager...} syntax to resolve the title, description and icon for the header area and the page to show as the body of the dialog. The DialogManager either returns the requested information from the dialog config object it was passed, or passes the request on to the underlying managed bean. For example, when the user presses the OK button, the DialogManager calls finish() on the managed bean. When a dialog:close request is received, the navigation handler examines the view stack to see what the item at the top of the stack is. If it is a dialog, the state object is retrieved from the stack and restored, and the dialog container page is then navigated to. If the top of the stack is a normal page, the page is retrieved and navigated to. If the close request is overridden by an outcome of dialog:close:browse, the view stack is emptied and the overridden outcome processed.Dialog Framework Reference<dialog/> attributes name The unique name (id) of the dialog. page The path to the JSP page to be used for the dialog.130 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client managed-bean The name of the JSF managed bean to be used as the backing bean for the dialog. icon The path to the icon displayed in the header area of the dialog. title-id The id of an I18N string to be used as the title of the dialog. title A literal string to be used in place of title-id if you do not want an I18N string. description-id The id of an I18N string to be used as the description of the dialog. description A literal string to be used in place of description-id if you do not want an I18N string. error-message-id The id of an I18N string to be used in the case of an error. actions-config-id The id of a configured action group. The actions are displayed in the header area of the dialog. show-ok-button Flag to determine whether the OK button should be displayed. This allows dialogs to display just the Cancel button.Dialog<button/> attributes id The unique id of the button. label-id The id of a string to be used as the label of the button. label A label attribute can be used in place of label-id if you want to use a literal string instead of an I18N string. action The action method binding to call when the button is clicked. disabled Flag to determine whether the button should be rendered as disabled, this can be a JSF binding expression. onclick The JavaScript onclick handler code to execute when the button is clicked. API Development Course 131
    • Extending the Alfresco Web ClientWizard FrameworkIntroduction The wizard framework manages the user interface wizards in the Alfresco Web Client. Most of the standard Web Client wizards are managed by the framework and it is possible to create your own custom wizards using the framework. Each wizard has three main components: • A <wizard/> configuration element in a web-client-config.xml file.performed in exactly the same • A JSP for each step in the wizard that only contains the HTML and JSF components to define the “body” of the step. • A JSF managed bean that is used as a backing bean for the wizard. At the centre of the wizard framework is the Wizard Manager. When a wizard is opened, the Alfresco Navigation Handler looks up the <wizard/> configuration then calls the Wizard Manager to initialise the wizard and instantiate the managed bean before navigating to the JSP page for the first step. See The Wizard Manager on page 138 for more details.Custom Wizard Howto Before starting the tutorial, you should be familiar with Introduction on page 106. The tutorial customises the Create Content wizard to add an extra step allowing a user to add an aspect to the created content. The examples are taken from the CustomWizard SDK sample. 1. Defining the wizard An individual wizard is defined by a <wizard/> element in a Web Client configuration file. All <wizard/> elements must be contained within a <wizards/> element. Default wizards are defined in the global <config/> section (i.e. there is no evaluator or condition) of the web-client-config-wizards.xml file. These may be overridden using a more specific <config/> section, for example, a wizard definition could be overridden for a particular node type. All new wizards or customisations to default wizards should be defined in the standard web-client-config-custom.xml extension file. Combining of configuration is NOT supported for wizard definitions. This means the whole wizard definition has to be copied to the extension file and adjusted appropriately. Example taken from web-client-config-custom.xml. <wizards> <wizard name="createContent" managed-bean="CustomCreateContentWizard" title-id="custom_create_content_wizard_title" description-id="create_content_desc" icon="/images/icons/new_content_large.gif"> ... <step name="aspect" title-id="select_aspect" description-id="create_content_step3_desc"> <page path="/jsp/extension/select-aspect.jsp" title-id="create_content_step3_title" description-id="create_content_step3_desc" instruction-id="default_instruction" />132 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client </step> ... </wizard> </wizards> name Defines the unique name (or id) of the wizard. The above wizard can be referenced in an action definition as wizard:createContent. managed-bean Defines the name of the JSF managed bean to be used as the backing bean for the wizard. path Defines the path to the JSP page to be used for each step. See <wizard/> attributes, <step/> attributes on page 140 and <page/> attributes on page 140 for a complete description of all wizard attributes.2. Defining an action The createContent wizard is already called from the standard Create Content action. The action is part of the browse_create_menu action group (Create menu on the Web Client browse page): Example taken from web-client-config-actions.xml: <!-- Create content --> <action id="create_content"> <permissions> <permission allow="true">CreateChildren</permission> </permissions> <label-id>create_content</label-id> <image>/images/icons/new_content.gif</image> <action>wizard:createContent</action> </action> <!-- Actions Menu for Create in Browse screen --> <action-group id="browse_create_menu"> <action idref="create_content" /> <action idref="create_form" /> <action idref="create_website_wizard" /> <action idref="create_space" /> <action idref="create_space_wizard" /> </action-group>3. Writing the JSPs for the wizard For each <page/>, the JSP referenced by the path attribute should only contain the HTML and JSF components to define the “body” of the wizard. It will be included into the wizard container page, defined by the <wizard-container/> element in the web-client- config-wizards.xml file: <wizard-container>/jsp/wizard/container.jsp</wizard-container> API Development Course 133
    • Extending the Alfresco Web Client The select-aspect.jsp example below displays a simple label and a drop-down list of aspects for the user to choose from. <%@ taglib 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 display is read from the webclient.properties file. The <h:selectOneMenu/> element will display the drop-down list of aspects. The list of aspects (<f:selectItems/>) is supplied by the getAspects() method on the wizards JSF managed bean. Once the user has selected an aspect and clicked on the Next button, the setAspect() method will be called on the wizards JSF managed bean to set the value of the selected aspect. The expression WizardManager.bean refers to the JSF managed bean being used by the current wizard and configured using the managed-bean attribute in the wizards configuration. Because an explicit bean is not being referenced, the same JSP page could be re-used for multiple wizards, each wizard defining its own specific bean implementation. The JSF “value binding” expressions #{WizardManager.bean.aspect} and #{WizardManager.bean.aspects} will actually call the WizardManager.getBean().setAspect() and WizardManager.getBean().getAspects() methods respectively. 4. Localising wizard messages All of the Web Client I18N strings are stored in a default message bundle called webclient.properties located in the alfresco/messages directory. Message translations can be provided in separate files, named with an extra suffix corresponding to the standard ISO language and country codes. For example, the French translations of the Web Client messages are found in the webclient_fr_FR.properties file and the French Canadian translations are found in the webclient_fr_CA.properties file. The messages in the base bundle (without a suffix) are in the en_US locale. Custom messages need to be provided in a custom message bundle in the alfresco/ extension directory. The base extension file is also named webclient.properties. Translations can be provided in separate files in the usual manner. The custom Create Content wizard messages are stored in a webclient.properties extension file: custom_create_content_wizard_title=Custom Create Content Wizard select_aspect=Select Aspect create_content_step3_title=Step Three - Select Aspect create_content_step3_desc=Select the aspect to apply to the content. 5. Implementing a JSF Managed Bean for the wizard Wizard JSF managed beans referenced by the managed-bean attribute must implement the IWizardBean interface. Generally, however, wizard beans just extend the org.alfresco.web.bean.wizard.BaseWizardBean abstract base class.134 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web ClientBaseWizardBean 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 to implement. • Subclasses can override BaseWizardBean methods to customise wizard behaviour.a. Creating the CustomCreateContentWizard class The aim of the tutorial is to customise the Create Content wizard to add an extra step allowing a user to add an aspect to the created content. The CustomCreateContentWizard managed bean, therefore, just needs to extend the existing CreateContentWizard bean. public class CustomCreateContentWizard extends CreateContentWizardb. Implementing the “getter” method for the aspects property The aspects property stores a list of aspects that the use can choose from. The list of aspects is read from the aspects element in the Content Wizards section of a Web Client configuration file. Once initialised, the list is no longer read from the configuration file on future calls. protected List<SelectItem> aspects; public List<SelectItem> getAspects() { if (this.aspects == null) { ConfigService svc = Application.getConfigService( FacesContext.getCurrentInstance()); Config wizardCfg = svc.getConfig("Content Wizards"); if (wizardCfg != null) { ConfigElement aspectsCfg = wizardCfg.getConfigElement("aspects"); if (aspectsCfg != null) { ... } } } return this.aspects; }c. Implementing the “getter” and “setter” methods for the aspect property The aspect property stores the value of the aspect selected by the user from the drop-down list. Once the user has selected an aspect and clicked on the Next button, the setAspect() method will be called to set the value of the selected aspect. If the user returns to the same step, the value of the aspect property will be read using the API Development Course 135
    • Extending the Alfresco Web Client getAspect() method to initialise the drop-down list to the aspect that the user has already selected. protected String aspect; public String getAspect() { return aspect; } public void setAspect(String aspect) { this.aspect = aspect; } d. Implementing the finishImpl() method The finishImpl() is called at the end of the wizard to do the actual work. In the case of our customised implementation, we just need to call the standard finishImpl() method on the CreateContentWizard superclass before adding the selected aspect to the created content. protected String finishImpl(FacesContext context, String outcome) throws Exception { super.finishImpl(context, outcome); // add the selected aspect (the properties page after the // wizard will allow us to set the properties) QName aspectToAdd = Repository.resolveToQName(this.aspect); this.nodeService.addAspect(this.createdNode, aspectToAdd, null); return outcome; } 6. Registering the JSF Managed Bean The customised CustomCreateContentWizard managed bean now has to be registered with JSF in a faces-config.xml file. The faces-config.xml file will be packaged in the META-INF directory for deployment. Example taken from META-INF/faces-config.xml: <managed-bean> <managed-bean-name>CustomCreateContentWizard</managed-bean-name> <managed-bean-class> org.alfresco.sample.CustomCreateContentWizard </managed-bean-class> <managed-bean-scope>session</managed-bean-scope> <managed-property> <property-name>nodeService</property-name> <value>#{NodeService}</value> </managed-property> ... </managed-bean> The CustomCreateContentWizard bean uses several public services such as nodeService . JSF will “inject” references to the public services defined as managed properties in the above example. The “setter” methods are implemented by the superclasses. See Introduction on page 106 for more information on registering managed beans. 7. Packaging and deploying136 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client In order to deploy a custom wizard to the Alfresco Web Client, the following files need to be packaged: • the web-client-config-custom.xml file containing the wizard and action configuration; • the wizard JSPs containing the HTML and JSF components to define the “body” of each wizard step; • the custom webclient.properties file containing custom wizard messages; • the compiled JSF managed bean class; • the custom JSF faces-config.xml file. The compiled class and META-INF/faces-config.xml file needs to be exported to a JAR file. The files then need to be deployed to the following directories in the Alfresco Web Client: • the JAR file to the WEB-INF/lib directory; • the wizard JSPs to the jsp/extension directory; • the web-client-config-custom.xml and webclient.properties to the WEB- INF/classes/alfresco/extension directory. For deployment in a production environment, the files should be packaged and deployed as an Alfresco Module Package (AMP). For more details, see Introduction on page 143. After deployment, the Create Content wizard now has en extra Select Aspect step:Further ReadingWizard Beans Wizard beans are JSF managed beans referenced by the managed-bean attribute in the <wizard/> configuration. Wizard beans must implement the IWizardBean interface that extends the IDialogBean interface from the Dialog Framework. API Development Course 137
    • Extending the Alfresco Web Client There is a base class for all wizard managed beans (org.alfresco.web.bean.wizard.BaseWizardBean). The base class extends the BaseDialogBean class used for dialogs and provides the default implementation for the IWizardBean interface. As BaseWizardBean extends BaseDialogBean most of the processing is performed in exactly the same way as for dialogs. For more information about BaseDialogBean,see Dialog Beans on page 128. The only difference is the default return values for some methods. The cancel() method returns an outcome of wizard:close, the getCancelButtonLabel() method returns Cancel, the getFinishButtonLabel() method returns Finish, the getNextButtonLabel() method returns Next, the getBackButtonLabel() method returns Back and the getNextButtonDisabled() method returns false. Finally, the default outcome returned from finish() is wizard:close. The next() and back() methods allow the bean implementation to be notified when the user presses the Next or Back button. This can be used to perform processing as the user progresses through the wizard steps. The only other method BaseWizardBean provides is the buildSummary() method, this is used by subclasses to help build the HTML summary table for use on the last page of all wizards.The Wizard Manager The Wizard Manager is at the centre of the wizard framework.138 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web Client When the Alfresco navigation handler gets a request to open a wizard it examines the current page. If it detects that it is a wizard, the state of the wizard is retrieved and stored on the view stack. If the current page is not a wizard the current page is added to the view stack. The navigation handler then looks up the configuration for the wizard and passes the configuration object to the WizardManager via the setCurrentWizard() method. The WizardManager then proceeds to instantiate the wizards managed-bean and set up any parameters passed into the wizard via the setupParameters() action listener method. It processes the list of steps the wizard has, and determines what the current page is. The navigation handler then navigates to the wizard container page. The wizard container page also uses the #{WizardManager...} syntax to resolve the title, description and icon for the header area, the list of steps on the left and the page to show as the body for the current step of the wizard. The WizardManager either returns the requested information from the wizard config object it was passed, or passes the request on to the underlying managed bean. For example, when the user presses the Finish button, the WizardManager calls finish() on the managed bean. When a user presses the Next or Back button, the WizardManager increases or decreases the current step counter and determines from the wizard config what page should be shown. When the container page renders the buttons, the Next and Finish buttons disabled state is controlled via the getNextButtonDisabled() and getFinishButtonDisabled() methods. These methods can be used to enable or disable the buttons appropriately, for example, if the wizard already has enough state to complete successfully. When a wizard:close request is received, the navigation handler examines the view stack to see what the item at the top of the stack is. If it is a wizard, the state object is retrieved from the stack and restored, and the wizard container page is then navigated to. If the top of the stack is a normal page, the page is retrieved and navigated to. If the close request is overridden by an outcome of wizard:close:browse, the view stack is emptied and the overridden outcome processed.Wizard Framework Reference<wizard/> attributes name The unique name (id) of the wizard. managed-bean The name of the JSF managed bean to be used as the backing bean for the wizard. icon The path to the icon displayed in the header area of the wizard. title-id The id of an I18N string to be used as the title of the wizard. title A literal string to be used in place of title-id if you do not want an I18N string. description-id The id of an I18N string to be used as the description of the wizard. description A literal string to be used in place of description-id if you do not want an I18N string. error-message-id The id of an I18N string to be used in the case of an error. API Development Course 139
    • Extending the Alfresco Web Client actions-config-id The id of a configured action group. The actions are displayed in the header area of the wizard.<step/> attributes name The unique name (within the wizard) of the step. title-id The id of an I18N string to be used as the title of the step (appears in the list of steps on the left hand side). title A literal string to be used in place of title-id if you do not want an I18N string. description-id The id of an I18N string to be used as the description of the step (the “tooltip” for the step on the left hand side). description A literal string to be used in place of description-id if you do not want an I18N string.<page/> attributes path The path to the JSP page to be used for the wizard step. title-id The id of an I18N string to be used as the title of the page (appears on the first line of the inner panel). title A literal string to be used in place of title-id if you do not want an I18N string. description-id The id of an I18N string to be used as the description of the page (appears on the second line of the inner panel). description A literal string to be used in place of description-id if you do not want an I18N string. instruction-id The id of an I18N string to be used as the instruction text for the page (appears on the last line of the inner panel). instruction A literal string to be used in place of instruction-id if you do not want an I18N string.<condition/> element A page can also be wrapped with a condition element as shown below: <condition if="#{CreateSpaceWizard.createFrom == scratch}"> <page path="/jsp/spaces/create-space-wizard/from-scratch.jsp" title-id="create_space_step2_title" description-id="create_space_step2_desc" instruction-id="default_instruction" /> </condition> if A value binding expression that must resolve to true or false.140 Alfresco Enterprise Edition Version 3.2
    • Extending the Alfresco Web ClientA <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. API Development Course 141
    • Packaging ExtensionsPackaging Extensions142 Alfresco Enterprise Edition Version 3.2
    • Packaging ExtensionsAlfresco Module PackagesIntroduction An Alfresco Module Package (AMP) is a collection of code, XML, images, CSS, etc. that collectively extend the functionality or data provided by the standard Alfresco Repository. An AMP file can contain as little as a set of custom templates or a new category. It can contain a custom model and associated UI customisations. It could contain a complete new set of functionality, for example records management. As a general rule of thumb, anything that is considered to be an “installable” extension to the Alfresco repository should be called a module and packaged as an AMP file. AMP files can be installed into the Alfresco WAR using the Module Management Tool. An AMP file has a standard format described below that can be customised if required. Once the contents of the AMP file has been mapped into an Alfresco WAR using the Module Management Tool, the WAR can be deployed to the application server. When the repository is next started, the installed module configuration will be detected, and the repository will be bootstrapped to include the new module functionality and data.Basic AMP Howto The following examples are taken from the Basic AMP SDK sample and assume use of the Eclipse IDE. 1. Structuring an Alfresco Module project An Alfresco Module project can be structured in any way that suits the developer environment. As long as the resulting AMP file is packaged correctly and the required property and context files are present, the module will install successfully. The recommended project structure is as follows: |-- source | |-- java |-- <module package structure starts here> | |-- web |-- css |-- images |-- jsp |-- scripts | |-- config |-- <resource package structure starts here> | |-- build |-- dist |-- lib | |-- build.xml source/java/ Contains the Java source for the Alfresco Module. source/web/ Contains any web UI resources (JSPs, images, CSS, JavaScript). config/ Contains configuration files and resources used by the module. API Development Course 143
    • Packaging Extensions build/ Build directory for compiled class files. build/dist/ Build directory for AMP files. build/lib/ Build directory for JAR files. The recommended package structure for Java source (source/java), configuration files and resources (config) is org.alfresco.module.<moduleid>, where moduleid is the unique module id of the module (see below). 2. The Module properties file The module.properties file is required by the module service to identify the module and its details, when it is installed. When the AMP file is built the module.properties file must be placed at the root of the AMP file, but during development it is recommended that it should reside in the package alfresco.module.<moduleid> as this is the location it will end up in once it is installed into the WAR. In this location it allows the developer to run unit tests within Eclipse and the embedded repository that is started will behave as if the module is installed. This is because the relevant module.properties file is on the class path in the correct location. The module.properties file itself contains the module id, version, title and description of the module. Example module.properties from the Basic AMP SDK sample: # SDK Sample module module.id=sdkDemoAmp module.title=SDK Demo AMP Project module.description=Skeleton demo project to build an amp file module.version=1.0 module.id The module id specified in this file will act as a unique identifier for this module. It is important that the module id is globally unique so that it never clashes with other modules when it is installed. For example: org.alfresco.module.RecordsManagement. It is possible to rename a module using the alias mechanism. Module IDs may contain 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 taken into consideration when installing the module into a WAR. It will determine whether it is a new install or an update to an existing installation of a previous version. The version number must be made up of numeric values separated by dots. For example 2.1.56 is a valid version number, 2.3.4a is not. module.title The title of the module. module.description The description of the module. module.aliases (optional) When a module gets renamed, it is necessary to add the original name to the list of aliases. This ensures that the module tool and repository startup can correctly match the new name to one of the old names.144 Alfresco Enterprise Edition Version 3.2
    • Packaging Extensions module.depends.* (optional) When a module is installed, it may be a requirement that another module is installed. It may be a requirement that a specific version, set of versions or range of versions is present. The dependency has been met as long as the installed version of the dependency matches any of the ranges or versions give. Here are some examples: # Any version of X must be installed module.depends.X=* # Need to have versions 1.0, 1.5 or 2.0 of Y installed module.depends.Y=1.0, 1.5, 2.0 # Need to have version any version of Z less than 1.0 installed module.depends.Z=*-0.9.93. The Module Context file A module is initialised when the Alfresco repository loads the root Spring configuration for that module. A modules root Spring configuration must be placed in the package alfresco.module.<moduleId> and should be called module-context.xml. When the module service is initialised all the module-context.xml configurations found are loaded, thus initialising the installed modules ready for use. The module-context.xml file is a standard Spring configuration file and typically new beans will be defined, custom content models and client configuration specified and data loaded or patched. In a big module the configuration may be split up into smaller Spring configurations which are included by module-context.xml. Example module-context.xml from the Basic AMP SDK sample: <beans> <import resource= "classpath:alfresco/module/sdkDemoAmp/context/service-context.xml"/> </beans> The imported context/service-context.xml file: <beans> <!-- A simple class that is initialized by Spring --> <bean id="sdk.demoAmp.exampleBean" class="org.alfresco.module.sdkdemoamp.Demo" init-method="init" /> <!-- A simple module component that will be executed once --> <bean id="sdk.demoAmp.exampleComponent" class="org.alfresco.module.sdkdemoamp.DemoComponent" parent="module.baseComponent" > <property name="moduleId" value="sdkDemoAmp" /> <property name="name" value="exampleComponent" /> <property name="description" value="A demonstration component" /> <property name="sinceVersion" value="1.0" /> <property name="appliesFromVersion" value="1.0" /> </bean> </beans> The Demo class prints a message to the console when it is initialised by Spring: public class Demo { public void init() { System.out.println("SDK Demo AMP class has been loaded"); } } API Development Course 145
    • Packaging Extensions The DemoComponent is an example component that will be executed just once during module initialisation: public class DemoComponent extends AbstractModuleComponent { @Override protected void executeInternal() throws Throwable { System.out.println("DemoComponent has been executed"); } } 4. Building the AMP file a. The structure of an AMP file An AMP file is ZIP compressed and has the following default structure: / | |-- /config | |-- /lib | |-- /licenses | |-- /web | |-- /jsp |-- /css |-- /images |-- /scripts | |-- module.properties | |-- file-mapping.properties config/ Contains the Spring module-context.xml and UI config that generally reside in the standard package structure (alfresco.module.<moduleId>) within this directory. Other resources, such as XML import files or ACPs, may also reside here. The contents are mapped into the /WEB-INF/classes directory in the WAR file and as such will be on the classpath. lib/ Contains any module specific JAR files. The contents are mapped into the /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. The contents are mapped to the equivalent directories in the WAR file. All sub-directory structures are preserved. If a file already exists it is overridden in the WAR and a recoverable backup is taken. module.properties The module.properties file is required. It contains meta-data about the module, including the module id and version number. file-mapping.properties A file-mapping.properties file can optionally be provided to customise the way AMP file are mapped into the WAR file. See Customising the structure of an AMP file on page 149 for more details.146 Alfresco Enterprise Edition Version 3.2
    • Packaging Extensionsb. 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 JAR file: <target name="package-jar" depends="compile"> <jar destfile="${jar.file}" > <fileset dir="${build.dir}/classes" excludes="**/custom*,**/*Test*" includes="**/*.class" /> </jar> </target> The package-amp target packages the JAR files and configuration files into the destination AMP file: <target name="package-amp" depends="mkdirs, package-jar" description="Package the Module" > <zip destfile="${amp.file}" > <fileset dir="${project.dir}/build" includes="lib/*.jar" /> <fileset dir="${project.dir}" includes="config/**/*.*" excludes="**/module.properties" /> <fileset dir="${project.dir}/config/alfresco/module/sdkDemoAmp" includes="module.properties" /> </zip> </target> The update-war target uses the Module Management Tool to install the AMP into an existing WAR file: <target name="update-war" depends="package-amp" description="Update the WAR file. Set -Dwar.file=..." > <echo>Installing SDK Demo AMP into WAR</echo> API Development Course 147
    • Packaging Extensions <java dir="." fork="true" classname="org.alfresco.repo.module.tool.ModuleManagementTool"> <classpath refid="class.path" /> <arg line="install ${amp.file} ${war.file} -force -verbose"/> </java> </target> 5. Installing the AMP AMP files are installed and managed using the Module Management Tool (MMT). See Module Management Tool on page 150 for more details. Once the AMP file has been packaged, it can be installed into an Alfresco WAR file either using Ant or directly from the command line. The Ant build.xml file in the Basic AMP SDK sample is an example of how to run the Module Management Tool from Ant. To install the AMP file (alfresco-sdk-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. The following command will do a dry-run (preview) installation: java -jar alfresco-mmt-2.1.jar install alfresco-sdk-custom-service.amp alfresco.war -preview The following example will install the AMP file in verbose mode: java -jar alfresco-mmt-2.1.jar install alfresco-sdk-custom-service.amp alfresco.war -verbose The modified Alfresco WAR can then be re-deployed back into your application server. On re-starting the application server, the console will show that the custom class has been initialised during startup:148 Alfresco Enterprise Edition Version 3.2
    • Packaging ExtensionsFurther ReadingCustomising the structure of an AMP file In order to customise the structure of your AMP file, a file-mapping.properties file must be provided which describes how the structure of your AMP file will be mapped to the Alfresco WAR when the AMP file is installed by the Module Management Tool. If no file-mapping.properties file is provided the default mapping will be used. The structure of file-mapping.properties file is that of a standard Java property file, with the “key” of each entry being the directory in the AMP file structure and the “value” being the location that the contents for that directory should be copied to in the WAR file. If the source directory does not exist in the AMP file, then the mapping will be ignored; however, if the destination directory in the WAR file does not exist then a runtime exception will be raised when the Module Management Tool tries to install the AMP. If a mapping provided in the file-mapping.properties file overrides one of the default mappings, then this will take precedence when the installation into the WAR takes place. If a mapping is declared that has a folder as the source, then the folder will be recursively copied into the WAR file. Below is an example of a file-mapping.properties file: # Custom AMP to WAR location mappings # # The following property can be used to include the standard set of mappings. # The contents of this file will override any defaults. The default is # true, i.e. the default mappings will be augmented or modified by values in # this file. # include.default=false # # Custom mappings. 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 using the Importer Module Component. The XML file or ACP file to be imported needs to be placed somewhere on the modules classpath and then a Spring bean similar to the following needs to be defined in the modules module-context.xml file: <bean id="myModule.bootstrap" class="org.alfresco.repo.module.ImporterModuleComponent" parent="module.baseComponent"> <!-- Module Details --> <property name="moduleId" value="myModule" /> <property name="name" value="myModuleBootstrap" /> <property name="description" value="My Modules initial data requirements" /> <property name="sinceVersion" value="1.0" /> <property name="appliesFromVersion" value="1.0" /> <!-- Data properties --> <property name="importer" ref="spacesBootstrap"/> API Development Course 149
    • Packaging Extensions <property name="bootstrapViews"> <list> <props> <prop key="path"> /${spaces.company_home.childname} </prop> <prop key="location"> alfresco/module/myModule-123/myACP.acp </prop> </props> </list> </property> </bean> The XML and/or ACP files to be imported and the destination location in the repository where the data should be imported are supplied as a list to the bootstrapViews property.Alfresco Module Package ReferenceModule Management Tool The Module Management Tool (MMT) has a number of commands. Details of these are outlined below:150 Alfresco Enterprise Edition Version 3.2
    • Packaging ExtensionsInstall usage: install <AMPFileLocation> <WARFileLocation> [options] valid options: -verbose : enable verbose output -directory : indicates that the amp file location specified is a directory. All amp files found in the directory and its sub directories are installed. -force : forces installation of AMP regardless of currently installed module version -preview : previews installation of AMP without modifying WAR file -nobackup : indicates that no backup should be made of the WAR Installs the files found in the AMP file into the Alfresco WAR, updates if an older version is already installed. This is done using the standard mapping, unless a custom mapping is provided. If the module represented by the AMP is already installed and the installing AMP is of a higher release version, then the files relating to the older version will be removed from the WAR and replaced with the newer files. It is the responsibility of the module developer to provide the appropriate Module components to bootstrap or patch any data as required when updated WAR is run. If the installing module version is less than or equal to the version already installed in the WAR then installation will be aborted unless the -force option is specified. In this case the installing AMP will always replace the currently installed version. This option is especially useful when developing an AMP. Before an AMP is installed into a WAR a copy of the original WAR is taken an placed in the same directory. Specifying the -nobackup option prevent this from occurring. It is considered good practice to do a -preview install prior to doing the install for real. This reports the modifications that will occur on the WAR without making any physical changes. The changes that are of most importance to note are those that are going to update existing files. As a general rule it is considered bad practice to overwrite an existing file in an AMP, however it is sometimes necessary. In these situations when the AMP is installed a backup of the updated file is taken and stored in the WAR. When an update of the module occurs and the old files are removed, this backup will be restored prior to the installation of the new files. Problems may occur if multiple installed modules modify the same existing file. In these cases a manual restore may be necessary if recovery to an existing state is required.list usage: list <warFile> Lists the details about all the modules currently installed in the WAR file specified. The output is directed to the console. API Development Course 151