Uploading Attachments to Work Items


A lot of users want to migrate work items to Rational Team Concert from some other system or set of data. There are some capabilities in RTC that allow to do this for certain products. The online help provides information how to import data from Bugzilla (search the help for Bugzilla) and ClearQuest. The image below shows all options to import work Items in the RTC Eclipse Client:

In most of the cases these solutions, unfortunately, lack the capability to bring over the attachments. So the question is how to upload an attachment to a work item. Freddy asked for the code to upload attachments. So, here goes.

* Update * Read this post to find code how to download attachments from work items.

* Update * You might want to uncouple uploading the attachment from the update operation of the work item. Otherwise you could get racing conditions with users changing the work item. See this forum post as a solution provided by Van Lepthien for how this looks like.

*Update* See this answer for a modified version to upload the attachments.

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

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

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

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

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

Download the Code

The example code can be downloaded from DropBox. Please note, there might be restrictions to access DropBox and the code in your company or download location.

The Code Explained

The example below is based on the wiki entry on Programmatic Work Item Creation and it can be used for two scenarios.

  • Run it as command line tool using the Plain Java Client Libraries
  • Run it as part of an an Eclipse Client extension, e.g. from a custom Wizard or the context menu

I would suggest to play with the Plain Java Client Libraries option first and read this and the additional blog posts mentioned in this article. The Jazz.net article on Automated Build Output Management Using the Plain Java Client Libraries describes how to set up an Eclipse project for development using the Plain Java Client Libraries.

One question that frequently comes up, is: can I do this with OSLC too? The answer is, you can, the tools do it. But it seems to be not really easy so far and I have no code for it. Here is an interesting Forum answer with code you could try.

Download the example source from the wiki entry on Programmatic Work Item Creation to get used to it, especially look at the example for the Plain Java Client Libraries. We are going to use the basic structure of the class, especially the main() method below.

The code in the examples is used to create a work item. It can be reused to create a work item and to integrate the code to upload an attachment. However, typically, in a migration scenario the import would be an operation which is running multiple passes. For example:

  1. Export the data into some file structure, for example a CSV file or a Bugzilla export and some folders with the attachments, one folder for each work item, potentially with the work item number as name.
  2. Consolidate the exported data, for example adding a row with the old item number, that gets imported into a read only, maybe even hidden attribute in the new work item to indicate the source and make the attachment import easier.
  3. Import the textual work item data, using Bugzilla or CSV import.
  4. Export the work item data into a CSV file and cut that down to the columns (new) work item ID and original Item ID.
  5. Run a second pass and use this file as an input to feed the attachment upload run with information.
  6. Run post processing to ‘massage’ the work item, e.g. create a URI reference to the source item, change attribute values etc.

So the code below is modified in a way that it assumes the work item is already created and all that needs to be done is to upload an attachment. It looks up the work item using the ID and uses the operation to update the work item. I leave the fun part – reading the CSV file and finding the attachment folder – to the reader, to keep the complexity low.

Apologies for the formatting in this blog, I just can’t get it to display as I want. You might want to paste the following code into some better editor and examine it more closely. So here is the code, some explanations later:

package com.ibm.js.team.workitem.plainjava.example;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.links.common.IItemReference;
import com.ibm.team.process.client.IProcessClientService;
import com.ibm.team.process.common.IProjectArea;
import com.ibm.team.repository.client.ITeamRepository;
import com.ibm.team.repository.client.ITeamRepository.ILoginHandler;
import com.ibm.team.repository.client.ITeamRepository.ILoginHandler.ILoginInfo;
import com.ibm.team.repository.client.TeamPlatform;
import com.ibm.team.repository.common.IContent;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.client.IWorkItemClient;
import com.ibm.team.workitem.client.WorkItemOperation;
import com.ibm.team.workitem.client.WorkItemWorkingCopy;
import com.ibm.team.workitem.common.model.IAttachment;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.WorkItemEndPoints;
import com.ibm.team.workitem.common.model.WorkItemLinkTypes;

/**
* Example code, see
* https://jazz.net/wiki/bin/view/Main/ProgrammaticWorkItemCreation.
*/
public class ModifyWorkItemUploadAttachmentOperation {

	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;
		}
	}

	private static class WorkItemUploadAttachmentModification extends WorkItemOperation {

		private String fFileName;
		private String fContentType;
		private String fEncoding;

		public WorkItemUploadAttachmentModification(String fileName, String contentType, String encoding) {
			super("Initializing Work Item",IWorkItem.FULL_PROFILE);
			fFileName=fileName;
			fContentType=contentType;
			fEncoding=encoding;
		}

		@Override
		protected void execute(WorkItemWorkingCopy workingCopy,
				IProgressMonitor monitor) throws TeamRepositoryException {
			try {
				attachFile(workingCopy, fFileName, fContentType, fEncoding, monitor);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		private static void attachFile( WorkItemWorkingCopy workingCopy, String name, String contentType, String encoding, IProgressMonitor monitor)
				throws TeamRepositoryException, IOException {
			File attachmentFile = new File(name);
			FileInputStream fis = new FileInputStream(attachmentFile);

			IWorkItem workItem = workingCopy.getWorkItem();
			IWorkItemClient workItemClient = (IWorkItemClient) ((ITeamRepository)workItem.getOrigin()).getClientLibrary(IWorkItemClient.class);
			try {
				IAttachment newAttachment = workItemClient.createAttachment(
					workItem.getProjectArea(), attachmentFile.getName(), "", contentType,
					encoding, fis, monitor);

				newAttachment = (IAttachment) newAttachment.getWorkingCopy();
				newAttachment = workItemClient.saveAttachment(newAttachment, monitor);
				IItemReference reference = WorkItemLinkTypes.createAttachmentReference(newAttachment);
				workingCopy.getReferences().add(WorkItemEndPoints.ATTACHMENT, reference);
			} finally {
				if (fis != null) {
					fis.close();
				}
			}
		}
	}

	public static void main(String[] args) {
		boolean result;
		TeamPlatform.startup();
		try {
			result = run(args);
		} catch (TeamRepositoryException x) {
			x.printStackTrace();
			result = false;
		} finally {
			TeamPlatform.shutdown();
		}

		if (!result)
			System.exit(1);
	}

	private static boolean run(String[] args) throws TeamRepositoryException {

		if (args.length != 5) {
			System.out.println("Usage: ModifyWorkItem ");
			return false;
		}

		String repositoryURI = args[0];
		String userId = args[1];
		String password = args[2];
		String projectAreaName = args[3];
		String idString = args[4];

		ITeamRepository teamRepository = TeamPlatform.getTeamRepositoryService().getTeamRepository(repositoryURI);
		teamRepository.registerLoginHandler(new LoginHandler(userId, password));
		teamRepository.login(null);

		IProcessClientService processClient = (IProcessClientService) teamRepository.getClientLibrary(IProcessClientService.class);
		IWorkItemClient workItemClient = (IWorkItemClient) teamRepository.getClientLibrary(IWorkItemClient.class);

		URI uri = URI.create(projectAreaName.replaceAll(" ", "%20"));
		IProjectArea projectArea = (IProjectArea) processClient.findProcessArea(uri, null, null);
		if (projectArea == null) {
			System.out.println("Project area not found.");
			return false;
		}

		int id = new Integer(idString).intValue();
		IWorkItem workItem = workItemClient.findWorkItemById(id, IWorkItem.FULL_PROFILE, null);

		WorkItemUploadAttachmentModification operation = new WorkItemUploadAttachmentModification("TestAttachment.txt",IContent.CONTENT_TYPE_TEXT,IContent.ENCODING_UTF_8);
		operation.run(workItem, null);

		WorkItemUploadAttachmentModification operation1 = new WorkItemUploadAttachmentModification("Test.pdf",IContent.CONTENT_TYPE_UNKNOWN,IContent.ENCODING_UTF_8);
		operation1.run(workItem, null);

		System.out.println("Modified work item " + workItem.getId() + ".");
		teamRepository.logout();

		return true;
	}
}

The code basically uses an inner class extending WorkItemOperation to do the modification. The beauty of this is, that this operation does all the complex handling to get a WorkItemWorkingCopy and saving for us and we just need to upload the attachment.

Lets look at the constructor and the execute method.

private static class WorkItemUploadAttachmentModification extends WorkItemOperation {

private String fFileName;
private String fContentType;
private String fEncoding;

public WorkItemUploadAttachmentModification(String fileName, String contentType, String encoding) {
	super("Initializing Work Item",IWorkItem.FULL_PROFILE);
	fFileName=fileName;
	fContentType=contentType;
	fEncoding=encoding;
}

@Override
protected void execute(WorkItemWorkingCopy workingCopy, IProgressMonitor monitor) throws TeamRepositoryException {
	try {
		attachFile(workingCopy, fFileName, fContentType, fEncoding, monitor);
	} catch (IOException e) {e.printStackTrace();}
}

The constructor calls the constructor of the super class, passing the work item load profile IWorkItem.FULL_PROFILE. Then it puts the data such as file name, content type and encoding into fields.

The execute method basically calls a helper method to attach the file. Exceptions thrown are just caught. This should be enhanced of course.

The upload happens in this code

private static void attachFile( WorkItemWorkingCopy workingCopy, String name, String contentType, String encoding, IProgressMonitor monitor) throws TeamRepositoryException, IOException {
	File attachmentFile = new File(name);
	FileInputStream fis = new FileInputStream(attachmentFile);
	IWorkItem workItem = workingCopy.getWorkItem();
	IWorkItemClient workItemClient = (IWorkItemClient) ((ITeamRepository)workItem.getOrigin()).getClientLibrary(IWorkItemClient.class);
	try {
		IAttachment newAttachment = workItemClient.createAttachment(workItem.getProjectArea(), attachmentFile.getName(), "", contentType, encoding, fis, monitor);
		newAttachment = (IAttachment) newAttachment.getWorkingCopy();
		newAttachment = workItemClient.saveAttachment(newAttachment, monitor);
		IItemReference reference = WorkItemLinkTypes.createAttachmentReference(newAttachment);
		workingCopy.getReferences().add(WorktemEndPoints.ATTACHMENT, reference);
	} finally {
		if (fis != null) {
			fis.close();
		}
	}
}

The code opens a FileInputStream to the file. Then it retrieves a IWorkItemClient client library that is used to do all the manipulations.
Attaching the file is done by creating a new attachment, getting a working copy of it and then saving the new attachment. The file is uploaded in this process. Last but not least the code creates a reference to the new attachment and stores it in the work item.

Calling the operation is simple.

int id = new Integer(idString).intValue();
IWorkItem workItem = workItemClient.findWorkItemById(id, IWorkItem.FULL_PROFILE, null);

WorkItemUploadAttachmentModification operation = new WorkItemUploadAttachmentModification("TestAttachment.txt",IContent.CONTENT_TYPE_TEXT,IContent.ENCODING_UTF_8);
operation.run(workItem, null);

Find the work item that the attachment belongs to by its ID. Create a new operation and run it with the workitem that we have found.

There are more content types and for example PDF would probably not work as text. Play around with the available content types and see what happens. Here an example I used for PDF.

WorkItemUploadAttachmentModification operation1 = new WorkItemUploadAttachmentModification("Test.pdf",IContent.CONTENT_TYPE_UNKNOWN,IContent.ENCODING_UTF_8);
operation1.run(workItem, null);

It is easy enough to enhance the code for other purposes. Please remember that there is few error handling at this point. You might want to enhance this.

Advertisements

About rsjazz

Hi, my name is Ralph. I work for IBM and help colleagues and customers with adopting the Jazz technologies.
This entry was posted in RTC, RTC Extensibility and tagged , , , , . Bookmark the permalink.

22 Responses to Uploading Attachments to Work Items

  1. Pingback: Using Work Item Queries for Automation | rsjazz

  2. James T says:

    Very helpful article – thanks for posting

  3. Alexander says:

    Hi,

    is it possible to upload attachments by using REST API?

    • rsjazz says:

      Hi Alexander, as stated in the text above, we talked to our development about that. We were told, it is possible to do that but it is not trivial. There is no documentation and the implementation might change in the future. I know of another colleague who traced the HTML requests and figured the pattern. So, it should be doable with HTML (not sure about REST), but it is probably not trivial and undocumented.

      I would suggest to write an enhancement request for it. Several people have asked for it, so it would be worth it.

  4. Hongwu Wang says:

    Hi Ralph,
    Could you help me on how to upload attachments from server? The createAttachment method seems quite different between client and server, I couldn’t find how to do that on the server.

    Thanks.

    • rsjazz says:

      Hi Hongwu,

      I have not done that myself. Here some hints you can try out.

      The createAttachment() is only available on the IWorkItemClient. I have not found something similar in the common or the server API. However if you open com.ibm.team.workitem.client.internal.WorkitemClient which is the implementation of the interface, you can find how this is implemented. You realize that createAttachment uses a WorkitemCommon method that is not exposed in the interface, however, all that is done is common API and available on the server. You could try to use the code in the WorkItemClient against WorkItemCommon. Best suggestion I have.

      • Hongwu Wang says:

        Hi Ralph,

        Thanks for the info. The createAttachment() on IWorkItemServer only accept two parameters (projectArea, monitor), so I don’t understand what it’s used for and how to attach the file. I will try to look into WorkitemCommon method.

        Thanks again for your help.

      • rsjazz says:

        The method only needs two parameters, because all the other parameters are used in the calling one. The Method basically creates an attachment without content and properties. The properties are set in the calling method.

  5. john says:

    Is there any sample code for me to add WorkItem through plain java client library?

    Many thanks!

  6. Sandi Mayo says:

    What would I need to do to make sure the attachment is text searchable? My users need to be able to do filters that will find text strings in the attachments to be able to find the appropriate work items that the attachments are attached to. Thx!

  7. Uma Rajaram says:

    Hi,

    Is it possible to Download and Upload the attachment using the OSLC API

    • rsjazz says:

      As I mention in that post, I am not aware of an official way to upload attachments using an OSLC like interface. You can, and others have, trace and reverse engineer the protocol that is used by the Java API or the Web UI and use that. It is unsupported of course.

  8. Jafar says:

    Hi Ralph,

    First I would like to thank you. All your blogs are extremely helpful to my team.

    We were following your code example for uploading asset and facing a permission Exception at the line
    IAttachment newAttachment = workItemClient.createAttachment(

    and the exception is
    “CRJAZ6053E The ‘Save Attachment’ operation cannot be completed. Permission is required to complete the operation.”

    What we can’t understand is we successfully upload and save attachments using the web front-end with the same user.

    can you put some light on what type of extra permission to achieve this?
    Our User has all the Save permission at work item level.

    We are using RTC 6.0.

    Thanks In advance.

    • rsjazz says:

      Jafar,

      I can’t reproduce this behavior and I don’t have time to dig deeper either. Make sure the user can upload manually and then debug the code. Maybe this is caused by a different exception.

      The code I am using is

      private void attachFile(String fileName, String description,
      String contentType, String encoding) throws TeamRepositoryException {

      File attachmentFile = new File(fileName);
      FileInputStream fis;
      try {
      fis = new FileInputStream(attachmentFile);
      try {
      IAttachment newAttachment = getWorkItemClient()
      .createAttachment(getWorkItem().getProjectArea(),
      attachmentFile.getName(), description,
      contentType, encoding, fis, monitor);

      newAttachment = (IAttachment) newAttachment.getWorkingCopy();
      newAttachment = getWorkItemCommon().saveAttachment(
      newAttachment, monitor);
      IItemReference reference = WorkItemLinkTypes
      .createAttachmentReference(newAttachment);

      getWorkingCopy().getReferences().add(
      WorkItemEndPoints.ATTACHMENT, reference);

      } finally {
      if (fis != null) {
      fis.close();
      }
      }
      } catch (FileNotFoundException e) {
      throw new WorkItemCommandLineException(
      “Attach File – File not found: ” + fileName, e);
      } catch (IOException e) {
      throw new WorkItemCommandLineException(
      “Attach File – I/O Exception: ” + fileName, e);
      }
      }

  9. Ralph says:

    hello Ralph,
    we are running the ModifyWorkItemUploadAttachmentOperation.java, but it occurred the (ModifyWorkItemUploadAttachmentOperation.java:123) error,
    May you advise how we can make the TeamPlatform running?
    herewith attach the error for your reference

    Thanks.
    Ralph

    Exception in thread “main” java.lang.NoClassDefFoundError: com.ibm.team.calm.foundation.common.internal.rest.dto.RestPackage
    at com.ibm.team.workitem.common.internal.web.rest.dto.impl.RestPackageImpl.init(RestPackageImpl.java:457)
    at com.ibm.team.workitem.common.internal.web.rest.dto.RestPackage.(RestPackage.java:69)
    at java.lang.J9VMInternals.initializeImpl(Native Method)
    at java.lang.J9VMInternals.initialize(J9VMInternals.java:199)
    at sun.misc.Unsafe.ensureClassInitialized(Native Method)
    at sun.reflect.UnsafeFieldAccessorFactory.newFieldAccessor(UnsafeFieldAccessorFactory.java:37)
    at sun.reflect.ReflectionFactory.newFieldAccessor(ReflectionFactory.java:134)
    at java.lang.reflect.Field.acquireFieldAccessor(Field.java:945)
    at java.lang.reflect.Field.getFieldAccessor(Field.java:912)
    at java.lang.reflect.Field.get(Field.java:371)
    at com.ibm.team.repository.common.internal.util.InternalTeamPlatform$1.handleExtensionAdded(InternalTeamPlatform.java:284)
    at com.ibm.team.repository.common.util.ExtensionReader.internalReadElement(ExtensionReader.java:202)
    at com.ibm.team.repository.common.util.ExtensionReader.readRegistry(ExtensionReader.java:350)
    at com.ibm.team.repository.common.util.ExtensionReader.start(ExtensionReader.java:380)
    at com.ibm.team.repository.common.util.ExtensionReader.earlyStart(ExtensionReader.java:127)
    at com.ibm.team.repository.common.internal.util.InternalTeamPlatform.initializeGeneratedPackages(InternalTeamPlatform.java:317)
    at com.ibm.team.repository.common.internal.util.InternalTeamPlatform.start(InternalTeamPlatform.java:108)
    at com.ibm.team.repository.client.TeamPlatform.startup(TeamPlatform.java:50)
    at com.ibm.js.team.workitem.automation.examples.ModifyWorkItemUploadAttachmentOperation.main(ModifyWorkItemUploadAttachmentOperation.java:123)
    Caused by: java.lang.ClassNotFoundException: com.ibm.team.calm.foundation.common.internal.rest.dto.RestPackage
    at java.net.URLClassLoader.findClass(URLClassLoader.java:432)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:676)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:358)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:642)
    … 19 more

    • rsjazz says:

      I think you have an issue with the plain java client libraries not being in the class path, using a wrong Java version or something like that. I don’t see a way for me to help you to solve that.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s