22 August 2011
LiveCycle services, process creation in Workbench, form design in Designer, thorough understanding of the Managed Review and Approval Solution Accelerator.
Additional Requirements:
Intermediate
The purpose for the RSS custom component was to handle a RSS XML file that was specific to the Managed Review & Approval application by containing a list of all comments made on a review document. The XML file was stored in Contentspace and the goal of the component was to retrieve the file and determine how many comments it contained per reviewer. This value would then be used for the Review Tracking Sheet to populate the number of comments field beside the respective reviewer. This would be effective tool because it would allow the review initiator to get a quick overview of how many changes they would have to consider, who suggested those changes, and who are actively participating in reviews.
A custom component was necessary because there was no available functionality in the solution accelerator or LiveCycle that would parse an XML and return the needed outputs.
The component used the Quickstart and custom component methodology and was developed in Java.
This article will outline how to create the custom component and how to integrate this feature into the existing Review Tracking Sheet process.
Structure
The component includes the ParseService interface which defined the parse method, the ParseServiceImpl which provided the functionality, a custom Comment object was created for testing purposes and the LivecycleImpl and BootStrapImpl are explained in the QuickStart reference attached at the bottom.
Technology
Some of the technology that this article will discuss and use:
Enhancements and pitfalls
When this component was deployed into Workbench it was required that the server name be hardcoded into the URL because there was no other way to draw the server name from Designer. In addition the component also logs in as a hardcoded person. If this component is to ever move from the myservername-w2k3 server and my username (alephill) becomes deactivated these features will need to be updated. If they are not the component will stall and cause reviews not to be updated after a stage it completes.
The Review Tracking Sheet is a form that was created in Designer as a XDP file (see Figure 1). We must first re-configure the bindings to recognize the changes that are about to occur.
To edit the Review Tracking Sheet follow these steps:
Change the binding from the original copy and pasted binding to $.no_of_comments and change the name to something descriptive (see Figure 2).
To parse the RSS XML the way we would like, it is most efficient to write a quick custom component in Java and install its service. Please refer to this documentation to find more on how custom components are configured and should be installed.
First we must define the interface for our service – I named mine CommentService.
package com.adobe.livecycle.parserss;
import java.io.IOException;
import java.util.ArrayList;
import com.adobe.livecycle.contentservices.client.impl.CRCResultImpl;
public interface CommentService {
/**
* @param args
*/
public int parse(String URL, String username, String password) throws IOException;
public ArrayList<String> getFileNames(CRCResultImpl files);
}
The CommentServiceImpl implementation class is also required.
package com.adobe.livecycle.parserss;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import javax.xml.parsers.*;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.adobe.livecycle.contentservices.client.impl.CRCResultImpl;
public class CommentServiceImpl implements CommentService {
/*
* parse() method will take a rss file belonging to a person
*/
public int parse(String URL, String username, String password) throws IOException {
Authenticator.setDefault(new MyAuthenticator(username, password) );
URL tempfile = new URL(URL);
URLConnection con = tempfile.openConnection();
InputStream in = con.getInputStream();
ArrayList<Comment> reviewComments = new ArrayList<Comment>();
try{
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
Document docu = docBuilder.parse(in);
docu.getDocumentElement().normalize();
NodeList commentList = docu.getElementsByTagName("item");
NodeList authorList = docu.getElementsByTagName("author");
int numComments = commentList.getLength();
in.close();
System.out.println(numComments);
for(int i = 0; i <numComments; i++){
Node tempItem = commentList.item(i);
Node tempAuthor = authorList.item(i);
System.out.println(tempItem.getFirstChild().getTextContent());
System.out.println(tempAuthor.getFirstChild().getTextContent());
Comment comment = new Comment("user", date(), tempItem.getFirstChild().getTextContent().toString());
reviewComments.add(comment);
System.out.println(((Comment)reviewComments.get(0)).toString());
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}
catch(Exception e){
e.printStackTrace();
}
System.out.println(reviewComments.size());
return reviewComments.size();
}
static class MyAuthenticator extends Authenticator {
private String username;
private String password;
public MyAuthenticator(String us, String pass){
this.username = us;
this.password = pass;
}
public PasswordAuthentication getPasswordAuthentication() {
return (new PasswordAuthentication(username,password.toCharArray()));
}
}
public static String date() {
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(cal.getTime());
}
@Override
public ArrayList<String> getFileNames(CRCResultImpl files) {
ArrayList<String> result = new ArrayList<String>();
System.out.println(files.getTitle());
return null;
}
}
A custom data structure was also used in the CommentServiceImpl class and needs to be defined.
package com.adobe.livecycle.parserss;
public class Comment {
private String userName;
private String dateString;
private String comment;
public Comment(){
this.userName = null;
this.dateString = null;
this.comment = null;
}
public Comment(String username, String date, String comment){
this.userName = username;
this.dateString = date;
this.comment = comment;
}
public String getUserName(){
return userName;
}
public String getDateString(){
return dateString;
}
public String getComment(){
return comment;
}
public void setUserName(String username){
this.userName = username;
}
public void setDateString(String date){
this.dateString = date;
}
public void setComment(String comment){
this.comment = comment;
}
public String toString(){
String result = "Username: " + userName + " Date: " + dateString + " Comment: " + comment;
return result;
}
}
In addition is it necessary to include the BootStrapImpl and the LivecycleImple classes in the project.
LivecycleImpl
package com.adobe.livecycle.parserss;
import java.util.logging.Level;
import com.adobe.idp.dsc.component.ComponentContext;
import com.adobe.idp.dsc.component.LifeCycle;
import com.adobe.logging.AdobeLogger;
public class LifeCycleImpl implements LifeCycle {
private static final AdobeLogger logger = AdobeLogger.getAdobeLogger(LifeCycleImpl.class);
private ComponentContext m_ctx;
//Sets the component's context
public void setComponentContext(ComponentContext aContext) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "setComponentContext: "
+ aContext.getComponent().getComponentId());
}
m_ctx = aContext;
}
//Invoked when the component is started
public void onStart() {
logger.log(Level.INFO, "Called onStart: "
+ m_ctx.getComponent().getComponentId());
}
//Invoked when the component is stopped
public void onStop() {
logger.log(Level.INFO, "Called onStop: "
+ m_ctx.getComponent().getComponentId());
}
}
BootstrapImpl
package com.adobe.livecycle.parserss;
import java.util.logging.Level;
import com.adobe.idp.dsc.component.Bootstrap;
import com.adobe.idp.dsc.component.BootstrapContext;
import com.adobe.logging.AdobeLogger;
public class BootstrapImpl implements Bootstrap{
private static final AdobeLogger logger = AdobeLogger.getAdobeLogger(BootstrapImpl.class);
private BootstrapContext m_ctx;
public void setBootstrapContext(BootstrapContext aCtx) {
logger.log(Level.INFO, "Set bootstrap context: " + aCtx.getComponent().getComponentId());
m_ctx = aCtx;
}
//Invoked when the component is uninstalled
public void onUnInstall() {
logger.log(Level.INFO, "Called onUnInstall: " + m_ctx.getComponent().getComponentId());
}
//Invoked when the component is installed
public void onInstall() {
logger.log(Level.INFO, "Called onInstall: " + m_ctx.getComponent().getComponentId());
}
}
Finally you must configure the components.xml.
<component xmlns="http://adobe.com/idp/dsc/component/document">
<!-- Unique id identifying this component -->
<component-id>com.adobe.livecycle.parserss</component-id>
<!-- Version -->
<version>1.0</version>
<!-- bootstrap implementation class -->
<bootstrap-class>com.adobe.livecycle.parserss.BootstrapImpl</bootstrap-class>
<!-- lifecycle implementation class -->
<lifecycle-class>com.adobe.livecycle.parserss.LifeCycleImpl</lifecycle-class>
<!-- Start of the Service definition -->
<services>
<!-- Unique name for service descriptor.
The value is used as the default name for
deployed services -->
<service name="AlexCommentService">
<!-- service implementation class definition -->
<implementation-class>com.adobe.livecycle.parserss.CommentServiceImpl</implementation-class>
<!-- description -->
<description>Allows you to parse RSS xml. Used to determine the number of comments in MRA implementation.</description>
<!-- automatically deploys the service and starts it after installation -->
<auto-deploy service-id="AlexCommentService" />
<operations>
<!-- method name in the interface-->
<operation name="parse">
<!-- input parameters to the "send" method -->
<input-parameter name="url" title="RSS URL" type="java.lang.String">
<hint>A URL to the RSS file.</hint>
</input-parameter>
<input-parameter name="username" title="LC Server User Name" type="java.lang.String">
<hint>Username to access the server</hint>
</input-parameter>
<input-parameter name="password" title="LC Server Password" type="java.lang.String">
<hint>Password to access the server</hint>
</input-parameter>
<output-parameter name="commentCount" title="Number of Comments" type="java.lang.Integer">
<hint>The count of comments in rss file</hint>
</output-parameter>
<faults>
<fault name="I/O Exception" type="java.io.IOException" />
<fault name="Exception" type="java.lang.Exception" />
</faults>
<hint>Counts the number of comments in an MRA comments RSS file</hint>
</operation>
</operations>
</service>
</services>
</component>
The structure should looks similar to the following illustration (see Figure 3).
To deploy the componet, follow the steps below:
When accessing the XML files in Contentspace, it must also be taken into consideration that the review initiator will always have an xml file created. To ensure the Review Tracking Sheet is populated correctly a process must be made to evaluate if the review initiator is also a reviewer and whether or not we should consider there file. I named mine Initiator Is Review Check (see Figure 5).
Here is the list of variables to add:
Follow the steps below to create the Initiator is Reviewer Check process:
/process_data/@counter -> /process_data/@counter + 1Finally we have to integrate our new processes into the Review Tracking Sheet Creation Process (see Figure 6). LiveCycle Contentspace ES2 automatically generates an XML file for each reviewer that leaves a comment in the review document so the process will loop through the files and identify the number of comments in each. If someone did not leave any comments they will have a default 0 value.
It is now necessary to add more variables to the process. To do so:
Note: The green box will be disabled if you are in recording mode.
Here is the list variables to add to the process to accommodate for the new fucntionality:
The following steps detail how to customize the process:
/process_data/currentReviewer -> /process_data/commenterList[/process_data/@reviewerCounter + 0]/process_data/@reviewerName -> /process_data/currentReviewer/object/@nodeName-> YOUR USERNAME/process_data/reviewContextXML/review_context/stages/stage[/process_data/@currentStage +0 ]/reviewers/reviewer[/process_data/@reviewerPosition + 0 ]/no_of_comments -> /process_data/@numComments/process_data/@reviewerPosition -> /process_data/@reviewerPosition +1-> /process_data/@reviewerCounter + 1To learn more about how to develop comments, refer to Programming in LiveCycle ES2.5.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.