A RTC WorkItem Command Line Version 3.0


I was interested in how complex it might be to export work items and import them again. So I looked into this and enhanced the work item command line (WCL) to support this.

I found it quite challenging to develop this. There are a lot of things to consider, so I drove this to a point that was sufficient for my purposes. The tool is used by my team to import work items from a CSV file we receive every now and then.

I implemented another export/import mode with some more capabilities and it works in tests, but is by no means production ready. The amount of necessary tests and test automation to make this reliable, is just overwhelming. So this is not thoroughly tested.

So be careful if using these commands, and do a good amount of testing before actually using this. The problem is, there are so many possible use cases and dependencies that it is very hard to develop this kind of capability and to test it.

One special case is importing/creating links. Some links have constraints i.e. parent and especially child links. A work item can not have multiple parents. So setting child links can cause the save to fail if the new child has already a different parent.

Solution Overview

The work item command line WCL now supports two new commands

  • exportworkitems
  • importworkitems

To export work items to a CSV file and import work items from a CSV file.

I chose to use a CSV file, because RTC itself can export and import that format already. It would be ideal if export and import from XML would be supported as well, but this would require a substantial effort to abstract the export and import operations to be able to use a strategy (or some other useful pattern to support abstraction).

No Support or Maintenance

This is provided as-is with no support or guarantee. This is not a tool that is officially supported by IBM or any other organization.

Please note, that I have very little time to do this and testing is always lacking. So take the code published here with a grain of salt. On the positive side, you have the code, can debug and enhance it.

Compatibility

This code has been used with RTC 4.x and 5.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC. The code requires two external libraries that need to be downloaded and installed separately.

License and Download

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

Update: Added switch to change the export and import formats for dates, see details below. I also added a switch to suppress attribute not found errors and other frequent errors during export.

Update: Fixed duration set problem. Version updated to 3.2

You can download the latest version from this post The RTC Work Item Command Line On Bluemix. The older version 3.0 can be found here:

Please note, there might be restrictions to access Dropbox and therefore the code in your company or download location.

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working. The code linked from this post contains Client API.

Setup and Usage

For the general setup follow the description in A RTC WorkItem Command Line Version 2 and look at the additional setup steps below.

For usage follow the description in A RTC WorkItem Command Line Version 2 and in A RTC WorkItem Command Line Version 2.1. Check the README.txt which is included in the downloads.

Export Work Items

The export work items command has the syntax

-exportworkitems {Switch} repository=”value” user=”value” password=”value” projectArea=”value” query=”value” exportFile=”value”  [columns=value] [encoding=value] [delimiter=value] [querysource=value]

Required Parameters are

  • repository=”value” – the repository URI, for example repository=”https://clm.example.com:9443/ccm
  • user=”value” – The user ID of the user executing the command, for example user=”ralph”
  • password=”value” – the password of the user, for example password=”password”
  • projectArea=”value” –  The project area to export items from, for example projectArea=”JKE Banking (Change Management)”
  • query=”value” – the name of the query to use, for example query=”All WorkItems”
  • exportFile=”value” – The path of the export file, for example exportFile=”C:\aaTemp\Export\Test.csv”; the folder that contains the export file must exist

Optional Parameters are

  • columns=value – The names or ID’s of the work item attributes to export; example columns=”Type,Id,Planned For,Filed Against,Description,Found In”; To specify the colums it is possible to use the name or the ID of the attribute, the switch headerIDs specifies the output format to use the ID instead of the name in the output; It is possible to use the values from an RTC Eclipse client export
  • encoding=value – The encoding; default encoding=”UTF_16LE”; options see available charset names; if the encoding is chosen different for export and import, the values will not be recognizable
  • delimiter=value – The delimiter to be used between the columns. Default is comma delimiter=”,”
  • querysource=value – If the parameter is omitted the command searches a personal query with the given name; if the value is provided a query shared by the process area is searched, a complete path from the project area to the sharing process area must be provided, for example querysource=”JKE Banking(Change Management),JKE Banking(Change Management)/Business Recovery Matters”
  • timestampFormat=value – To specify the time stamp format to be used; default “MMM d, yyyy hh:mm a”;  see SimpleDateFormat for the supported pattern

Available switches are:

  • /ignoreErrors – Ignore minor errors in mapping and value lookup
  • /asrtceclipse – Export in a format compatible to the RTC CSV export and import; if the switch is not provided, the data is exported in a format that is compatible with the syntax used by the work item command line WCL to identify elements; see A RTC WorkItem Command Line Version 2 for the supported value representations
  • /headerIDs – Export header values as attribute IDs and not as attribute names
  • /suppressAttributeExceptions – Suppresses exceptions thrown for attributes that are not available on the work item type of for attribute types that are not yet implemented

Example

-exportworkitems /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" exportFile="C:\aaTemp\Export\Test.csv" query="All" columns="workItemType,summary,Attachments"

Import Work Items

The import work items command has the syntax

-importworkitems{Switch} repository=”value” user=”value” password=”value” projectArea=”value” query=”value” importFile=”value”  [columns=value] [encoding=value] [delimiter=value] [querysource=value]

Required Parameters are

  • repository=”value” – the repository URI, for example repository=”https://clm.example.com:9443/ccm
  • user=”value” – The user ID of the user executing the command, for example user=”ralph”
  • password=”value” – the password of the user, for example password=”password”
  • projectArea=”value” –  The project area to export items from, for example projectArea=”JKE Banking (Change Management)”
  • importFile=”value” – The path of the import file, for example importFile=”C:\aaTemp\Export\Test.csv”

Optional Parameters are

  • mappingFile=”value” – A RTC work item import mapping file, for example mappingFile=”C:\temp\mapping.xml”; the file must be generated by RTC and customized to match the value mapping
  • encoding=value – The encoding; default encoding=”UTF_16LE”; options see available charset names; if the encoding is chosen different for export and import, the values will not be recognizable
  • delimiter=value – The delimiter to be used between the columns. Default is comma delimiter=”,”
  • timestampFormat=value – To specify the time stamp format to be used; default “MMM d, yyyy hh:mm a”;  see SimpleDateFormat for the supported pattern

Available switches are:

  • /ignoreErrors – Ignore minor errors in mapping and value lookup
  •  /importmultipass – Import the work items from the CSV file in a first iteration and build up a mapping for the ID’s provided in the import file and the actual ID’s created and recreate the work item links between the new work items based on that mapping in a second pass; the old work item ID for a work item has to be provided in a special column with header name com.ibm.js.oldid
  • /forcelinkcreation – if no target work item can be found in the map, use the given ID to create the link
  • /importdebug – Print more information during import attempts to help with finding issues
  • /enforceSizeLimits – Attributes such as description and medium strings have size limits, if this switch is set, the importer tries to clip content to avoid exceptions due to the size limits

Example

-importworkitems  /enforceSizeLimits  /importmultipass  /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="ImportTest1" importFile=""c:\aaTemp\ExportImport\TestExportAll.csv"

RTC Eclipse Compatible Export Mode

In mode asrtceclipse, all data is exported the way RTC would export them in Eclipse. This means that certain information for example links, team areas, iterations, attachments and other data is exported in a way that makes it hard to map to data in the repository.

The import command does its best to map based on names, but for complex hierarchical information such as iterations and team areas, there is currently no search implemented that will find the object successfully. It would be possible to implement such methods, with some effort.

Example: An iteration is part of a timeline. The timeline is needed to find the iteration within. If there is no information about the timeline, it would be required to iterate all timelines with a good chance of mismatch.

The import command will try to find things by name and ID, with the limitations above.

If the work item ID attribute is provided as a column the importer will try to find the work item and update it during the import.

Default Export Mode

In the default export mode, the RTC Work Item Command line export command exports the data in greater detail, which makes it easier for the importer to identify the item.

Attachments

In default mode, the attachment is exported as a file relative to the location of the generated csv file. The attachment is downloaded to a location ./attachments//. So for each exported work item with attachments, a separate folder is created. The attachments are stored in that folder and the export information in the csv file is created compatible to the WCL parameter format to allow later import of the attachments, including upload and applying the additional information.

Other Complex Items and Links

Other complex items such as iterations and team areas are also exported with a lot more details. An iteration is being exported as path, including the timeline and parent iterations. A team area is also exported as path containing the Project are name and parent team area names

Multi-Pass Import

Importing work items and recreating the link relationships between them is problematic, because while importing the work items the link target may not yet exist. To be able to import a set of work items and then recreate the linkage, it is necessary to do the import and then map the ID of the old work item to the ID of the new work item.

When using the RTC CSV importer in the Eclipse client, existing work items are provided with a # in front of the work item ID. To do an import and then recreate the links between the new work items (and not to the old ones in the import), a user would have to run the import without the links, then replace the work item ID’s in the import file by the new work item ID’s and update the work items with a second import. This is very manual and error prone.

The switch importmultipass  enables an import mode, where the WCL tries to create the links between the imported work items, rather to the old ones. It imports the work items in two passes. It creates the work items in the first pass and ignores the link creation. In the second pass it tries to create the links. For links between work items WCL tries to find the work items that were created during the import and tries to match the links to the new work items, where possible.

Note: Only links between work items are handled this way. Links to objects other than work items are recreated using the values provided in the import file.

To be able to do this, the import file has to provide the old work item ID of the work items that are imported. The import requires a special ID for the columns containing the old ID’s. The column header for this column has to be specified with com.ibm.js.oldid.

The import file below has been created using an export that included the ID of the work item in the export. The old column header for example ID of the column has been replaced by com.ibm.js.oldid. The work item links show the ID’s of the linked work items with their old ID’s.

Import Work Items With LinksThe import works as follows.

The WCL runs the first pass and imports the work items. It stores the mapping between the original work item ID from the column com.ibm.js.oldid and the ID of the newly created work item in a map. Links are not created in this pass.

In the second pass WCL reads the import file again and only handles the columns that represent links. It detects if the link target represents a work item. If not, it tries to recreate the link as it is. If the link is a work item link, WCL tries to calculate if a new work item was created for the target using the map. If the work item was imported and a new ID is available, the new work item ID is used to create the link.

If the ID of the link target can not be found in the mapping, WCL can either ignore the link or it can try to create the link to the original work item. WCL supports these two modes. By default, the link is not created. If the switch forcelinkcreation is specified, the original value of the target work item ID is used as target for the link, if no mapping to a newly imported item was found.

Creating links is not trivial. One special case is importing/creating links. Some links have constraints i.e. parent and especially child links. A work item can not have multiple parents. So setting child links can cause the save to fail if the new child has already a different parent. This can create issues in import scenarios, especially if an export from the same repository is imported and the import causes child links to be created that have already another parent. In this case the import will fail with an error.

Limitations

Approvals and comments are imported into one comment. The effort to recreate approvals is just too big and I can’t see the added value.

Special Notes On Setup

For the general setup follow the description in A RTC WorkItem Command Line Version 2 and look at the additional setup steps below.

The export and the import commands of WCL need two libraries that are not shipped the downloads.

If you use the packaged WCL and want to use the export/import capability follow the steps below to add the required libraries to the folder lib in the folder lib in the WCL folder.

If you use the Eclipse project for WCL and want to use the export/import capability follow the steps below to add the required libraries to the folder lib in the Eclipse project com.ibm.js.team.workitem.commandline.

The export and the import commands of WCL use the Open CSV Library. I had issues with the newer versions of Open CSV that I could not resolve, so this code assumes the version 2.3. Download the version 2.3 from here. Uncompress and untar the the file opencsv-2.3-src-with-libs.tar.gz you downloaded. Look for the folder opencsv-2.3\deploy\ copy the JAR file opencsv-2.3.jar and put it into the lib folder of your version of WCL.

The import commands of WCL can only provide the capability to use a mapping file by using a JAR file that only ships with the RTC Eclipse client and the SDK. The classes used for the mapping file capability are located in the library com.ibm.team.workitem.rcp.core.  Open the Install location of the RTC Eclipse client and search for com.ibm.team.workitem.rcp.core*. You should find a file names similar to this one: com.ibm.team.workitem.rcp.core_3.1.900.v20141010_0124.jar. The version numbers at the end could be different. Copy the JAR file into the into the lib folder of your version of WCL.

The packaged version should look like below.

Deployed Packaged WCLIf you have imported the Eclipse project for WCL open Configure Build Path and create a user library named openCSV and add the Open CSV library opencsv-2.3.jar. Create a user library named rtcmapping and add the com.ibm.team.workitem.rcp.core library you just copied to it.

Your Eclipse Project should now look like below.

Eclipse Project and LibrariesCode Changes

During the work on import and export, the code structure was left untouched. Some classes were added to be able to handle the column header and some additional mapping of id’s and names i.e. for link types. In addition some of the code that was piling up in the WorkItemUpdateHelper (formerly known as WorkItemHelper) was moved to utility classes. this makes it also easier to look for useful API in case you are interested in how things work in the RTC API. See the scree shot below.

Code StuctureSummary

This WorkItem Command Line should allow for most of the automation needs when creating work items. In addition it is a nice resource for the RTC work Item API.

As always, I hope the post is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

Advertisements

A RTC WorkItem Command Line Version 2.2


Creating links is not easy. Many things can go wrong.  Testing by a user showed that there was an issue with links between work items and build results. I found that I got the link direction wrong. I fixed that. Here is the updated source code.

Latest Version

See A RTC WorkItem Command Line Version 3.0 for the latest version.

Related posts

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license.

Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee.

On the other hand, you have the code and are able to add your own code to it. It would be nice to know what you did and how, if you do so.

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the code attached to this post in the development environment you set up in the Rational Team Concert Extensions Workshop and get your own extensions or automation working there as well.

Download

You can download the latest version here:

Setup and Usage

Follow the description in A RTC WorkItem Command Line Version 2 and in A RTC WorkItem Command Line Version 2.1. Check the README.txt which is included in the downloads.

The WorkItem Command Line Explained


This post explains how the WorkItem Command Line works. It explains its structure and the main classes. This should allow users to extend the capabilities the code and add new commands or extend the current commands.

Please note, as this is work in progress, things might change slightly in future versions, however the general structure should persist.

Latest Version

See A RTC WorkItem Command Line Version 3.0 for the latest version.

Related posts

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license.

Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee.

On the other hand, you have the code and are able to add your own code to it. It would be nice to know what you did and how, if you do so.

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the code attached to this post in the development environment you set up in the Rational Team Concert Extensions Workshop and get your own extensions or automation working there as well.

Importing The Project

Download the code from the post A RTC WorkItem Command Line Version 2. The file with the source code is named WorkItemCommandLine_Project-Vx-YYYYMMDD.zip. The x represents the version number and is followed by the date it was created. The file is an exported Eclipse project.

The project expects the Eclipse workspace to be set up as described in the posts Setting up Rational Team Concert for API Development. It requires the SDK to be set up as well as the Plain Java Client Libraries. The SDK is needed, because the project is a Plugin Project. this is done to be able to use the Eclipse Plugin Development Environment (PDE) to look at the API source code. The Plain Java Client Libraries are needed to run the code a Java application.

Use the Eclipse import File>Import. In the wizard window select “Existing Projects into Workspace” in the section General. Click Next and chose the option “Select archive file”, browse to the file you downloaded and select it. Make sure you see the project com.ibm.js.team.workitem.commandline selected and press Finish to start the import.

After the import you should see the project in your workspace. You should see no errors in the project. If you see errors, the most likely reasons for that are:

  1. The SDK is not set up correctly and the classes can not be resolved
  2. The SDK version is prior to RTC 4.0.1
  3. The Plain Java Client Libraries are not installed or the User Library has a different name

The first two will show in the plugin.xml and the manifest file. Setup the SDK correctly, or change the minimal versions needed in the dependencies.

The third will show as an error in the the build path. Define a user library named PlainJavaApi as explained in  Setting up Rational Team Concert for API Development or remove the existing user library entry and add you own. Make sure the dependency order of SDK and user library are correct as explained in  Understanding and Using the RTC Java Client API.

In case you have other errors you should search the internet for a solution.

Explore the Project

You can now explore the project. The folder structure is shown below.

WCL Project Overview

There are the following files and folders

  • src – contains the source code files.
  • build – contains a jardesc file to build a jar file for packaging
  • Launches – contains launch files used for testing
  • License – contains the license files
  • scripts – contains the script files used to start WCL, as well as a file with help information
  • the root contains a readme file, explaining how to build a releasable version of WCL, scripts used to start WCL in the development setup and a test file for upload attachment tests.

The Source Files

The image below shows the structure of the source code.

Source StructureThe package com.ibm.js.team.workitem.commandline contains the class WirkitemCommandLine, which has the main method to call WCL. The class OperationResult is used to pass result information. This is necessary, since the code could run in RMI mode and the output needs to be transferred to the RMI client. This class needs to support serializing in order to pass the result back. IWorkItemCommandLineConstants contains various constants used by WCL.

The package com.ibm.js.team.workitem.commandline.commands contains the classes that implement the currently available commands. CreateWorkItemCommand creates a work item of a specific type in a specific project area and sets the attributes as provided. PrintTypeAttributesCommand prints the attributes of a specific work item type in a specific project area. UpdateWorkItemCommand finds a work item and updates its attribute values.

The package com.ibm.js.team.workitem.commandline.framework contains a basic framework that is used by commands that are implemented in WCL. The main class requires the interface IWorkItemCommand to run the command. I ended up using this kind of framework, because all commands required some kind of parameters. The command should be able to define the parameters needed. The commands also require to do error handling. To interact with the RTC repository commands also need to login. The framework handles all the common activities and allows to create new commands without having to redevelop all this.

The class AbstractCommand implements the interface IWorkItemCommand and leaves some methods abstract that extending classes need to implement.

The class AbstractTeamRepositoryCommand adds a login to the team repository and the class AbstractWorkItemModificationCommand adds a WorkItemOperation to perform the changes to the work item. WorkItemCommandLineException is the exception class that is used to wrap other exceptions and thrown in case of unrecoverable errors.

The package com.ibm.js.team.workitem.commandline.helper contains helper classes. The class DevelopmentLineHelper is from another blog post Handling Iterations – Automation for the “Planned For” Attribute. It allows to find development lines and iterations on a development line. WorkItemHelper implements modifying work item attribute modification. Most of the RTC API related code is in there. WorkItemTypeHelper helps with printing the attribute information for a work item type.

The package com.ibm.js.team.workitem.commandline.parameter contains classes that implement all the parameter handling needed. The class Parameter is used to describe a parameter, if it is required, if it was already consumed, if it is a switch and the like. ParameterIDMapper defines a list of aliases that can be used instead of an attribute ID. You can add your own aliases that can be used for convenience. ParameterList represents a list of parameters. The class ParameterManager manages a parameter list and provides the central access to the parameters. The class ParameterParser is used to parse the parameters passed from outside and store them in a parameter list.

The package com.ibm.js.team.workitem.commandline.remote contains the remote interface IRemoteWorkItemOperationCall that is used in RMI mode.

The package com.ibm.js.team.workitem.commandline.utils contains some utility classes (providing static methods as interfaces). The class ProcessAreaUtil allows to search process areas. The class SimpleDateFormatUtil helps with conversion of timstamps from and to a string representation.

 How The Code Works

The main method of the WorkitemCommandLine basically instantiates the class and then calls the method run(). We will look at that method later.

/**
 * The main entry point into the work item commandline
 * 
 * @param args
 *            - the arguments to be used by the commandline
 * @throws RemoteException
 */
public static void main(String[] args) {

	OperationResult result = new OperationResult();
	System.out.println("WorkItemCommandLine Version "
			+ IWorkItemCommandLineConstants.VERSIONINFO + "\n");
	WorkitemCommandLine commandline;
	try {
		commandline = new WorkitemCommandLine();
		result = commandline.run(args);
	} catch (RemoteException e) {
		result.appendResultString("RemoteException: " + e.getMessage());
		result.appendResultString(e.getStackTrace().toString());
	}
	System.out.println(result.getResultString());
	if (TeamPlatform.isStarted()) {
		TeamPlatform.shutdown();
	}
	if (!isServer()) {
		// If I am not in server mode, I need to exit and return success or
		// failure
		if (result.isSuccess()) {
			// If the operation was unsuccessful, terminate with an error
			System.exit(0);
		}
		System.exit(1);
	}
}

The operation run() will return a result if it terminates. The information in this result is used to create the exit code to terminate the call.

In case this WCL is started as RMI server, the process can not terminate with System.exit(). It needs to persist registered to the RMI registry. The static method isServer() is used to communicate this information.

The method run() parses the parameters passed. It then checks if it is supposed to run as RMI server or as RMI client. If that is the case it starts the RMI server mode or, uses RMI to call the server as client. If this is a normal run, it calls runCommands() with the parameters that have been parsed.

If started as RMI server, the method startRMIServer() is used to initialize RMI and to register the class to the registry. The method runOperation() is basically the interface that is used to run the command on the server and is called by RMI clients. The method runOperation() parses the parameters and calls runCommands() as well.

The method runCommands() really executes the command requested in the parameters.  The first steps it does is to initialize the data it needs. Then it runs addSupportedCommands() to add the commands that are available.

/**
 * Add the supported commands. If introducing a new command, add it here.
 * 
 * @param parameterManager
 */
private void addSupportedCommands(ParameterManager parameterManager) {
	addSupportedCommand(new PrintTypeAttributesCommand(
			new ParameterManager(parameterManager.getArguments())));
	addSupportedCommand(new CreateWorkItemCommand(new ParameterManager(
			parameterManager.getArguments())));
	addSupportedCommand(new UpdateWorkItemCommand(new ParameterManager(
			parameterManager.getArguments())));
}

Now the method runCommands() gets the command from the Parameter Manager. If there is a command string, it gets the class that implements the command. If there is a command registered for this command string, runCommand() calls the command to validate if the required parameters for it to run are available. If this is the case, runCommand() calls the command and returns the result back.

In all other cases runCommand() prepares a result error and also uses the method helpGeneralUsage() to print a help for the command.

Adding Commands to the WorkItemCommandLine

It is easy to add new commands to the WorkitemCommandLine. You need to implement a new command and add a new entry for it in the method addSupportedCommands().

How Commands Work

Commands have to implement the IWorkItemCommand interface. You should pick one of the abstract classes in the framework and extend them. This makes sure the basic workflow will work. If you command needs to create or modify a work item based on property values, use the class AbstractWorkItemModificationCommand. If you only need to have a repository connection, use the class AbstractTeamRepositoryCommand. In both cases all you need to do really is to override and implement the methods required. There are three things that need to be there.

In the method getCommandName() you need to return the name of the command you implement.

@Override
public String getCommandName() {
	return IWorkItemCommandLineConstants.COMMAND_CREATE;
}

If your command needs additional parameters, override the method setRequiredParameters(). Call the method of the superclass to have it add its required parameters and add your parameters. Here is an example

/*
 * (non-Javadoc)
 * 
 * @see com.ibm.js.team.workitem.commandline.framework.
 * AbstractWorkItemCommandLineCommand#setRequiredParameters()
 */
public void setRequiredParameters() {
	super.setRequiredParameters();
	// Add the parameters required to perform the operation
	// getParameterManager().syntaxCommand()
	getParameterManager()
			.syntaxAddRequiredParameter(
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY,
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY_EXAMPLE);
	getParameterManager()
			.syntaxAddRequiredParameter(
					IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY,
					IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY_EXAMPLE);
	getParameterManager().syntaxAddSwitch(
			IWorkItemCommandLineConstants.SWITCH_IGNOREERRORS);
	getParameterManager().syntaxAddSwitch(
			IWorkItemCommandLineConstants.SWITCH_ENABLE_DELETE_ATTACHMENTS);
	getParameterManager().syntaxAddSwitch(
			IWorkItemCommandLineConstants.SWITCH_ENABLE_DELETE_APPROVALS);
}

Parameters added with syntaxAddRequiredParameter() will be assumed to be required. If they are not available the command line will show an error during the parameter validation. The error message is automatically created from the parameter information provided here.

Finally you have to override and implement the method process() to implement the command.

/*
 * (non-Javadoc)
 * 
 * @see com.ibm.js.team.workitem.commandline.framework.
 * AbstractWorkItemCommandLineCommand#process()
 */
@Override
public OperationResult process() throws TeamRepositoryException {
	// Get the parameters such as project area name and Attribute Type and
	// run the operation
	String projectAreaName = getParameterManager()
			.consumeParameter(
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY)
			.trim();
	// Find the project area
	IProjectArea projectArea = ProcessAreaUtil.findProjectArea(
			projectAreaName, getProcessClientService(), getMonitor());
	if (projectArea == null) {
		throw new WorkItemCommandLineException("Project Area not found: "
				+ projectAreaName);
	}

	String workItemTypeID = getParameterManager().consumeParameter(
			IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY)
			.trim();
	// Find the work item type
	IWorkItemType workItemType = WorkItemHelper.findWorkItemType(
			workItemTypeID, projectArea.getProjectArea(),
			getWorkItemCommon(), getMonitor());
	// Create the work item
	createWorkItem(workItemType);
	return this.getResult();
}

To complete the code of this command, here is the method that creates the work item and uses the parameters to update the attributes.

/**
 * Create the work item and set the required attribute values.
 * 
 * @param workItemType
 * @return
 * @throws TeamRepositoryException
 */
private boolean createWorkItem(IWorkItemType workItemType)
		throws TeamRepositoryException {

	ModifyWorkItem operation = new ModifyWorkItem("Creating Work Item");
	this.setIgnoreErrors(getParameterManager().hasSwitch(
			IWorkItemCommandLineConstants.SWITCH_IGNOREERRORS));
	IWorkItemHandle handle;
	try {
		handle = operation.run(workItemType, getMonitor());
	} catch (TeamOperationCanceledException e) {
		throw new WorkItemCommandLineException("Work item not created. "
				+ e.getMessage(), e);
	}
	if (handle == null) {
		throw new WorkItemCommandLineException(
				"Work item not created, cause unknown.");
	} else {
		IWorkItem workItem = getAuditableCommon().resolveAuditable(handle,
				IWorkItem.SMALL_PROFILE, getMonitor());
		this.appendResultString("Created work item " + workItem.getId()
				+ ".");
		this.setSuccess();
	}
	return true;
}

In case you wonder where the actual work gets done – I wondered looking at it. The line

handle = operation.run(workItemType, getMonitor());

does all the work. By calling it this way, the WorkItemOperation creates the work item. The operation is based upon the code in the class AbstractWorkItemModificationCommand.

In that class, the execute() method is overwritten with this code:

/*
 * This is run by the framework
 * 
 * (non-Javadoc)
 * 
 * @see
 * com.ibm.team.workitem.client.WorkItemOperation#execute(com.ibm.team
 * .workitem.client.WorkItemWorkingCopy,
 * org.eclipse.core.runtime.IProgressMonitor)
 */
@Override
protected void execute(WorkItemWorkingCopy workingCopy,
		IProgressMonitor monitor) throws TeamRepositoryException,
		RuntimeException {
	// run the special method in the execute.
	// This is called by the framework.
	update(workingCopy);
}

The call to the method update() does the real work. It walks through all the unconsumed parameters in the parameter list – which should contain the attributes and values to be set and applies the changes to the work item.

/**
 * This operation does the main task of updating the work item
 * 
 * @param workingCopy
 *            the workingcopy of the workitem to be updated.
 * 
 * @throws RuntimeException
 * @throws TeamRepositoryException
 */
public void update(WorkItemWorkingCopy workingCopy)
		throws RuntimeException, TeamRepositoryException {

	ParameterList arguments = getParameterManager().getArguments();

	// We use a WorkItemHelper to do the real work
	WorkItemHelper workItemHelper = new WorkItemHelper(workingCopy,
			arguments, getMonitor());

	// Run through all properties not yet consumed and try to set the values
	// as provide
	for (Parameter parameter : arguments) {
		if (!(parameter.isConsumed() || parameter.isSwitch() || parameter
				.isCommand())) {
			// Get the property ID
			String propertyName = parameter.getName();
			// Get the property value
			String propertyValue = parameter.getValue();
			try {
				workItemHelper.updateProperty(propertyName, propertyValue);
			} catch (WorkItemCommandLineException e) {
				if (this.isIgnoreErrors()) {
					this.appendResultString("Exception! " + e.getMessage());
					this.appendResultString("Ignored....... ");
				} else {
					throw e;
				}
			} catch (RuntimeException e) {
				this.appendResultString("Runtime Exception: Property "
						+ propertyName + " Value " + propertyValue + " \n"
						+ e.getMessage());
				throw e;
			} catch (IOException e) {
				this.appendResultString("IO Exception: Property "
						+ propertyName + " Value " + propertyValue + " \n"
						+ e.getMessage());
				throw new RuntimeException(e.getMessage(), e);
			}
		}
	}
}

The Class WorkItemHelper

This class is basically doing all the work related to modifying work item data. The helper needs to be instantiated. Then the method updateProperty() can be called.

public void updateProperty(String propertyID, String value)
		throws TeamRepositoryException, WorkItemCommandLineException,
		IOException {
.
.
.
}

The method checks if the attribute is one of the special ones like the type, or complex attributes such as workflow or state changes, approvals or other pseudo attribute ID’s and handles these if detected. Otherwise it calls the method updateGeneralAttribute() to handle the update.

private void updateGeneralAttribute(ParameterValue parameter,
		List exceptions) throws TeamRepositoryException,
		WorkItemCommandLineException {
.
.
.
}

The method updateGeneralAttribute() checks if this attribute is actually available on the work item. If so it calls getRepresentation() to get a value that can be set for the attribute.

private Object getRepresentation(ParameterValue parameter,
		List exceptions) throws TeamRepositoryException,
		WorkItemCommandLineException {
.
.
.
}

The method getRepresentation() basically is a huge list of checks to narrow down what type the attribute to modify is. If the type is narrowed down, it calls a related methods to parse the input data and to create a value for the attribute, that can be returned and set.

Summary

This post explains how the code works and how you can utilize it to implement your own commands. As always, I hope that helps someone out there.

While creating this post, I realized, that I should have named some of the classes differently. This framework is not only good for a work item command line. This code could be used for any command line. Maybe I will adjust this a bit in later versions, should time permit.

A RTC WorkItem Command Line Version 2


After publishing the first version of the RTC WorkItem Command Line  I realized several things that users might want to do that were not supported. So I updated the version to 2.0 and added these capabilities. I also found an issue with setting string lists, which is also now fixed.

This post, like the previous, provides a simple Work Item Command Line Client and explains the usage. It comes with code, so you can also enhance it if you need more features.

Latest Version

See A RTC WorkItem Command Line Version 3.0 for the latest version.

What’s new?

This version of the RTC WorkItem Command Line is complete as far as I am concerned.

  • It still supports work item creation and update.
  • It now supports all attribute types and the link types that I think can be supported. The missing Item types are now supported.
  • It now supports several modes to modify the work item properties such as set the value, add to the available value or remove data.
  • It supports a RMI server mode to enhance performance if multiple calls are needed
  • It supports the RMI client mode to delegate requests to the RMI server portion.

Related posts

WorkItemCommandLine Summary

The WorkItemCommandLine – in short WCL – works on Windows and Unix clients. It requires a JDK and the Plain Java Client Libraries to be installed. Please note, I had issues running the Plain Java Client Libraries with just a JRE. You can try to use a JRE, if you like.

It currently allows to

  • Create work items
  • Update work items
  • Show the attributes ID’s available for a work item type in a project area

The WCL allows to set and update all available attribute types (I am aware of) in RTC 4.x up to 5.0.2.

  • String based attributes
  • Number based attributes
  • Enumeration and Enumeration List based attributes
  • Tag based attributes
  • Typed and untyped Item type attributes
  • Typed and untyped ItemList type attributes
  • ……

This works for built in attributes as well as for custom attributes.

In addition to these attribute types various not attribute based work item values can be modified:

  • Subscription
  • Comments
  • Approvals
  • Links to work items
  • CLM links to work items
  • CLM links to Requirements Management and Test artifacts and to SCM change sets
  • Links from build results
  • Attachments can be uploaded
  • Trigger a workflow action

This should be sufficient for most of the automation needs, especially during builds.

The WCL supports the following operations modes on work item attributes:

  • set – set the value of the attribute, overwriting or deleting existing information
  • add – add values or data to the attribute
  • remove – delete values or data from the attribute
  • default – dependent on the type of the attribute set the value or add values

The WCL supports RMI where the WCL runs as a RMI server and WCL can delegate calls to that server to have them processed. This only requires starting the team platform once and saves several seconds in subsequent calls.

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license.

Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee.

On the other hand, you have the code and are able to add your own code to it. It would be nice to know what you did and how, if you do so.

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

Download

You can download the latest version here:

Setup

Download the packaged executable application. The file is compressed and will be named like wcl-Vx-YYYYMMDD.zip. The x represents the version number and is followed by the date it was created.

If you have installed an RTC API development environment following the RTC Extensions workshop and this post, you have all else that is needed and can use  the installs folder of your extension development install, for example C:\RTC401Dev\installs.

Extract the file e.g. using 7Zip to a folder, for example C:\RTCWCL (or C:\RTC401Dev\installs).

The destination folder should now contain a folder wcl.

If you don’t have an extension development environment set up, download and install the Plain Java Client Libraries for your version of RTC. Open the All Downloads tab of the RTC version you are interested in. For example https://jazz.net/downloads/rational-team-concert/releases/4.0.1?p=allDownloads and scroll down to the Plain .zip Files section.

PlainJavaDownloadDownload the Plain Java Client Libraries file.

Use 7Zip and unzip the Plain Java client Libraries download file (for example named RTC-Client-plainJavaLib-4.0.1.zip). Use 7Zips Extract Files command and provide the extraction Path C:\RTCWCL\PlainJavaAPI .

If you don’t have an extension development environment set up, download and install a Java JDK. If you have the Rational Team Concert client installed a compatible JDK is available in the install location e.g. TeamConcert\jazz\client\eclipse\jdk. The easiest way is to download the zip version of the Rational Team concert Client and extracting it to C:\RTCWCL\TeamConcert.

The folder should now look similar to this image.

Install Folder

Adjusting the Scripts to the Environment

If you downloaded a different JDK or have the RTC Eclipse client installed in a different location, follow the next steps to adjust the WCL to the different paths.

Open the folder created when extracting the WCL for example C:\RTCWCL\wcl. The folder contains the script files

  • wcl.bat – for Windows clients
  • wcl.sh – for client with Unix/*ux operating systems such as Linux
  • rmi_no.policy – a RMI policy file
  • README.txt – the online help printed in a file

Open the files relevant for your operating system. They should look similar to this:

Script

The scripts assume an install structure where the JDK and the Plain Java Client Libraries are installed like in the image before. If your setup uses different paths, adjust then according to your setup. The scripts for usage with RMI only add a statement to the rmi_no.policy file.

On Unix operating systems chmod the shell scripts so that they are executable and the RMI policy is readable.

You should now be able to run the WorkItemCommandLine.

In order to allow RMI to work, WCL requires a policy file. Modify the file rmi_no.policy to your requirements and make sure it is in the same wcl folder on the server and the client. Make sure the policy file it is readable for the user that runs WCL.

Test the Environment

Open a shell or cmd window. change the directory to where you extracted WCL for example C:\RTCWCL\wcl. Type wcl and run the WorkItemCommandLine. The command should be executed and print help content like below.

Start WCLIf this does not happen, make sure the paths are set correctly and the JDK is compatible.

Please note, because the version 2.0 supports so much more, the help is very long and the windows shell can cut it off. To avoid this, you can

  1. Redirect the output into a file for example by typing wcl>wcl_help.txt
  2. Change the console setting to increase the size and buffer using mode con: lines=1100 cols=150

The README.txt is provided as help for convenience.

The Syntax of WCL

WCL uses the following syntax:

wcl – {/} {[:]=}

Where , at this time, can be

  • create
  • update
  • printtypeattributes

The commands have their own requirements for base parameters such as repository URL, users, password and the like.

Switches

WCL provides several switches that influence the behavior. Some switches are command specific, others are general. Available values for are:

  • ignoreErrors allows to successfully perform the create and update command if minor errors happen. Errors covered are for example if an optional attribute or its value was not found. If the flag is provided, WCL will continue to perform the next operations and print the error. If the flag is not provided any error will cause the operation to fail.
  • enableDeleteAttachment enables deletion of attachments using the set or the remove mode.
  • enableDeleteApprovals enables deletion of approvals using the set or the remove mode.
  • rmiServer is used to start the RMI server, see the RMI section below.
  • rmiClient is used to run the Workitem Command Line  against a RMI server instead of processing the command itself. See the RMI section below

Parameters

Values for are usually the ID of a work item attribute. The WCL defines various pseudo attribute names, typically prefixed with an @ e.g. to create links and upload files as attachment. The reason for the prefix is that RTC does not allow to start attribute ID’s with special characters and this makes it impossible to define custom attributes with conflicting names.

The parameter sections = must not have spaces in the or in the or before or after the =. The value of , or the whole term can be enclosed in quotation marks “.

Parameter and value example:

projectArea=”JKE Banking (Change Management)”

Each parameter can only be used once in the command line. In some cases like attachment uploads a special section needs to be added in the parameter to allow for multiple specifications in one call.

Multiple parameter example, ‘_2’ is used to make the second parameter unique:

@attachFile:add="./Test.txt:Some Description:text/plain:UTF-8" @attachFile_2:add="./Test.txt:Some Description:text/plain:UTF-8"

The WCL also has an alias mechanism built in, that allows to map different external names for attributes to the internal representation. Currently the names of the AttributeCustomization described here are built in. This allows, for example, to use FOUND_IN=”Sprint 2 Development” instead of foundIn=”Sprint 2 Development”. You can add your own aliases if needed.

Values
The s are specified by a string. Parameter values are usually the display value of elements (enumerations) or composed of display values of the path to this item (category, iterations, process areas). For example setting an enumeration attribute would use the display name “High”, instead of the literal ID. This makes it easier to use. In some cases e.g. for links, subscriber lists and other user lists, it is necessary to specify the ID of the element instead of the display name.

Value examples

  • For enumeration based attributes use the display value for the enumeration literal:
    internalPriority=High
  • For HTML and string based attributes use a string. HTML types like summary, description, comment and HTML support the syntax below:
    description=”Plain text
    bold text
    italic text
    External RSJazz Link
    User link to @ralph
    Work Item link to Defect 3
  • For Wiki and multi line text attributes use
    or \n for line breaks and check the syntax in the wiki editor. The full description for the Wiki Syntax can be found here. Example for a Wiki entry:
    custom.wiki=”
    =Heading1

    Plain text\n==Heading 2\n\nNormal Text **bold text**
    **bold text**
    //Italics//”

  • For work item type, owner and some other attributes use the object ID:
    workItemType=task
    owner=tanuj
  • Use the display name for simple attributes or the path composed out of the display names for hierarchical attributes;
    category=JKE/BRN
    foundIn=”Sprint 2 Development”
    target=”Main Development/Release 1.0/Sprint 3″
    custom.process.area=”JKE Banking (Change Management)/Release Engineering”
  • Dates have to be specified in the Java SimpleDateFormat notation:
    dueDate=”2015/02/01 12:30:00 GMT+01:00″
  • Duration values are specified in milliseconds:
    duration=1800000 correctedEstimate=3600000 timeSpent=60000Since version 3.2 the value can also be specified in hours and minutes
    timeSpent=“3 hours 3 minutes” or timeSpent=“3 hours” or timeSpent=“3 minutes”

Lists

Work item attribute values of List with a specified item type such as userList. Use the separator  ‘,‘ like in “value1,value2,…,valueN” to separate values.

Example user list: custom.user.list:add=”deb,al,bob,tanuj”

Work item attributes with an general attribute value type such as Item or itemList require encoding to locate the items. The format is: custom.item.list=value

Where value has the form: {,}

Each having the form : with no spaces allowed in the value list.

Available values for and examples for Item and itemList attribute values:

  • ProjectArea – specified by its name.
    Example: “ProjectArea:JKE Banking (Change Management)”
  • TeamArea – specified by its name path from the project area to the team area.
    Example: “TeamArea:JKE Banking (Change Management)/Release Engineering”
  • ProcessArea – specified by the name path from the project area to the process area.
    Example: “ProcessArea:JKE Banking (Change Management)/Business Recovery Matters”
  • Category – specified by the category path.
    Example: “Category:JKE/BRM”
  • User – specified by the users user id.
    Example: “User:tanuj”
  • Iteration – specified by the iterations name path (including the development line name).
    Example: “Iteration:Main Development/Release 1.0/Sprint 3”
  • WorkItem – specified by the work items id.
    Example: “WorkItem:20”
  • SCMComponent – specified by the Jazz SCM components display name.
    Example: “SCMComponent:Build”

Modes

Modes allow different types of changes to attributes such as add values, append text or remove and set other data. The mode is specified using this syntax:

[:]=

Supported values for are default (no mode specified), add, set and remove.

If no mode is specified, the default mode for the parameter is used.

  • Example for default mode: summary=”This is a summary.”.
  • Example for add mode: summary:add=” Add this to the summary.”.
  • Example for set mode: summary:set=”Overwite the existing summary with this text.”.
  • Example for remove mode: custom.enumeration.list:remove=$,Unassigned.

Which modes are supported and their behavior depends on the attributes type.

  • Single value attributes typically support default and set mode, but not add and remove mode.
    • Default mode for single value attributes is set the value.
  • Multiple value attributes, such as lists and links, typically support default, add, set and remove mode.
    • Default mode for multiple value attributes is add, which adds the value(s).
    • Set mode for multiple value attributes removes the old values and then adds the new value(s).
    • Remove mode for multiple value attributes removes the specified values that can be found.
  • String values such as HTML, Summary, Wiki type attributes support default (same behavior as set mode), set and add mode.

The Print Type Attributes Command to get the Attribute ID’s and Types

To set work item attributes, WCL needs the ID of the attribute. You can look up the ID of an attribute in the process configuration. The command printtypeattributes prints the attribute ID’s for the built-in and for the custom attributes of a work item type in a project area. The command requires, in addition to the repository URL, the user and password, at least the project area and the work item type to look up. Syntax and required parameters:

wcl -printtypeattributes repository=RepositoryURI user=userID password=password  projectArea=ProjectAreaName workItemType=WorkItemTypeID {parameter[:mode]=value}

Example

wcl -printtypeattributes repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task

Please note, for the built in attributes this returns an internal ID that might not show up if you look into the process configuration. You can use the ID’s you find there too, the API should translate them correctly.

The Create Command

The command create can be used to create a new work item and set its attributes.

The command requires, in addition to the repository URL, the user and password, at least the project area and the work item type to create. Please note, if the process specifies additional required attributes, these need to be provided as well, otherwise the creation and save operation will fail. Syntax and required parameters:

wcl -create repository=RepositoryURI user=userID password=password  projectArea=ProjectAreaName workItemType=WorkItemTypeID {parameter[:mode]=value}

Here an example for creating a work item.

wcl -create /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task summary="New Item" category=JKE owner=ralph

The command will report back the ID of the newly created work item if the operation was successful.

The Update Command

The command update can be used to update a work items attributes.

The command requires, in addition to the repository URL, the user and password, at least the ID of the work item to update. Syntax and required parameters:

wcl -update repository=RepositoryURI user=userID password=password  id=workItemID {parameter[:mode]=value}

Please note, if the process specifies additional required attributes, these need to be provided as well, otherwise the save operation will fail. This can be relevant if the state of a work item is changed.

Here is an example where a work item gets heavily updated:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 SUMMARY="New summary" FOUND_IN="Sprint 2 Development" owner=ralph target="Main Development/Release 1.0/Sprint 3" internalSeverity=Major foundIn="Sprint 2 Development" internalPriority=High attachFile="./Test.txt:Some Description:text/plain:UTF-8" internalApprovals="approval:Please Approve:ralph,deb" internalSubscriptions=al,ralph,deb internalState="In Progress" internalTags="test1,test2" custom.duration=1800000 custom.boolean=true custom.contributor=al custom.contributor.list=al,deb,tanuj custom.decimal=1500200 custom.integer=234 custom.long=567 custom.tag=tag1,tag2 custom.timestamp="2014/12/31 12:30:00 GMT+01:00" custom.wiki="My Wiki" custom.projectarea.list="JKE Banking (Change Management)" custom.project.area="JKE Banking (Change Management)" custom.teamarea.list="JKE Banking (Change Management)/Business Recovery Matters,JKE Banking (Change Management)/Release Engineering" custom.team.area="JKE Banking (Change Management)/Release Engineering" custom.process.area="JKE Banking (Change Management)/Release Engineering" custom.processarea.list="JKE Banking (Change Management)/Business Recovery Matters,JKE Banking (Change Management)/Release Engineering" custom.workitem=3 custom.workitem.list=9,20,7

The command will report back the ID of the updated work item if the operation was successful.

Special Attributes and not  Attribute Based Work Item Modifications

Some attribute types need special treatment or require more complex values to be specified. Some have other limitations and considerations. These are explained below.

For Item List attributes the items need to be provided as a list of items with the separator “,”. As an example a work item attribute of type TeamAreaList would be set like this:

custom.teamarea.list="JKE Banking (Change Management)/Business Recovery Matters,JKE Banking (Change Management)/Release Engineering"

Please note, that this implies that the separator ‘,’ can not be part of any of the display names of the elements.

Special Properties Handling

Some special properties are protected from changing.

  • Work Item ID: can not be changed
  • Project Area: parameter “projectArea” can only be specified when creating the work item. It can not be set to a different value later.

There might be other limitations imposed by the process e.g. against changing the creator of a work item.

Comments

The parameter “internalComments” can be used to add a comment. This pseudo attribute only supports the default and add mode. Removing comments is not supported in the WCL. Comments support the HTML syntax mentioned above allowing to create web links, user and work item links. Example:

internalComments=””Plain Text
Bold Text
Italic Text
External RSJazz Link
@ralph
Defect 3

User, User Lists

For attributes that require users or user lists the value of the property needs to specify the user or the list of users with the ID. Examples:

internalSubscriptions=al,ralph,deb custom.contributor.list=al,deb,tanuj

Subscriptions

The parameter internalSubscriptions can be used to subscribe a list of users to a work item using their user ID’s. The syntax is:

internalSubscriptions[:mode]={,}

This attribute supports the modes default (same as) add, set and remove mode.

  • Example set specific users (removing all others):
    internalSubscriptions:set=al,tammy
  • Example add users:
    internalSubscriptions:add=deb,tanuj,bob
  • Example remove users:
    internalSubscriptions:remove=sally,bob

Example:

wcl -update repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalSubscriptions:add=al,ralph,deb

Tags

The parameter internalTags can be used to add a list of tags. This attribute supports the modes default (same as) add, set and remove. The syntax is:

internalTags[:] ={,}

Example to set the tag list of the work item to two tags test1 and test2, removing all other tags:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalTags:set="test1,test2" custom.tags="MyTag"

Approvals

The parameter internalApprovals can be used to add approvals and approvers. Approvals only support the modes default (same as) add, set and remove. Set and remove only affects approvals of the same approval type. For example, mode set for approval type review will only remove existing reviews. The syntax is:

internalApprovals[][:mode]=”:{: {,userIDn}}”

The name of the approval to be created is required. The Approver ID list is optional and it is possible to add one or more approver userID’s. Example without approvers

internalApprovals:add=”verification:Please Verify”

The section can be left out if only one approval is specified. If multiple approvals are specified, it needs to be a unique string. In the example below “_1” and “_2” make the parameters uniquely distinguishable:

internalApprovals_1:add="approval:Please approve:ralph,deb" internalApprovals_2:add="verification:Please verify:tanuj"

Available values for are

  • approval – to create an approval record
  • review – to create a review record
  • verification – to create a verification record

Examples:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="approval:Please approve:ralph,deb"
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="review:Please review:deb"
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="verification:Please verify:tanuj"
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="verification:We need a verification"

The implementation of the modes set and remove is as follows. Removal of approvals by either set or remove mode must be explicitly enabled using the switch enableDeleteApprovals, otherwise the command will fail if one of these modes is used with a internalApproval parameter.

The mode set removes all existing approvals of the specified and then adds a new approval of this type as specified.

The mode remove searches for all approvals of the specified and deletes those found with a matching the Approval Name.

Workflow and State Change

The WCL allows to set the state of the work item in different ways. Please note, the state is reached after the save operation, if it can be set.

A pseudo parameter @workflowAction can be used to set a workflow action to change the work item state when saving. This attribute supports only the modes default and set.

When using this pseudo parameter WCL looks up the current state of the work item, tries to find a workflow action with the given display name. If one exists, it  sets the save operation to trigger this action when the work item gets saved.

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=115 @workflowAction="Reopen"

Another example: @workflowAction=”Stop working” . Please note, the workflow change is governed by the RTC process engine. If RTC prevents the state change, the operation will fail on save. It is impossible to detect this prior to the save.

The parameter internalState representing the attribute to read the state can be used to set the state. The parameter only implements the modes default and set, which act equal. The syntax is:

internalState=[:]StateName

Where is the value forceState.

Without the forceFlag provided WCL acts similar to using the pseudo parameter @workflowAction. It looks up the current state, and checks if any workflow action from the current state exists, that leads to the specified target state. If there is one, it sets the workflow action to be performed during the save operation. If there is no workflow action the state change is not performed.

Example with a target state:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalState="In Progress"

If the flag forceState: is added before the target state, WCL uses a deprecated API to forcefully set the state. Please note, that this does not trigger a workflow action and does also not trigger operational behavior. It should be used with caution. If the target state exists in the workflow of the work item type, the state is set, regardless if it is reachable directly or using multiple workflow actions or even if it is not reachable by the workflow at all.

Example with a target state forcefully set:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalState="forceState:New"

State Resolution

The resolution of a work item can be set using the attribute internalResulution. The parameter only implements the modes default and set, which act equal. The parameter value provided is the display value of the resolution.

Example:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalResolution=Invalid

 Attachments

The WCL provides a capability to manipulate work item attachments. The pseudo parameter @attachFile can be used to upload and remove attachments. This attribute supports the modes default (same as) add, set and remove. The syntax format is:

@attachFile[]=”SomeFilePath:Some Description::”

Where:

has one of the following values:

  • text/plain
  • application/unknown
  • application/xml

has one of the following values:

  • UTF-8
  • UTF-16LE
  • UTF-16BE
  • us-ascii.

As above, must be unique for multiple attachments in one command. If only one attachment is uploaded, the can be left empty.

The file must be accessible and in the correct encoding for the operation to perform correctly.

As above for approvals the mode set is implemented to remove all attachments first and then add the new attachment. The mode remove is implemented to search for an attachment with the same file path and description and remove it if it is available.

Some examples:

-update /ignoreErrors  repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=150 @attachFile="./Test.txt:Some Description:text/plain:UTF-8" @attachFile_2:add="./Test.txt:Some Description:text/plain:UTF-8"
-update /ignoreErrors /enableDeleteAttachment repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=150 @attachFile:remove="./Test.txt:Some Description:text/plain:UTF-8"
-update /ignoreErrors /enableDeleteAttachment repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=150 @attachFile:set="./Test.txt:Some Description:text/plain:UTF-8"

Duration Types

In duration types provide the value in milliseconds. For example

custom.duration=1800000

Timestamps

Timestamps need to be provided as string in the SimpleDateFormat using the format pattern “yyyy/MM/dd hh:mm:ss z”. For example:

custom.timestamp="2014/12/31 12:30:00 GMT+01:00"

Links

The pseudo parameter @link_ can be used to link the current work item to other objects. The syntax is

@link_={|}

Where specifies the type of link to be created, for example reportAgainstBuild and the values on the right side specifies one target object or a list of target objects to be linked to the current work item using the link type. The separator used here is the pipe symbol ‘|‘. The reason is, that the links can be URI’s and the naming conventions are problematic. It is hard to find a character that is likely not to appear in that string. Not everyone sticks to the specification and the pipe symbol seemed to be appropriate.

The parameter supports the modes default (same as) add, set and remove.  Similar to other implementations above the mode set removes all links of the specified before creating the new links. The mode remove tries to find an existing link of the with the same target and removes this link, if it exists.

There are different ways the links get created, dependent on what link type and what target elements are specified.

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 @link_parent=1 @link_blocks=2|3 @link_reportAgainstBuild=P20141208-1713|@_IjluoH-oEeSHhcw_WFU6CQ|P20141208-1713
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=150 @link_tracks_workitem="https://clm.example.com:9443/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/80|4|5" @link_affected_by_defect=123 @link_affects_plan_item=20|30 @link_related_change_management=4|7
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=150 @link_related_artifact="https://rsjazz.wordpress.com/"  @link_affects_requirement="https://clm.example.com:9443/rm/resources/_6c96bedb0e9a490494273eefc6e1f7c5" @link_tested_by_test_case="https://clm.example.com:9443/qm/oslc_qm/contexts/_6u2zcH-nEeSJhuhJc8_drg/resources/com.ibm.rqm.planning.VersionedTestCase/_N6HHYX-oEeSJhuhJc8_drg"

Work Item Links – links between this work item and another work item within the same repository

The following are supported from the current work item to a target work item. These links are local to the repository this work item belongs to. This means the value list is a list of work item numbers separated by pipe ‘|’ symbols.

  • copied
  • copied_from
  • successor
  • blocks
  • resolves
  • mention
  • predecessor
  • parent
  • duplicate_of
  • duplicate
  • related
  • depends_on
  • child
  • resolved_by

Please note, that if you try to create a link that can not be supported on the target end, save errors will show up. As an example if a target is set to be the child of this work item and that work item has already some other work item set as parent, the save will fail.

Format example:

@link_related=123|80

CLM Work Item Links – CLM links between this work item and another work item within the same or across repository boundaries

The following are supported from the current work item to a target work item. These links can be local to the repository this work item belongs to, or to work items in another repository. The parameter value is a list of one or more work items specified by their ID (if they are in the same repository) or by their Item URI separated by pipe ‘|’ symbols. To understand the URI format, look at an existing link in the RTC web UI and inspect the link target. Wrong target formats can lead to corrupt data.

  • affects_plan_item
  • tracks_workitem
  • related_change_management
  • affected_by_defect

Format example:

@link_tracks_workitem="https://clm.example.com:9443/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/80|120|150"

CLM URI Links – CLM links between this work item and another item, described by a valid URI, in a different application or repository.

The following are supported from the current work item to a target item such as a requirement, test case or test result. These links are across repositories and applications. The parameter value is a list of one or more items, that support this link type, specified by their Item URI separated by pipe ‘|’ symbols. To understand the URI format, look at an existing link in the RTC web UI and inspect the link target. Wrong target formats can lead to corrupt data.

  • related_test_plan
  • affects_requirement
  • tested_by_test_case
  • blocks_test_execution
  • implements_requirement
  • affects_execution_result
  • related_artifact
  • related_test_case
  • elaborated_by
  • tracks_requirement
  • scm_tracks_scm_changes
  • related_execution_record

Format example:

@link_affects_requirement=https://clm.example.com:9443/rm/resources/_848a30e315524069854f55e1d35a402d|https://clm.example.com:9443/rm/resources/_6c96bedb0e9a490494273eefc6e1f7c5

Please note that the link “Associate Work Item” between a change set and the work item can only be created by the SCM component. The link created here is the looser CLM link. Create the work item change set link using the SCM command line.

Build result Links – Links from a work item to a build result in the same repository.

The following are supported from the current work item to a build result. These links are within a repository. The parameter value is a list of one or more build results. The Build result can be specified by its Build Result Label or by the Build Result ID separated by pipe ‘|’ symbols. The parameter value is a list of one or more Buildresults specified by their ID or their label. The WCL distinguishes between build result ID and Build Label, add @ as prefix to the Build Result Label.

  • reportAgainstBuild
  • includedInBuild

Please note that the link includedBuild should only be created by the SCM system from the snapshot, it is only available for completeness.

Format example: @link_reportAgainstBuild=@_IjluoH-oEeSHhcw_WFU6CQ|P20141208-1713

RMI Modes

The WCL can be run as a local Java application. This is fine if only one work item needs to be modified. However, since each call requires the RTC API TeamPlatform to be started and the process takes around 6 seconds, this does not scale for a lot of calls, it is possible to run the work item command line in a RMI server mode on the same or a different machine. This server waits for incoming requests and only needs to initialize the API once.

Called from another RMI client process, the server can process requests very fast and return the result. This can be achieved with two switches to set up the server and to delegate the call to the server.

The syntax for the switches is

/[=]

Where is one of the following values.

  • rmiServer
  • rmiClient

By default the RMI Name used to connect to the RMI server is “//localhost/RemoteWorkitemCommandLineServer” using a default port of 1099 for the RMI registry.

It is possible to define a different name and port by providing a value. The example below starts WCL as RMI server with name “//clm.example.com/WorkItemCommandLine” with a RMI registry listening to port 1199:

wcl /rmiServer=//clm.example.com:1199/WorkItemCommandLine

By providing the same naming in the rmiClient switch for the requested command, the connection can be established.

wcl -create /rmiClient=//clm.example.com:1199/WorkItemCommandLine /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task summary="New Item" category=JKE owner=ralph target="Main Development/Release 1.0/Sprint 3" internalSeverity=Major foundIn="Sprint 2 Development" internalPriority=High creator=myadmin

If the WCL is started as RMI server, the process will not terminate, but RMI will listen to requests and delegate them to that process. It is not necessary to provide a command or any other input values, when starting the WCL in RMI server mode as they will be ignored. RMI will make the process available and call it to service commands requested by other client instances that are started with the additional switch /rmiClient added to the command that is supposed to be performed.

Please note, that the server and the client require a policy file for the security manager.  A policy file rmi_no.policy is shipped with the download. The policy file opens up everything.

So please rename and modify the file to your requirements

To enable security Java requires to call the class with the additional vm argument -Djava.security.policy=rmi_no.policy where the policy file name must exist and be readable on the server and on the client side.

Predefined Attribute ID Aliases

Aliases for attribute ID’s have been coded into the application. You can add your own aliases in the mapping table.
Available mappings:

RESOLUTION_DATE: resolutionDate
FOUND_IN: foundIn
PRIORITY: internalPriority
RESOLVER: resolver
SUMMARY: summary
ESTIMATE: duration
MODIFIED: modified
FILED_AGAINST: category
CREATOR: creator
RESOLUTION: internalResolution
MODIFIED_BY: modifiedBy
PLANNED_FOR: target
CREATION_DATE: creationDate
STATE: internalState
PROJECT_AREA: projectArea
OWNER: owner
TAGS: internalTags
DUE_DATE: dueDate
TYPE: workItemType
ID: id
TIME_SPENT: timeSpent
DESCRIPTION: description
SEVERITY: internalSeverity
CORRECTED_ESTIMATE: correctedEstimate

Summary

This WorkItem Command Line should allow for most of the automation needs when creating work items. In addition it is a nice resource for the RTC work Item API.

In later posts I will explain the code for users that are interested in adding their own implementation.

As always, I hope the post is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

A RTC WorkItem Command Line V1.0 – Deprecated


Please refer to the new Version of the RTC WorkItem Command Line. The code has been enhanced and received a lot of testing and will be the basis for future efforts.

Version 1.0 is Deprecated

Version 1.0 is Deprecated

Please refer to the new Version of the RTC WorkItem Command Line. The code has been enhanced and received a lot of testing and will be the basis for future efforts.

I have seen many requests to be able to create and update work items from a command line in the forum. There are enhancement requests and a story for it in the Rational Team Concert development repository. I had a lot of the required code already available and thought I should provide a solution if possible.

This post provides a simple Work Item Command Line Client and explains the usage. It comes with code, so you can also enhance it if you need more features.

 

Related posts

  • Installing and using the WorkItemCommandLine – this post

WorkItemCommandLine Summary

The WorkItemCommandLine – in short WCL – works on Windows and Unix clients. It requires a JDK and the Plain Java Client Libraries to be installed.

It currently allows to

  • Create work items
  • Update work items
  • Show the attributes ID’s available for a work item type in a project area

The WCL allows to set and update almost all available attribute types.

  • String based attributes
  • Number based attributes
  • Enumeration and Enumeration List based attributes
  • Tag based attributes
  • ……

This works for built in attributes as well as for custom attributes.

The only attribute types currently not supported are Item and ItemList, where the type of the item is not specified or where the type is an SCM object. This might get implemented in the future. The problem is that the syntax needs to be able to specify what Item Type to look for in order to implement this, and to include the SCM component for searching.

Please note, for List attribute types it is currently only possible to set the list, unless described otherwise in the help. I am looking into a common parameter value encoding that can help more flexibility with being able to add and remove items as well.

In addition to these attribute types various not attribute based work item values can be modified:

  • Subscription
  • Comments
  • Approvals
  • Links to work items
  • Links from build results
  • Attachments can be uploaded
  • Trigger a workflow action

This should be sufficient for most of the automation needs, especially during builds.

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license.

Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee.

On the other hand, you have the code and are able to add your own code to it. It would be nice to know what you did and how, if you do so.

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

 

Download

You can download the tool here:

Setup

Download the packaged executable application. The file is compressed and will be named like wcl-Vx-YYYYMMDD.zip. The x represents the version number and is followed by the date it was created.

If you have installed an RTC API development environment following the RTC Extensions workshop and this post, you have all else that is needed and can use  the installs folder of your extension development install, for example C:\RTC401Dev\installs.

Extract the file e.g. using 7Zip to a folder, for example C:\RTCWCL (or C:\RTC401Dev\installs).

The destination folder should now contain a folder wcl.

If you don’t have an extension development environment set up, download and install the Plain Java Client Libraries for your version of RTC. Open the All Downloads tab of the RTC version you are interested in. For example https://jazz.net/downloads/rational-team-concert/releases/4.0.1?p=allDownloads and scroll down to the Plain .zip Files section.

PlainJavaDownloadDownload the Plain Java Client Libraries file.

Use 7Zip and unzip the Plain Java client Libraries download file (for example named RTC-Client-plainJavaLib-4.0.1.zip). Use 7Zips Extract Files command and provide the extraction Path C:\RTCWCL\PlainJavaAPI .

If you don’t have an extension development environment set up, download and install a Java JDK. If you have the Rational Team Concert client installed a compatible JDK is available in the install location e.g. TeamConcert\jazz\client\eclipse\jdk. The easiest way is to download the zip version of the Rational Team concert Client and extracting it to C:\RTCWCL\TeamConcert.

The folder should now look similar to this image.

WCL Folder Structure

Adjusting the Scripts to the Environment

If you downloaded a different JDK or have the RTC Eclipse client installed in a different location, follow the next steps to adjust the WCL to the different paths.

Open the folder created when extracting the WCL for example C:\RTCWCL\wcl. The folder contains the script files

  • wcl.bat – for Windows clients
  • wcl.sh – for client with Unix/*ux operating systems such as Linux

Open the file relevant for your operating system. It should look similar to this:

Script

The script assumes an install structure where the JDK and the Plain Java Client Libraries are installed like in the image before. If your setup uses different paths, adjust then according to your setup.

On Unix operating systems chmod the shell script so that it is executable.

You should now be able to run the WorkItemCommandLine.

Test the Environment

Open a shell or cmd window. change the directory to where you extracted WCL for example C:\RTCWCL\wcl. Type wcl and run the WorkItemCommandLine. The command should be executed and print help content like below.

Start WCLIf this does not happen, make sure the paths are set correctly and the JDK is compatible.

The Syntax of WCL

WCL uses the following syntax:

wcl -<command> [/<flag>] [<parameter>=<value>]

Where <command> can be

  • create
  • update
  • printtypeattributes

The commands have their own requirements for base parameters such as repository URL, users, password and the like.

The flag

  • ignoreErrors allows to successfully perform the create and update command if minor errors happen. For example if an optional attribute or its value was not found. If the flag is not provided any error will cause the operation to fail.

The parameter sections <parameter>=<value> must not have spaces in the <parameter> or in the <value> or before or after the =. You can enclose the value of <parameter>, <value> or the whole term in quotation marks. For example projectArea=”JKE Banking (Change Management)”.

The WorkItemCommandline also defines various pseudo attribute names e.g. to upload files as attachment.

Each <parameter> can only be used once in the command line. In some cases like attachment uploads a special section needs to be added in the parameter to allow for multiple specifications.

The WorkItemCommandline also has an alias mechanism built in, that allows to map different external names for attributes to the internal representation. Currently the names of the AttributeCustomization described here are built in. This allows, for example, to use FOUND_IN=”Sprint 2 Development” instead of foundIn=”Sprint 2 Development”. You can add your own aliases if needed.

Parameter values <value> are usually the display value of elements. For example setting an enumeration attribute would use “High” instead of the literal ID. This makes it easier to use.

In some cases e.g. for links, subscriber lists and other user lists, it is necessary to specify the ID of the element instead of the display name.

The help given by the tool should be enough to figure out how to use it. However, here some more details and examples.

The Command to get the Attribute ID’s and Types

To set work item attributes, wcl needs the ID of the attribute. You can look up the ID of an attribute in the process configuration. The command printtypeattributes prints the attribute ID’s for the built-in and for the custom attributes of a work item type in a project area.

wcl -printtypeattributes repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task

Please note, for the built in attributes this returns an internal ID that might not show up if you look into the process configuration. You can use the ID’s you find there too, the API should translate them correctly.

The Create Command

The command create can be used to create a new work item and set its attributes.

The command requires, in addition to the repository URL, the user and password, at least the project area and the work item type to create. Please note, if the process specifies additional required attributes, these need to be provided as well, otherwise the creation and save operation will fail.

Here an example for creating a work item.

wcl -create /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task summary="New Item" category=JKE owner=ralph

The Update Command

The command update can be used to update a work items attributes.

The command requires, in addition to the repository URL, the user and password, at least the ID of the work item to update. Please note, if the process specifies additional required attributes, these need to be provided as well, otherwise the save operation will fail. This can be relevant if the state of a work item is changed.

Here is an example where a work item gets heavily updated:

-update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 SUMMARY="New summary" FOUND_IN="Sprint 2 Development" owner=ralph target="Main Development/Release 1.0/Sprint 3" internalSeverity=Major foundIn="Sprint 2 Development" internalPriority=High attachFile="./Test.txt:Some Description:text/plain:UTF-8" internalApprovals="approval:Please Approve:ralph,deb" internalSubscriptions=al,ralph,deb internalState="In Progress" internalTags="test1,test2" custom.duration=1800000 custom.boolean=true custom.contributor=al custom.contributor.list=al,deb,tanuj custom.decimal=1500200 custom.integer=234 custom.long=567 custom.tag=tag1,tag2 custom.timestamp="2014/12/31 12:30:00 GMT+01:00" custom.wiki="My Wiki" custom.projectarea.list="JKE Banking (Change Management)" custom.project.area="JKE Banking (Change Management)" custom.teamarea.list="JKE Banking (Change Management)/Business Recovery Matters,JKE Banking (Change Management)/Release Engineering" custom.team.area="JKE Banking (Change Management)/Release Engineering" custom.process.area="JKE Banking (Change Management)/Release Engineering" custom.processarea.list="JKE Banking (Change Management)/Business Recovery Matters,JKE Banking (Change Management)/Release Engineering" custom.workitem=3 custom.workitem.list=9,20,7

Special Attributes and not  Attribute Based Workitem Modifications

Some attribute types need special treatment or require more complex values to be specified. Some have other limitations and considerations. These are explained below.

For Item List attributes the items need to be provided as a list of items with the separator “,”. As an example a work item attribute of type TeamAreaList would be set like this:

custom.teamarea.list="JKE Banking (Change Management)/Business Recovery Matters,JKE Banking (Change Management)/Release Engineering"

Please note, that this implies that the separator can not be part of any of the display names of the elements.

Special Properties Handling

Some special properties are protected from changing.

  • Work Item ID: can not be changed
  • Project Area: can not be changed – use the category

There might be other limitations imposed by the process e.g. against changing the creator of a work item.

User, User Lists, Subscribers

For attributes that require users or user lists the value of the property needs to specify the user or the list of users with the ID. Examples:

internalSubscriptions=al,ralph,deb custom.contributor.list=al,deb,tanuj

Links

To create links to other elements a special pseudo attribute/parameter link is used. The syntax is:

link_<linktype>=<value>

The supported <linktype> values are printed in the help. The value is a list of item ID’s to link to. The list of one or more elements can either be a list of one or more work item ID’s or the label or ID, in case of a build result. Examples:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 link_parent=1 link_blocks=2,3 link_reportAgainstBuild=P20141208-1713

 Workflow, States and Resulutions

The WorkItemCommandline allows to set the state of the work item in different ways. Please note, the state is reached after the save operation, if it can be set.

You can specify a workflow action by using the pseudo parameter workflowAction. In this case wcl looks up the current state of the work item, tries to find a workflow action with the given name and sets the save operation to trigger this action when the work item gets saved. An example:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=115 workflowAction="Reopen"

In addition the attribute internalState can be used to set the state.

Syntax is:

internalState=[<forceFlag>:]StateName

In this case wcl looks up the target state and tries to reach the state, using any available direct workflow action. If there is no workflow action the state change is not performed. If the flag forceState: is added before the target state, wcl uses a dprecated API to forcefully set the state. Please note, that this does not trigger a workflow action and does also not trigger operational behavior. It should be used with caution.

Example with a target state:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalState="In Progress"

Example with a target state forcefully set:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalState="forceState:New"

The resolution can be set using the attribute internalResulution. Example:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 workItemType=defect internalResolution=Invalid

Approvals

The WorkItemCommandline allows to create approvals, reviews and verifications. The syntax is

internalApprovals=”<approval_type>:<Approval Name String>:{<userID1>{,<userID>}}

Where <approval_type> is one of

  • approval
  • review
  • verification

The name of the approval <Approval Name String> is required and it is possible to add one or more approver userID’s

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="approval:Please approve:ralph,deb"
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="review:Please review:deb"
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="verification:Please verify:tanuj"
wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111  internalApprovals="verification:We need a verification"

Attachments

The WorkItemCommandline allows to upload attachments to work items. The syntax is

attachFileIDString{<uniqueID>}=”<pathToFile>:<description>:<contentTypeID>:<encodingID>”

Where the <uniqueID> is optional, but must be unique if multiple attachment uploads are specified, the <pathToFile> needs to exist  and be accessible, the description is some string.

The values for <contentTypeID> and <encodingID> need to match the file.

Supported values for <contentTypeID> are:

  • text/plain
  • application/unknown
  • application/xml

Supported values for <encodingID> are:

  • UTF-8
  • UTF-16LE
  • UTF-16BE
  • us-ascii

Example:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 attachFile="./Test.txt:Some Description:text/plain:UTF-8" attachFile_1="./LintResult.txt:Lint result:text/plain:UTF-8"

Subscriptions

WCL allows to add subscribers to work items. The syntax is:

internalSubscriptions=<userID1>{,<userID>}

To remove subscribers use the pseudo attribute unsubscribe. The syntax is the same as when subscribing:

unsubscribe=<userID1>{,<userID>}

Example:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph internalSubscriptions=al,ralph,deb unsubscribe=myadmin

Tags

WCL allows to set tags in the built in tag attribute as well as for custom attributes of the type tag. The syntax is:

<parameter>=<Tag>{,<Tag>}

Example:

wcl -update /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph id=111 internalTags="test1,test2" custom.tags="MyTag"

Duration Types

In duration types provide the value in milliseconds. For example

custom.duration=1800000

Timestamps

Timestamps need to be provided as string in the SimpleDateFormat using the format pattern “yyyy/MM/dd hh:mm:ss z”. For example:

custom.timestamp="2014/12/31 12:30:00 GMT+01:00"

Summary

This WorkItemCommandLine should allow for most of the automation needs when creating work items. In addition it is a nice resource for the RTC work Item API. If my schedule allows, I will enhance it to support the missing types and potentially some export and import options.

In later posts I will explain the code for users that are interested in adding their own implementation.

As always, I hope the post is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

Manage Scheduled Absences Using The PlainJava Client Libraries


I have seen questions in the Jazz.net forum around how to manage public holidays or other scheduled absences for a large user base. I have heard this kind of questions from others as well. And thought a solution would be quite interesting, so here goes.

Since I hate repetitive, boring, time consuming tasks as any one else, I wanted to do something about this for a while. In the context of this question two colleagues from Japan, Saitoh-san and Kobayashi-san approached me. They already had created a solution but were not sure how to publish it. They invited me into their IBM DevOps Services project to share what they had done. I looked into the code and found they had actually implemented an Eclipse Wizard to import scheduled absences for a user.

Since I can’t blog about things I haven’t done, I decided to take a deeper look at what they had done and create a solution from there. I finally ended up creating some tooling that allows to manage single absences and collections of scheduled absences for one or many users.

This image shows the scheduled absences added to a user.

Absences

The code in this post is client API.

This blog post uses internal API which can be changed at any time.

This blog post uses internal API which can be changed at any time. If the Internal API changes, the code published here will no longer work.

Warning, some of the code uses internal API that might change in the future. If the Internal API changes, the code published here will no longer work.

The code in this post hides the RTC API for scheduled absences, which is actually an internal API, from the user. It provides methods to conveniently work with absences. It allows to create, read and delete absences for one or many users. Before we continue, the usual ceremony:

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

The Code

The code discussed in this post can be downloaded from here. Please note, the code might change over time, although I hope to keep the interfaces stable.

 The Absence Manager Overview

The source code of the Absence Manager is separated into three projects. The core project com.ibm.js.team.admin.automation.absence.core contains all the code required to create tooling to manage absences.

Core Absence Manager Project

Core Absence Manager Project

The package with suffix core contains the interfaces to work with, for most of the time.

  • IAbsence represents the Interface to scheduled absences in an external format used to store the data in a common format  and to make the data accessible
  • IAbsenceFactory is an interface that provides ways to create scheduled absences in the external format in different ways; the pattern to convert strings and timestamps can be set in the constructor; see the section Date, Timestamp and String Representation Troubles – Here be Dragons below
  • IAbsenceManager is an interface that the Absence Manager provides to allow to create, read and delete absences; it uses an IAbsenceFactory to create absence objects where needed

The package with suffix impl contains implementations for the interfaces that do the real work.

The package with suffix utils contains a utility class that basically manages the conversion of timestamps and string representations.

I ended up with this structure, because I wanted clear abstractions of the concepts and allow to easily enhance or replace the implementations if one so desires. Before I finally ended up with this clear structure, there where a lot of inter-dependencies in the code that where hard to handle. They tended to break the code when introducing small changes and where very confusing in general.

This is by far the most complex automation I blogged about so far and I needed to be able to test it during refactoring. Once I had the first snippets available I used a test driven approach to finalize the solution. This also made the whole refactoring required to get to a clean structure possible in the first place.

The project com.ibm.js.team.admin.automation.absence.core.tests contains unit test for the core classes and interfaces. The unit tests should cover most of the interface and its implementation. I did not make sure all is covered, but the main functionality should be covered.

  • AbsenceDataTest basically runs some simple tests to create absences in different formats
  • AbsenceManagerTest runs tests against a test repository and manages test scheduled absences in that repository for the logged in user testing, leaving the user with no scheduled absences
  • AllTests is a suite that runs all tests above

The third project com.ibm.js.team.admin.automation.absence.csv basically has two classes, that implement CSV import and -export of scheduled absences for all active (not archived) users.

CSV Absence Manager

CSV Absence Manager

The classes can be used as prototype for a custom implementation.

To read and write CSV files I used opencsv to avoid having to implement CSV reading and writing. This made it very easy to implement the functionality after the fundamental interfaces where working.

NOTE: I will not include opencsv in the download. You can download it from sourceforge, unzip it and place the library in the lib folder of the project.

There are other open and free Java implementations of CSV file readers for example SuperCSV for download as well.

  • ScheduledAbsenceCSVImporter uses a CSV file with a comma separated format to read scheduled absences and adds them to all users that are not archived
  • ScheduledAbsenceCSVExporter exports all scheduled absences for all active users to a CSV file, with a similar format, except it contains the user ID as a leading column

Here an example file for using as import source:

CSV Import Example File

CSV Import Example File

Other Uses of the AbsenceManager

If  you have a common system with an Interface to get at absence data, you can create an integration to that system with the attached code. Such an integration could, as an example, synchronize the absences between the other system and Rational Team Concert servers. The simplest approach would be to always delete all absences for a user in RTC and then recreate the absences from that system. This could be done in a nightly run.

RTC, Absences and the AbsenceManager

RTC stores the absences as java.sql.Timestamps in the CCM databse. Absences basically have the following data:

  • Summary – a text that describes the absence
  • StartDate – a Timestamp of the start date
  • EndDate – a Timestamp of the end date, the same as the start date in case of one day long absences

Absences are defined by the three attributes. To be able to find absences it is necessary to find one with the same summary and the same dates. While developing the Absence Manager, it became apparent that matching for the exact data is sometimes not desirable. Therefore the date is, in some cases, only compared to the same day, to avoid missing matches. For the summary the match is implemented as ignore-case.

It would be easy to implement a way to find all absences by the summary. This would potentially be a collection of items. For the use cases so far it was not necessary to implement it and thus I left it out.

During testing, when absences are manually created, the time created for the absence seemed to be 2pm in the timezone of the server. While specifying the absence the user actually only selects the date and not the time. If using automation, first check what the server would create and specify the times accordingly.

The RTC Absence API

The API to get absences is very easy. The code below shows how to access the scheduled absences for a contributor. All the code is hidden in the AbsenceManagerImpl.

	/**
	 * Get the internal representation of all absences of a contributor.
	 * 
	 * @param contributor
	 * @param monitor
	 * @return
	 * @throws TeamRepositoryException
	 */
	private ItemCollection getContributorAbsences(
			IContributorHandle contributor, IProgressMonitor monitor)
			throws TeamRepositoryException {
		final IResourcePlanningClient resourcePlanning = (IResourcePlanningClient) fTeamRepository
				.getClientLibrary(IResourcePlanningClient.class);

		IContributorInfo info = resourcePlanning.getResourcePlanningManager()
				.getContributorInfo(contributor, true, monitor);
		ItemCollection absences = info
				.getAbsences(contributor);
		return absences;
	}

The code basically gets the IResourcePlanningClient to get the ResourcePlanningManager and uses this to get the IContributorInfo. This contains the absences as as well as the team allocations.  The call .getAbsencs(IContributorHandle) returns an ItemCollection with all the IContributorAbsences. All the classes and interfaces, except IContributorAbsence are internal API. This is the reason why it should be encapsulated so that most of the implementation does not interfere with it. This will make it easier to adjust to changing API’s later.

The code below shows how to create a IContributorAbsence

	/**
	 * Create an internal IContributorAbsense from an IAbsence 
	 * 
	 * @param contributor
	 * @param iAbsence
	 * @return
	 */
	private IContributorAbsence createContributorAbsence(
			IContributorHandle contributor, IAbsence iAbsence) {
		ContributorAbsence absence = (ContributorAbsence) IContributorAbsence.ITEM_TYPE
				.createItem();
		absence.setContributor(contributor);
		absence.setSummary(iAbsence.getSummary());
		absence.setStartDate(iAbsence.getStartDate());
		absence.setEndDate(iAbsence.getEndDate());
		return absence;
	}

The data provided during creation is String and timestamps. Please note, this class is not part of the plain java client libraries but shipped with the RTC Eclipse client plugins in the file com.ibm.team.apt.common_*.jar. You need to add this jar file to your classpath, if you run this outside of the RTC SDK as described below.

This code shows how new absences are saved using saveAbsences().

	/**
	 * Add absences from a collection to a user using the contributor object of
	 * the user. The method checks if an absence with the same summary, same
	 * start- and end- date already exist. The comparison converts the dates and
	 * uses a precision of a day to find matches.
	 * 
	 * @throws TeamRepositoryException
	 */
	@Override
	public void addAbsences(IContributorHandle contributor,
			Collection absences, IProgressMonitor monitor)
			throws TeamRepositoryException {
		final IResourcePlanningClient resourcePlanning = (IResourcePlanningClient) fTeamRepository
				.getClientLibrary(IResourcePlanningClient.class);

		List absencesToBeCreated = new ArrayList();

		IContributorInfo info = resourcePlanning.getResourcePlanningManager()
				.getContributorInfo(contributor, true, monitor);
		/**
		 * Can't access all absences, need to narrow down to contributor
		 */
		ItemCollection existingAbsences = info
				.getAbsences(contributor);

		for (Iterator iterator = absences.iterator(); iterator
				.hasNext();) {
			IAbsence iAbsence = (IAbsence) iterator.next();

			/**
			 * Check if the absence is already there to avoid entering it
			 * multiple times. The check is for an match of all data. It does
			 * not prevent from entering absences that overlap or different
			 * summaries. The check of the dates is not precise, but on a day
			 * level.
			 */
			if (!exists_SameDay(existingAbsences, iAbsence)) {
				IContributorAbsence absence = createContributorAbsence(
						contributor, iAbsence);
				absencesToBeCreated.add(absence);
			}
		}
		resourcePlanning.saveAbsences(absencesToBeCreated
				.toArray(new ContributorAbsence[absencesToBeCreated.size()]),
				monitor);
	}

The code for removing absences is similar, only the call is to a different method – deleteAbsences().

	/**
	 * Remove a collection of absences from the absences. Matches by summary, as
	 * well as start- and end- date. Date match is don one a same-day basis.
	 * 
	 * @param contributor
	 *            contributor to remove absences from, must not be null.
	 * @param absences
	 *            collection of absences, must not be null.
	 * @param monitor
	 * @throws TeamRepositoryException
	 */
	@Override
	public void removeAbsences(IContributorHandle contributor,
			Collection absences, IProgressMonitor monitor)
			throws TeamRepositoryException {
		Assert.isNotNull(contributor);
		Assert.isNotNull(absences);
		final IResourcePlanningClient resourcePlanning = (IResourcePlanningClient) fTeamRepository
				.getClientLibrary(IResourcePlanningClient.class);

		List absencesToBeRemoved = new ArrayList();

		IContributorInfo info = resourcePlanning.getResourcePlanningManager()
				.getContributorInfo(contributor, true, monitor);
		/**
		 * Can't access all absences, need to narrow down to contributor
		 */
		ItemCollection existingAbsences = info
				.getAbsences(contributor);

		// For all absences
		for (Iterator iterator = absences.iterator(); iterator
				.hasNext();) {
			IAbsence iAbsence = (IAbsence) iterator.next();

			// search if the absence is available (match same day)
			IContributorAbsence found = findContributorAbsence(
					existingAbsences, iAbsence);
			if (null != found) {
				absencesToBeRemoved.add(found);
			}
		}
		IContributorAbsenceHandle[] remove = absencesToBeRemoved
				.toArray(new IContributorAbsenceHandle[absencesToBeRemoved
						.size()]);
		resourcePlanning.deleteAbsences(remove, monitor);
	}

That’s it. Nothing big. But….

The Rest of the Code

All the 9/10th rest of the code is basically to make it easy and convenient to manage the scheduled absences and to hide the internal API within. In addition tests and usage examples make up a reasonable amount of the code.

Date, Timestamp and String Representation Troubles – Here be Dragons

The biggest trouble in the whole implementation was the conversion of date and time to timestamps. This occurs in several areas. The general problem here is that there is no general tool that is able to parse any string defining a date/time without problems.

To overcome this, the AbsenceManager uses java.text.SimpleDateFormat. This requires a string expression to parse and map the external data to the internal representation.

By default the mapping pattern used is “yyyy/MM/dd hh:mm:ss z”. However, this requires to provide date, time and timezone. If it is necessary, e.g. to avoid the time and timezone, a different mapping string can be used. To provide a different mapping string, use the second constructor of the AbsenceFactoryImpl and provide the mapping string as shown below.

		new AbsenceFactoryImpl("yyyy/MM/dd")
		IAbsenceManager absenceManager= new AbsenceManagerImpl(teamRepository, new AbsenceFactoryImpl("yyyy/MM/dd"));

Please note, if no time is provided, the server will pick an hour on its own.

The IAbsenceFactory provides also ways to create new absence instances with different representations. However, keep in mind that some comparisons are done internally and it is best to have a common schema.

How to use the AbsenceManager

How to use the absence manager is shown in the classes AbsenceManagerTest and ScheduledAbsenceCSVImporter.  You have to be connected to a team repository with a user that has sufficient permissions to read/write the absences.

To get the AbsenceManager use:

		IAbsenceManager manager = new AbsenceManagerImpl(fTeamRepository,new AbsenceFactoryImpl());

		String absence1Summary="Absence 1";
		Date today= new Date();
		IAbsence absence1= manager.getAbsenceFactory().newInstance(absence1Summary, today);
		IAbsence absence1_sameday= manager.getAbsenceFactory().newInstance(absence1Summary, new Timestamp(today.getTime()+60000));
		IAbsence absence2= manager.getAbsenceFactory().newInstance(absence1Summary, "2014/07/17 02:00:00 CEST");
		IAbsence absence3= manager.getAbsenceFactory().newInstance(absence1Summary, "2014/07/17 02:00:00 CEST", "2014/07/30 02:00:00 CEST");

You can add single or multiple absences like this:

		// Add a single absence using a contributorHandle
		manager.addAbsence(contributorHandle, absence1, monitor);
		// Add a collection of absences
		ArrayList addAbsences= new ArrayList();
		addAbsences.add(absence1);
		addAbsences.add(absence2);
		manager.addAbsences(contributorHandle, addAbsences, monitor);

		// Add a single absence using a userId
		manager.addAbsence(manager.getContributor("ralph"), absence1, monitor);
		// Add a collection of absences
		ArrayListaddAbsences= new ArrayList();
		addAbsences.add(absence1);
		addAbsences.add(absence2);
		manager.addeAbsences(manager.getContributor("ralph"), addAbsences, monitor);

The interface implements a convenience method getContributor(String userID) to allow to get the IContributor interface, which also implements IContributorHandle, from the userId.

You can get absences for a user.

		ArrayList userAbsences=manager.getAbsences(contributorHandle, monitor);

You can test for an existing absence

		if(manager.hasAbsences(contributorHandle, absence1, monitor)){

You can remove specific absences

		manager.removeAbsence(contributorHandle, absence3, monitor);
		manager.removeAbsence(contributorHandle, absenceCollection, monitor);

You can delete all absences up to a specific date for

		// Clear all absences for user
		manager.purgeAbsences(contributorHandle, null , monitor);
		// Clear all absences up to a certain date for user
		manager.purgeAbsences(new Date(), monitor);
		// Clear all absences for all users up to a certain date (including archived users)
		manager.purgeAbsences(new Date(), monitor);
		// Clear all absences for all users (including archived users)
		manager.purgeAbsences(null, monitor);

Import the code

The code can be downloaded here. Save the code on the local disk. Set up an Eclipse client for example the RTC Eclipse client. Follow the section Setting Up The Plain Java Client Libraries and Setting Up Your Workspace for Plain Java Development in the post Setting up Rational Team Concert for API Development to set up at least the Plain Java Client Libraries. You don’t have to set up the SDK to run the code. This step would however provide you with access to the API classes and their source code. Make sure to create the user Library for the Plain Java Client Libraries.

Import the compressed file as archived project file into Eclipse.

Open CSV

The example does not ship the opencsv library. Download it by following the link e opencsv  and clicking on the download link in the General section of the description shown below.

Open CSV download link

Open CSV download link

Store the download file e.g. opencsv-2.3-src-with-libs.tar.gz in a temporary folder. Use 7Zip to extract the file. Use 7Zip again to extract the file content. Find the folder deploy in the extracted folder structure. E.g. C:\temp\opencsv-2.3\deploy  and copy the enclosed JAR file e.g. named opencsv-2.3.jar into the folder lib underneath the project com.ibm.js.team.admin.automation.absence.csv. Please note, opencsv also ships the file junit.jar. that is not the file you want.

IContributorAbsence

The planning component does not contribute any files to the plain Java Client Libraries. Unfortunately the interface com.ibm.team.apt.common.resource.IContributorAbsence that is used is part of that API. Similar to the opencsv jar file, find the files com.ibm.team.apt.common_*.jar and com.ibm.team.apt.client_*.jar in the Eclipse Plugins folder of an RTC Eclipse client installed from a zip file. If you install RTC with Installation Manager the files might be located in a shared folder.

Find the file com.ibm.team.apt.common_*.jar and com.ibm.team.apt.client_*.jar for your version of RTC and add it to the classpath e..g. by copying it into the plain Java Libraries folder.

For all the projects you just imported starting with com.ibm.js.team.admin.automation.absence.core. Run a clean build and check the build path for errors.  If you used the proposed name PlainJavaApi, for the Plain Java Client Libraries user library you should be fine. If you see errors, you probably have a different name. In this case configure the build path. Remove the Plain Java Client Library user library from the build path and add your user library.

Finally all errors should be gone.

Run the ScheduledAbsence Code

You are now ready to use the code and run the Unit Tests, the CSV importer and the CSV exporter.

The code ships with launches. They are located in a sub folder named Launches in the projects.The launches should now be available in the Eclipse Debug Configurations and Run Configurations menu.

Open the configuration e.g. for the ScheduledAbsenceCSVImporter launch. The Arguments tab will show

"https://clm.example.com:9443/ccm/" "ralph" "ralph" "USFederalHolidays2014.csv"

The  ScheduledAbsenceCSVImporter  and the ScheduledAbsenceCSVExporter require

  • the Repository URL
  • a user ID
  • a password
  • a name for the CommaSeperatedFile

Replace the information with your own values. Then you can run the importer and the exporter.

The same information is required in the AbsenceManagerTest Junit test. It is hard coded in the AbsenceManagerTest. Replace the values with your own information if you intent to run the test against you own test system.

This image shows the result of a run:

ImportOutput

Next Steps

I will try to find some time to be able to create a version that can be shipped as binaries with batches so that they can easily be run from the command line. In general the post Understanding and Using the RTC Java Client API should give you all the information you need to get this done yourself.

Summary

This post provides you with all the code and information needed to automate managing scheduled and other absences in RTC repositories. The code is obviously not production code, so you should make sure it works as advertized in your environment. the provided tests should help you to fix errors, should they occur.

As always, I hope this post saves users out there some time – or is at least fun to read.

Maintaining User Photos With the Plain Java Client Libraries


There is an interesting question on Jazz.net asking if it is possible to download and re-size the personalized user images in Jazz using the Java or Rest API. This post shows the Java API to do that. If you are just starting with extending Rational Team Concert, start reading this and the linked posts to get some guidance on how to set up your environment.

Why is this of interest?

Users can upload a personalized photo to their account in Jazz, to support easier collaboration. The images are loaded and displayed in plans. Today Jazz does not rescale the images that are uploaded, it just stores them. So the size of the photo uploaded has an impact on the UI in plans, especially in the Web UI. Related, this work item asks for the feature to crop and optimized photos on upload. In the meantime it would be nice if it would be possible to automate downloading, rescaling and uploading of the images. The Java API allows to do just that.

License and how to get started with the RTC API’S

As always, our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license, which basically means you can use it for internal usage, but not sell. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

To keep it simple this example is, as many others in this blog, based on the Jazz Team Wiki entry on Programmatic Work Item Creation and the Plain Java Client Library Snippets. The example in this blog shows RTC Client API.

Download

You can download the code from here.

The code contains only one class ManageContributorPhoto is the main class that needs to be called

Please consider to test this in a test environment until it works as desired. Please also be aware that the code is only barely tested and you might want to change how it works for your environment

How to use the tool

You need to import the sources into Eclipse and provide the plain java client libraries as described in this article, in order to be able to run it. In this case a sample launch comes with the code. Alternatively you can compile the example code and provide the plain Java Client Libraries in the classpath.

The code has been developed using the SDK to be able to browse the API better and is also configured as a plug-in. If you don’t have your Client set up with an SDK you can remove the file plugin.xml and the folder META-INF and all its content.

Call ManageContributorPhoto.main with parameters repositoryURI AdminUserId AdminPassword where is down or up for download of the images or upload of modified ones. You can add another parameter pointing to a folder that should be used to store the images. For example “C:/temp/images/” or “/tmp/images”. Make sure to provide the trailing slash and make sure the folder exists.

For example call it with the following parameters to download the images into “C:/temp/images””

"https://clm.example.com:9443/jts" user password down "C:/temp/images/"

To upload the images after modification use

"https://clm.example.com:9443/jts" user password up "C:/temp/images/"

The Example ships with two launches as presented above that you should be able to use.

Please be aware that the context root jts is not a typo. You want to change the users in JTS, which promotes the changes to all the other applications. Please check that the uid’s have been changed in the registered applications after running the tool.

The tool downloads the available images for all contributors in the repository and stores them in a file created from the user name,the user ID and an extension ‘.jpg’ as shown in the image below. For uploading it expects the same format. It will not upload or change anything if no image is available.

UserPictures

Contributor Details

As already published, for instance in the post Changing the Jazz User ID Using the RTC Plain Java Client Libraries the information about the user is kept in a Contributor object. However, the photo image is not directly stored in this object. Instead there is an object implementing the IContributorDetails interface that has holds additional information to the contributor. These details also contain the reference to the picture or image we are interested in.

The data is accessible using this code:

IContributorDetailsHandle detailsHandle = contributor.getDetails();

The handle needs to be resolved or fetched to get at the data as usual. We will look at the details soon.

The Method run

The main code is done in the method run, which extracts the parameters, connects to the team repository and iterates all contributors of the repository. It calls the code to download or upload the image for each contributor based on the direction parameter provided.

Downloading Files

If the direction parameter down is provided the method downloadPhoto() is called that deals with the details. The code looks as follows.

/**
 * Download a photo (if exists) from a contributor to a file. The image file is
 * made available in the images folder (by default the execution folder). 
 * The Filename is composed from the base location user name, ID and the extension '.jpg'.
 * 
 * @param teamRepository
 *            must not be null
 * @param contributor
 *            must not be null
 * @param monitor
 * @throws TeamRepositoryException
 * @throws IOException 
 */
public static void downloadPhoto(ITeamRepository teamRepository,
		IContributor contributor, IProgressMonitor monitor)
			throws TeamRepositoryException, IOException {
	System.out.print(" Download ");
	// Get the contributor details to be able to access the photo
	IContributorDetails details = getDetails(teamRepository, contributor, monitor);
	if (details != null) {
		IContent photo = details.getPhoto();
		if (photo != null) {
			try {
				downloadSaveContent(teamRepository, photo, getFileName(contributor), monitor);
			} catch (IOException e) {
				System.out.println("\nException "+ e.getMessage());
				e.printStackTrace();
				throw e;
			}
		}
	}
}

The code tries to access the details of a contributor, gets the photo, if available, and tries to download the photo to a given file name constructed from the contributor information. The methods getDetails() and getFileName() are used by the upload and download code to get the details and construct the file name. The code of these methods looks as below.

/**
 * Create a file name string from a root path and 
 * the contributor information
 * 
 * @param contributor must not be null
 * @return
 */
public static String getFileName(IContributor contributor) {
	return imageRoot + contributor.getName() + "_" + contributor.getItemId() + ".jpg";
}

/**
 * Try to get the contributor details
 * 
 * @param teamRepository
 *            must not be null
 * @param contributor 
 *            must not be null
 * @param monitor
 * 
 * @return the resolved details if found or null
 * 
 * @throws TeamRepositoryException
 */
public static IContributorDetails getDetails(
		ITeamRepository teamRepository, IContributor contributor,
		IProgressMonitor monitor) throws TeamRepositoryException {
	// Try to get the contributor details handle
	IContributorDetailsHandle detailsHandle = contributor.getDetails();
	if (detailsHandle == null) {
		System.out.print("Details null");
		return null;
	}
	// Fetch the details if the handle is not null
	IContributorDetails details = (IContributorDetails) teamRepository
			.itemManager().fetchCompleteItem(detailsHandle,
					IItemManager.DEFAULT, null);
	return details;
}

The method downloadSaveContent below, is used to download the image and store it in a local file. The way it is set up the whole operation fails if the file can’t be saved.

/**
 * Download content to a given file.
 * 
 * @param teamRepository
 *            must not be null
 * @param content
 *            must not be null
 * @param filename
 *            must not be null
 * @param monitor
 * 
 * @return
 * @throws TeamRepositoryException
 * @throws IOException 
 */
public static void downloadSaveContent(ITeamRepository teamRepository,
		IContent content, String filename, IProgressMonitor monitor)
		throws TeamRepositoryException, IOException {
	File save = new File(filename);

	OutputStream out;
	try {
		out = new FileOutputStream(save);
		try {
			teamRepository.contentManager().retrieveContent(content, out, monitor);
			System.out.print(" " + filename + " download succeeded..."
					+ " Type: " + content.getContentType() + " Encoding: "
					+ content.getCharacterEncoding() + " LineDelemiter: "
					+ content.getLineDelimiter());
		} finally {
			out.close();
		}
	} catch (FileNotFoundException e) {
		System.out.print("File: " + filename
				+ "filename could not be created...");
		throw e;
	}
}

This is all the code needed for downloading. Please be aware that the download uses the file type and encoding set for the content when downloading.

Uploading an Image File

If the direction parameter up is provided the method uploadPhoto() is called that deals with the details of uploading an image to the details. The code looks as follows.

/**
 * Uploads a photo from an image file to the contributor. 
 * The image file is expected to be available in the images folder (by 
 * default the execution folder). 
 * The Filename is composed from the base location user name, ID and the extension '.jpg'.
 * 
 * @param teamRepository
 *            must not be null
 * @param contributor
 *            must not be null
 * @param monitor
 * 
 * @throws TeamRepositoryException
 * @throws IOException 
 */
public static void uploadPhoto(ITeamRepository teamRepository,
		IContributor contributor, IProgressMonitor monitor)
		throws TeamRepositoryException, IOException {

	System.out.print(" Upload ");
	// Try to upload the image as content.
	IContent newPhoto = uploadContent(teamRepository,
			getFileName(contributor), monitor);
	if (newPhoto != null) { // If the image was uploaded and content created
		// Get the details for the user. Create the details in case they are missing
		IContributorDetails details = getDetails(teamRepository,contributor, monitor);
		if (details == null) { 
			details = (IContributorDetails) IContributorDetails.ITEM_TYPE.createItem(teamRepository);
		}
		// Set the photo
		IContributorDetails detailsWorkingCopy = (IContributorDetails) details.getWorkingCopy();
		detailsWorkingCopy.setPhoto(newPhoto);
		// Set the details
		IContributor contributorWorkingCopy = (IContributor) contributor.getWorkingCopy();
		teamRepository.contributorManager().setContributorDetails(
				contributorWorkingCopy, detailsWorkingCopy, monitor);
	}
}

The code first tries to upload the content from the file then it tries to get the details and, if there was an upload either uses the existing contributor details or creates new details if needed.
It gets a working copy of the details, sets the photo content to it. Last but not least, it gets a working copy for the contributor and sets the contributor details that have been modified.

The code of the method uploadContent() to upload the image and to create the content with the image is shown below.

/**
 * Uploads an image to the repository and creates a new content.
 * 
 * @param teamRepository
 *            must not be null
 * @param filename
 *            must not be null
 * @param monitor
 * 
 * @return the content if created, null otherwise
 * 
 * @throws TeamRepositoryException
 * @throws IOException 
 */
public static IContent uploadContent(ITeamRepository teamRepository,
		String filename, IProgressMonitor monitor)
		throws TeamRepositoryException, IOException {
	IContent newContent = null;
	try{
		File uploadFile = new File(filename);
		FileInputStream fin = new FileInputStream(uploadFile);
		try {
			// Store the content with a meaningful content type, encoding and line delimiter.
			newContent = teamRepository.contentManager().storeContent(
					"application/octet-stream", IContent.ENCODING_UTF_8,
					LineDelimiter.LINE_DELIMITER_NONE, fin, null, monitor);
			System.out.print("Uploaded: " + filename);
		} finally {
			fin.close();
		}
	} catch (FileNotFoundException e) {
		System.out.print("File: " + filename
				+ " Filename could not be found...");
		// If there is no image file, we don't upload one.
	}
	return newContent;
}

Other than for the download, if the file for the image is not found, this code continues and just does not change the details.
The code provides content type and encoding similar to what was downloaded.

Summary

The code above allows to easily first download the images, then run some tool to scale them down and finally upload all the images for all users (that have an image) or even only for the images changed. I hope it is worth reading and helps some administrator out there to save some time.

A Create Approval Work Item Save Participant


This post is about a Participant that creates an approval when saving a work item. I was interested in posting this, because I was interested on how to get at project member and role information in a server extension. I had already helped a partner with a similar effort in the past, where the approver information was supposed to be read in an external system. Back then I couldn’t find how to access the project area information and to find the roles.

License and how to get started with the RTC API’S

As always, our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license, which basically means you can use it for internal usage, but not sell. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

The example in this blog post shows RTC Server and Common API.

Download

You can download the code here. The API code in this post is Server and Common API.

Solution Overview

The code provides you with several classes. The interesting one is com.ibm.js.team.workitem.createapproval.participant.CreateApprovalParticipant. This class implements the participant that creates an approval if a workitem changes into the state "com.ibm.team.workitem.common.model.IState:com.ibm.team.apt.story.tested".

In case this state change is detected, the participant runs the following code to get approvers by their role Product Owner.

/**
 * @param workItem
 * @param collector
 * @param role
 * @param monitor
 * @throws TeamRepositoryException
 */
private void createApprovalByRole(IWorkItem workItem,
		IParticipantInfoCollector collector, String role, IProgressMonitor monitor) throws TeamRepositoryException {

	IContributorHandle[] approvers=findApproversByRole(workItem, "Product Owner", monitor);
	createApproval(workItem, collector, approvers, monitor);
}

The method called to find the approvers looks like the following code. The code gets the process area that governs the work item. and tries to get contributors with matching roles.

If there are no contributors that could be found with a matching role, it tries the same with the project area. The contributors are returned to create the approval.

Please note, this strategy could be changed into recursively start at the project area an find the enclosed team area hierarchy and then try all team areas in the hierarchy from the one that owns the work item up to the project area. This is left as a good example for the you to implement.

/**
 * Finds Approvers by role. Looks in the process area that owns the work item first, 
 * then looks at the project area if it was not already looking at it.
 * 
 * @param newState
 * @param roleName
 * @param monitor
 * @return
 * @throws TeamRepositoryException
 */
private IContributorHandle[] findApproversByRole(IWorkItem newState,
		String roleName, IProgressMonitor monitor) throws TeamRepositoryException {
	IProcessAreaHandle processAreaHandle = fWorkItemServer.findProcessArea(
		newState, monitor);
	IProcessArea processArea = (IProcessArea) fAuditableCommon.resolveAuditable(processAreaHandle,
		ItemProfile.createFullProfile(IProcessArea.ITEM_TYPE), monitor);
	IContributorHandle[] contributors = findContributorByRole(processArea, roleName, monitor);

	if(contributors.length==0){
		IProjectAreaHandle projectAreaHandle = processArea.getProjectArea();
		if(!projectAreaHandle.getItemId().equals(processAreaHandle.getItemId())){
			IProcessArea projectArea = (IProcessArea) fAuditableCommon.resolveAuditable(projectAreaHandle,
				ItemProfile.createFullProfile(IProcessArea.ITEM_TYPE), monitor);
			return findContributorByRole(projectArea, roleName, monitor);
		}
	}
	return contributors;
}

The code to find the approvers by role gets the members of the process area, then gets the contributors with the role name provided and returns the result. The code can be seen below.

/**
 * Find contributors by role on a process area.
 * 
 * @param processArea
 * @param roleName
 * @param monitor
 * @return
 * @throws TeamRepositoryException
 */
public IContributorHandle[] findContributorByRole(
		IProcessArea processArea, String roleName,
		IProgressMonitor monitor) throws TeamRepositoryException {
	fProcessServerService = getService(IProcessServerService.class);
	IContributorHandle[] members = processArea.getMembers();
	IContributorHandle[] matchingContributors = fProcessServerService
		.getContributorsWithRole(members, processArea,
		new String[] { roleName });
	return matchingContributors;
}

Finally the code below creates the approval if there are approvers that are passed. It gets the full state of the work item. Then it gets the approvals and creates a new descriptor for the new approval. For each approver it creates an approval with the new descriptor and then adds it to the approvals. Finally it saves the work item.

In case there are no approvers or the save is prevented, an error info is generated.

/**
 * Creates an approval and adds all approvers from an array
 * 
 * @param workItem
 * @param collector
 * @param monitor
 * @throws TeamRepositoryException
 */
private void createApproval(IWorkItem workItem,
		IParticipantInfoCollector collector, IContributorHandle[] approvers, 
		IProgressMonitor monitor) throws TeamRepositoryException {

	if (approvers.length==0) {
		String description = NLS.bind("Unable to create the Approval",
			"Unable to find an approver for the work item ''{0}''.",
			workItem.getItemId());
		IReportInfo info = collector.createInfo(
			"Unable to create approval.", description);
		info.setSeverity(IProcessReport.ERROR);
		collector.addInfo(info);
		return;
	}
	// Get the full state of the parent work item so we can edit it
	IWorkItem workingCopy = (IWorkItem) fWorkItemServer.getAuditableCommon()
		.resolveAuditable(workItem, IWorkItem.FULL_PROFILE, monitor)
		.getWorkingCopy();

	IApprovals approvals = workingCopy.getApprovals();
	IApprovalDescriptor descriptor = approvals.createDescriptor(
		WorkItemApprovals.REVIEW_TYPE.getIdentifier(), APPROVAL_NAME);
	for (IContributorHandle approver : approvers) {
		IApproval approval = approvals.createApproval(descriptor, approver);
		approvals.add(approval);
	}
	IStatus saveStatus = fWorkItemServer.saveWorkItem2(workingCopy, null, null);
	if (!saveStatus.isOK()) {
		String description = NLS.bind("Unable to create the Approval",
			"Unable to save the work item ''{0}''.",
			workItem.getItemId());
		IReportInfo info = collector.createInfo(
			"Unable to create approval.", description);
		info.setSeverity(IProcessReport.ERROR);
		collector.addInfo(info);
	}
}

The code to download contains other examples for how to get approvers.

Summary
The code is experimental. I have tested it in a Jetty based test server using the Scrum template. It is by no means production ready and can be enhanced for various scenarios. However, as always, I hope the code is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

Resolve Parent If All Children Are Resolved Participant


This post is about a Participant that resolves the parent work item, if all children are resolved. It is the example I used to understand the server API for manipulating work item states. I would probably not use it on a production system, because I strongly believe that a human being should do a conscious decision in cases like that, but it is a nice example about the RTC Work Item Server API.

License and how to get started with the RTC API’S

As always, our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license, which basically means you can use it for internal usage, but not sell. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

The example in this blog shows RTC Server and Common API.

Download

You can download the code here.

The download contains the (slightly enhanced) UpdateParentDuration Participant described in this post, the UpdateParentStateParticipant and the WorkFlowPathfinder from this post as Eclipse projects. In Eclipse use File>Import and select Existing Projects into Workspace. Select the downloaded archive file and do the import. This version of the WorkFlowPathFinder has the Plain Java Client Libraries dependency removed and is treated as real plug in, to provide its services to the other plug ins.

Please note, If you just get started with extending RTC, I would suggest to start reading this and the linked posts to get some guidance on how to set up your environment. Then read the article Extending Rational Team Concert 3.x and follow the Rational Team Concert 4.0 Extensions Workshop at least through the setup and Lab 1. This provides you with a development environment and with a lot of example code and information that is essential to get started. You should be able to use the following code in this environment and get your own extension working.

UpdateParentSateParticipant

The project com.ibm.js.team.workitem.extension.updateparent.participant contains the class UpdateParentState. This implements the Participant that

  1. Checks if there was a state change
  2. Checks if the new state is a closed state
  3. Checks if there is a parent
  4. If there is a parent, checks if all children are closed
  5. Resolves the parent If all children are closed

The UpdateParentStateParticipant can use different strategies to do the state change. The code below shows some of the available options.

if (!resolveBF(parentHandle, monitor)) {
// if (!resolveDF(parentHandle, monitor)) {
// if (!resolveWithResolveActionID(parentHandle, monitor)) {
// if (!gotoStateBF(parentHandle,"4",true, monitor)) {

The methods resolveBF() and resolveDF() use the Breadth First and the Depth First strategy against the resolve action. The method gotoStateBF() uses the Breadth First strategy but uses a target state instead. The method resolveWithResolveActionID() only tries to apply the action ID. You can play around with the different strategies and pick the one you like best.

The resolve Action needs to be defined in the RTC work Item workflow as shown below, otherwise the state change can’t be done, because no path can be found.

Define teh Resolve Action The WorkflowPathFinder class in the download provides two other ways to find a target state, instead of a target action. The methods are called findPathToState_DF() and findPathToState_BF().

UpdateParentDurationParticipant

The UpdateParentDurationParticipant is slightly enhanced compared to the state in RTC Update Parent Duration Estimation and Effort Participant post. It checks if there are changes to the duration and estimate values before doing anything else.

The code also contains several methods to analyze the work item links on the server as described in The RTC WorkItem Server Link API post.

Summary
The code is experimental. I have tested it in a Jetty based test server using the Scrum template. It is by no means production ready and can be enhanced for various scenarios. However, as always, I hope the code is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

Manipulating Work Item Workflow States


I was interested in modifying the workflow state of a work item using the Java API. This is interesting for various types of automation on the server as well as on the client. If you ever wondered how to drive a work item through its workflow, you will find this post of interest. If you are just starting with extending Rational Team Concert, start reading this and the linked posts to get some guidance on how to set up your environment.

License and how to get started with the RTC API’S

As always, our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license, which basically means you can use it for internal usage, but not sell. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the following code in this environment and get your own automation or extension working.

To keep it simple this example is, as many others in this blog, based on the Jazz Team Wiki entry on Programmatic Work Item Creation and the Plain Java Client Library Snippets. The example in this blog shows RTC Client and Common API.

Updates

*Update* The post A RTC WorkItem Command Line Version 2 contains downloadable code that performs most of the activities required for reading and modifying work items, their attributes, and all kinds of links. This includes reading and writing work item attribute of all kinds, including state changes. The interesting code can be found in the com.ibm.js.team.workitem.commandline.helper package in the class WorkItemHelper. All techniques described below are used there. You can familiarize yourself with the concepts in this post and then look into that project for how it is used.

*Update* I published the server API code in the post Resolve Parent If All Children Are Resolved Participant
*Update* changed the title to reflect we are talking about the workflow state. A work item has a history. Each historical version is also called state and is accessible through the API for example using ItemManager.fetchAllStateHandles(). This blog does talk about the work item state and not historical states of a work item.

Modifying the workflow state of a work item is interesting in various scenarios such as

  • Data migration tooling
  • Copying or moving work item data between project areas or repositories
  • Modifying the workflow state of a work item based on the state of related work items for example in Participants

Work Item Workflow

Initially I looked at the server API and wanted to figure out how to close a parent work item when all children were closed. I found some code in the SDK that actually does this and immediately recognized that there is a general challenge when trying to code this kind of automation. The challenge is that the server API does not allow to set the workflow state of a work item directly. It requires to provide a workflow action that changes the workflow state to the desired target state. If the work item is in a specific workflow state and there is no valid workflow action from that state into the desired target state, it would be necessary to find a path of several workflow actions from the current state to the target state.

I also looked at the Client Libraries and the client work item API. IWorkItem exposes a method setState2(Identifier value) which could be used to directly set the workflow state of a work item. However, this method is deprecated. The reason to deprecate it is most likely related to the fact that directly setting the workflow state of a work item does not respect the workflow. Essentially it would be possible to set a workflow state that is not even reachable from the current workflow state. Instead the method WorkItemWorkingCopy.setWorkflowAction(String action) is provided to allow to apply a workflow action to the work item.

This leaves you with the same challenge: find a path of workflow actions from the current workflow state of a work item to the desired state. If you ever lost in a computer game because your NPC followers staggered around an impossible trail and got stuck, you realize this is not a trivial challenge.

Resolving a Work Item

Lets look at a method that is based on code I discovered when looking into the SDK that tries to resolve a work item. That code shows all the issues described above.
It gets the IWorkflowInfo for the work item. It then checks if the work item is in the group of closed states.

If this is not the case, it tries to get the resolves workflow action defined in the workflow. If there is one defined it tries to find this action in the list of workflow actions that are defined for the current workflow state.

If there is no valid action the code does nothing. An alternative would be to stop the operation. If there is a valid action available the code saves the work item providing the workflow action to close the work item.

/**
 * Resolve using the resolve transition
 * 
 * @param workItem
 * @param monitor
 * @throws TeamRepositoryException
 */
public void resolve(IWorkItem workItem, IProgressMonitor monitor)
		throws TeamRepositoryException {
	wiServer = getService(IWorkItemServer.class);
	IWorkItem wi = (IWorkItem) wiServer.getAuditableCommon()
		.resolveAuditable(workItem, IWorkItem.FULL_PROFILE, monitor)
		.getWorkingCopy();

	IWorkflowInfo workflowInfo = wiServer.findWorkflowInfo(wi, monitor);
	String actionId = null;
	if (workflowInfo != null) {
		// check if already closed
		if (workflowInfo.getStateGroup(wi.getState2()) == IWorkflowInfo.CLOSED_STATES) {
			// nothing to do.
			return;
		}
		if (workflowInfo.getStateGroup(wi.getState2()) != IWorkflowInfo.CLOSED_STATES) {
		// Get the resolve action
			Identifier resolveActionId = workflowInfo.getResolveActionId();
			// If there is a resolve action 
			if (resolveActionId != null) {
				Identifier availableActions[] = workflowInfo.getActionIds(wi.getState2());
				// Find the resolve action in the actions available for this workflow state
				for (int i = 0; i < availableActions.length; i++) {
					if (resolveActionId == availableActions[i]) {
						actionId = resolveActionId.getStringIdentifier();
						break;
					}
				}
			}
		}
		if (actionId == null) {
			// can't use an action from the current state
			// Do nothing or throw some error
			return;
		}
		// This can be used to filter it out for preconditions and follow up
		// Actions
		Set additionalParams = new HashSet();
		additionalParams.add(IExtensionsDefinitions.UPDATE_PARENT_STATE_EXTENSION_ID);
		// We found a valid action
		IStatus status = wiServer.saveWorkItem3(wi, null, actionId, additionalParams);
		if (!status.isOK()) {
			// TODO: Throw an exception or do nothing
		}
	}
}

The solution above would obviously only work for workflows where there is a resolving workflow action for each given workflow state. This is not always the case, not even in the out of the box workflows.

Workflow Considerations

Lets look at the example workflow below.

This workflow is set up in a way that the Done state can only be reached by entering the In Progress state first. That means an algorithm as described above won’t work for example in the New state.

To be able to get to the Done state an algorithm would have to find a series of actions from the given state to the Done state.

Another issue with the workflow above is the Dead State Verified. There is no way out of this workflow state and once it is set, it is impossible to get anywhere else.

I would consider to avoid dead states when designing workflows. One example of dead states in the out of the box RTC workflows is the Invalid state in the Scrum Process Templates User Story workflow. There are reasons for the chosen design, for example documentation of a decision.

However, dead states cause issues and I would consider to always have some reopen or similar workflow action to get out of a dead state.

Workflow Path Finder

I finally decided to create some code to solve the dilemma of finding paths for general workflows. The Code can be downloaded from DropBox.

The code provides the following classes:

The class PathElement stores a state transition described by a state and an action. It can be uses to store a source state and an outgoing action as well as to store an incoming action and the target state.

The class Path is used to create and store paths using an ArrayList of PathElements.

The class VisitTracker uses a HashMap to store objects that have already been visited while searching the path. The class is necessary to avoid loops like the Still working action in the Example workflow above. It prevents from going to the same workflow state or using the same action to a target state more than once.

The Class WorkflowPathFinder finally implements the search strategies to find a path.

In general the class provides two strategies.

Depth First Recursive Descent

Depth first recursive descent basically picks one action and tries to find a path to the target using this action. It recursively calls itself as long as there are available options depth first. If it can’t find a path it returns and the next upper recursion that has option will try the alternatives. It tracks actions and target states to avoid crashing in the recursion. It only takes one action to a specific state once.

  • public Path findPathToState_DF(Identifier currentState, String targetStateID) tries to find a path from a current state to the target state.
  • public Path findPathToAction_DF(Identifier currentState, Identifier targetActionId) tries to find a path from a current state to the target action.

The depth first algorithms are very easy to implement and reliable, the only issue is that, dependent on the order of the actions, a recursive descent approach does not necessarily find the shortest path.

Breadth First Recursive Descent

Breadth first recursive descent basically tries to find paths to the target using any available action and can be set to return the shortest path found. This algorithm has to find more paths and is therefore slower than the depth first search, but it would likely not return a path that runs through unnecessary transitions.

  • public Path findPathToState_BF(Identifier currentState, String targetStateID, boolean shortestDistance) tries to find a path from a current state to the target state.
  • public Path findPathToAction_BF(Identifier currentState, Identifier targetActionId, boolean shortestDistance) tries to find a path from a current state to the target action.

All algorithms have the issue, that they don’t understand the context and don’t necessarily return a path that a human would have used. By providing the desired target action instead of the target state you can restrict the path likely to be found a bit.

Usage Examples

The classes can be used with the client API as well as the Server API.

This example code shows how to run it on the client:

IWorkItemClient workItemClient = (IWorkItemClient) teamRepository.getClientLibrary(IWorkItemClient.class);
IWorkflowInfo workflowInfo = workItemClient.findWorkflowInfo(workItem,	monitor);
if (workflowInfo != null) {
	// check if already closed
	if (workflowInfo.getStateGroup(workItem.getState2()) != IWorkflowInfo.CLOSED_STATES) {
		Identifier resolveActionId = workflowInfo.getResolveActionId();
		if (resolveActionId != null) {
			WorkflowPathFinder tManager = new WorkflowPathFinder(
				workflowInfo, monitor);
			Path path = tManager.findPathToAction_BF(workItem.getState2(), resolveActionId, true);
			WorkItemSetWorkflowActionModification workflowOperation = new WorkItemSetWorkflowActionModification(null);
			ArrayList transitions = path.getElements();
			for (Iterator iterator = transitions.iterator(); iterator.hasNext();) {
				PathElement pathElement = (PathElement) iterator.next();
				workflowOperation.setfWorkFlowAtion(pathElement.getActionID());
				workflowOperation.run(workItem, monitor);
				System.out.println("Workflow Action for work item " + workItem.getId() + " : "+pathElement.getActionID());
			}
		}
	}
}

The WorkItemOperation used in the example looks as follows:

private static class WorkItemSetWorkflowActionModification extends WorkItemOperation {

	private String fWorkFlowAtion;

	public WorkItemSetWorkflowActionModification(String workFlowAtion) {
		super("Modifying Work Item State", IWorkItem.FULL_PROFILE);
		fWorkFlowAtion = workFlowAtion;
	}

	@Override
	protected void execute(WorkItemWorkingCopy workingCopy,
			IProgressMonitor monitor) throws TeamRepositoryException {
		workingCopy.setWorkflowAction(fWorkFlowAtion);
	}

	public void setfWorkFlowAtion(String fWorkFlowAtion) {
		this.fWorkFlowAtion = fWorkFlowAtion;
	}
}

You can download the Plain Java Client Library based tool here.

This example is based on the wiki entry on Programmatic Work Item Creation. The API used in the following example is client API.

On the Server the difference is to get wiServer = getService(IWorkItemServer.class); instead of IWorkItemClient. The following code does the save operation.

/**
 * Save a state transition
 * 
 * @param workItem
 * @param pathElement
 * @param workflowInfo
 * @param monitor
 * @throws TeamRepositoryException
 */
public void saveWorkItemStateTransition(IWorkItem workItem,
		PathElement pathElement, IWorkflowInfo workflowInfo,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkItem saveWi = (IWorkItem) wiServer.getAuditableCommon()
		.resolveAuditable(workItem, IWorkItem.FULL_PROFILE, monitor)
		.getWorkingCopy();

	// This can be used to filter it out for preconditions
	// and followup Actions
	Set additionalParams = new HashSet();
	additionalParams.add(IExtensionsDefinitions.UPDATE_PARENT_STATE_EXTENSION_ID);
	IStatus status = wiServer.saveWorkItem3(saveWi, null,
	pathElement.getActionID(), additionalParams);
	if (!status.isOK()) {
		// Do something
	}
}

As always, to keep it simple, there is few error handling. You might want to enhance this for later usage. I hope the code helps someone out there to save some time and get the work done.