Managing Workspaces, Streams and Components Using the Plain Java Client Libraries


How can I manage the streams, repository workspaces and components in a RTC project area using the Plain Java Clients Library?

As described in the last post I want to automatically set up a project, manage the streams and components and seed the SCM system with SCM data. The last post shows how to create and manage the project area.

This post will talk about managing the components and the stream and repository workspaces.

The related posts are:

  1. Deploying Templates and Creating Projects using the Plain Java Clients Library
  2. This post
  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.

Managing Components, Streams and Workspaces

Continuing in the run method of Deploying Templates and Creating Projects using the Plain Java Clients Library we now want to create some required components and then set up the stream and repository workspace with these components.

The requirement is to have three components and to have them on the repository workspace and the stream. If there is a default component, we want to rename it to what we need.

The code below coordinates that. First it creates a list of components we want. the method manageComponents() takes care of renaming or creating the required components and provides with a map we can use later to get the one we want. Then, the code tries to find and rename or create a stream that will later be used, and add the required components.

The method manageWorkspaces() does the same for a repository workspace. After this has been performed the whole SCM structure is set up.

.
public static void main(String[] args) throws Exception {
.
.
.
.	List<String> requiredComponents = new ArrayList<String>();
	requiredComponents.add("Component 1");
	requiredComponents.add("Component 2");
	requiredComponents.add("Component 3");
	HashMap<String, IComponentHandle> components = manageComponents(
			teamRepository, area, requiredComponents, monitor);

	IWorkspaceConnection myStream = manageStream(teamRepository,
			area, "Stream Name", "Stream Description", components, monitor);
	if (extensionStream == null) {
		System.out.println("Could not create or retrieve a stream.");
		return false;
	}
	IWorkspaceConnection workspace = manageWorkspace(teamRepository, area,
			myStream, "Workspace name", "Workspace Description", components,
			monitor);
	if (workspace == null) {
		System.out.println("Could not create or retrieve a workspace.");
		return false;
	}
.
.
.
.
.
}

Manage Components

The method manageComponents() creates a map with the components that exist in the project area. Using the list of required components, it looks if there is a component with the correct name already. If there isn’t it tries to find any component that is already available and rename it. If there is no component for reuse, a new component is created.

private HashMap<String, IComponentHandle> manageComponents(
		ITeamRepository teamRepository, IProjectArea area, List requiredComponents,
		IProgressMonitor monitor) throws TeamRepositoryException {

	IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
	HashMap<String, IComponentHandle< allComponents = getComponentMap(wm, monitor);

	HashMap<String, IComponentHandle> components = new HashMap<String, IComponentHandle>();
	// process all required components.
	// Look up if we have the component already.
	// If not, grab an existing component we have found and reuse it by
	// renaming.
	// If there is none, create a new component.
	Set componentsRemaining = allComponents.keySet();
	Iterator remainingComponentsIterator = componentsRemaining
			.iterator();
	for (String requiredComponent : requiredComponents) {
		IComponentHandle required = allComponents.get(requiredComponent);
		if (required != null) {
			// Reuse Component
			components.put(requiredComponent, required);
		} else {
			// We don't have it yet.
			if (remainingComponentsIterator.hasNext()) {
				String compName = remainingComponentsIterator.next();
				IComponentHandle renameMe = allComponents.get(compName);
				// Create Component
				IComponentHandle component = renameOrCreateComponent(
						teamRepository, wm, area, renameMe,
						requiredComponent, monitor);
				extComponents.put(requiredComponent, component);
			} else {
				// Create Component
				IComponentHandle component = renameOrCreateComponent(
						teamRepository, wm, area, null, requiredComponent,
						monitor);
				components.put(requiredComponent, component);
			}
		}
	}
	return components;
}

The method getComponentMap() that gets all available components and places the handles into a map looks as below. It uses the IWorkspaceManager to get all component names. Then it uses IComponentSearchCriteria with the retrieved names to look up the handle for each component. Currently the situation that components have the same name is ignored, only a warning is issued. It would be possible to rename components with the same name.

Both name and handle are stored in a map for later usage and the map is returned.

private HashMap<String, IComponentHandle> getComponentMap(
		IWorkspaceManager wm, IProgressMonitor monitor)
		throws TeamRepositoryException {
	HashMap<String, IComponentHandle> allComponents = new HashMap<String, IComponentHandle>();
	// These are the components I need

	// Try to find all components
	Set<String> components = wm.findAllComponentNames(monitor);
	for (String compName : components) {
		IComponentSearchCriteria criteria = IComponentSearchCriteria.FACTORY
				.newInstance();
		criteria.setExactName(compName);
		List<IComponentHandle> found = wm.findComponents(criteria,
				Integer.MAX_VALUE, monitor);

		if (found.size() > 1) {
			System.out.println("Ambiguous Component Names");
		}

		for (IComponentHandle iComponentHandle : found) {
			allComponents.put(compName, iComponentHandle);
		}
	}
	return allComponents;
}

The code for renameOrCreateComponent() to create or rename a component is shown below. Again IWorkspaceManager is used to do the work.

If the component handle passed is not null , then there is an existing component, and the name of this component is changed.

If the parameter for the component handle is null a new component is created and named as desired. For learning purposes the component is created as private component of the automation user first.

In any case the ownership of the component is set to the project area.

private IComponentHandle renameOrCreateComponent(
		ITeamRepository teamRepository, IWorkspaceManager wm,
		IProjectArea area, IComponentHandle componentHandle,
		String componentName, IProgressMonitor monitor)
		throws TeamRepositoryException {
	if (componentHandle == null) {
		// Rename component
		componentHandle = wm.createComponent(componentName,
				teamRepository.loggedInContributor(), monitor);
		wm.setComponentOwner(componentHandle, area, monitor);
	} else {
		// Rename component
		wm.renameComponent(componentHandle, componentName, monitor);
		wm.setComponentOwner(componentHandle, area, monitor);
	}
	return componentHandle;
}

Now we have the components that we need and they are returned in a convenient way in a map for easier access.

Manage Streams

We have the components that we need. Now we need a stream and the stream should have the components. This is managed in the method manageStream().

The method tries to find or create a IWorkspaceConnection (a Stream in this case). The IWorkspaceConnection is the abstraction of a repository workspace or a stream.

The code uses the method findAndRenameConnection() which is explained later. The code tries to fist find a WorkspaceConnection (a stream or workspace) by name. If that does not work it tries to find any workspace.  If there is one found, it gets renamed, otherwise a new one is created.

This method also removes all components from the IWorkspcaceConnection and adds the required components.

private IWorkspaceConnection manageStream(ITeamRepository teamRepository,
		IProjectArea area, String streamName, String description,
		HashMap<String, IComponentHandle>components,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkspaceConnection connection = findAndRenameConnection(
			teamRepository, IWorkspaceSearchCriteria.STREAMS, streamName,
			description, monitor);
	if (connection == null) {
		IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
		connection = wm.createStream(area, streamName, description, monitor);
	}
	// Remove all components
	removeAllCompoentsFormWorkspaceConnection(connection, monitor);
	addCompoentsToWorkspaceConnection(connection, components, monitor);
	return connection;
}

The method findAndRenameConnection() as shown below finds a workspace or stream, selectable by kind with a certain name if given. Again, the IWorkspaceManager is the main interface used. The method tries to find the connection by name and kind first. If that does not succeed. It uses findConnectionByName() to find a connection by name and kind,if it already exists.

The code could be enhanced to delete any additional workspaces that are not needed.

private IWorkspaceConnection findAndRenameConnection(
		ITeamRepository teamRepository, int kind, String name,
		String description, IProgressMonitor monitor)
		throws TeamRepositoryException {
	IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
	// Find the exact name if available use that.
	List<IWorkspaceHandle> workspaces = findConnectionByName(teamRepository,
			name, kind, monitor);
	IWorkspaceHandle firstFound = null;
	IWorkspaceConnection connection = null;
	if (!workspaces.isEmpty()) {
		firstFound = workspaces.get(0);
		connection = wm.getWorkspaceConnection(firstFound, monitor);
	} else {
		// Find an existing stream or workspace (providing a null name) and rename it
		workspaces = findConnectionByName(teamRepository, null, kind,
				monitor);
		for (IWorkspaceHandle workspaceHandle : workspaces) {
			IWorkspace ws = (IWorkspace) teamRepository.itemManager()
					.fetchCompleteItem(workspaceHandle,
					IItemManager.DEFAULT, monitor);
			if (firstFound == null) {
				firstFound = workspaceHandle;
			} else {
				// wm.deleteWorkspace(workspaceHandle, monitor);
				System.out.println("Should be deleted.... ");
			}
		}
		if (firstFound != null) {
			connection = wm.getWorkspaceConnection(firstFound, monitor);
			System.out.println("Renaming: " + connection.getName());
			connection.setName(name, monitor);
			connection.setDescription(description, monitor);
		}
	}
	return connection;
}

The method findConnectionByName() finds a workspace or stream, selectable by kind with a certain name. If the name is not specified, it just look for any object of the kind. The method uses IWorkspaceSearchCriteria to create the criteria to search for.

private List findConnectionByName(
		ITeamRepository teamRepository, String name, int kind,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
	IWorkspaceSearchCriteria criteria = IWorkspaceSearchCriteria.FACTORY
			.newInstance().setKind(kind);
	if (name != null) {
		criteria.setExactName(name);
	}
	List<IWorkspaceHandle>workspaces= wm.findWorkspaces(criteria,
			Integer.MAX_VALUE, monitor);
	return workspaces;
}

The code that is missing is to remove and add components to the stream.

The code of the method removeAllCompoentsFormWorkspaceConnection() below removes all components from a workspace connection, which can be a stream or repository workspace. It uses an IComponentOp retrieved from the IComponentOpFactory to remove the components. Please note, that the Plain Java Client Library snippets currently refer to the deprecated API IWorkspaceConnection.addComponent(). The code below is the way the API works since 4.x.

private void removeAllCompoentsFormWorkspaceConnection(
		IWorkspaceConnection workspaceConnection, IProgressMonitor monitor)
		throws TeamRepositoryException {
	// Remove all components
	List wsComponents = workspaceConnection.getComponents();
	for (Object comp : wsComponents) {
		IComponentHandle cHandle = (IComponentHandle) comp;
		workspaceConnection.applyComponentOperations(Collections
				.singletonList(workspaceConnection.componentOpFactory()
						.removeComponent(cHandle, false)), true, monitor);
	}
}

The code below shows the method addCompoentsToWorkspaceConnection() which adds the components from our component map to the workspace connection. Again, an IComponentOp retrieved from the IComponentOpFactory is used.

private IWorkspaceConnection addCompoentsToWorkspaceConnection(
		IWorkspaceConnection workspaceConnection,
		HashMap<String, IComponentHandle> extComponents,
		IProgressMonitor monitor) throws TeamRepositoryException {

	// Add new components
	Collection components = extComponents.values();
	for (Object comp : components) {
		IComponentHandle cHandle = (IComponentHandle) comp;
		workspaceConnection.applyComponentOperations(Collections
				.singletonList(workspaceConnection.componentOpFactory()
						.addComponent(cHandle, false)), true, monitor);
	}
	return workspaceConnection;
}

Manage Workspaces

The last method to describe is manageWorkspace(). This method does exactly what manageStream() does for a stream, but only for a repository workspace. It tries to find a repository workspace. If it finds one it renames it. Otherwise it creates a new workspace. Then it makes sure the correct components are in the workspace. It uses the same helper methods described above to do its task.

private IWorkspaceConnection manageWorkspace(
		ITeamRepository teamRepository, IProjectArea area,
		IWorkspaceConnection extensionStream, String workspaceName, String description,
		HashMap<String, IComponentHandle> extComponents,
		IProgressMonitor monitor) throws TeamRepositoryException {
	IWorkspaceConnection connection = findAndRenameConnection(
			teamRepository, IWorkspaceSearchCriteria.WORKSPACES,
			workspaceName, description,
			monitor);
	if (connection == null) {
		IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(teamRepository);
		connection = wm.createWorkspace(
				teamRepository.loggedInContributor(), workspaceName,
				description, monitor);	
	}
	removeAllCompoentsFormWorkspaceConnection(connection, monitor);
	addCompoentsToWorkspaceConnection(connection, extComponents, monitor);
	return connection;
}

Summary

The code in this post basically shows how to manage connections, streams and workspaces to prepare Jazz SCM for later usage. Please be aware that, so far, there is no connection between the workspace and the stream. The Flow Target will be set later, once we begin delivering from the workspace to the stream.

Please note, there might be other ways of dealing with this, especially getting components from a stream and the methods used here might not apply for all use case, especially reusing components,streams and workspaces. You can always pick the parts you need.

Now that the project is prepared with the required SCM, the next post will show how it is possible to upload code from compressed files into the components, share and baseline the code.

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.

13 thoughts on “Managing Workspaces, Streams and Components Using the Plain Java Client Libraries

  1. Ralph, nice stuff.. one question.. where is ‘save workspace’? I want to update the description, and that is not rename, or component based.

  2. Because PostDeliver ignores “merge conflicts” and replaces components, we currently have to manually deliver from “stream to stream”. Is there really no way to do this via PlainJavaAPI or CLI?

    Deliver x or all components and do a “dry-run for merge conflicts” before delivering first component.

    • I would assume you can use the SCM API to find conflicts.

      However, my point would be, that you really should not even get into a situation with merge conflicts in post build deliver. The target stream should only get “green” deliveries from the build and not for somewhere else – my personal oppinion.

      • Also, I would suggest to check the help information about the RTC SCM CLI. I think that should definitely be able to detect conflicts.

      • I was wrong and did. I also removed the e-mail, just in case Ralf.

        Your mane should not have been exposed for too long. About 10 minutes, I think.
        This is moderated and I have to accept any comment personally.

  3. Hi,

    Thanks for the details.
    I used below code to find the stream in the repository.

    /////////////////////////////////////

    IWorkspaceSearchCriteria search = IWorkspaceSearchCriteria.FACTORY.newInstance().setKind(IWorkspaceSearchCriteria.STREAMS);
    search.setExactName(MajorMinorStream);
    ItemQueryResult foundStream = ScmQueryservice.findWorkspaces(search, Integer.MAX_VALUE, null);
    try {
    if (foundStream != null) {
    DestworkspaceHandle = (IWorkspaceHandle)foundStream.getItemHandles().get(0); // Move to catch through this statement error index=0,size=0
    }

    /////////////////////////
    We have created 4 team areas for 4 application and we have assigned application wise TQC_Major stream per Team area eg: If my application in DCP then we have assigned DCP team as owned by to DCP_TQC_Major_stream and Team Private as visibility and if my application is POP then we have assigned POP team as owned by to POP_TQC_Major_Stream and kept Team Private as visibility.
    I have added APP RM to all Team areas so only APP RM will have read access to this Streams means only APP RM can view all application streams. I have condition that I have to change state of work item using ITPM role which don’t have read access to streams and plugin will invoke once ITPM will change the state of work item.but still for few application like RDP,POP ( Read access only to APP RM role) can able to find stream using above code and for DCP and DSQ not able to find stream.

  4. Hi i want to create an application in RTC based on this source control automation .How can i get started and help?

    • If you had read the post, you would have noticed the “License and how to get started with the RTC API’S” section. That is the best way I am aware of. Perform lab 1 of the RTC/EWM Extensions Workshop. The last part of Lab 1 is setting up for development with the Plain Java Client Libraries and the SDK. If you did this, you have access to the source code, including the JUnit tests.

      There might be examples here: https://github.com/jazz-community

      You can also ask on the Jazz Forum: https://jazz.net/forum .

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.