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.

2 thoughts on “Maintaining User Photos With the Plain Java Client Libraries

  1. Can you store additional attributes from LDAP? For example, if I want the users phone number, pager or manager’s name. Is it possible to add this information into RTC User profile and page.

Leave a comment

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