A component naming convention advisor

Organizations sometimes would like to implement naming conventions for components based on the architecture for example. This post shows a simple example advisor that checks for a naming convention.

License

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

Just Starting With Extending RTC?

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

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

Compatibility

This code has been used with RTC 6.0 and is prepared to be used with RTC 6.0.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC. It should run with any version that provides the operation ID.

The code in this post uses common and server libraries/services that are available in the RTC Server SDK.

Download

The code is included in the download in the post DIY stream naming convention advisors.

Solution

In the last few versions of RTC several operations have been made available for operational behavior. At least since RTC 5.0.2 the operation to modify a component is available with the operation ID com.ibm.team.scm.server.component. This allows to create advisors/preconditions as well as participants/follow up actions that operate on such events. An example shipped with the product is implemented in the class com.ibm.team.scm.service.UniqueComponentNameAdvisor.

There are examples shipped with the product in the SDK that you can look at for more sample code. For example: com.ibm.team.scm.service.internal.process.advisors.UniqueComponentNameAdvisor

The code below shows a very basic example how to test the component name for some simple naming schema.

The important information to take away is that the information about the save operation is provided in a special interface IComponentModificationData which allows access to the type of the operation, to old and new properties and to the component directly.

componentmodification

So it is possible to find out what operation is done on the component and based on that look at the properties that the component has.

The code below does exactly that. It checks what operation is going on and then takes the new name of the component and checks it.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2017. 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.scm.naming.advisor.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.TeamRepositoryException;
import com.ibm.team.scm.service.internal.AbstractScmService;
import com.ibm.team.scm.service.internal.process.IComponentModificationData;
import com.ibm.team.scm.service.internal.process.IComponentModificationData.OpType;

/**
 * An example advisor that checks the name of a component for some naming convention
 *
 * Also @see com.ibm.team.scm.service.UniqueComponentNameAdvisor for product example code
 */
@SuppressWarnings("restriction")
public class ComponentNamingAdvisor extends AbstractScmService implements
		IOperationAdvisor {
	public static final String REQUIRED_PREFIX = "TEST_";
	public static final String COMPONENT_NAMING_ADVISOR = "com.ibm.js.scm.naming.advisor.service.componentNaming";

	@Override
	public void run(AdvisableOperation operation,
			IProcessConfigurationElement advisorConfiguration,
			IAdvisorInfoCollector collector, IProgressMonitor monitor)
			throws TeamRepositoryException {

		IComponentModificationData data = (IComponentModificationData) operation
				.getOperationData();
		if (data == null) {
			throw new TeamRepositoryException("Missing component data"); //$NON-NLS-1$
		}

		String compName;

		if (data.getOpType() == OpType.CREATE) {
			compName = data.getNewName();
		} else if (data.getOpType() == OpType.RENAME) {
			compName = data.getNewName();
		} else {
			return;
		}
		if (validateName(compName)) {
			// Nothing to do
			return;
		}
		String description = "The component name violates the naming conventions component name must have prefix '"
				+ REQUIRED_PREFIX + "'!";
		String summary = description;
		IAdvisorInfo info = collector.createProblemInfo(summary, description,
				COMPONENT_NAMING_ADVISOR);//$NON-NLS-1$
		collector.addInfo(info);
	}

	/**
	 * Validate the name against the convention
	 * 
	 * @param compName
	 * @return
	 */
	private boolean validateName(String compName) {
		// Implement your own naming convention here
		if (compName.startsWith(REQUIRED_PREFIX)) {
			return true;
		}
		return false;
	}
}

Summary

This is as usual a very basic example with no or very limited testing and error handling. See the links in the getting started section for more examples. As always I hope this helps someone out there with running RTC.

Advertisement

The Day The JBE Stood Still

Last week I realized my Jazz Build Engines for my test environments where not working any longer. Since I need them to work, I tried to fix this. It turned out that this was a major effort and I thought I should share what I found and how I was able to fix it – hopefully forever.

The Problem

The initial symptom was that the JBE process shells I usually see running, terminated immediately.

My initial thought was, that recently there were a lot of updates running on my machine to remove all Java versions with security issues and installing new versions. I suspected an issue with the Java version running.

Finally this proved to be true, however, during the attempt to come up with a working fix, I ran into a variety of other issues, I will summarize below.

The Solution

The final solution for me was to provide a working JRE to the Jazz Build Engine. To avoid it getting removed during security upgrades and to make sure it is compatible, I used a JRE that comes with the Jazz products.

One important detail is, that the JBE is currently a 32 bit application. If you use a 64 bit JRE with it, a variety of error symptoms crop up.

So the solution for me is, to

  1. Download the Plain ZIP package for the Eclipse client – make sure to download the 32 bit version
  2. Extract the download to some temporary location
  3. Locate the folder jazz\client\eclipse\jdk in the extracted folder
  4. Copy the folder jdk and all of its content into some folder you want to keep; for example: C:\CLMJava32
  5. Edit the file jbe.ini in \jazz\buildsystem\buildengine\eclipse and add the following lines where indicated

    -vm

    C:\CLMJava32\jdk\jre\bin

Your jbe.ini file should now look similar to the image below:

jbe.ini

You can probably get away with just the jre/bin folder, but I wanted the full JDK for other uses.

If you restart the JBE processes, everything should work.

Problems With the JBE and Wrong JRE’s

During my attempts to fix it I ran into various other issues, which I will try to list below.

  • The JBE shell just terminates

    This seems to happen due to incompatible JRE e.g. a newer version and for 64 bit JRE

  • The build process finishes with errors in the build result like
    !ENTRY org.eclipse.core.filesystem 1 1 2015-04-07 14:18:52.303
    !MESSAGE Could not load library: localfile_1_0_0.dll.  This library provides platform-specific optimizations for certain file system operations.  This library is not present on all platforms, so this may not be an error.  The resources plug-in will safely fall back to using java.io.File functionality.
    !STACK 0
    java.lang.UnsatisfiedLinkError: C:\CLM2014\5.0.2\jazz\buildsystem\buildengine\eclipse\configuration\org.eclipse.osgi\bundles\184\1\.cp\os\win32\x86\localfile_1_0_0.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform
    

    This seems to happen due to using a 64 bit JRE; Errors can be found in the build logs or in the log of the build engine workspace; the log is usually located in \jazz\buildsystem\buildengine\eclipse\workspace\.metadata

  • The build process finishes with errors in the build result like
    2015-04-07 14:23:23 [Jazz build engine] Fetching files to fetch destination "C:\somefolder\JKEBuild\I20150407-1423" ... com.ibm.team.build.internal.scm.SourceControlUtility$2: Status ERROR: com.ibm.team.filesystem.client code=0 Cannot create sandbox at C:\somefolder\JKEBuild\I20150407-1423 because one already exists at C:\somefolder\ null

    This seems to happen due to using a 64 bit JRE

  • The build process finishes with errors in the build result like
    Failed to load the JNI shared library "C:\CLM2014\5.0.2\jazz\client\eclipse\jdk\jre\bin\j9vm\jvm.dll".

    This seems to happen due to using a 64 bit JRE

  • The build process is abandoned This was the toughest issue; the builds where abandoned with the error message below
    2015-04-07 14:18:51 [Jazz build engine] Deleting fetch destination "C:\CLM2014\5.0.2\JazzTeamServer\server\JKEBuild\I20150407-1418" before fetching ...
    2015-04-07 14:18:51 [Jazz build engine] Fetching files to fetch destination "C:\CLM2014\5.0.2\JazzTeamServer\server\JKEBuild\I20150407-1418" ...
    com.ibm.team.build.common.TeamBuildStateException: Unable to "complete" build activity with label "_QCYgmd0gEeSrJ4S35NXZHw" because the build with ID "_P_aFEd0gEeSrJ4S35NXZHw", build definition ID "jke.dev", label "I20150407-1418" is in the "INCOMPLETE" state.
    	at com.ibm.team.build.internal.service.TeamBuildService.createIllegalBuildStateForActivityException(TeamBuildService.java:1599)
    	at com.ibm.team.build.internal.service.TeamBuildService$22.run(TeamBuildService.java:1471)
    	at com.ibm.team.build.internal.service.AbstractTeamBuildService.runAsRetryable(AbstractTeamBuildService.java:435)
    

    The reason for this finally was that the JBE processes that terminated left daemon Java processes, these processes tried to consume the build requests as well, which resulted in the build being abandoned

The abandoned builds cost me the most time, because it is completely hidden why this happens. Looking into the process list finally revealed the reason.

Summary

Providing a stable and compatible JRE is important to keep the build engines running. The solution above should work on all architectures. Searching the internet I did not find a lot of hints how to solve this, so I hope providing this post will come in handy and helps users that run into the same problem.

Maintaining User Photos With the Plain Java Client Libraries

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

Why is this of interest?

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

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

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

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

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

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

Download

You can download the code from here.

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

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

How to use the tool

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

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

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

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

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

To upload the images after modification use

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

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

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

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

UserPictures

Contributor Details

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

The data is accessible using this code:

IContributorDetailsHandle detailsHandle = contributor.getDetails();

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

The Method run

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

Downloading Files

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

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

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

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

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

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

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

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

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

Uploading an Image File

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

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

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

The code first tries to upload the content from the file then it tries to get the details and, if there was an upload either uses the existing contributor details or creates new details if needed.

It gets a working copy of the details, sets the photo content to it. Last but not least, it gets a working copy for the contributor and sets the contributor details that have been modified.

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

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

Other than for the download, if the file for the image is not found, this code continues and just does not change the details.

The code provides content type and encoding similar to what was downloaded.

Summary

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