Note:
Adobe is migrating 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 steps
To 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 environment
To 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 logic
Creating a custom model
If 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 class
An 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 parameters
The
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 logic
The
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 implementation
This 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 class
An 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 resources
Creating the action JSP
An 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 file
In 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 file
This 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 messages
The
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 bean
An 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 handler
In 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 component
Summary 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
, w
ebclient.properties
,
web-client-config-custom.xml
,
tagging-context.xml
Packaging the Eclipse project
Export 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 component
There 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.
|
|
|