Only Owner Can Close WorkItem Advisor

I always wanted to do a Server Work Item Save advisor, so here is a simple example. This advisor will prevent closing work items for any user that is not the owner of the work item. Since the code turned out to be very simple, I will try to emphasize some other useful details about creating it. Please note, that this code can be easily changed to do more complex things e.g. only work for certain work item types or workflows, look at roles of the user that does the save to act on that and much more. There are various code examples in this blog that could be used to extend the code below to achieve those goals. The API code in this post is Server and Common API.

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!

You can download the final code here.

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 example in this blog post shows RTC Server and Common API.

Creating the Plug-in(s)

The first step is, as usual, to create a plug-in. This is easily enough using the New Project wizard and choosing the Plug-in Project. All you need to provide is a name for the project and some settings.

Using a name like “OnlyOwnerCanSaveAdvisor” looks great and a lot of people do this. Its a trap!

When choosing the name of the project I have learned over the years, it is a good idea to have a certain naming convention. The most important point here is to be able to easily find your plug-ins and features again, once they are deployed. If you deploy into the Eclipse Client, your extension might end up between 1500 other extensions. If you don’t know an easy way to locate your code, you have a problem. It can take a lot of time to find it. If you don’t know where stuff is going to end on disk and if something is deployed or not, being able to search for a file name helps a lot.

Tip: Name your extensions in a way that allows you to easily search and identify them. Create a namespace pattern to support this.

I use a Java namespace structure to name my projects. I always use com.ibm.js as prefix in the name. So I can easily search for files named com.ibm.js. The rest of the name usually has something to do with the purpose. In this case I chose com.ibm.js.team.workitem.extension.advisor.statechange.service as name for the plug-in project. I chose service, because this plug-in runs on the server.

If I have to develop more complex extensions, I often end up having several plug-in projects that belong together. To be able to easily locate them in my workspace I ended up to have a common name part and a special suffix for each of the projects. The common name here would be com.ibm.js.team.workitem.extension.advisor.statechange and suffixes would be service, component, feature, updatesite and potentially others. This makes it so much easier to find around in the Eclipse UI.

The other choices here are trivial. We don’t need an activator class for this. Keep the other defaults. There is no template to choose from, so finish the wizard.

Once the plug-in project is created, give it a useful name and leave the other information as it is. Especially leave the .qualifier suffix in the version. This allows Eclipse to create a unique version extension. Your plug-in overview tab should look like below.

Plug-in Overview

Tip: Keep the Version number structure with the .qualifier suffix as provided by the default. This allows Eclipse to create a unique version extension.

The next step is typically adding the dependencies. I usually start with some I have from other extensions. org.eclipse.core.runtime is almost always needed.

Tip: Start with dependencies used in other extensions. It is easy to remove dependencies later.

You can add dependencies easily by following the namespace pattern used in RTC. The pattern starts with com.ibm.team then there is a domain such as workitem and the suffix is typically service, common, client where

  • service is API that is only available on the server
  • common is API that is available to the server and (Java/Eclipse) clients
  • client is API only available to (Java/Eclipse) clients

The domains are

  • process for API related to the process specification for process areas (project areas/team areas)
  • repository for API related to accessing data in the repository
  • workitem for work item related API
  • filesystem for the SCM API
  • interop for API to develop work item synchronizers

amongst others.

Tip:Use the namespace pattern provided by the RTC SDK to find the API plug-ins you have dependencies to.

To add dependencies, use the add button and the namespace pattern to find interesting plug-ins.

Search and add dependencies

Please be aware that there is a domain reports that continues with the usual pattern, including the domain names in the suffix as subdomains. Avoid to accidentally pick one of those if you don’t work in the reports API. If you want to use API and the classes can not be found, although the dependency was meant to be added, check if you accidentally picked the reports domain and fix the dependency. This happened to me many, many times.

Tip: The reporting API can sneak in because its namespace space includes the other domains as subdomain. Make sure to pick the right plug-ins.

It is a good idea to start with adding the usual suspects as dependencies. If you need additional API later, you can always add it on the fly and save the plugin.xml to be able to access the API.

This is a typical first iteration of dependencies:

Typical dependencies When adding the dependency, there is compatibility information added. This information would require at least a certain version of the dependency to be available. It is possible to remove this information to make the extension more compatible e.g. to earlier versions of RTC. In the dependencies above, I removed the minimal version. Since this extension was developed with RTC 4.0.5, but would work with other, earlier versions, as well this would now be deployable e.g. in RTC 4.0 or even 3.0.

Tip: Manage the required versions in the dependencies, if you want to be able to deploy in RTC versions with lower version numbers than the version you use to develop your plug-in.

The next step is to add the extension you want to hook up to. In our case we want an operationAdvisor. It can be found the same way we found the dependencies. If you can’t find your extension point, please uncheck the option Show only extension points from the required plug-ins, to make sure you can see all the extension points, even if you have not yet added the required dependency.

The full ID of the extension point is com.ibm.team.process.service.operationAdvisors  from the list of Extension Points and Operation ID’s.

Tip: Make sure to pick all extension points and use the namespace patterns already described to find extension points.

Tip: Look in the list of Extension Points and Operation ID’s to learn more about what is available.

Find and add extension pointsSelect the extension point and add it.

Once the extension point is added, provide the required information. You need to provide data for the operation advisor.

Specify basic informationThere are several mandatory fields here. I stick to my namespace pattern and provide the following information:

  • id – com.ibm.js.team.workitem.extension.advisor.statechange.RestrictClosingToOwner
  • class – com.ibm.js.team.workitem.extension.advisor.statechange.service.RestrictClosingToOwner
  • name – Restrict Closing Work Item to Owner
  • operationId – we want to react on work item save so choose com.ibm.team.workitem.operation.workItemSave from the list of available Extension Points and Operation ID’s.

Clicking in the field name class* in front of the class definition allows to create a class. It also shows you what the class is required to provide to be able to conform to the specification of the extension point. When you create the class make sure to use com.ibm.team.repository.service.AbstractService as superclass and choose the com.ibm.team.process.common.advice.runtime.IOperationAdvisor interface as implemented. The class definition should look like:

public class RestrictClosingToOwner extends AbstractService implements IOperationAdvisor {

The code of the class is going to be presented later below. For now, just quickfix and let it add the methods to implement. In order for the code to run later, you need to specify an extension service to provide the component ID this extension belongs to and specify the implementation class for this service. The interface is required in the operationAdvisor specification. The AbstractService comes in in the next step.

Tip: Make sure to create the extending class with the right interface by looking at the editor.

Click on the operationAdvisor node and add the extension service. You can also add a description.

Specify Extension Service

The extension service needs to be specified.  Stay with the namespace pattern and provide a component ID. As implementationClass, select the one that was just created.

  • componentId – com.ibm.js.team.workitem.extension.advisor.statechange.common.component
  • implementationClass – com.ibm.js.team.workitem.extension.advisor.statechange.service.RestrictClosingToOwner

Tip: Server extensions usually extend AbstractService which provides capabilities needed later e.g. to get services.

This class needs to be based on AbstractService, which was already dealt with.

The Jazz compnent extension with the ID that was just chosen still needs to be defined. There is a special extension point for this. The extension point to specify the Jazz component is com.ibm.team.repository.common.components.

Tip: Create the Extension providing a jazz component in a different plug-in. This allows to use the component later if server as well as client extensions are needed, e.g. to provide an aspect editor to configure the extension in the UI.

It would be possible to define the component in the current plug-in. However, if the component is needed in server as well as client extensions, it is necessary to bundle it with both. In this case it is better to create a special plug-in for the component. Provide the same ID that was used in the definition of the extension service and provide a name for the component.

Specify a component extensionTip: To get more information about the extension point look at the description, the schema, declaration and references. You can find all kinds of information easily, including plug-ins that extend this point and the classes that implement the extension.

There are several means that allow you to find out more about the extension point and implementations. Just be curious and look at it. The image beow shows where to access this information.

Extension Point DetailsAnother place is where you add new extensions to the point.

Add more advisorsIt will be necessary later, to declare the services that are used by the extension. Unfortunately the schema does not contain the node to do so. This is a manual that needs to be done in the plugin.xml.

Implement the Extension

The class that is to be called by the extension point has already been created. However, the implementation is still missing and needs to be provided. It is easy to open the class from the plug-in editor.

The entry point into the class called by the extension point is the run() method. The implementation code is provided below. As all advisors (preconditions)  and participants (follow-up actions) the run() method gets the information about the work item and other information about the context it is running.

This information is checked first and the work item is extracted. The advisor code contains a section that is commented out right now.This code could later be used to limit the restriction only to specific work item types.

The advisor shall not limit saving the work item in closed stats, it should only preventing to change the state to a state in the closed group. So the next check is looking at the workflow action to determine if there is a state change. If not, the advisor does not prevent the operation.  So a user could still update attributes, while it is closed,however, only the owner can change states to states in the closed group.

The last section checks if the new state of the work item is in the closed group. If not, nothing needs to be done.

If there is a workflow action, the next step is to check the state the work item will enter. If the new state is not in the closed group, nothing needs to be done.

If the new state is in the close groups, the final check is comparing the owner and the current user. If the ID’s match, nothing needs to be done. Note, this is also the case if the work item is already closed and someone wants to move it to another closed state. Only the owner will be able to do this, provided the advisor is configured for all roles.

Finally, this is a state change into a closed state and the current user is not the owner. The advisor provides a problem info and returns it. This will block the save. Please note, this is also true if the owner is unassigned.

Here is the code:

/*******************************************************************************
 * Licensed Materials - Property of IBM (c) Copyright IBM Corporation 2005-20014.
 * All Rights Reserved.
 * 
 * Note to U.S. Government Users Restricted Rights: Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 ******************************************************************************/
package com.ibm.js.team.workitem.extension.advisor.statechange.service;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.process.common.IProcessConfigurationElement;
import com.ibm.team.process.common.advice.AdvisableOperation;
import com.ibm.team.process.common.advice.IAdvisorInfo;
import com.ibm.team.process.common.advice.IAdvisorInfoCollector;
import com.ibm.team.process.common.advice.runtime.IOperationAdvisor;
import com.ibm.team.repository.common.IAuditable;
import com.ibm.team.repository.common.IContributorHandle;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.service.AbstractService;
import com.ibm.team.workitem.common.ISaveParameter;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.workflow.IWorkflowInfo;
import com.ibm.team.workitem.service.IWorkItemServer;

public class RestrictClosingToOwner extends AbstractService implements IOperationAdvisor {

	@Override
	public void run(AdvisableOperation operation,
			IProcessConfigurationElement advisorConfiguration,
			IAdvisorInfoCollector collector, IProgressMonitor monitor)
			throws TeamRepositoryException  {
		Object data = operation.getOperationData();
		if (data instanceof ISaveParameter) {
			IAuditable auditable = ((ISaveParameter) data).getNewState();
			if (auditable instanceof IWorkItem) {
				IWorkItem workItem = (IWorkItem) auditable;

//				// If this needs to be limited to a special type
//				if (workItem.getWorkItemType() != "Enter Type ID Here")
//					return;
				
				// We want to allow saving the work item, if there is no state change happening.				
				String action = ((ISaveParameter) data).getWorkflowAction();
				if(action==null)
					return;
				
				// Get the workflow info and check if the new state is in the closed group.
				IWorkItemServer iWorkItemServer = getService(IWorkItemServer.class);
				IWorkflowInfo workflowInfo = iWorkItemServer.findWorkflowInfo(workItem,
						monitor);
				if (!(workflowInfo.getStateGroup(workItem.getState2()) == IWorkflowInfo.CLOSED_STATES)) {
					return; // nothing to check if the new state is not closed.
				}

				// work item is going to a state in the closed group.
				// Check if the current user is owner of the work item.
				IContributorHandle loggedIn = this
						.getAuthenticatedContributor();
				IContributorHandle owner = workItem.getOwner();
				if ((owner != null && owner.getItemId().equals(
						loggedIn.getItemId())))
					return;
				
				IAdvisorInfo info = collector.createProblemInfo(
						"The work item can only closed by its owner!",
						"The work item can only closed by its owner! If the owner is unassigned and it can also not be closed.",
						"error");
				collector.addInfo(info);
			}
		}
	}
}

Before we can do the debugging the last thing we need to do is to require the service IWorkItemServer we use to be available to the server in the plugin.xml. The plugin.xml needs to be changed to reflect that.

Add the prerequisite section with the service(s) required as presented below.

Prerequisite for the required serviceTip: Since the extension point schema does not have the prerequisite added, this is something you simply have to know how to do it.

Please also see Creating Custom Link Types for Rational Team Concert, especially the sections Prepare to Deploy and the following 2 sections describing the additional project to make deployment a little easier.

Download

You can download the final code here.

Summary

The advisor presented above will prevent anyone, except the owner, to use any action that changes the state of a work item to a closed state. Users, other than the owner can still update work item attributes, but if it is configured for a user, state changes to a closed state are only possible for the owner of a work item.

As always, I hope the code above helps someone out there with extending RTC.

I will blog about the next steps, like debugging and deploying the advisor in one of the next posts.

Using Reference Presentations to Create Links and Show Linked Work Items in RTC

I recently complained to our development that creating and displaying links between work items is too complex. I wanted a special presentation for that purpose. To my excitement I was instructed, that we have such a feature. Joy! This post explains how you use it.

I have done so much customization in RTC, that I was convinced I knew all of it already. Hubris! My bad. So there are things I haven’t done in RTC. I assume others also have not spotted this and I think it is worth blogging about.

Add a Presentation of Type References

To be able to show specific references (aka links) from the current work item to other items you basically have to add a new presentation to your editor presentation. On the Editor presentation editor select the section where you want to show the presentation and click Add Presentation….

The Presentation you want is a Non-Attribute-based Presentation. Choose this option and select References from the available set of choices.

In the Link Type selection drop down, select the reference (aka link type) you want to see and maintain here.

The image below shows the steps.

Add Reference Presentation

Use a Presentation of Type References

Once you have done that, you have a new presentation that will show only the references/links that qualify. It will also allow you to create new references/links from that dialog. The image below shows what you can do:

Use Reference PresentationSummary

This kind of presentation allows you to view and modify relationships in a more organized way on the work item editor and can help you understanding the relationships better, especially if the relationship represents important information in your object model.

As always I hope this information is useful to someone out there and helps using RTC more effective.

Using RTC Work Item Attributes to Display RTC Components and other Item Data

The RTC Work Item shall guide the user to the component, where the fix needs to be done. Is it possible to implement this requirement in RTC?

This was basically a requirement that I was asked for how to implement this with RTC. I also remember it being asked in the Jazz.net forum some time ago. I remember this, because my immediate thought was, this is only going to work using customization or extending RTC. I was quite surprised when a colleague monitoring the forum came up with a simple solution. I tried it out and it is really simple. However, it is quite hard to spot how this can be done. Therefore this post summarizes how you can easily do this yourself.

If you are just starting with customizing RTC Work Items, have a look at Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management 2012 especially Lab 3-5.

This was done with RTC 4.0.5 but it should work with earlier versions of RTC 4.x.

Create a New Attribute of Type Item

First you need to create a new attribute that will contain the information you want. There are a lot of attribute types you can choose from. There are special types that allow to select RTC Data such as users, team areas and the like. All these are for data related to the work item model. There is a special type for RTC data that is not work Item data from the RTC process model. It is easy to overlook. The type can be used as single selection or as a list. When creating the Attribute use

  • Item
  • ItemList

dependent upon your needs to select one or multiple elements.

Attribute Type Item
Attribute Type Item

If you select a ItemList, the work item UI supports selecting multiple items.

Configure the Editor Presentation

Once you have added the attribute, you need to configure the editor presentations to show the attribute as usual. You select

  • The Attribute
  • The Kind of the presentation

The available presentation kinds depend on the attribute type you selected. For the type ItemList, select Item List as kind.

Configure Editor PresentationFinally you can select which kinds of items the user can select. There are several choices that match types where RTC also provides a specialized attribute type, such as WorkItem or TeamArea.

The type Item or ItemList is more flexible and allows you to select any of these kinds and additional types that are not available in special attribute types such as SCM Component.

In the ItemList, you could have various objects that are interesting in this context. For example if you want to express what is affected by a work item.

You can select

  • Let the user choose
  • A specific item type

for the item type type be selectable in the presentation.

Save your changes, to make them available in the process.

Create a Work Item

Now you can create a new work item of the type you modified and select the new attribute. The presentation allows you to choose or add an element. The Select Component Dialog provides you with search and filter mechanisms to find what you need.

Select a ComponentSummary

You can use RTC work items to express relationships of a work item to other objects using special attribute types.

You can then use these new attributes and their values to drive new automation, e.g. check if a change set is actually for the component the related work item claims it to be. I shall see, if I can publish an article about such an advisor soon.

Deploying Templates and Creating Projects Using the Plain Java Clients Library

How can the Rational Team Concert Extensions workshop be changed to be easier to use with newer versions of Rational Team Concert?

This is a question that I was contemplating about since I picked up the maintenance of the workshop. The issue with the workshop is the requirement to import database content using the repotools.

Why is that step required? The main reason is, that the workshop provides a project with SCM data that you use to perform the various labs. It also provides the Launch files needed for launching the Jetty server and the Eclipse clients.  This avoids a lot of work in typing source code and importing projects.

However, it causes a lot of issues.

  •  This used to work with various versions of RTC in the past but since 4.0 it seems to be tied to the version that was used to export the databases. So in order to run the workshop with a newer versions, it is neccessary to set it up with the RTC version it was created with and then upgrade the repository to the desired version.
  • By importing the repository, the public URI is set.
  • When setting the repository up, it is necessary to decide which applications to set up and register. If using a different install method and not installing all applications the JTS will show broken application registrations.
  • It is necessary to run a custom setup, due to the usage of the user ID ADMIN

There are some other issues with this approach that add unnecessary complexity to a workshop that is already challenging enough (for example the usage of user ADMIN and how to provide that user with a license). These can hopefully be addressed somehow.

Solution Approach

The only feasible approach that I could come up with was to provide some automation to perform the steps I have to do to set up the repository to export it for a new workshop version. The automation would run against a RTC Server that has a finalized set up of any kind (Express or Custom) with or without existing projects, create and provide the required data.

The minimal set of steps required would be to:

  1. Deploy the required Template for a Project Area, if it is not already deployed.
  2. Create the required Project Area for the Extension Workshop.
  3. Set the required User Membership and Roles for the Project Area.
  4. Set up the required Stream, Components and the Repository workspace
  5. Share the SCM Data to the Components, deliver it to the Stream and create the required Baselines. This needs to be done multiple times for the workshop code adding new files and modifying files that already exist.
  6. Set the repository workspace components to the baselines needed to start the workshop.

Recently I found some time to explore how this can be achieved. I made considerable process and I think there is a solution to this challenge that I might be able to publish. While I will try to find time to finalize the work and get approval to publish the results, I wanted to publish some of the lessons learned from trying to create the solution.

The solution is described in:

  1. This post
  2. Managing Workspaces, Streams and Components using the Plain Java Client Libraries
  3. Delivering Change Sets and Baselines to a Stream Using the Plain Java Client Libraries
  4. Extracting an Archive Into Jazz SCM Using the Plain Java Client Libraries

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.

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.

Preparing the Project

This post will show how the required project can be created. Some of the code used here is from Snippet 3 of the plain Java Client Libraries and from code already published in this blog. The code that follows is wrapped into the code I usually use for my Plain Java Client Library examples, derived from the Programmatic Work Item Creation Wiki entry and the Plain Java Client Libraries Snippets.

The main() method starts the Team Platform, creates the new class object and passes the arguments on to perform the work. I have discussed what this code does so often in other posts, please refer to them if you have questions.

public static void main(String[] args) throws Exception {
	boolean result;
	System.out.println("Starting Team Platform");
	if (!TeamPlatform.isStarted())
		TeamPlatform.startup();
	try {
		System.out.println("Team Platform started");
		ServerSetup setupExtDevServer = new ServerSetup();
		result = setupExtDevServer.run(args);
	} catch (TeamRepositoryException x) {
		x.printStackTrace();
		result = false;
	} finally {
		TeamPlatform.shutdown();
	}
	if (!result)
		System.exit(1);
}

In the run() method the work to do is coordinated and performed. The method checks if the required parameters are available. Then it logs into the repository with the given user name and password. This code is presented at the end of this post, as it is just refactored out and is simply the code I am always using in this blog. The user has to have the JazzAdmin repository role.

In the next step the automation deploys the required process template. It then tries to find if the project area exists and creates a new one if not. It adds the required user with role to the project area. The following steps will be explained in later posts.

private boolean run(String[] args) throws Exception {
	if (args.length != 3) {
		System.out.println("Usage: ServerSetup repositoryURI userId password");
		return false;
	}

	String repositoryURI = args[0];
	String userId = args[1];
	String password = args[2];

	IProgressMonitor monitor = new NullProgressMonitor();
	ITeamRepository teamRepository = logIntoTeamRepository(repositoryURI,
				userId, password, monitor);

	IProcessDefinition scrumProcess = findOrDeployTemplate(teamRepository,
				"scrum2.process.ibm.com", monitor);
	IProjectArea area = findOrCreateProjectArea(teamRepository,
				"RTC Extension Workshop", scrumProcess, monitor);
	addUsersAndRoles(teamRepository, area, monitor);
.
.
.
.
.

In the deployTempate() method the automation tries to find the required process template and tries to deploy it, if it was not already deployed.

private IProcessDefinition findOrDeployTemplate(ITeamRepository teamRepository,
		String processID, IProgressMonitor monitor)
		throws TeamRepositoryException {
	System.out.println("Trying to find or deploy template: " + processID);
	IProcessItemService service = (IProcessItemService) teamRepository
				.getClientLibrary(IProcessItemService.class);
	IProcessDefinition[] definitions = null;
	// Find the process definition
	IProcessDefinition processDefinition = processItemService
			.findProcessDefinition(processID, null, monitor);
	if (processDefinition == null) {
		// Create the definition if it does not exist.
		definitions = processItemService
				.deployPredefinedProcessDefinitions(
						new String[] { processID }, monitor);
		System.out.println("Template deployed...");
		if (definitions.length != 0) {
			processDefinition = definitions[0];
		} else {
			throw new TeamRepositoryException("Process template "
					+ processID + " does not exist.");
		}
	}
	System.out.println("Template found...");
	return processDefinition;
}

The main task is now to find the project area if it exists and to create a new project area in case it is not available.

private IProjectArea findOrCreateProjectArea(
		ITeamRepository teamRepository, String projectName,
		IProcessDefinition processDefinition, IProgressMonitor monitor)
		throws TeamRepositoryException {

	// Create Project Area based on Scrum
	System.out.println("Trying to find or create Project Area: " + projectName);

	IProcessItemService service = (IProcessItemService) teamRepository
				.getClientLibrary(IProcessItemService.class);
	IProjectArea area = null;
	List areas = service.findAllProjectAreas(
			IProcessClientService.ALL_PROPERTIES, monitor);
	for (Object anArea : areas) {
		if (anArea instanceof IProjectArea) {
			IProjectArea foundArea = (IProjectArea) anArea;
			if (foundArea.getName().equals(projectName)) {
				area = foundArea;
				System.out.println("Project Area found: " + projectName);
				return area;
			}
		}
	}

	// Could not find the the project area, create one
	if (area == null) {
		System.out.println("Trying to create Project Area: " + projectName);
		area = service.createProjectArea();
		area.setName(projectName);
		area.setProcessDefinition(processDefinition);
		IDescription description = area.getDescription();
		description.setSummary("Project to be used running the RTC Extension Workshop");
		area = (IProjectArea) service.save(area, monitor);
		area = (IProjectArea) service.getMutableCopy(area);
		service.initialize(area, monitor);
		System.out.println("Created and initialized Project Area: " + projectName);
	}
	return area;
}

Now that the project area is available make sure to add the required user with role ID “ScrumMaster”. The method addUsersAndRoles() does this.It first tries to find the role. Once found it adds the currently logged in user, the user that runs this automation, to the administrators section of the project and to the members section with the role obtained.

private IProjectArea addUsersAndRoles(ITeamRepository teamRepository,
		IProjectArea area, IProgressMonitor monitor)
		throws TeamRepositoryException {

	IProcessItemService service = (IProcessItemService) teamRepository
				.getClientLibrary(IProcessItemService.class);
	area = (IProjectArea) service.getMutableCopy(area);
	System.out.println("Trying to add members with roles: "
			+ "ScrumMaster" + " to Project Area" + area.getName());

	// IProjectArea wArea = (IProjectArea)service.getMutableCopy(area);
	IContributor member = teamRepository.loggedInContributor();

	IRole role = getRole(area, "ScrumMaster", monitor);
	area.addAdministrator(member);
	area.addMember(member);
	area.addRoleAssignments(member, new IRole[] { role });
	IProcessItem[] items = service.save(new IProcessItem[] { area },
				monitor);
	System.out.println("Users with Roles added to " + area.getName());
	return area;
}

To get the role the getRole() method tries to discover the roles in the process that have a matching ID and returns it, if it was found. Please note that IRole objects have no access to the role name. they have just the ID. If you need the role name you need to get the IRole2 interface from the IRole object. The code that does this is commented out below.

	private IRole getRole(IProcessArea area, String roleID,
			IProgressMonitor monitor) throws TeamRepositoryException {
		ITeamRepository repo = (ITeamRepository) area.getOrigin();
		IProcessItemService service = (IProcessItemService) repo
				.getClientLibrary(IProcessItemService.class);
		IClientProcess clientProcess = service.getClientProcess(area, monitor);
		IRole[] availableRoles = clientProcess.getRoles(area, monitor);
		for (int i = 0; i < availableRoles.length; i++) {
			IRole role = availableRoles[i];
			// IRole2 role2 = (IRole2)role;
			// role2.getRoleName();
			if (role.getId().equalsIgnoreCase(roleID))
				return role;
		}
		throw new IllegalArgumentException("Couldn't find roles");
	}

The code left out is for logging into the repository, for completeness it is presented below.

private ITeamRepository logIntoTeamRepository(String repositoryURI,
		String userId, String password, IProgressMonitor monitor)
		throws TeamRepositoryException {
	System.out.println("Trying to log into repository: " + repositoryURI);
	ITeamRepository teamRepository = TeamPlatform
		.getTeamRepositoryService().getTeamRepository(repositoryURI);
	teamRepository.registerLoginHandler(new LoginHandler(userId, password));
	teamRepository.login(monitor);
	System.out.println("Login succeeded.");
	return teamRepository;
}

/**
 * Login Handler implementation
 *
 */
private static class LoginHandler implements ILoginHandler, ILoginInfo {

	private String fUserId;
	private String fPassword;

	private LoginHandler(String userId, String password) {
		fUserId = userId;
		fPassword = password;
	}

	public String getUserId() {
		return fUserId;
	}

	public String getPassword() {
		return fPassword;
	}

	public ILoginInfo challenge(ITeamRepository repository) {
		return this;
	}
}

Summary

The code in this post basically shows how to prepare a project for later usage. It does the steps requires such as deploying a process template in case it is not yet available. It finds or creates the project area and it makes sure to add the user required as administrator and member with the required role.

Now that the project is prepared, the next steps will be all the required preparation in the Projects SCM to be able to upload the code. This is going to be presented in a later post.

As always, I hope that sharing this code helps users out there, with a need to use the API’s to do their work more efficient.

Publish and Host XML Data Using Tomcat – The Easy Way

Why I missed this easy solution beats me, however, as my team mate Freddy points out in his (e-mail) answer to my comment on his blog about HTTP Filtered Value Sets it is even easier to publish XML data – or any other data – on Tomcat than by creating a web application as described in my last post.

* Update *: See Publishing XML and Other Data With the WAS Liberty Profile for how to do this with the WAS Liberty Profile.

Solution

Basically what you have to do is to publish it in the folder $CATALINA_HOME/webapps/ROOT/. In the context of a Jazz Team Server that is the folder /server/tomcat/webapps/ROOT/. If you have a file test.xml you want to publish on your Tomcat server you can copy it directly into that folder or into a sub folder structure.

Example

To publish the given file makers.xml from the last post as  https://localhost:9443/PEWEnactmentData/makers.xml, assuming a standard Jazz Tomcat deployment.

  • Create a folder PEWEnactmentData in the folder <InstallDir>/server/tomcat/webapps/ROOT/
  • Copy the file makers.xml into the newly created folder

The File is now accessible as https://localhost:9443/PEWEnactmentData/makers.xml as intended. Replace localhost with your fully qualified hostname in your deployment.

Note: Please make sure that your Folder Name does not conflict with the context root of any web application you have deployed on Tomcat.

Summary

Given this approach it is as easy to use Tomcat to maintain your data for HTTP Filtered value providers as using Apache.

This is also a good example how hard it can be to find information in the Internet. I tried to search for publishing XML on Tomcat with what I learned from Freddy, and I did not find an answer to the question really. All the discussions are around deploying applications.

Publish XML Data Using Tomcat – Hotfix for The Process Enactment Workshop

Can you publish XML files for the HTTP Filtered Value Set on Tomcat? This was a question I asked myself when working on Lab 4 of the Process Enactment Workshop. My first answer, and also response to this forum question was: No.

Dead Wrong. It is of course possible to do so. I just did not know how to do that. This post shows how you can indeed do this.

You can download a WAR File that contains the data for the Process Enactment Workshop here.

* Update *: See this post for an even easier way to publish your data on Tomcat.

* Update *: See Publishing XML and Other Data With the WAS Liberty Profile for how to do this with the WAS Liberty Profile.

Background

I needed to provide an example for the HTTP Filtered Value Set in the workshop. Since this Value Set requires the XML published by a HTTP server, I basically saw two options: download and install Apache as described here, and place the XML document into the htdocs sub-directory for publishing. I could also use an existing example like http://cars.flashmx.us/makes. I did not want to add the effort and time to install Apache, even if it is quite straight forward. I thought about publishing on Tomcat but digging the internet did not show an easy way to do it. So I chose to use http://cars.flashmx.us/makes. Some days ago the inevitable happened. That web site does not provide the information I need any longer.

Available Options

Now I was again down to the two choices:

  1. Install Apache
  2. Find a solution with Tomcat.

I still did not like the Apache solution, at least for the workshop, as it adds a lot of waiting time due to the install, although I will describe it shortly below. So I decided to take a deeper look at Tomcat. I found a relatively easy solution to make it possible with Tomcat too.

Apache Solution

Download and install Apache, e.g. into the folder C:/Apache and provide a reasonable host name during the install. After the install is done, by default the sub-directory C:/Apache/htdocs is used as root for publishing. If you run C:/Apache/bin/httpd and place a document, for example makers.xml into the folder it is accessible as http://:8080/makers.xml and you are done.

Install Apache HTTP Server
Install Apache HTTP Server

You can easily maintain the documents there, update them and as long as the server is accessible use them in an HTTP Filtered Value Set.

Tomcat Solution

With Tomcat it is not quite that easy to create a solution, but if you have Tomcat already installed, once you have a solution, it is just trivial to deploy and use.

1. Install

Install The Web, XML, and Java EE Development Tools as described in the Process Enactment Workshop Lab 5, at the beginning.

Select Help>Install New Software and add an update site. I used the Helios Update Site for RTC 4.0. up to 4.0.3 vanilla zip install. Make sure to pick the right version for your Eclipse version. Select the update site. Select and install the whole package Web, XML, and Java EE Development Tools, not just JavaScript. Restart Eclipse.

I tried to use the Tomcat that comes with RTC for testing, but had issues, so I would suggest to Download a vanilla Tomcat for testing. I used the ZIP version and unpacked it into the folder C:/Apache Tomcat.

Tomcat Installed
Tomcat Installed

2. Create Dynamic Web Project

In your Eclipse RTC Client open the J2EE Perspective. Use File>New>Dynamic Web Project to create a new project.

Create Dynamic Web Project
Create Dynamic Web Project

In the wizard enter a project name. Check the default settings and create a new Target Runtime based on Tomcat v7.0 or whatever version you are using.  As Tomcat Installation Directory provide C:/Apache Tomcat or wherever you installed to. Back in the wizard should show something similar to this image.

Dynamic Web Project Wizard Step 1
Dynamic Web Project Wizard Step 1

Click Next until you reach the wizard page Web Module.

If you want to fix your context root, you can do that now. The context root is the name of the folder in which the data will later be published to the web. It is case-sensitive and you need to remember the name later. In my example my data will be published at HTTPS://:9443/PEWEnactmentData/ once I deploy on my RTC Tomcat. Make sure to choose WebContent or webcontent in the Content Directory. Check Generate web.xml deployment descriptor. See the image below as example.

Dynamic Web Project Wizard Web Module Page
Dynamic Web Project Wizard Web Module Page

Press Finish.

You have now successfully created a Dynamic Web Project, that will later be used to publish the content.  Browse the project. You are only interested in the folder WebContent.

New Web Project
New Web Project

3. Provide The Data

You now need to provide your XML data. Put the file you want to publish into the folder WebContent. To make it easier for users to locate the files I added a file index.html too. This file just has some text that points to the XML files that are hosted. The link is relative as shown in the screenshot below.

Project With Data Ready To Publish
Project With Data Ready To Publish

4. Publish And Test

Now that you are ready to publish and test, select the Project node and use the context menu to run it on your test server.

Run On Server Step 1
Run On Server

In the Wizard check that he correct runtime environment is selected and that the configuration works. Press Next and check your project is Configured to be deployed. Press Finish.

Your Server starts and after a few seconds the index page should come up like below.

Your Web Site
Your Web Site

You can click on the link in your index page to bring up your xml code. If you don’t have an Index.html, you can directly open your XML page. For example using the URL http://localhost:8080/PEWEnactmentData/makers.xml.

Now that everything works, you are ready to export the project and deploy it on your RTC Tomcat server.

5. Create A War File and Deploy

Right Click on your project again. Select Export>WAR file Enter a suitable destination folder. You can directly export into the /server/tomcat/webapps folder if you like. I chose a temporary folder. You can play around with the other options too e.g. if you want to deploy on WAS. Press Finish to run the export.

Export WAR File
Export WAR File

If you have exported to /server/tomcat/webapps you have already also deployed. If not, copy and paste your war file into /server/tomcat/webapps. If the server is running the deployment process starts right away. If not, start the server.

Once the server is up, you should be able to open your web resources using your server URL and the context root you chose. In my example https://localhost:9443/PEWEnactmentData opens the index page and https://localhost:9443/PEWEnactmentData/makers.xml opens the XML file.

Configuring the HTTP Filtered Value Set

Please be aware, that if you use HTTPS with your HTTP Filtered Value Set, you might have to check the check box to Ignore  invalid SSL certification if you did not fix your certificate.

Summary

I hope I could show that it is easily possible to store your XML files required for access from an HTTP Filtered Value Set. I have to go now and fix the Process Enactment Workshop.

Attribute Customization – Java Based Value Providers, Conditions and Validators

I had to look into how RTC Attribute Customization can be done using Java based Eclipse Extensions. This post shows how this works and provides with examples if you need to get started on the topic.

Update: Also look at A Custom Condition to Make Attributes Required or Read-Only by Role to see more on this kind of extensions.

The Lab 5 talks about using JavaScript for implementing custom providers. It also talks about limitations of the JavaScript API. The current JavaScript API limits you to accessing attributes of the current work items and is also very limited when working with more complex attributes. The JavaScript API in RTC 3.x and 4.x is so limited as it

  • Does not provide means to access extended user information such as team membership or roles
  • Does not allow to access links to Items such as other work items and other work items attributes
  • Does not allow to access information such as subscriptions or attachments

In addition there are few built in providers (see Lab 4 in the workshop) that can be used with list type attributes.

Recently I had to work for a customer request where the built in functionality and the available JavaScript API were clearly not sufficient to match the requirements. This gave me the opportunity to look into extending Attribute Customization using Java based extensions. I thought this would be a nice experience to share.

License and Download

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.

The code presented in this post can be downloaded from here. The code is also available in the RTC Extensions Stream (RTC AttributeCustomization component) in the Jazz In Flight project @ JazzHub. The code shows some simple providers as an entry point and some more complex ones to show the potential.

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 working there as well.

In this context, please also consider to at least read through the Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management lab 4 and lab 5 to understand how attribute customization works.

I looked into Attribute Customization for the RTC Process Enactment Workshop. You should look into Lab 4 if you don’t know how value providers work and are configured in RTC.

See JavaScript Attribute Customization – Where is the log file? for the log file location for debugging.

The code in this post is common code running in the Eclipse client and the RTC Server.

Finding Information in the SDK

Because I never did this kind of extension before, I started with reviewing the Wiki Article about Attribute Customization on Jazz.net. It is also a good idea to review existing java code to get some ideas.

This section describes how to find example code in the SDK, which is very important to be able to do this kind of work.

The following steps require the development environment to be set up like described in this post. This allows basically to search for references to the extension point and find the code that is involved.

First create a plugin project based on the suggestions in the Wiki Article. Then search for references to the Extension point.

Search for References to an extension point
Search for References to an extension point

The search will find some plugins extending this extension point.

Available extensions
Available extensions

Just double click the plugin com.ibm.workitem.common to open the plugin XML and review the extensions. You can switch to the Extensions tab in the plugin editor and get a better overview. You can navigate to the extending classes by simply looking their name up in the Extension Element Details section, copying the class name and running a Java Search for the declaration for the type. In the search result, find the result related to the SDK and open it in the package explorer.

Type in the search result - Show in Package Explorer
Type in the search result – Show in Package Explorer

Browse the package explorer and explore the SDK source of all the interesting classes. Open classes to look into the code and find out how they work.

Explorer the SDK code
Explore the SDK code

There are some very interesting examples that are worth looking into. Especially the RoleBasedUserProvider and the SecurityContextProvider.

Code to explore
Code to explore

I find the SecurityContextProvider interesting, because I am interested in automation to control the visibility of work items in the context of working with customers. I have a todo created to try to find time to create my own implementation.

Example Implementations

As usual I started with some very simple examples.

  • A default value provider for string type attributes that returns some text
  • A calculated value provider for string and string list type attributes that returns some text
  • A validator for string type attributes that looks if the string contains some sub string and is configurable in the process configuration

These are available out of the box, but as examples easy enough to do without having to fight with more API and I learned a lot from them for reasons I did not expect.

Example Implementations: attributeType

The first decision necessary when following the suggestions in the Wiki Article and creating the extension, is to decide which type the provider should work for. The Article provides a list of examples for the type ID’s, but this is by no means complete. I am not sure yet, where to find the complete list. This section of a Wiki article could be a good starting point. You can look up examples in the extensions mentioned above. Another Theme seems to be to add List in case you want a list type attribute. For example if the return type is string and you want to support a list attribute, the attribute type is stringList.

Also be aware you can and should add several attribute type definitions if you want the extension to support more than one type. String provider are typical in this case. If you want to be able to select them for all string type attributes, define small, medium and large string like below.

ExtensionPointDefinitonsYou can leave out the settings for isDefault and requiresConfiguration in theExtension Elements Details.

API Available in Providers

All provider implementations get called with the following data:

  • IAttribute attribute, the attribute the provider is called for (not available for conditions)
  • IWorkItem workItem, the workitem context the provider is called for
  • IWorkItemCommon workItemCommon, as an entry point to get more API services
  • IConfiguration configuration, the configuration in the process XML related to this provider
  • IProgressMonitor monitor, the progress monitor (can be null)

You can use the workItemCommon to get other common services. Since the providers run in the client as well as in the server the API you have access to seems to be the API common to both. I found IAuditableCommon and IAuditableCommonProcess so far.

Default Value Provider

The code for a default value provider must implement the interface com.ibm.team.workitem.common.internal.attributeValueProviders.IDefaultValueProvider. The type is correctly generated if you follow the Wiki Article. The implementation here is very simple and just returns a string.

The code below shows a very simple default value provider.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IDefaultValueProvider;
import com.ibm.team.workitem.common.model.IAttribute;
import com.ibm.team.workitem.common.model.IWorkItem;

public class SampleStringDefaultProvider implements IDefaultValueProvider {

	public SampleStringDefaultProvider() {
	}

	@Override
	public String getDefaultValue(IAttribute attribute, IWorkItem workItem,
			IWorkItemCommon workItemCommon, IConfiguration configuration,
			IProgressMonitor monitor) throws TeamRepositoryException {
		return "String Default Value";
	}
}

Calculated Value Provider

The next example is a simple calculated value provider that returns a string containing the time since 1970 in milliseconds. The interface implemented is com.ibm.team.workitem.common.internal.attributeValueProviders.IValueProvider. Please note, all examples below are typed and specify the return type e.g. .

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import java.util.Date;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IValueProvider;
import com.ibm.team.workitem.common.model.IAttribute;
import com.ibm.team.workitem.common.model.IWorkItem;

public class SampleStringCalculatedValueProvider implements IValueProvider {

	public SampleStringCalculatedValueProvider() {
	}

	@Override
	public String getValue(IAttribute attribute, IWorkItem workItem,
			IWorkItemCommon workItemCommon, IConfiguration configuration,
			IProgressMonitor monitor) throws TeamRepositoryException {
		Date date = new Date();
		return "String Calculated Value: "+date.getTime();
	}
}

Again, very basic to show and understand the concept.

A second example is showing a calculated value for a stringList attribute type. The difference is that it returns a List that contains several values.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IValueProvider;
import com.ibm.team.workitem.common.model.IAttribute;
import com.ibm.team.workitem.common.model.IWorkItem;

public class SampleStringListProvider implements IValueProvider {

	public SampleStringListProvider() {
	}

	@Override
	public List getValue(IAttribute attribute, IWorkItem workItem,
			IWorkItemCommon workItemCommon, IConfiguration configuration,
			IProgressMonitor monitor) throws TeamRepositoryException {
		List result = new ArrayList();
		result.add("Test 1");
		result.add("Test 2");
		result.add("Test 3");
		return result;
	}
}

Validator

The last simple example is a validator that validates if a text field contains a certain sub string. It implements the interface com.ibm.team.workitem.common.internal.attributeValueProviders.IValidator. The validator, configured for a string attribute, by default searches for the search string “Test” in the attribute value and issues a warning.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;

import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IValidator;
import com.ibm.team.workitem.common.model.IAttribute;
import com.ibm.team.workitem.common.model.IWorkItem;

/**
 * Validates if a string is contained in the text.
 * 
 * Configure in Process XML
 * 
 * 
 *
 */
public class SampleStringContainsSubstring implements IValidator {

	private static final String SMAPLE_STRING_CONTAINS_SUBSTRING = "com.ibm.js.team.workitem.attribute.customization.providers.SmapleStringContainsTestValidator";
	private static final String REQUIRED_CONTENT = "requiredContent";
	private static final String WARNING_LEVEL = "warning";
	private static final String SEVERITY = "severity";
	private static final String VALIDATION_OK_STATUS = "ValidationOK";
	private static final String CONFIGURATION = "configuration";

	private String fSearchString;
	private int fValidationSeverity;

	public SampleStringContainsSubstring() {
	}

	@Override
	public IStatus validate(IAttribute attribute, IWorkItem workItem,
			IWorkItemCommon workItemCommon, IConfiguration configuration,
			IProgressMonitor monitor) throws TeamRepositoryException {
		getConfiguration(configuration);

		String value = (String) attribute.getValue(workItemCommon.getAuditableCommon(), workItem, monitor);
		if(value.indexOf(fSearchString)>=0){
			return new Status(Status.OK, SMAPLE_STRING_CONTAINS_SUBSTRING,VALIDATION_OK_STATUS);
		}
		return new Status(fValidationSeverity, SMAPLE_STRING_CONTAINS_SUBSTRING, "Validation: String must contain '"+fSearchString+"'");
	}

	private void getConfiguration(IConfiguration configuration) {
		fSearchString = "Test";
		fValidationSeverity=Status.WARNING;
		IConfiguration parameters= configuration.getChild(CONFIGURATION);
		if(null!=parameters){ // We got a configuration
			String customSearchString=parameters.getString(REQUIRED_CONTENT);
			if(null!=customSearchString)
				fSearchString=customSearchString;
			String configuredValidationSeverity=parameters.getString(SEVERITY);
			if(null!=configuredValidationSeverity)
				fValidationSeverity =  configuredValidationSeverity.equalsIgnoreCase(WARNING_LEVEL)?Status.WARNING:Status.ERROR;
		}
	}
}

The code is a bit more complex, because it allows to configure the test string and the severity in the process XML. The configuration and defaults are read in getConfiguration() and stored in fields. Then the attribute value is read and tested if it contains the substring. If not, a message is created that shows in the UI. Based on the configuration the severity is of level error or warning. Error would allow to prevent saving a work item together with the Attribute Validation work item save precondition. The configuration would look like this:

Validator configuration in the process XML
Validator configuration in the process XML

Condition

I could not come up with an easy condition. Instead I looked into more complex things next. The next examples I did, were focused to automate something based on the role of a user.

The following condition provides information, if the user has a set of roles in the process area the workitem is owned by. It can for instance be used to make attributes mandatory or read only based on this information. The condition is fully configurable in the process XML and can manage one or more roles.

Please be aware that the extension point schema has an issue. If you create the class to handle the extension, you get an error. Create it manually or fix it. The class you create should implement com.ibm.team.workitem.common.internal.attributeValueProviders.ICondition.

Please see the code blow for details.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.IContributorHandle;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.ICondition;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.model.IWorkItem;

/**
 * Condition that checks if the user editing the work item has a specific role
 * 
 * Configure in Process XML
 * 
 * 
 * 
 *
 */
public class SampleCurrentUserHasRoleCondition implements ICondition {

	/* (non-Javadoc)
	 * @see com.ibm.team.workitem.common.internal.attributeValueProviders.ICondition#matches(com.ibm.team.workitem.common.model.IWorkItem, com.ibm.team.workitem.common.IWorkItemCommon, com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration, org.eclipse.core.runtime.IProgressMonitor)
	 * 
	 * Return true if the user has the specified role, else false
	 */
	@Override
	public boolean matches(IWorkItem workItem, IWorkItemCommon workItemCommon,
			IConfiguration configuration, IProgressMonitor monitor)
			throws TeamRepositoryException {
		List lookupRoles = ContributorUtil.getConfiguration(configuration);
		IContributorHandle user= workItemCommon.getAuditableCommon().getUser();
		return ContributorUtil.hasRole(workItem, user, lookupRoles, workItemCommon, monitor);
	}
}

That is short. The reason is, that all the code is in the class ContributorUtil. I was able to reuse that code in several ways and ended up collecting it in the utility class. The method getConfiguration() basically gets the roles to look for from the process configuration.

Then the code gets the IContributorHandle of the current user and the method hasRole check if the user has the role.

The code for the ContributorUtil is below.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.process.common.IProcessArea;
import com.ibm.team.process.common.IProcessAreaHandle;
import com.ibm.team.process.common.IRole;
import com.ibm.team.repository.common.IContributor;
import com.ibm.team.repository.common.IContributorHandle;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IAuditableCommonProcess;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.ItemProfile;

/**
 * Utility class to provide contributor related utilities
 *
 */
public class ContributorUtil {

	private static final String DEFAULT_ROLE_TEAM_MEMBER = "ScrumMaster";
	static final String ROLE = "role";
	static final String PROCESSAREA = "processArea";

	/**
	 * Returns a list of members that have a certain role in the process area a work item is filed against
	 * 
	 * @param workItem
	 * @param lookupRoles
	 * @param workItemCommon
	 * @param monitor
	 * @return a list of contributor handles
	 * @throws TeamRepositoryException
	 */
	public static List findProcessAreaMembersByRole(IWorkItem workItem, Collection lookupRoles,
			IWorkItemCommon workItemCommon, IProgressMonitor monitor)
			throws TeamRepositoryException {
		IProcessAreaHandle processAreaHandle = workItemCommon.findProcessArea(workItem, monitor);
		IProcessArea processArea = (IProcessArea) workItemCommon.getAuditableCommon().resolveAuditable(processAreaHandle,
				ItemProfile.PROCESS_AREA_DEFAULT,monitor);
		IAuditableCommonProcess auditableCommonProcess= workItemCommon.getAuditableCommon().getProcess(processArea, monitor);
		IContributorHandle[] userhandles = auditableCommonProcess.getContributorsWithRole(processArea.getMembers(), processArea, lookupRoles.toArray(new String[lookupRoles.size()]), monitor);
		return workItemCommon.getAuditableCommon().resolveAuditables(Arrays.asList(userhandles), ItemProfile.CONTRIBUTOR_DEFAULT, monitor);
	}

	/**
	 * Returns true if the user working on the work item has a specified role in the process area 
	 * the work item is filed against
         *
	 * @param workItem
	 * @param user
	 * @param lookupRoles
	 * @param workItemCommon
	 * @param monitor
	 * @return true if the user has the specified role, else false
	 * @throws TeamRepositoryException
	 */
	public static boolean hasRole(IWorkItem workItem, IContributorHandle user, List lookupRoles,
			IWorkItemCommon workItemCommon, IProgressMonitor monitor)
			throws TeamRepositoryException {
		IProcessAreaHandle processAreaHandle = workItemCommon.findProcessArea(workItem, monitor);
		IProcessArea processArea = (IProcessArea) workItemCommon.getAuditableCommon().resolveAuditable(processAreaHandle,
				ItemProfile.PROCESS_AREA_DEFAULT,
				monitor);
		IAuditableCommonProcess auditableCommonProcess= workItemCommon.getAuditableCommon().getProcess(processArea, monitor);
		Collection roles = auditableCommonProcess.getContributorRoles(user, processArea, monitor);
		for (IRole aRole : roles) {
			if(lookupRoles.contains(aRole.getId())){
				return true;
			}
		}
		return false;
	}

	/**
	 * Reads the role configuration
	 * 
	 * Configure in Process XML
	 * 
	 * 
	 * 
	 * 
	 * @param configuration
	 * @return
	 */
	public static List getConfiguration(IConfiguration configuration) {
		ArrayListlookupRoles=new ArrayList();

		List processAreaConfigurations= configuration.getChildren(PROCESSAREA);
		if(null!=processAreaConfigurations&&!processAreaConfigurations.isEmpty()){ // We got a configuration
			for (IConfiguration roleConfiguration : processAreaConfigurations) {
				String foundRole=roleConfiguration.getString(ROLE);
				if(foundRole!=null){
					lookupRoles.add(foundRole);
				}
			}
		} else {
			lookupRoles.add(DEFAULT_ROLE_TEAM_MEMBER);
		}
		return lookupRoles;
	}
}

The method ContributorUtil.hasRole() basically gets the process area and then gets the roles of the user in this process area and then checks if one of the roles matches a configured role.

ContributorUtil.getConfiguration() basically reads the process XML configured for the extension and returns a list of roles configured. By default and unconfigured it looks for Team Member. Please be aware it looks for the role ID and not the display name.

Value Set Provider

As an example for a value set provider that implements com.ibm.team.workitem.common.internal.attributeValueProviders.IValueSetProvider see the example that provides a set of users with roles. The code looks as shown below:

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.IContributor;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IValueSetProvider;
import com.ibm.team.workitem.common.model.IAttribute;
import com.ibm.team.workitem.common.model.IWorkItem;

/**
 * This ValueSet Provider returns a list of users that have the configured role 
 * in the process area the work item is filed against
 * 
 * Configure in Process XML
 * 
 *   
 * 
 *
 */
public class SampleUsersWithRoleValueSetProvider implements IValueSetProvider {

	@Override
	public List<? extends IContributor> getValueSet(IAttribute attribute, IWorkItem workItem,
			IWorkItemCommon workItemCommon, IConfiguration configuration,
			IProgressMonitor monitor) throws TeamRepositoryException {
		List lookupRoles = ContributorUtil.getConfiguration(configuration);
		return ContributorUtil.findProcessAreaMembersByRole(workItem, lookupRoles,
				workItemCommon, monitor);
	}
}

It uses the same methods provided by the ContributorUtil class used by the other more complex providers and conditions.

It works for contributor as well as contributorList attribute types.

Additional Providers

ContributorUtil.findProcessAreaMembersByRole() basically finds the users that have a role matching the list of roles in the process area and returns the users as list. I used this in several other providers.

  • Sample User With Role In ProcessArea Default provides a default value for one user with matching roles (the first of the returned users) for a user type attributes
  • Sample User With Role In ProcessArea Default provides a default value for all users with matching roles  for a userList type attributes
  • Sample User With Role In ProcessArea Calculated Value returns the first user in the list of found users with one of the roles back as calculated value for user type attributes
  • Sample Users With Role In ProcessArea Calculated Value returns all matching users found for userList attribute types
  • Sample Users With Role In ProcessArea provides users with matching roles

The image below shows the classes that implement the required interfaces.

Additional Providers for users with roles
Additional Providers for users with roles

All the provider classes look very similar. They implement one or more interfaces and use the utility class to get the required data. Here an example:

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2013. All Rights Reserved. 
 *
 * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.workitem.attribute.customization.providers;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.IContributor;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.common.IWorkItemCommon;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IDefaultValueProvider;
import com.ibm.team.workitem.common.internal.attributeValueProviders.IValueProvider;
import com.ibm.team.workitem.common.model.IAttribute;
import com.ibm.team.workitem.common.model.IWorkItem;

/**
 * Implements a value provider that returns the users that have a specific role in the process
 * area the work item is filed against.
 * 
 * Configure in Process XML
 * 
 * 
 * 
 * 
 *
 */
public class SampleUserWithRoleValueProvider implements IDefaultValueProvider,IValueProvider {

	public SampleUserWithRoleValueProvider() {
	}

	@Override
	public IContributor getValue(IAttribute attribute, IWorkItem workItem,
			IWorkItemCommon workItemCommon, IConfiguration configuration,
			IProgressMonitor monitor) throws TeamRepositoryException {

		List lookupRoles = ContributorUtil.getConfiguration(configuration);
		List users = ContributorUtil.findProcessAreaMembersByRole(workItem, lookupRoles,
				workItemCommon, monitor);
		if(!users.isEmpty()){
			return users.get(0);
		}
		return null;
	}

	@Override
	public IContributor getDefaultValue(IAttribute attribute,
			IWorkItem workItem, IWorkItemCommon workItemCommon,
			IConfiguration configuration, IProgressMonitor monitor)
			throws TeamRepositoryException {

		return getValue(attribute, workItem, workItemCommon, configuration, monitor);
	}
}

All these providers can be configured for one or more roles in the process XML like shown below. The configuration is within the entry for the provider as described in the Jazz.net Wiki entry about attribute customization.

Provider configured with two roles
Provider configured with two roles

Deploying

I ran this in a test environment set up as described here.

The downloadable code contains a feature and an update site project. Follow the Rational Team Concert Extension Workshop and the instructios in the Wiki Article about Attribute Customization to deploy the code on the client and the server.

Related Posts

Summary

The code above is a good starting point if you want to create your own providers. There are more capabilities that you can use with Java compared to JavaScript. What data really is accessible remains to be seen. I still have to look if it would be possible to for example access SCM components and the like. I hope it is work reading and helps some users out there to save some time to get started with this topic.

Meet Us at Innovate 2013

We are presenting at Innovate 2013 in Orlando, Florida, meet us there!

My team will be attending and presenting at Innovate 2013 in Orlando, Florida, June 2-6.

Rosa Naranjo published all the activities of my team in her blog. Here some details on my schedule.

Jim Ruehlin, Jorge Diaz and I will deliver the CLM Process Enactment Workshop Tuesday June 4th (3pm-6pm). This hands-on workshop, based on the Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management 2012 is all about doing process enactment. We will guide you through describing and defining your development process with Rational Method Composer, publishing it to Rational Team Concert. In Rational Team Concert (RTC), you will then use the built in capabilities to customize the process further, adjust workflows, add custom attributes, modify work item editor presentations and adjust the process to your needs. In the last sections you will learn how to customize your work Item’s attributes to reflect the need for your business based on declarative customization and (if time permits) Java Script.

On Thursday, June 6th (8:30-9:30 am) Jorge Diaz and I will present All You Need to Know About Customizing RTC to provide you with a comprehensive view on what you can do to customize Rational Team Concert. We will introduce how process enactment in RTC really works, what the concepts are, what to do and what to avoid. We will also guide you through the current capabilities to customize Rational Team Concert Work Items. We will provide you with insight about what can be done in this area, how to best address it and how to decide on the easiest possible approach. This includes understanding where the limits are and which more complex options are available. What can you do within the tool UI, what can you do with JavaScript and when do you have to extend the tool using Java.

Fariz Saracevic and I will talk to you about IBM Rational Collaborative Lifecycle Management Solution Deployment Tips and Tricks Thursday, June 6th (11am-12am). This talk is all about how to get started with deploying the CLM solution, what to pay attention to when planning, things you need to consider for upgrading and the like.

Tox published the bios of the members of our team attending Innovate 2013 and Rosa where you are likely to find us. If you happen to be at Innovate in Orlando, try to meet us. We will be around and the easiest way to locate us will be joining one of our workshops and presentations. I am looking forward to meet you during these most interesting days in the year 2013!

RTC Process Enactment Workshop – Customize Attributes Using JavaScript

Almost all customers need to customize their RTC process, especially Work Items. Especially Attribute Customization, including JavaScript based customization is very popular on the Jazz.net forums. There are a lot of questions around how these customizations can be done and what is actually possible.

Since this is so popular Jim Ruehlin, Jorge Diaz and I made an effort to create a workshop that explains the concepts involved. The Process Enactment Workshop was published the first time in November last year. When it was first published we had finalized 4 labs.

  • Lab 1: Set Up the Process Enactment Environment
  • Lab 2: Understand the Process Development Lifecycle
  • Lab 3: Configuring Work Items
  • Lab 4: Work Item Customization

Lab 4 talks only about the Attribute Customization that can be done using the built in declarative customization.

Yesterday we published Lab 5. This Lab talks about Work Item Customization with JavaScript. The lab uses examples to explore the JavaScript related capabilities. We tried to pick some examples that would be close to requests we have seen in the forum. In addition there is a section about what can be done with JavaScript today – and what is not possible – as far as we can tell.

JavaScript Debugging is described in Millard’s Article on debugging JavaScript.

I hope we addressed interesting examples that help with real world challenges. It is possible to run the whole workshop and it should also help if you just read specific sections. Enjoy!

PS:

Jorge and Jim have their own blogs that provide interesting solutions and insight into using RTC and the Jazz based solutions.