You are not logged in. Click here to log in.

codeBeamer ALM

Search In Project

Search inClear

codeBeamer Listener API

This functionality can only be used in conjunction with Intland professional services. To become part of the early release community please contact sales@intland.com.

Overview

Listeners give you the power of extending or customizing codeBeamer by running your plug-in code in a controlled and well-defined way inside the codeBeamer server. The clear interface between the codeBeamer core and your custom code will ensure portability for future versions of codeBeamer. The codeBeamer event listener architecture is based on the well-known and established “Observer” design pattern. An explanation of the pattern is available here: http://en.wikipedia.org/wiki/Observer_design_pattern. Developers that have worked with Swing, SAX XML parsers, windowing toolkits or any other event-driven system should be already familiar with this pattern.

Figure: codeBeamer Event Listeners

Listeners in codeBeamer

You can write so-called listener classes by implementing Java interfaces defined by codeBeamer and register these listeners either declaratively in an XML configuration file or programmatically in Java code, to the centralized event source called EventMulticaster in codeBeamer. At startup time, codeBeamer will automatically instantiate your listener classes, and later notify those objects by calling one of their event handler methods when an appropriate event occurs. codeBeamer will notify the listener objects by detecting what listener interface or interfaces they implement. For example if you write a TrackerItemListener, it will be notified about events related to tracker items.

The listener interfaces defined by codeBeamer in the com.intland.codebeamer.event package, so the API documentation of this package will provide you with all the information you need. As a short summary, you can handle the following events:

  • CRUD operations (create, read, update, delete) related to any of the following entities: artifacts, associations, forums, projects, tracker item attachments, tracker item comments, tracker items, trackers and users
  • modification committed event in the SCC listener
  • “artifact opened” event in the artifact listener
  • “transition taken” event in the workflow listener

Please note that the listeners’ code run in the same JVM like the codeBeamer core, so bugs in the listeners can lead to serious system failures. Before putting to production, your listeners need to be carefully tested. It is recommended to extensively test for any kind of exceptions that the listener code might raise.

As a rule of thumb the code in the interface methods should be wrapped in a try-catch block. All Exception instances should be catched here and a VetoException should be thrown. This ensures that regardless of what unpredictable internal exceptions the listener might raise (e.g. java.lang.NullPointerException), in the worst case the event vetoed. Please consult the Quick Start section for more information on this.

Supported Events

Use Case Examples

This section shows some real-life examples to give some insights how you can customize your codeBeamer using the listener architecture. You can use listeners for generic notification purposes. For example, you can generate and publish up-to-date RSS/Atom feeds about the tracker items entered. In this case, you simply implement RssGeneratorTrackerItemListener which can use Rome (https://rome.dev.java.net/) to generate feed entries in the identifiableCreated()event handler method.

Another good example is virus checking on the document uploads. You implement your custom VirusCheckerArtifactListener. In its identifiableCreated() handler, you pass the uploaded document to the virus checker module and then use the result returned by this. If the document was found infected, you can throw a VetoException from the event handler which will cause rejecting the upload and the document will not appear in the repository. See more about “vetoing” in the next section.

You can extend codeBeamer with your custom validation rules. For example, you don’t allow deleting a tracker item unless its status is “Closed”. All you have to do is implement a StatusCheckerTrackerItemListener, and check the trackerItem.status property in its identifiableDeleted()handler. If it’s not closed, you can choose to veto the deletion. Similarly, you can cancel any other entity CRUD operation, thus custom-tailoring codeBeamer to your internal business rules. The listeners can be used also as enterprise integration platform by implementing reliable, high performance, asynchronous messaging to external systems with JMS (Java Message Service): a codeBeamer listener can connect to a standard JMS message queue and send JMS messages to this. All the external subscribers will be notified whenever some event happens in codeBeamer and can react to this immediately.

Basic Concepts

Listener Types

The codeBeamer Listener API provides a set of listener interfaces for implementing event handlers for the various entities from the repository.

The listeners are located in the com.intland.codebeamer.event package. All listeners derive from the com.intland.codebeamer.event.EventListener interface. This interface provides methods for handling pre-creation, pre-deletion and pre-update vetoable events. The following listener types are available in the codeBeamer Listener API:

  • ArtifactListener - provides additional notification for artifact opening
  • AssociationListener
  • ForumListener
  • ForumPostListener
  • ProjectListener
  • SccListener - provides an additional method for handling commit events
  • TrackerListener
  • TrackerItemListener - additionally a method is available for post-item removal logic
  • TrackerItemAttachmentListener
  • TrackerItemCommentListener
  • UserListener
  • WorkflowListener

Please consult the API docs for more details on the listeners.

Pre- and Post Events

By timing, there are two types of events in codeBeamer. “Pre” type events occur before the actual operation, while “post” type events occur after that. It means that the regular sequence when creating a tracker looks like this:

  • all tracker listeners receive the “pre tracker created” event
  • if vetoed, the operation is rejected, end
  • the tracker gets created
  • all tracker listeners receive the “post tracker created” event
  • end

Vetoing

Vetoing means that a listener can actually cancel an operation while the operation is in its “pre” phase. All the listener has to do is throwing a VetoException. When codeBeamer catches that exception, the operation will be cancelled. In other words, an operation is performed only if none of the registered listener has thrown the VetoException. For obvious reasons, an operation cannot be cancelled in its “post” phase. Please note that vetoing is a powerful mechanism, you have to use with special care, as it can lead to lost information.

Figure: Vetoable listeners behavior for pre-events

The above figure shows that if the operation is vetoed by the custom listener then the initiating business flow will be cancelled.

Quick Start

Implementing a Logger Listener

The following listing shows a trivial implementation of a listener that logs any event happening. You can use this as template code:

public class LoggerListener implements ArtifactListener, ForumListener,
                                       ProjectListener, TrackerListener,
                                       TrackerItemListener, UserListener {
  final static protected Logger log
                          = Logger.getLogger(LoggerListener.class);

  public void identifiableCreated(BaseEvent event) {
    log.debug("identifiableCreated: " + getEventDescriptor(event));
  }

  public void identifiableUpdated(BaseEvent event) {
    log.debug("identifiableUpdated: " + getEventDescriptor(event));
  }

  public void identifiableDeleted(BaseEvent event) {
    log.debug("identifiableDeleted: " + getEventDescriptor(event));
  }

  public void artifactOpened(BaseEvent event) {
    log.debug("artifactOpened: " + getEventDescriptor(event));
  }

  public void trackerItemRemoved(BaseEvent event) {
    log.debug("trackerItemRemoved: " + getEventDescriptor(event));
  }

  protected String getEventDescriptor(BaseEvent event) {
    return (event.isPreEvent() ? "PRE " : "POST ") + event.getUser()
           + " ["+ event.getRemoteAddress() +"] on "+ event.getSource();
  }
}

Note that for logging the Log4j library is used. By doing this logs will be output to the codeBeamer log file.

Implementing adding a user automatically at project creation

When you want to add a user as "super project administrator" to each newly created project you can use the following template code:

package com.intland.codebeamer.event.impl.sample;

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

import org.apache.commons.lang.StringUtils;

import com.intland.codebeamer.event.BaseEvent;
import com.intland.codebeamer.event.impl.DefaultProjectListener;
import com.intland.codebeamer.manager.ProjectManager;
import com.intland.codebeamer.persistence.dto.GroupDto;
import com.intland.codebeamer.persistence.dto.ProjectDto;
import com.intland.codebeamer.persistence.dto.RoleDto;
import com.intland.codebeamer.persistence.dto.UserDto;
import com.intland.codebeamer.persistence.util.PersistenceUtils;
import com.intland.codebeamer.remoting.GroupType;
import com.intland.codebeamer.security.acl.Acl;

/* Example in general.xml:
	<listener class="com.intland.codebeamer.event.impl.sample.AssignAdminAccountToNewProjectListener" id="assignAdminAccountToNewProjectListener">
		<init-param>
			<param-name>accountId</param-name>
			<param-value>1 44</param-value><!-- user account IDs or user names separated by space or ';' character -->
		</init-param>
	</listener>
*/

/**
 * Assigns a list of accounts with "project admin" role to
 * a newly created project.
 *
 * @author <a href="mailto:zsolt.koppany@intland.com">Zsolt Koppany</a>
 */
public class AssignAdminAccountToNewProjectListener extends DefaultProjectListener {
	private final static ProjectManager projectManager = ProjectManager.getInstance();

	public void identifiableCreated(BaseEvent event) {
		if (event.isPreEvent()) {
			return;
		}

		UserDto user = event.getUser();
		ProjectDto project = (ProjectDto)event.getSource();
		GroupDto projectGroup = Acl.findGroup(project.getId().intValue(), GroupType.PROJECT);
		RoleDto projectAdminRole = Acl.findRole(RoleDto.PROJECT_ADMIN, projectGroup);

		String accountIds = getProperty("accountId");
		if (StringUtils.isNotBlank(accountIds)) {
			List users = new ArrayList();
			for (StringTokenizer st = new StringTokenizer(accountIds, " ;", false); st.hasMoreTokens(); ) {
				String accountId = st.nextToken().trim();

				UserDto account = Acl.findUser(accountId);

				if (account == null) {
					log.warn("Cannot find accountId: " + accountId);
					continue;
				}

				users.add(account);
			}

			try {
				projectManager.addUser(user, project.getId(), users, PersistenceUtils.createSingleItemList(projectAdminRole));
			} catch (Exception e) {
				log.warn("Assigning project admin failed", e);
			}
		}

		super.identifiableCreated(event);
	}
}

Checking Uploaded Documents for Viruses

A very useful extension of codeBeamer is the addition of virus checking for documents. This check needs to be done at upload time for both document creation and update.

To do this the com.intland.codebeamer.event.ArtifactListener interface needs to be implemented. In this listener the both the identifiableCreated(BaseEvent event) and identifiableUpdated(BaseEvent event) methods need to call a virus checking routine. A dummy implementation of the method follows:

protected void checkArtifact(BaseEvent event) throws VetoException {
  Object source = event.getSource();

  if(source instanceof ArtifactDto) {
    ArtifactDto artifact = (ArtifactDto)source;

    // The 'v' character in the name signals a virus
    if(artifact.getName().toLowerCase().indexOf('v') != -1) {
      log.error(getClass() + " has detected a virus in file "
                           + artifact.getName());
      log.error("Upload aborted.");

      // Virus detected
      throw new VetoException();
    } else {
      // The file is fine
      log.info("Artifact successfully created for file "
               +artifact.getName());
    }
  } else {
    log.error("Invalid artifact object of type " + source.getClass());
  }
}

The ArtifactDto instance contains all the information related to the uploaded document. A simple check is being made regarding the occurence of the v character in the file name. Virus detection is signalled by throwing a VetoExceptioninstance.

Creating Tracker Item Validation Rules

The codeBeamer Listener API can be used for implementing custom fine-grained validation rules for tracker items. In this section a rule is created for allowing the deletion of only the closed tracker items.

The TrackerItemListener interface suits this purpose. From this interface the identifiableDeleted(BaseEvent) method is overridden as follows:

public void identifiableDeleted(BaseEvent event) throws VetoException {
  try {
    Object source = event.getSource();

    if(source instanceof TrackerItemDto) {
      TrackerItemDto item = (TrackerItemDto)source;

      if(item.getStatus().getName() == null
         || !item.getStatus().getName().toLowerCase().equals("closed")) {
        log.info("Tracker item " + item.getId()
                 + " is not closed. Deletion aborted.");

        // Cannot delete an unclosed tracker item
        throw new VetoException();
      } else {
        // Tracker item can be deleted
        log.info("Tracker item "  + item.getId() + " successfully deleted.");
      }
    } else {
      log.warn("Invalid tracker object type " + source.getClass());
    }
  } catch(Exception ex) {
    // Make sure that all exceptions are caught
    throw new VetoException();
  }
}

The source of the event is a TrackerItemDto object. Unclosed tracker items are not allowed to be deleted by throwing a VetoExceptioninstance. As a safeguard, all exceptions are caught inside the handler method to not propagate any errors further.

Registering and Deploying Listeners

You can register listeners declaratively or programmatically.

Declarative Registration

This section explains how to register listeners in the CB_HOME/tomcat/webapps/cb/WEB-INF/classes/my-applicationContext.xml configuration file. This file configures the Spring Framework-managed beans that compose the application itself. If you want to know more about Spring and its bean factories, please read this page. If all you want to do is adding some custom listeners, you can skip reading that page, just read on here.

First you have to declare your listener bean:

<bean id="exampleListener" class="com.mycompany.cb.ExampleListener"></bean>

Make sure your class file is available on the classpath. Declarations can appear in any order, the file structure is flexible until it is kept a syntactically correct XML file.

CB-6.0 and older:

You also have to add your listener to the tail of this list:

<bean id="myListenerListFactory" parent="listenerListFactory">
  <property name="sourceList">
    <list merge="true">
      <!-- ... some other beans referenced here -->
      <ref bean="exampleListener"></ref>
    </list>
  </property>
</bean>


Finally, activate this bean declaration if this is commented out. Until this commented out (meaning that this is inactive), your other changes are ignored:

<bean id="eventMulticaster" class="com.intland.codebeamer.event.EventMulticaster" factory-method="getInstance" lazy-init="false" >
  <property name="listeners"><ref bean="myListenerListFactory"></ref></property>
</bean>

CB-6.1 and newer:

Listeners declared in my-applicationContext.xml will be registered automatically.

You have to remove any old

  • <bean id="myListenerListFactory" ...> and
  • <bean id="eventMulticaster" ...>

entries from my-applicationContext.xml, otherwise codeBeamer will not start at all!

When finished, don't forget to restart CB.

Migrating Listener Declarations from CB 5.5 (or Earlier) to CB 5.5.1 (or Later)

Prior to codeBeamer 5.5.1, listeners were registered in general.xml. For technical reasons, in all versions starting from 5.5.1 they are registered in my-applicationContext.xml, which also means that existing listener registrations needs to be converted. Converting the old-style declarations in general.xml to the new-style declarations in my-applicationContext.xml is fairly trivial, but here is a example to help your migration.

For each declaration like this in general.xml:

<listener id="myExistingListener" class="com.mycompany.cb.ExistingListener" />

...add its replacement to my-applicationContext.xml:

<bean id="myExistingListener" class="com.mycompany.cb.ExistingListener"/>

Then delete the original declaration from general.xml. You can actually delete the whole <listeners> group, as all listener need to be moved to my-applicationContext.xml.

Finally, uncomment the "eventMulticaster" bean in my-applicationContext.xml as it is written in the previous section.

Then restart CB and that's it!

Programmatic Registration

However the recommended way is using the declarative listener registration, it is possible to instantiate your listeners for yourself and register them using the following code snippet:

Listener myCustomListener = new MyCustomListener();
// …
EventMulticaster.getInstance().addListener(myCustomListener);

As you can see, EventMulticasteris a singleton in codeBeamer, because to avoid confusion there must a single centralized event source in the system.

Listener Deployment

The binary code for the listeners has to be made available for the codeBeamer installation. These are the two preferred ways for doing this: copy the class files to the <codeBeamer Root>/tomcat/webapps/cb/WEB-INF/classes directory package the classes into a JAR file and place it in the <codeBeamer Root>/tomcat/ webapps/cb/WEB-INF/lib directoryIn either case the classes will be automatically made available in the classpath. There are other ways of adding the listener classes to the classpath, however the above mentioned ones are recommended.

Frequently Asked Questions

  • What should I do in the event handlers that are insignificant for me, but which I am forced to implement?

You can simply leave those method bodies empty, it has no side-effects at all.

  • Can I safely override the default listeners shipped with codeBeamer?

No. codeBeamer relies on actively using its own listeners to implement several mechanisms. Disabling the default listeners might result in undefined behavior and data failure.

  • Is the order of the listeners calls defined by their list in general.xml?

No, there is absolutely no guarantee for which order they receive the notifications. Please don not rely on this.

  • If a listener vetoes an operation, will the other listeners get any kind of notification about this?

No. If the listeners are normally notified in this order A, B, C, D and B vetoes the operation, then:

  • A will not know that B vetoed it
  • C and D will not be called at all

This is a design decision to keep the behavior simple, but powerful enough for most real-life applications.

Adding Indexers to codeBeamer

codeBeamer indexes for search Excel, MS-Word, Power-Point, RTF, PDF, Html, xml, Wiki and Plain text documents and attachments.

Following steps must be executed to add additional Indexers:

A java class must implement the interface com.intland.codebeamer.search.handler.DocumentHandler and the Indexer must be added to /install_dir/tomcat/webapps/cb/WEB-INF/classes/general.xml to get it known for codeBeamer.

The example below shows the plain-text Indexer assigned to the mime-type text/plain:

public class PlainTextHandler implements DocumentHandler {
	private String encoding = null;

	public Document getDocument(InputStream is) throws DocumentHandlerException {
		try {
			String body = Common.readFileToString(is, getEncoding());
			if (body != null && body.length() != 0) {
				Document doc = new Document();
				doc.add(new Field(Fields.BODY, body, Field.Store.NO, Field.Index.TOKENIZED));
				return doc;
			}
			return null;
		} catch (IOException e) {
			throw new DocumentHandlerException(e);
		}
	}

	public String getEncoding() {
		return encoding;
	}

	public void setEncoding(String enc) {
		encoding = StringUtils.trimToNull(enc);
	}
} 

The appropriate entry in general.xml:

<mime-mapping>
	<class>com.intland.codebeamer.search.handler.types.text.PlainTextHandler</class>
	<mime-type>text/plain</mime-type>
</mime-mapping>

The indexer class must be in CLASSPATH of codeBeamer (for example under /install_dir/tomcat/webapps/cb/WEB-INF/classes) and after modifying general.xml Codebeamer must be re-started.