# Custom Workflow In TeamForge: A Guide To TeamForge Event Handlers

*Imagine! you could add arbitrary workflow rules to TeamForge…*

TeamForge is definitely one of the most open and expandable ALM platform out there. 
A very powerful mechanism to customize its behavior is so called 
custom event handlers which allow you to intercept arbitrary TeamForge events, block them 
if necessary or trigger follow up actions. To name couple of scenarios:

- Block project deletion for all users
- Add a comment to a tracker item whenever an association has been modified
- or come up with your own tracker workflow engine 

TeamForge custom event handlers let you handle all that. 

TeamForge custom event handlers are part of the Integrated Application Framework (IAF) introduced in 
TeamForge 5.4.

In this guide you will learn

- How custom event handlers interact with TeamForge
- How to write your own custom event handlers (including best practices)
- How to benefit from custom event handlers without having to write a single line of Java
- How to avoid common pitfalls in event handler design
- How to trouble shoot your event handlers
- How you can help us improving our existing documentation on event handlers

In addition, this guide also details about couple of examples that you can put into action straight away.

## Prerequisites
Writing custom event handlers requires at least some basic knowledge in the Java programming 
language. In addition, having knowledge of any script language that is installed 
on the TeamForge server (shell, Python, Perl, …) will also help.

## Introduction
TeamForge’s custom event handling framework implements an extended flavor of the observer pattern. 
It allows third party event handlers to register for TeamForge specific application events and will 
notify them whenever such an event occurs. TeamForge application events are triggered whenever a 
property of a TeamForge object (e.g. tracker item, discussion item, wiki page) has been changed or 
is going to be changed if no event handler objects (i. e. blocks the event).

The framework supports two different types of event handlers : 

1. Asynchronous : In this case, the handler is informed that a change had just happened. The handler 
can decide to trigger further changes by calling TeamForge web services, but it cannot block the change 
because it has already happened.
 
2. Synchronous : In this case, the handler gets informed whenever a change has been anticipated by a 
user. It can examine the properties that should be changed and decide whether to accept the change or 
block it. A synchronous event handler cannot trigger further changes on the currently processed object 
since other handler in the event handler chain must also have the chance to block the anticipated 
change.

Technically, all event handlers have to be part of a Java archive (jar-file) with a TeamForge 
specific deployment descriptor that describes which events should be intercepted. 

This jar file then can be used to create a new customization in TeamForge application 
via Admin -> System Tools -> Customizations.

The event handler will be enabled by default after creating the customization. 
You can disable the same if you just want to keep it but not listen to any events.

## What is inside a custom event handling jar file?

If you extract the jar file, you should see a structure like this:

- com/collabnet/ctf/events : This directory contains the java class and source files of the event 
handler (has to be in that package)
- META-INF/event.xml: This file contains the events and operations, your handler class should intercept
- README.md : This guide!

## What to put inside the event.xml file?
If you open the event.xml file of hook example, you will find 

    <?xml version="1.0"?>
    <!DOCTYPE event-handler
          PUBLIC "-//VA Software, Inc.//DTD Data Object 1.1//EN"
                "http://schema.vasoftware.com/sf/dtd/sf-event-handler_1_0.dtd">
    <event-handler>
          <event api="6.1" mode="asynchronous">
                <type>*</type>
                <operation>*</operation>
                <handler>com.collabnet.ctf.events.AsynchronousHookScriptEventListener</handler>
          </event>
          
          <event api="6.1" mode="synchronous">
                <type>*</type>
                <operation>*</operation>
                <handler>com.collabnet.ctf.events.SynchronousHookScriptEventListener</handler>
          </event>
    
    </event-handler>

This configuration tells the event handling framework that 

1. There are two event listeners : one asynchronous and one synchronous

2. The class AsynchronousHookScriptEventListener is responsible to intercept events of *any* type and 
*any* possible operation. As mentioned earlier, this handler will be called after the event actually 
happened (asynchronous mode) and will be passed in data structures will be compatible with the events 
format introduced in TF 6.0 (IOW, the event handler extends the EventHandler60 class). 

3. The class SynchronousHookScriptEventListener is responsible to intercept events of *any* type and 
*any* possible operation and is called in anticipation of the change.

If you want to intercept certain operations alone, you can specify those instead of the wildcard 
character (*) as shown in case of SynchronousHookScriptEventListener. Supported operations are usually 
*create*, *update*, *move* and *delete* but every event may define its own operations.

That’s pretty much what you can define in event.xml. It is possible to register multiple handlers for 
different events but you could also use one handler to intercept both synchronous and asynchronous 
events. 

By default, the user triggering the event is also the user executing the event handler. If you like to 
run your event handler with a different user account, you can specify this in the user element like

    <event api="6.1" mode="synchronous">
          <type>*</type>
          <operation>*</operation>
          <handler>com.vasoftware.sf.plugin.SynchronousHookScriptEventListener</handler>
          <user>foo</user>
    </event>


Be careful with that option since running code on behalf of a different user opens the door for all 
kind of exploits if you do not check the user’s input properly. Also, It will not allow you to access 
the original user’s session id any more (whereas you can always create a new session with a super user 
account (credentials saved in a property file) if you have to.

## Which Java class do I have to extend for my handler classes?
All event handlers (Synchronous and Asynchronous) have to extend 

    com.vasoftware.sf.events.EventHandler60

EventHandler60 will work for TeamForge version 6.1 onwards. This is how example hook does it:

    package com.collabnet.ctf.events;
    
    public class SynchronousHookScriptEventListener extends EventHandler60 {
        ...
    }

While writing your own event handler, one need EventHandler60 class. Following are the event jar file 
locations across different TF versions.

| TF Version | Jar File |
| ------------- |-------------|
| 6.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/sfevents.jar |
| 6.1.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/sfevents.jar |
| 6.2 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/sfevents.jar |
| 6.2.0.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/sfevents.jar |
| 7.0 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/sfevents.jar |
| 7.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/sfevents.jar |
| 8.0 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/events-api.jar |
| 8.0.0.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/events-api.jar |
| 8.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/events-api.jar |
| 8.1.0.1 | /opt/collabnet/teamforge/dist/deployments/saturn.ear/lib/events-api.jar |
| 16.3 | /opt/collabnet/teamforge/dist/deployments/lib/events-sdk.jar |
| 16.7 onwards | /opt/collabnet/teamforge/dist/deployments/lib/event.jar |

EventHandler60 subclasses has to override:

    @Override
    public void processEvent() throws Exception {
    ...
    }
    
This method does not take any parameters but one can use the other available methods to access required
information about the event that caused the handler invocation. Following are some of the important ones

- getSessionKey: Returns a session id of the user who is going to (synchronous handler) / has triggered
 (asynchronous handler) the event we just intercepted. If you used the user element in event.xml, it 
 will contain a session id for the user you specified there.
- getEventContext: Returns a data structure containing the event type, operation, project, comment and 
user name
- getOriginalData: In case of a synchronous event handler, this will return a representation of the 
object the event is going to change. In case of an asynchronous event handler, this will return the 
representation of the object before it was changed by the event. The data structure used to represent 
the object is the same that would have been used in CollabNet’s SOAP API.
- getUpdatedData: In case of a synchronous event handler, this will return a representation of the 
object how it will look after the event has happened (you can still block it). In case of an 
asynchronous event handler, this will return the representation of the object after it was changed 
by the event.


The last two methods need some additional explanation: 

Let’s assume a user wants to change the priority of a tracker item from 3 to 4. If you have registered 
a synchronous event handler, this one will be triggered before the change will actually be performed. 
getOriginalData will return an ArtifactSoapDO object of the tracker item with the priority field set 
to 3. getUpdatedData will contain an ArtifactSoapDO object of the tracker item with the priority field 
set to 4. If you block the event (by throwing an exception), the change will not happen and the user w
ill be presented an error message (see next section how to influence this error message). 

If you do not block the event (by just returning from the processEvent method), all registered 
*asynchronous* handlers will be called. getOriginalData and getUpdatedData will contain exactly the 
same objects as in the synchronous case. However, the semantic is different: They are no longer 
representing the current and anticipated state but the previous and current state of the object in 
question.

The following java code snippet shows how to retrieve all information available to an event handler:

    String type = getEventContext().getType();
    String operation = getEventContext().getOperation();
    String projectId = getEventContext().getProjectId();
    String comment = getEventContext().getComment();
    String userName = getEventContext().getUsername();
    String originalDataClassName = getOriginalData().getClass().toString();
    String updatedDataClassName = getUpdatedData().getClass().toString();
    Object originalData = getOriginalData();
    Object updatedData = getUpdatedData();

## How can I interact with the TeamForge UI / Block events?
Only synchronous event handler can directly communicate with TeamForge’s UI. The reason is simple: 
If the event has already happened (as it is the case for asynchronous handlers), the user who triggered 
the event may already have been logged out. 

You basically have three independent actions to interact with TeamForge’s UI:

1. Add a success message to the UI that gets displayed as the result of the action just triggered by 
the user: This can be done by calling the addSuccessMessage method of the EventHandler base class 
(see SynchronousHookScriptEventListener.java of example two for details)
2. Add an error message to the UI that gets displayed as the result of the action just triggered by 
the user: This can be done by calling the addErrorMessage method of the EventHandler base class
3. Block the event you intercepted. This can be done by throwing an exception in the processEvent 
method. The payload of your exception will be displayed in the UI.

All three ways of UI feedback can be used in combination: It is possible to display an error message 
although you did not block the event and it is also possible to show many error and success messages 
together.

What happens if the event in question was not triggered by a user logged into TeamForge’s Web UI but 
by a client using TeamForge’s web services? In this case, error and success messages will not reach 
the SOAP client. However, the payload of the exception object thrown when the event was blocked will 
be delivered as part of the SOAP fault element.

While synchronous event handlers enable you to block events and/or to provide additional feedback to 
the currently logged in user, they should not be used to trigger follow up actions (like changing 
TeamForge artifacts or interacting with external systems). Remember that these handlers are running 
in the main TeamForge event loop and nothing else will happen until you return from the processEvent 
method, so, **return as fast as you can**. The next section will cover how to trigger follow up actions in 
asynchronous event handlers.

## How can I trigger follow up actions?
Use only **asynchronous** event handlers (and **not** synchronous event handlers) to communicate with 
TeamForge, external systems, processes or system resources in order **not** to accidentally lock the main 
TeamForge event queue down (and essentially render the system unusable).

Interacting with external systems, processes or resources is done as you would do in any other Java 
program. You may use external libraries in your event handler by placing their .class files into your 
event jar file. The only tricky part is if TeamForge is using a different version of this library 
(which will take precedence). In this case, you would have to recompile your library with a different 
package namespace.

Interacting with TeamForge is done as you would do it if you had to write a Java program that should 
interact with TeamForge using its web services API. The only difference is that you will connect to 
localhost (since your handler is running locally) and that you already have a valid session id. 
You will not have to include the SOAP SDK classes in your event jar file since they are already in 
TeamForge’s class path. The following java code snippets shows how to do it:

    ITrackerAppSoap trackerClient = (ITrackerAppSoap) ClientSoapStubFactory.getSoapStub(
    ITrackerAppSoap.class, "http://localhost:8080");
    ...
    ArtifactSoapDO artifact = trackerClient.getArtifactData(getSessionKey(), originId);
    
    trackerClient.setArtifactData(getSessionKey(), artifact, finalComment, null, null, null);
    
Using the session key provided by the event handler is actually only going to work if the SOAP call 
you are using is not throwing an exception: The session id passed into your handler is associated with 
an already running transaction that will be aborted if an exception is thrown as part of this session. 
Part of rolling back the transaction is rolling back the JVM’s call stack which contains your event 
handling code, so you will not be able to catch the web services exception. If you like to to handle 
web service exceptions, you have to create your own session id by logging into TeamForge again by 
calling ICollabNetSoap.login with some credentials stored as part of your handler (you may store 
them in a property file of the META-INF directory).
    
## Event handler life cycle
For every event that a listener is listening to, a new object that listener class will be instantiated. 
A best practice to avoid costly reinitialization every time (remember that the TeamForge event loop 
thread is blocked while you are doing this) is to delegate all synchronization work to a method you 
always call in your constructor which checks a static variable whether the initialization has already 
been done and if not, just returns without any further action:
    
    private static boolean initialized = false;
    
    public  AsynchronousRelationshipEventListener() {
        initialize();
    }
    
    private synchronized void initialize() {
        if (initialized ) {
            return;
        }
        initialized = true;
        // proceed with initialization
        ...
    }

Logging in into TeamForge / initializing network connections / file resources are costly operations 
that should be handled in such a method instead of doing it all over again.

While it is true that asynchronous handlers may consume considerably more time than synchronous ones, 
there is only one thread for those handlers so events may queue up if you do expensive operations. 
A best practice is to capture the event in your asynchronous event handler, write all necessary 
information to the local file system (comparable to a mail spoiling directory) and return. At the 
same time, you can have a separate application reading from the spooling directory. Doing so, you 
never get into a situation where you miss TF events/things queue up just because you run into a 
blocking operation.

## Event Operation
Now that you know how to intercept events, interact with TeamForge and trigger follow up actions, 
the only remaining question is which events and operations are available and how to inspect them 
properly.

Which events and operations are available?
We already briefly covered the available operations (*create*, *update*, *delete* and *move*) but as 
mentioned before, every event is free to introduce its own operations. The number of events is 
growing with every version of TeamForge. The easiest way to figure out which events exist (and 
which you have to intercept) is to write an asynchronous event handler that intercepts all possible 
events and operations (use * wildcard in event.xml) and write them to a log file. Then, install the 
handler, trigger the event you are interested in and have a look at the log file. Fortunately, the 
event handler from example two (hook script executor) already does something like this:

    String type = getEventContext().getType();
    String operation = getEventContext().getOperation();
    String hookScriptName = hookDirectory + type + "_" + operation;
    log.info("Going to call " + hookScriptName + " if it does exist.");

From the output you can conclude that there are event types *user*, *documentfolder*, *project*, 
*forum*, *tracker*, *role*, *relationship*, *planningfolder*, *artifact*, *repository* 
and many many more.

## Event properties
If you know which event types you are expecting, you can just call the getter functions of the 
original and updated data object after you casted it to the exact class you expected.

    RelationshipSoapDO relationShip = (RelationshipSoapDO) getUpdatedData();
    String originId = relationShip.getOriginKey();
    String targetId = relationShip.getTargetKey();
    String description = relationShip.getDescription();
    
The code above is listening for a relationship event, so we cast it to RelationshipSoapDO 
and have a look at its properties by calling the getOrigin, getTargetKey and getDescription methods. 

If you know the class name of the event type you are expecting, you can also use our Javadocs to 
find out about the provided information.

Following java code shows how to inspect properties of unknown event types in a generic way 
by introspecting the event’s class get – methods, calling them and store the result type in an 
environment variable with the name of the property:

    Class originalDataClass = getOriginalData().getClass();
    Method[] methods = originalDataClass.getMethods();
    for (Method m : methods) {
        // we are only interested in getter methods
        if (!m.getName().startsWith("get")) {
        continue;
    }
    // we are only interested in methods that do not take any parameters
    if (m.getParameterTypes().length != 0) {
        continue;
    }
    // should we only invoke methods with a simple return type?
    Object result = m.invoke(originalData);
    environment.put("tf_original_"+ m.getName().substring(3), result.toString());

The code above stores the properties in system environment variables. This way you can work with 
script files that can work with system environment variables. In other words, you can benefit from the 
custom event handling framework without having to write a single line of Java code.

## About the examples...

### Event Example : Project Create Event Listener

Important Note: This example event handler is not meant for production use as-is.

This example is a minimalistic setup  to listen to Project Creation events.
The listener does not do any ground breaking things but just prints out the information. It also,
sends out an email on project creation.

Do note that this is an Asynchronous event listener. That means it gets invoked
AFTER the project creation.

### Event Example : Association Converter

Important Note: This example event handler is not meant for production use as-is.

This example contains an event handler that adds a comment to a tracker item whenever an association 
is added or deleted to this tracker item. This example shows how to intercept a specific event, 
trigger a follow up action by calling TeamForge’s web services and adds a comment based on the 
formatting template which is specified as part of a property file.

Along with the standard event.xml, this example uses a file called “config.properties” that is used 
to control the formatting of the comment that gets added whenever an association has been modified.
The initialize-method of the handler class (AsynchronousRelationshipEventListener) shows how property 
files can be parsed within a custom event handler.

### Event Example : Repository Create Event Listener

Important Note: This example event handler is not meant for production use as-is.

Another minimalistic example that show cases repository create event listener.

### Event Example : Hook Script
**Important Note:** This example event handler is not meant for production use as-is.

This example is more generic (and more complicated to understand since it is making heavy use of Java 
reflection) and shows how to intercept arbitrary TeamForge events, examine the event’s properties, 
map them to system environment variables and call a script in the file system whose name corresponds 
to the intercepted event (for ex scripts like *project_create*, *tracker_delete* etc...). 

You can use this event handler to customize TeamForge’s behavior without 
any knowledge of the Java programming language as long as you can write scripts in a language that 
can deal with environment variables, write to standard out/error (to influence what will be displayed 
in TeamForge’s UI as result of the handler’s execution) and influence the return code (to decide 
whether to block the event or not).

In a sense, this example basically exposes all TF events to non Java programmers. Whenever a TF event 
arrives, it looks whether there is a script in the TF file system with the name/operation of the event 
and will then call this script with all information from the event contained within environmental 
variables.

The example uses two types of hook scripts; Synchronous and Asynchronous.

### Event Example : Default Tracker Creator
**Important Note:** This example event handler is not meant for production use as-is.

This example show cases the ability of a custom event handler to make both SOAP and REST calls.
Starting from TeamForge 19.1, SOAP session key is enhanced to be used for TeamForge REST calls as well.

This example basically listens for Project Creation event and then creates a new Tracker with randomized
details. In a way like a default templated tracker.

### Event Example : Artifact Update Validator
**Important Note:** This example event handler is not meant for production use as-is.

This example show cases the ability of a custom event handler to validate artifact update activities.
Basically, it makes sure that the users of the site wont be able to update an artifact if its in
'Closed' Meta-status.

This example basically listens for Artifact update events and then fails the update if the Artifact is
in 'Closed' meta-status. User can only update the artifact after re-opening it.

#### Synchronous Hooks

Invoked by a Synchronous event handler, these hooks are called BEFORE an event is completed. If the 
hook script exits with a non-zero error 
status then the event is not allowed. Anything that got printed to STDERR gets printed to the screen 
as an error message to the user. Synchronous hook scripts need to perform well. A badly behaved 
synchronous script can lock whole TF down if it does not quit, to that end please consider deploying 
a wrapper script that terminates your main script after a certain timeout.

#### Asynchronous Hooks
Invoked by an Asynchronous event handler, these hooks are called AFTER an event has taken place, the 
application does not wait at all for these 
to complete and there is no opportunity to provide any feedback into the user interface.

To see what hooks are available all you need to do is look into the server.log. 
Assuming that the log level is set to INFO you’ll see lines like the following:

    Going to call /opt/collabnet/teamforge/hooks/asynchronous/user_update if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/project_update if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/documentfolder_create if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/document_create if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/document_delete if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/documentfolder_delete if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/tracker_create if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/tracker_update if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/tracker_delete if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/role_create if it does exist.
    Going to call /opt/collabnet/teamforge/hooks/synchronous/forum_create if it does exist.

#### Hook script

What Information gets passed to a hook? 
The hooks are initialized with environment variables corresponding to all the available information for 
that event. All the variables carry a tf_ prefix.

The number of environmental variables passed depends from event to event. The easiest way to find out 
would be to create a shell script with following code

    #!/bin/sh
    set > /tmp/hook_output

For ex, in case of the repository_create script, the above code will output following

    tf_operation=create
    tf_original_Id=reps1005
    tf_updated_Version=100
    tf_updated_Title=repos-07-name
    tf_original_Class=class
    com.collabnet.ce.soap50.webservices.scm.RepositorySoapDO
    tf_updated_SystemId=exsy1002
    tf_original_SystemId=exsy1002
    tf_updated_LastModifiedBy=admin
    tf_comment=null
    tf_original_Version=100
    tf_projectId=proj1011
    tf_original_ProjectId=proj1011
    tf_updated_ScmViewerUrl=http://localhost/integration/viewvc/viewvc.cgi/?root=repos-07
    tf_original_Path=projects.project_title/scm.repos_07_name
    tf_original_Title=repos-07-name
    tf_original_IsOnManagedScmServer=true
    tf_updated_ProjectId=proj1011
    tf_original_LastModifiedBy=admin
    tf_updated_IsOnManagedScmServer=true
    tf_originalDataClassName=class
    com.collabnet.ce.soap50.webservices.scm.RepositorySoapDO
    tf_updated_ParentFolderId=ScmApp1005
    tf_original_ScmViewerUrl=http://localhost/integration/viewvc/viewvc.cgi/?root=repos-07
    tf_original_IdRequiredOnCommit=false
    tf_updated_Id=reps1005
    tf_original_SystemTitle=Subversion
    tf_updated_SystemTitle=Subversion
    tf_updated_Path=projects.project_title/scm.repos_07_name
    tf_updated_CreatedBy=admin
    tf_updated_LastModifiedDate=Mon Dec 07 08:15:40 PST 2009
    tf_original_CreatedBy=admin
    tf_updatedDataClassName=class
    com.collabnet.ce.soap50.webservices.scm.RepositorySoapDO
    tf_updated_Class=class
    com.collabnet.ce.soap50.webservices.scm.RepositorySoapDO
    tf_original_LastModifiedDate=Mon Dec 07 08:15:40 PST 2009
    tf_original_RepositoryDirectory=/svnroot/repos-07
    tf_updated_IdRequiredOnCommit=false
    tf_updated_CreatedDate=Mon Dec 07 08:15:40 PST 2009
    tf_original_ParentFolderId=ScmApp1005
    tf_userName=admin
    tf_original_Description=
    tf_original_CreatedDate=Mon Dec 07 08:15:40 PST 2009
    tf_original_ScmAdapterName=Subversion
    tf_updated_Description=
    tf_type=repository
    tf_updated_ScmAdapterName=Subversion
    tf_updated_RepositoryDirectory=/svnroot/repos-07
    
What permissions do I need on the hook script?
Hooks need to be owned by sf-admin for security. And need to have the executable bit set.

Can you give us examples for hook scripts?
1. To configure a site to prevent projects being deleted, we could create the following file: 

        /opt/collabnet/teamforge/hooks/synchronous/project_delete

    that has following code

        #!/bin/sh
        echo Sorry, projects cannot be deleted on this site 1>&2
        exit 1
    
2. To prevent any update of a document stored in TeamForge without providing a version comment, 
we would create the following file: 

        /opt/collabnet/teamforge/hooks/synchronous/document_update

    that has following code

        #!/bin/sh
        if [ $tf_comment="null" ]; then
        echo "Sorry, you have to specify a comment during document update" 1>&2
        exit 1
        fi
        exit 0
    
3. If you like to asynchronously (using nohup and Jenkins CLI) trigger a Jenkins job called TestJob on a 
Jenkins instance running on http://127.0.0.1:8088, whenever a tracker artifact in tracker tracker1011 
gets transitioned to status BUILD APPROVED, we would create the following file : 

        /opt/collabnet/teamforge/hooks/asynchronous/artifact_update

    that has following code

        #!/bin/bash
        # tracker1011 is the tracker id of the tracker you are interested in, change to your specific tracker 
        if [ "$tf_original_FolderId" = "tracker1011" ] then
                if [ "$tf_original_Status" != "BUILD APPROVED" ] && [ "$tf_updated_Status" = "BUILD APPROVED" ]
                then
                        echo "Going to trigger Jenkins job"
                        # Modify Jenkins URL, location of jenkins cli war and job (TestJob) appropriately
                        nohup java -jar /opt/collabnet/teamforge/hooks/asynchronous/jenkins-cli.jar -s http://127.0.0.1:8088/ build TestJob 1>&2 2>/dev/null &
                else
                        echo "Correct tracker but no status transition to trigger a Jenkins build"
                fi
        
        else
                echo "Different tracker, do not inspect status transitions "
        fi
        
4. If you want to create an initial directory structure in an SVN repository once it is created, create 
the following file: 

        /opt/collabnet/teamforge/hooks/asynchronous/repository_create

    with following code

        #!/bin/sh
        /usr/bin/svn mkdir
        http://localhost/svn/repos/${tf_original_RepositoryDirectory:9:999}/trunk
        http://localhost/svn/repos/${tf_original_RepositoryDirectory:9:999}/tags
        http://localhost/svn/repos/${tf_original_RepositoryDirectory:9:999}/branches
        -m "Inital Structure"
        --username admin --password mypassword --non-interactive --no-auth-cache
        exit 0
        
Please note that this concrete event handler is not officially supported by CollabNet and have to be 
used at your own risk. Its main purpose is to give you an idea how custom event handlers can be written.
Feel free to adjust it to your own needs.

There are some caveats of this sample handler you absolutely have to know:

1. The calling and waiting for synchronous hooks currently doesn’t have a timeout. As long as your 
synchronous hook is running, the whole TF site will be blocked for all users accessing the site.
2. Some events trigger other events. For example creating a project actually calls the create project 
hook, wiki page hooks etc etc. Again, badly written or slow hooks could cripple a site.
3. Write your diagnostics messages in a file and not on stdout/stderr since TF does not read from 
stdout/stderr before the script completed which in case of synchronous hooks could leave to a 
situation where the script blocks because the pipe’s buffer between the script process and the 
TF process is completely filled.


## Things to consider

Are there any pitfalls with custom event handlers I should know?
The official documentation already mentions some points you have to be careful about when writing 
custom event handlers. 
- Most importantly, be aware of the fact that while your synchronous event handler
is running, no other events can be processed, so if your code contains any operation that might block, 
you could lock the whole TeamForge site down.

- Your follow up actions may trigger your handler to be called again. You have to protect your handler 
from an infinite update loop in that case. A best 
practice is to add a check to your event handler whether the user initiating the event is the user you 
are using to perform follow up actions.

- You learned that throwing an exception in a synchronous event handler will block the intercepted event 
and roll back the transaction associated with the change. Rolling back transactions also means that the
data the user entered will not be saved. If this happened accidentally due to a wrongly programmed 
event handler, this would be really frustrating to your users, so please make sure that you only 
throw exceptions in your handler code when you really want to enforce the rollback. This statement 
sounds trivial at the first glance but it is quite easy to miss an exception you did not expect 
(like a null pointer exception, parsing exception, time out exception, any other malfunction in 
your own code). A best practice is to introduce a generic catch block in your handler and only 
rethrow the exception if it was an “intended” exception (see SynchronousHookScriptEventListener):

        } catch (Exception e) {
            if (!intendedException) {
                log.error("Exception occured: " + e.getMessage(), e);
            } else {
                ...
                throw e;
            }
        }

Writing event handlers from scratch is really hard because the event handler parser is really picky 
on the exact format of your jar file. A best practice is to base your work on an already existing 
event handler and then adapt it to your own needs by doing incremental changes while checking whether 
it still works.

If you get a message in server.log (or events.log) that your event handler could not be parsed, these 
are some points you might check:

- Did you change the package of your class containing handler code (this is not supported)
- Did you move event.xml to another directory (not supported either)?
- Did you compile your java code with a Java version that is not supported by the JVM running on the 
TeamForge server?
- Did you use a very long file name for your jar file (only 31 characters supported)?
- Did you include libraries in your jar file that have already been part of the default TeamForge 
class path (in this case, your libraries will not be picked up)?
- Did you try to reference internal TF classes from packages other than the SOAP namespace (in this 
case, the TF security classloader will reject your jar file)?
- Did you change the DTD location referenced in events.xml (this will not work)?
- Did you try to trigger a follow up action in a synchronous event handler (this will most likely 
result in a time out exception since your code is already running in a transaction that locks the 
resources you are trying to obtain a lock for)?
- Did you trigger a TeamForge SOAP call with the session id passed to your handler which threw an 
exception (see section “How can I trigger follow up actions for an explanation why this does not 
work”)?
