Note: Adobe is migrating Adobe® LiveCycle® Content Services ES customers to the Content Repository built on the modern, modular CRX architecture, acquired during the Adobe acquisition of Day Software. The Content Repository is provided with LiveCycle Foundation and is available as of the LiveCycle ES4 release.
LiveCycle Content Services (deprecated) is a flexible platform
for developing content management applications, and comes with a
web client that can be customized for your needs. One of the ways
in which you can customize the web client is by developing custom
actions.
An action is a discrete unit of work that a user applies to a
piece of content, such as check out, check in, update, cut, copy,
edit, and delete. You can configure any type of action at run time,
and these actions are available in the Content Services (deprecated) web client
interface.
To understand how custom actions are used, consider the following
scenario, which will be used as the basis for all code examples
in this section.
Suppose that you are adding a Web 2.0 style tagging feature to
the web client. You need to then consider the content model, the
business logic, and the user interface.
The content model requires that you define a taggable aspect
available in the Content Services (deprecated) content model so that it can be
applied using the custom tag action in the repository.
Business logic is needed to add a list of tags to an item.
The user interface must be configured to show the taggable aspect
in the custom tag action’s property sheet.
After you create the logic, you must package and deploy your
custom action to Content Services (deprecated).
The tagging example discussed in this section shows how to create
a tagging action, add a parameter to that tagging action, and incorporate
it into the web client. It is based on a sample located within your
SDK installation at \sdk\misc\ContentServices\adobe-contentservices-sdk.zip\SDK TaggingSample.
Summary of stepsTo develop a custom action, which is a configurable
component, you must perform the following steps:
Set
up your development environment.
Define your application logic.
Define the user interface resources.
Package and deploy the component.
Set up your development environmentTo create a custom action, set the Eclipse Compiler Compliance
Level to Java SE Development Kit version 1.5 (5.0) or later. Next
import the JAR files located in \lib\server folder located in the
SDK zip file, including the following JAR files:
alfresco-repository.jar
alfresco-core.jar
alfresco-web-client.jar
Also, include the following
JAR files in the build path:
myfaces-api-1.1.5.jar located in the \dependencies\ folder
spring-2.0.8.jar located in the \dependencies\ folder
servlet.jar located in the \dependencies\devenv\ folder
Defining your application logicCreating a custom modelIf you would like to define a custom model, create a custom
model to contain the new aspect and tags to assign by the user.
In this example, the model uses a prefix of tag, the aspect
is called tag:taggable, and the property is called tag:tags.
This example contains a sample implementation of the tags custom
model and is defined in the tagsModel.xml file. Place this
XML file in a folder outside of the Eclipse project (for example, ...\AlfrescoExtension\tagsModel.xml).
<?xml version="1.0" encoding="UTF-8"?>
<!-- Definition of new Model -->
<model name="tag:tagsmodel" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<!-- Optional meta-data about the model -->
<description>Tagging Model</description>
<author>Gavin Cornwell</author>
<version>1.0</version>
<!-- Imports are required to allow references to definitions in other models -->
<imports>
<!-- Import Alfresco Dictionary Definitions -->
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d"/>
<!-- Import Alfresco Content Domain Model Definitions -->
<import uri="http://www.alfresco.org/model/content/1.0" prefix="cm"/>
</imports>
<!-- Introduction of new namespaces defined by this model -->
<namespaces>
<namespace uri="extension.tags" prefix="tag"/>
</namespaces>
<aspects>
<!-- 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>
</aspects>
</model>
Creating the TagActionExecutor classAn action is a unit of work performed against a node, such
as moving a node, transforming its contents, or checking the node
in or out. At a minimum, an action consists of an ActionExecutor class
and its associated bean declaration.
An action must implement the org.alfresco.repo.action.executor.ActionExecuter interface. The ActionExecuter interface
is best implemented by extending the abstract class ActionExecuterAbstractBase,
which provides basic services for action executor implementations.
Your implementation of the ActionExecuterAbstractBase interface must
include an addParameterDefinitions method, which
is used to add parameters to the custom repository action, as well
as an executeImpl method, which contains the actual
business logic.
The name of the action executor is defined by the static NAME attribute,
which is assigned the value of "tag" in this case,
as shown in the following code example. This example shows how to
define the name of the action executor through the static NAME attribute.
public class TagActionExecuter extends ActionExecuterAbstractBase
{
/** The name of the action */
public static final String NAME = "tag";
Adding action parametersThe addParameterDefinitions method implementation
is required in all cases, even if no parameters are used. To add
parameters to an action, you must perform the following steps:
Implement the addParameterDefinitions method.
Use the parameters in the business logic contained in the executeImpl method.
Define the parameter I18N messages to be displayed in the
user interface.
To implement the addParameterDefinitions method,
you must go into its definition and create a new ParameterDefinition object,
as shown in the following code example.
This example shows
how to use the parameter list to add a new parameter definition.
In this example, PARAM_TAGS contains the name of
the parameter.
/** The parameter names */
public static final String PARAM_TAGS = "tags";
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
{
// Specify the parameters
paramList.add(new ParameterDefinitionImpl(PARAM_TAGS,
DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_TAGS)));
}
Implementing the business logicThe executeImpl method is where the custom
action business logic resides. It receives an Action object
and the NodeRef of the node on which the action
is performed. In this example, the executeImpl method
adds the taggable aspect to the node, creates a
list of tags, and uses the list to set the node’s tags property.
Because the executeImpl method uses the NodeService,
inject the NodeService using Spring dependency
injection, as shown in the following code example.
private NodeService nodeService;
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
Defining the TagActionExecuter implementationThis example shows
the entire implementation of the TagActionExecutor.java class,
in which the business logic is contained in the executeImpl method
and the parameters are added through the addParameterDefinitions method.
package org.alfresco.sample;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.namespace.QName;
public class TagActionExecuter extends ActionExecuterAbstractBase
{
/** The name of the action */
public static final String NAME = "tag";
/** The parameter names */
public static final String PARAM_TAGS = "tags";
/**
* The node service
*/
private NodeService nodeService;
/**
* Sets the node service
*
* @param nodeService the node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* This action will take the comma separated list of tags and add them
* separately to the tags property after applying the taggable aspect.
*
* If no tags are supplied the aspect is still applied.
*/
@Override
protected void executeImpl(Action action, NodeRef actionedUponNodeRef)
{
if (this.nodeService.exists(actionedUponNodeRef) == true)
{
// add the aspect if it is not already present on the node
QName tagAspect = QName.createQName("extension.tags", "taggable");
if (this.nodeService.hasAspect(actionedUponNodeRef, tagAspect) == false)
{
this.nodeService.addAspect(actionedUponNodeRef, tagAspect, null);
}
// 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);
}
}
/**
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
*/
@Override
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
{
// Specify the parameters
paramList.add(new ParameterDefinitionImpl(PARAM_TAGS,
DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_TAGS)));
}
}
Creating the TagActionHandler classAn action handler is required to direct the wizard to the
page to collect the parameters and marshall them between the wizard
and the repository. The action handler is implemented by extending
the BaseActionHandler class.
If the page collecting the parameters requires a default setup,
you must override the setupUIDefaults method. The getJSPPath method
must return the path to the action’s JSP. In this example, it returns
a path to /jsp/extension/tag.jsp.
The prepareForSave method places the tags that
the user entered into the repository properties map.
The prepareForEdit method takes the tags stored
in the action and places them in the properties map for the wizard.
The generateSummary method is used to generate
a summary string for the action, which typically includes the parameters
added by the user.
The following code, contained in the TagActionHandler.java file,
shows the action handler implementation used by the wizard.
package org.alfresco.sample;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Map;
import javax.faces.context.FacesContext;
import org.alfresco.web.app.Application;
import org.alfresco.web.bean.actions.handlers.BaseActionHandler;
import org.alfresco.web.bean.wizard.IWizardBean;
public class TagActionHandler extends BaseActionHandler
{
public static final String PROP_TAGS = "tags";
public String getJSPPath()
{
return "/jsp/extension/tag.jsp";
}
public void prepareForSave(Map<String, Serializable> actionProps,
Map<String, Serializable> repoProps)
{
repoProps.put(TagActionExecuter.PARAM_TAGS, (String)actionProps.get(PROP_TAGS));
}
public void prepareForEdit(Map<String, Serializable> actionProps,
Map<String, Serializable> repoProps)
{
actionProps.put(PROP_TAGS, (String)repoProps.get(TagActionExecuter.PARAM_TAGS));
}
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});
}
}
Defining the user interface resourcesCreating the action JSPAn action has an associated title and description. The ActionExecuterAbstractBase implementation
looks for messages in the I18N resource bundles using an identifier
of the form:
<action-name>.title
<action-name>.description
where <action-name> is the name of the
action defined by the static NAME attribute in the action executor
class.
Each action parameter has an associated display label. The getParamDisplayLabel method
of the ParameterizedItemAbstractBase implementation
looks for a message in the I18N resource bundles using an identifier
of the form:
<action-name>.<param-name>.display-label
where <param-name> is the parameter value
passed to the getParamDisplayLabel method.
The action title, description, and parameter display labels are
defined as shown in the following example.
The following file, tag-action-messages.properties, contains
the action title and description, as well as the parameter display
labels. This file should be added to the Eclipse package, next to
both Java files.
# 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
Although there are action wizards, the JSPs that collect parameters
for actions and conditions are not displayed within the wizard container,
so you must use the whole page structure in your action JSP. One
convenient way to accomplish this task is to copy an existing one
and modify it for your needs.
Modified portions of the add-features.jsp fileIn this example,
you must copy the jsp/actions/add-features.jsp action, since you only
need to replace the drop-down list with a text field and modify
a few labels such as the page title. The modified portions are shown
in the following code example.
<r:page titleId="title_action_tag">
<f:view>
<%-- load a bundle of properties with I18N strings --%>
<f:loadBundle basename="alfresco.messages.webclient" var="msg"/>
<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>
The tag.jsp fileThis example shows the entire contents of
the tag.jsp file needed for the user interface. This file
should be placed in another seperate folder outside of the Eclipse
project (for example, ...\JSPExtension\tag.jsp).
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="/WEB-INF/alfresco.tld" prefix="a" %>
<%@ taglib uri="/WEB-INF/repo.tld" prefix="r" %>
<%@ page buffer="32kb" contentType="text/html;charset=UTF-8" %>
<%@ page isELIgnored="false" %>
<%@ page import="org.alfresco.web.ui.common.PanelGenerator" %>
<r:page titleId="title_action_tag">
<f:view>
<%-- load a bundle of properties with I18N strings --%>
<f:loadBundle basename="alfresco.messages.webclient" var="msg"/>
<f:loadBundle basename="alfresco.extension.webclient" var="customMsg"/>
<h:form acceptcharset="UTF-8" id="tag-action">
<%-- Main outer table --%>
<table cellspacing="0" cellpadding="2">
<%-- Title bar --%>
<tr>
<td colspan="2">
<%@ include file="../parts/titlebar.jsp" %>
</td>
</tr>
<%-- Main area --%>
<tr valign="top">
<%-- Shelf --%>
<td>
<%@ include file="../parts/shelf.jsp" %>
</td>
<%-- Work Area --%>
<td width="100%">
<table cellspacing="0" cellpadding="0" width="100%">
<%-- Breadcrumb --%>
<%@ include file="../parts/breadcrumb.jsp" %>
<%-- Status and Actions --%>
<tr>
<td style="background-image: url(<%=request.getContextPath()%>/images/parts/statuspanel_4.gif)" width="4"></td>
<td bgcolor="#EEEEEE">
<%-- Status and Actions inner contents table --%>
<%-- Generally this consists of an icon, textual summary and actions for the current object --%>
<table cellspacing="4" cellpadding="0" width="100%">
<tr>
<td width="32">
<h:graphicImage id="wizard-logo" url="/images/icons/new_rule_large.gif" />
</td>
<td>
<div class="mainTitle"><h:outputText value="#{WizardManager.title}" /></div>
<div class="mainSubText"><h:outputText value="#{WizardManager.description}" /></div>
</td>
</tr>
</table>
</td>
<td style="background-image: url(<%=request.getContextPath()%>/images/parts/statuspanel_6.gif)" width="4"></td>
</tr>
<%-- separator row with gradient shadow --%>
<tr>
<td><img src="<%=request.getContextPath()%>/images/parts/statuspanel_7.gif" width="4" height="9"></td>
<td style="background-image: url(<%=request.getContextPath()%>/images/parts/statuspanel_8.gif)"></td>
<td><img src="<%=request.getContextPath()%>/images/parts/statuspanel_9.gif" width="4" height="9"></td>
</tr>
<%-- Details --%>
<tr valign=top>
<td style="background-image: url(<%=request.getContextPath()%>/images/parts/whitepanel_4.gif)" width="4"></td>
<td>
<table cellspacing="0" cellpadding="3" border="0" width="100%">
<tr>
<td width="100%" valign="top">
<a:errors message="#{msg.error_wizard}" styleClass="errorMessage" />
<% PanelGenerator.generatePanelStart(out, request.getContextPath(), "white", "white"); %>
<table cellpadding="2" cellspacing="2" border="0" width="100%">
<tr>
<td colspan="2" class="mainSubTitle"><h:outputText value="#{msg.set_action_values}" /></td>
</tr>
<tr><td colspan="2" class="paddingRow"></td></tr>
<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>
<tr><td class="paddingRow"></td></tr>
</table>
<% PanelGenerator.generatePanelEnd(out, request.getContextPath(), "white"); %>
</td>
<td valign="top">
<% PanelGenerator.generatePanelStart(out, request.getContextPath(), "blue", "#D3E6FE"); %>
<table cellpadding="1" cellspacing="1" border="0">
<tr>
<td align="center">
<h:commandButton value="#{msg.ok}" action="#{WizardManager.bean.addAction}" styleClass="wizardButton" />
</td>
</tr>
<tr>
<td align="center">
<h:commandButton value="#{msg.cancel_button}" action="#{WizardManager.bean.cancelAddAction}"
styleClass="wizardButton" />
</td>
</tr>
</table>
<% PanelGenerator.generatePanelEnd(out, request.getContextPath(), "blue"); %>
</td>
</tr>
</table>
</td>
<td style="background-image: url(<%=request.getContextPath()%>/images/parts/whitepanel_6.gif)" width="4"></td>
</tr>
<%-- separator row with bottom panel graphics --%>
<tr>
<td><img src="<%=request.getContextPath()%>/images/parts/whitepanel_7.gif" width="4" height="4"></td>
<td width="100%" align="center" style="background-image: url(<%=request.getContextPath()%>/images/parts/whitepanel_8.gif)"></td>
<td><img src="<%=request.getContextPath()%>/images/parts/whitepanel_9.gif" width="4" height="4"></td>
</tr>
</table>
</td>
</tr>
</table>
</h:form>
</f:view>
</r:page>
Creating the action handler messagesThe generateSummary method in the action
handler uses a new add_tags message identifier.
Custom messages are defined in the webclient.properties file, as
shown in the following example.
The following code, contained in the webclient.properties file,
shows the add_tags message identifier. This file
should be placed in the AlfrescoExtension folder next to the TagsModel.xml
file.
title_action_tag=Tag Action
tags=Tags
add_tags=Add tags ''{0}''
Registering the action as a beanAn action, message bundle, and dictionary model are registered
as beans in a Spring configuration file.
In this example, the tag action executor bean is defined as nodeService.
The action-executer bean must be specified as its
parent bean.
The resourceBundles property contains the list
of I18N message bundles to be registered. The class attribute must
be defined as org.alfresco.i18n.ResourceBundleBootstrapComponent.
In this example, the tag-action-messages.properties file is registered
as an I18N resource bundle.
The dictionary model must be registered by defining the dictionary ModelBootstrap bean
as its parent bean, which depends on the dictionaryBootstrap bean.
The models property contains the list of models
to be registered.
The following example shows the file needed to register the action,
message bundle, and dictionary model.
The following code, contained in the tagging-context.xml file,
shows how to register the action as a bean in a Spring configuration
file. This file should be placed in the AlfrescoExtension folder.
Note that the action-executer parent bean is defined
in action-services-context.xml.
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<!-- Tag Action Bean -->
<bean id="tag" class="org.alfresco.sample.TagActionExecuter" parent="action-executer" >
<property name="nodeService">
<ref bean="nodeService" />
</property>
</bean>
<!-- 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>
<!-- Tag Model Registration -->
<bean id="tags.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap">
<property name="models">
<list>
<value>alfresco/extension/tagsModel.xml</value>
</list>
</property>
</bean>
</beans>
Registering the action handlerIn order to be able to add tags, you must configure the
property sheet for the taggable aspect so that nodes having the
taggable aspect will display a multi-value editor when editing properties.
The action handler is registered using an Action Wizards entry in
a web client configuration file. To view and modify the tags property
in the web client, the property sheet must be configured for the taggable aspect,
as shown in the following example.
The following code, contained in the web-client-config-custom.xml file,
shows how the action handler is registered. This file should be
placed in the AlfrescoExtension folder.
<alfresco-config>
<config evaluator="aspect-name" condition="tag:taggable">
<property-sheet>
<show-property name="tag:tags" />
</property-sheet>
</config>
<config evaluator="string-compare" condition="Action Wizards">
<action-handlers>
<handler name="tag" class="org.alfresco.sample.TagActionHandler" />
</action-handlers>
</config>
</alfresco-config>
Packaging and deploying the custom action componentSummary of files and folders:
The Eclipse project should contain both Java files and
the tag-action-messages.properties in the org.alfresco.sample package.
The JSPExtension folder should contain the tag.jsp file
only.
The AlfrescoExtension folder should contain the remaining
4 files: tagsModel.xml, webclient.properties, web-client-config-custom.xml, tagging-context.xml
Packaging the Eclipse projectExport the eclipse project into a JAR file, for example,
you can name your JAR file tag.jar. The JAR files in the class path
are also located on the server, and do not need to be packaged along
with the tag JAR.
Deploying the componentThere are two ways to deploy the component to the server.
To manually deploy the Custom Action, follow these steps:
Open {LiveCycle Installation Root}\jboss\server\lc_turnkey\deploy\adobe-contentservices.ear
(WinRAR works well)
Inside that adobe-contentservices.ear file, open the contentservices.war
file.
Copy your tag.jar (from Eclipse export) into the \WEB-INF\lib\
folder in the contentservices.war file.
Copy your tag.jsp (from JSPExtension) into the \jsp\extensions\
folder in the contentservices.war file.
Copy the 4 files (from AlfrescoExtension) into the \WEB-INF\classes\alfresco\extension
folder in the contentservices.war file. Overwrite if necessary.
Re-package the contentservices.war file into the adobe-contentservices.ear file
Restart the JBoss server for the new component to be deployed
into the working directory.
After deploying the custom action component, the Add tags to item action
have an action JSP that can be used to define default tag values.
The summary page shows the tags that are automatically added. When
the action is run against a content item, the taggable aspect
is added and automatically initialized with the default tag values
defined for the action.
|
|
|