Only the last few years I’ve had a chance to write many custom components to use in LiveCycle, but I’ve never had to do one that required a custom editor. So I decided to go ahead and learn how to do it in case I ever needed to know how in the future. I decided to create a logging component that allows you to select at design time which of the process variables you want logged. The reason it requires a custom editor is the editor will update it’s controls when given focus to match the available process variables that can be logged.
The first thing I did was look around for some docs to find out how to get started, unfortunately I found that no docs existed. So I figured once I was done I’d blog about it and hopefully it will help someone else out as well. I’ve also archived up my Eclipse project, so if anyone wants to see the code it can be found here: http://d2x45prcet210j.cloudfront.net/LoggerDSC.rar .
Confession: I cheated. In my service operation the easiest way to query the process variables at runtime was to use an instance of PATExecutionContext. Unfortunately this meant requiring a library be in the class path that is not distributed with the public SDK. Namely adobe-wkf-client.jar. So if you grab the project and are looking for this, you can extract it from the root of the main LC ear file. I could have done things differently and not needed this, but it would have been more work, and the point of this for me was to learn how to build a custom editor and I wasn’t too focused on the actual service operation.
The Pieces of a Custom Editor
A custom editor consists of four parts, each of which I’ll discuss further below:
- Value Object (VO) – Stores the data manipulated by the editor. Essentially the model to the editor’s view.
- Converter – Provides a method to convert between the VO and the data type expected as input to the service operation.
- Serializer – Provides the ability to serialize the VO into a String representation, as well as deserialize a String into an instance of the VO.
- Editor – Contains the actual GUI code.
How do you debug your code?
A lot of your code is going to be running on the client (the Workbench JVM) so methods for debugging are going to be a bit different than what you would normally do with server side components. Here’s the two things I did:
Start Workbench with the -console argument. Doing this shows a console window along with the normal Workbench window. Anything you then log in your code will show up in this console window. So the command I use to start Workbench now looks like this: “C:\Program Files (x86)\Adobe\Adobe LiveCycle Workbench 9.5\workbench\workbench.exe” -console
Pass JVM arguments to Workbench to allow you to connect a remote debugger to it. To do this you need to add some lines to the workbench.ini file located in %WORKBENCH_DIR%\workbench\ . The following two lines need to be added immediately after the -vmargs line:
You can, of course, change the port number from 5005 to another port of your choice. After setting these and starting workbench you can then attach a remote debugger to it using Eclipse (or the IDE of your choice) in order to set breakpoints, step through code, or any of the normal good stuff you’d do when debugging your code.
The VO is the object that contains the data being manipulated by your editor. Any POJO will work there aren’t any restrictions I’m aware of. In the code I attached, the VO I’m using is LoggerVO. As you can see, it is a pretty simple JavaBean that contains an enum, a Map, and a boolean. For simpler situations you can even use a standard Java class and avoid writing any code for this if desired. For example, a Map, or a List, etc.
A converter is used by the container to attempt to coerce your VO into the object type expected as input to your service operation at runtime. There are two methods to implement a converter:
- Create a class that implements the com.adobe.idp.dsc.datatype.Converter interface and declare it in the editor declaration of the component.xml using a <converter-class> tag.
- Have your VO class implement the com.adobe.idp.dsc.datatype.Convertable interface
In my code I decided on option #2, and LoggerVO implements Convertable. However, my service operation expects a LoggerVO as input so this was not actually necessary. I did this just to illustrate the use of the interface for this blog, in a real case I would not have defined a converter since no coercion of the VO is required.
At runtime the container decides how to coerce the VO in the following order before calling the service operations:
- If the VO class implements Convertable, then its convert() method is called.
- If a <converter-class> has been defined that implements Converter, then its convert() method is called.
- If neither of the above 2 are satisfied then the container will try its best to coerce the VO to the object required by the service operation itself. If it fails then a com.adobe.idp.dsc.util.InvalidCoercionException is thrown.
A serializer is used to translate the VO to and from its text representation. A text representation of the VO is required for it to be stored inside the service registry and the process template. A serializer is a class that implements the com.adobe.idp.dsc.datatype.TextSerializer interface. It is declared inside the editor declaration of the component.xml using a <serializer-class> tag.
In the code I provided you can see this implemented in the LoggerSerializer class. The serializeValue() method converts a LoggerVO into a String, and the deserializeValue() method goes the other way around.
If no serializer is defined for your editor then the container will automatically serialize/deserialize your VO in the following way:
- Primitive data types are converted directly into a java.lang.String
- Complex data types are serialized using standard Java object serialization and then Base-64 encoded so that the result is a String.
The Editor class is the one that contains your UI code. In theory your editor can be rendered in multiple presentation platforms (ie: Eclipse, Swing, web, etc), and an editor class is required for each of these presentations that you wish to support. Each of these editor classes must implement the com.adobe.idp.dsc.propertyeditor.UIComponent interface. At a minimum, and most commonly all you would implement, is an editor that renders in Eclipse (Workbench). For this the com.adobe.idp.dsc.propertyeditor.eclipse.EclipseUIComponent interface should be implemented, which means there will be a renderComponent() method in your class that is called to render your custom editor.
Keep in mind that the GUI technology used by Eclipse is SWT, so your Eclipse Editor will need to use that as well. This is the first time I’ve ever written any SWT code, so I won’t get into any of that in this blog since I’m far from an expert. I’ll just point out that there is a lot of information and tutorials for SWT freely available on the web. What I will talk quickly about are two properties available for your editor, the PropertyContext and the ProcessContext.
The PropertyContext is an instance of a class that implements the com.adobe.idp.dsc.propertyeditor.PropertyContext interface. It can be used by the editor to retrieve context information relating to the current property’s value, the VO your editor is a front for, as well as the property sheet, the panel that your custom editor along with any other editors is being displayed within in Workbench.
Likely the two most important methods provided by the PropertyContext are the PropertyContext.getCurrentPropery().getValue() and PropertyContext.getCurrentProperty().setValue(). These provide you the ability to retrieve and store the VO being edited in your editor. It is important that any value changes made in the editor are reflected in the values stored in the VO.
You can see the use of the PropertyContext inside the LoggerEditor class. You’ll also notice that LoggerEditor extends AbstractUIComponent. This abstract class provides the getter and setter for the property context so that I did not have to implement those myself.
The ProcessContext is an instance of a class that implements the com.adobe.idp.workflow.propertyeditor.ProcessContext interface. It provides methods that allow you to query for the context information of the process in which your service is being called from.
Note: This will only be available if your service editor is being rendered after it has been dragged and dropped onto a process in Workbench.
In order to have access to the ProcessContext you must implement a getter and setter with the following signature:
public void setProcessContext(ProcessContext aContext)
public ProcessContext getProcessContext()
If available the container will call the setter to populate the ProcessContext when your editor is rendered.
In the LoggerEditor class I use the process context to get access to the process template and query for the variables available so that they can be shown in my editor. You can see the use of it primarily in the buildVO() method.