ITSM Outbound Web Services and Workflows
LiveTime v6.5 introduces the ability to add customized extensions to request workflow state transitions and item lifecycle state transitions. In order to implement custom functionality on these state transitions, there are several steps a user must follow.
1. Build the extension
2. Make the extension available to LiveTime & configure the system to enable the calls
Building the extension
Two java interfaces are provided, in a standalone jar file ‘livetime-listen.jar’, which can be found in:
{LiveTime Install Folder}/LiveTime.woa/Contents/Resources/Java/
This jar file contains two interfaces, each of which has two methods:
WorkflowListener public Map<String, String> stateEntered(Map<String, Object> argsMap) throws Exception public Map<String, String> stateExited(Map<String, Object> argsMap) throws Exception LifecycleListener public Map<String, String> stateEntered(Map<String, Object> argsMap) throws Exception public Map<String, String> stateExited(Map<String, Object> argsMap) throws Exception
Naturally the WorkflowListener is used for integrating with the Request Workflow, whilst the LifecycleListener is used for integrating with the Item Lifecycle.
The task for the developer is to create a java class that implements the appropriate interface to achieve the integration objective. The two methods exist to provide flexibility to the developer implementing the integration, by allowing them to perform tasks based on a state being ‘exited’ and then a state being ‘entered’.
The implementing class is required to return a Map
‘success’ : ‘true’ or ‘false’
‘message’ : Description to be stored against the history of the request or item
Each method is passed a Map of parameters that relate to the request or item being updated. LiveTime v6.5 will pass a String for all values, the method takes
WorkflowListener Arguments
The parameter Map passed in to the WorkflowListener interface methods consists of:
| Parameter | Description |
|---|---|
| triggerStatusId | The ID of the status that has triggered the listener event statusExited: originalStatus statusEntered: newStatus |
| triggerStatusName | The name of the status that triggered the listener event |
| requestId | The ID of the request being updated |
| requestType | The type of request being updated 1000 = Incident 2000 = Problem 3000 = Change Request 7000 = Service Request 9000 = Deployment Task |
| statusId | The current state of the request: statusExited: newStatus statusEntered: same as triggerStatus |
| statusName | The name of the state defined by statusId above |
| classificationId | The ID of the classification assigned to the request |
| customerId | The ID of the customer assigned to the request |
| customerFirstName | The first name of the customer assigned to the request |
| customerLastName | The last name of the customer assigned to the request |
| customerEmail | The email address of the customer assigned to the request |
| orgUnitId | The ID of the Org Unit associated with the request |
| orgUnitName | The name of the Org Unit associated with the request |
| itemNumber | The item number of the CI associated with the request |
As this is a first iteration of the call-out interface, these parameters have been deemed sufficient to allow a developer to perform non-trivial tasks like update an external system that may require request updates.
LifecycleListener Arguments
The parameter Map passed in to the LifecycleListener interface methods consists of:
| Parameter | Description |
|---|---|
| triggerStatusId | The ID of the status that has triggered the listener event statusExited: originalStatus statusEntered: newStatus |
| triggerStatusName | The name of the status that triggered the listener event |
| itemNumber | The Item Number assigned to the CI |
| itemStatusId | The current state of the request: statusExited: newStatus statusEntered: same as triggerStatus |
| itemStatusName | The name of the state defined by statusId above |
| itemtypeId | The Item Type ID |
| itemtypeName | The name of the Item Type the Item is an instance of |
| categoryId | The Category ID |
| categoryName | The name of the Category the Item Type belongs to |
These are the initial parameters deemed appropriate to allow a developer to perform non-trivial tasks like (for example) to feed state changes into a monitoring tool to reset an alert trigger.
Implementing a Listener
This requires some Java knowledge, to either define the entire functionality, using these entry points, or to create a JNI wrapper to page out to code written in an alternate language, although this does have platform implications. A JNI (Java Native Interface) implementation is outside the scope of this document, and the following sample focuses on a Java Implementation.
The user needs to create a class, for example ‘MyWorkflowListener’ which implements the WorkflowListener Interface, or MyLifecycleListener, which implements the LifecycleListener interface. These methods then need to be made to perform some work. A non-trivial example would be one where the listener calls a web service to an external system. Consider the following usage scenario.
A company has (for whatever reason) two LiveTime instances, and they are needing to, on occasion feed updates to the secondary system on request state changes. LiveTime has an inbound web services interface, and now, an outgoing interface for communicating changes to third parties.
The SOAP WSDL’s can be used to generate Java classes to make calls into the secondary system, which can now be called from the outgoing interface. Generating the SOAP equivalent java classes, and calling them from a WorkflowListener might yield a listener class that looks something like the following.
package com.livetime.sample;
import com.livetime.ws.listen.WorkflowListener;
import java.util.Map;
public class WorkflowListenerImpl implements WorkflowListener{
public WorkflowListenerImpl() {}
public Map<String, String> statusEntered(Map<String, Object> argsMap) throws Exception {
BaseRequest baseRequest = new BaseRequest();
String requestId = (String)argsMap.get("requestId");
String statusName = (String)argsMap.get("triggerStatusName");
Map temp = new HashMap();
temp.put("subject", "Request Created via Outbound WebServices Call");
temp.put("description", "Request #" + requestId + " has entered status " + statusName);
return baseRequest.createRequest(temp);
}
public Map<String, String> statusExited(Map<String, String> argsMap) throws Exception
{
BaseRequest baseRequest = new BaseRequest();
String requestId = (String)argsMap.get("requestId");
String statusName = (String)argsMap.get("triggerStatusName");
Map temp = new HashMap();
temp.put("subject", "Request Created via Outbound WebServices Call");
temp.put("description", "Request #" + requestId + " has exited status " + statusName);
return baseRequest.createRequest(temp);
}
}
With the createRequest method in the class BaseRequest looking like this:
public Map<String, String> createRequest(Map<String, String> properties) {
// try and connect, if successful, create request and logout
if(connect()) {
java.net.URL requestURL = null;
HashMap response = new HashMap();
try {
// Service endpoint
requestURL = new java.net.URL(props.getRequestServiceURL());
// Get handle to the service
Request_Service service = new Request_ServiceLocator();
Request_PortType port = service.getRequest(requestURL);
// Call BaseClient method to populate persistent headers
populateHeaders((javax.xml.rpc.Stub)port);
String subject = (String)properties.get("subject");
String description = (String)properties.get("description");
response = port.createIncident(props.getTargetItemNumber(),
props.getTargetClassificationId(), subject,
description, new HashMap());
}
catch(Exception ex) {
return buildErrorMessage("An error occurred whilst creating”);
}
if(disconnect()) {
return response;
}
else {
return buildErrorMessage("An error occurred whilst disconnecting");
}
}
else {
return buildErrorMessage("An error occurred whilst connecting");
}
}
In this example, the listener simply creates a new request in the second system, stating the nature of the change, but this highlights some of the possibilities of outbound functionality.
Note this example has been heavily truncated to illustrate the key functionality.
Making the extension available to LiveTime
This is a rather simple process for users with an install on their own infrastructure. Users of the virtual appliance and SaaS customers on version 6.5 will need to contact LiveTime Support (support@livetime.com) to have their extensions made available. Version 6.6 allows users to upload these directly.
In order for the class to be accessible to LiveTime, the compiled code needs to be on the LiveTime classpath. In this case, this means the compiled jar, along with any associated components, need to be copied into the lib folder of the LiveTime installation ({LiveTime Path}/WEB-INF/lib), and the LiveTime instance needs to be restarted, so these resources are picked up by the classloader.
A note about JNI implementations
The earlier reference to JNI code having platform implications is of particular relevance at this point. If a native interface is being developed, it must suit the deployment platform. The LiveTime appliance and SaaS instances are UNIX based. It is not possible to provide a dll written for windows on these platforms as it simply won’t be loaded.
This outbound interface is written primarily for Java as that is the only case where platform considerations can be (mostly) ignored.
Configuring LiveTime to use the extension
At this point a new class (or classes) exist and have been loaded, now LiveTime simply needs to be configured to use them. This is a two part process. Firstly the option needs to be enabled by a system administrator to enable outbound web services. This option can be found in the Administrator portal, under Setup > Privileges > System (Outbound Web Services).
Once set to ‘On’ and saved, a new field appears in both the Workflow State and Lifecycle State Editors respectively. The ‘Listener Class’ can now be populated per workflow (or lifecycle) state, allowing each state to call the same, or different implementations as necessary. This allows different workflows to behave per the designers’ requirements.