Injecting Spring AmqpTemplate into HibernateEventListener Instance

Hi Folks,

I'm trying to build an EventListener that sends messages to a Rabbit Message Queue. I want to use Spring AMQP to wire up and send messages during build events.

I was able to to successfully wire up a very simple build listener using the following configuration:

<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">

    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}" />
    </plugin-info>

    <bambooEventListener key="buildStateListener.event" name="Build State Listener"
                         class="com.jeffdoto.sherlock.bamboo.BuildStateListener">
        <description>Listens for events and adds them to a message queue if enabled.</description>
    </bambooEventListener>

</atlassian-plugin>

I cannot, however, seem to inject my own custom beans into my listener. I get a working listener that receives events, but I get null pointer exceptions because my custom beans are not getting injected.

Here's my implementation of the HibernateEventListener interface.

public class BuildStateListener implements HibernateEventListener {

    Logger logger = LoggerFactory.getLogger(BuildStateListener.class);
    private AmqpTemplate amqpTemplate;
    private String completedTopicAddress;
    
    @Override
    public void handleEvent(Event event) {

        if (event instanceof BuildCompletedEvent){
            String planKey = ((BuildCompletedEvent) event).getBuildPlanKey();
            logger.info(String.format("BuildCompletedEvent received.  Publishing message to %s", completedTopicAddress));
            amqpTemplate.convertAndSend(completedTopicAddress, planKey);
        }
    }

    @Override
    public Class[] getHandledEventClasses() {

        return new Class[]{
                BuildCompletedEvent.class
        };
    }

    @Required
    public void setAmqpTemplate(AmqpTemplate amqpTemplate) {
        this.amqpTemplate = amqpTemplate;
    }

    @Required
    public void setCompletedTopicAddress(String completedTopicAddress) {
        this.completedTopicAddress = completedTopicAddress;
    }
}

Now, my question is, is it possible for me to inject the AmqpTemplate and topic address? If so, what is the correct way to do so? I've tried using OSGi Felix instructions in my pom.xml to no avail (I had a bunch of issues with BundleExceptions and constraint errors), and I've tried using a custom Spring Dynamic Modules config file:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:osgi="http://www.springframework.org/schema/osgi"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:rabbit="http://www.springframework.org/schema/rabbit"
             xsi:schemaLocation="
    http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"
             default-autowire="autodetect">

    <rabbit:connection-factory id="connectionFactory"/>

    <rabbit:amqpTemplate id="amqpTemplate" exchange="bamboo-build-events" connection-factory="connectionFactory"/>

    <rabbit:admin connection-factory="connectionFactory"/>

    <rabbit:queue name="default"/>

    <bean id="buildStateListener" class="com.jeffdoto.sherlock.bamboo.BuildStateListener">
        <property name="amqpTemplate" ref="amqpTemplate"/>
        <property name="completedTopicAddress" ref="bamboo.build.completed"/>
    </bean>

    <osgi:service id="buildStateListenerOsgi" ref="buildStateListener"
                  interface=" com.atlassian.bamboo.event.HibernateEventListener"/>

</beans:beans>

But i'm not making any progress here. Any suggestions? This has to be doable.

I do see that there are separate Notification plugin modules available, but there is no direct support for anything other than email and IM, hence my desire to wire up my own AMQP stuff here. This also seems simpler, just having the logic right in the event listener.

Thanks for your help!

Jeff

2 answers

1 accepted

This widget could not be displayed.

Update: I was able to solve my issue with a little help from the guys in the Atlassian IRC chatroom (thanks Swoon!).

There were a few things of issue in my initial configuration. The AmqpTemplate stuff I was trying to use relied on a newer version of Spring which is not included in the 3.3 Bamboo release. I stripped out the AmqpTemplate, and replaced it with RabbitMQ specific classes, not dependent on Spring.

Also, it is very important to make sure that your Spring is in the correct place on your classpath, specifically META-INF/spring/spring-file.xml. Be sure to use the correct 'beans' namespace prefix for your configured beans, as well.

Here's the atlassian-plugin.xml (pretty much the same as my first cut):

<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.name}" plugins-version="2">

    <plugin-info>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}"/>
    </plugin-info>

    <bambooEventListener key="buildStateListener.event" name="Build State Listener"
                         class="com.jeffdoto.sherlock.bamboo.BuildStateListener">
        <description>Listens for BuildComplete events and sends a message to a configured destination.</description>
    </bambooEventListener>

</atlassian-plugin>

And here is the resulting spring-config.xml, located at src/main/resources/META-INF/spring/spring-context.xml in my project. Note the addition of the 'beans' prefix to each bean.

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
             default-autowire="autodetect">

    <beans:bean id="messager" class="com.jeffdoto.sherlock.bamboo.messaging.RabbitMessager">
        <beans:property name="messagerConfig" ref="messagerConfig"/>
    </beans:bean>
    
    <beans:bean id="connectionFactory" class="com.rabbitmq.client.ConnectionFactory">
        <beans:property name="host" value="localhost"/>
        <beans:property name="port" value="5672"/>
    </beans:bean>
    
    <beans:bean id="messagerConfig" class="com.jeffdoto.sherlock.bamboo.messaging.MessagerConfig">
        <beans:property name="connectionFactory" ref="connectionFactory"/>
        <beans:property name="exchangeName" value="bamboo-build-events"/>
        <beans:property name="completedTopicName" value="bamboo.build.completed"/>
    </beans:bean>

</beans:beans>

And the final HibernateEventListener instance, which depends on a simple Messager class I wrapped around the RabbitMQ specific classes:

package com.jeffdoto.sherlock.bamboo;

import com.atlassian.bamboo.event.BuildCompletedEvent;
import com.atlassian.bamboo.event.HibernateEventListener;
import com.atlassian.event.Event;
import com.jeffdoto.sherlock.bamboo.messaging.Messager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

/**
 * Listens for Bamboo Build Lifecycle events.
 *
 * Sends a message to a configured messaging entity announcing the received event Plan Information, Build Number,
 * and State.
 * User: jdoto200
 * Date: 1/29/12
 * Time: 10:55 PM
 */
public class BuildStateListener implements HibernateEventListener {

    Logger logger = LoggerFactory.getLogger(BuildStateListener.class);
    private Messager messager;

    @Override
    public void handleEvent(Event event) {

        if (event instanceof BuildCompletedEvent) {
            logger.info(String.format("BuildCompletedEvent received.  Publishing message to %s", messager.getName()));
            messager.sendMessage(
                    String.format(((BuildCompletedEvent) event).getBuildPlanKey()
                            + " Build #" + ((BuildCompletedEvent) event).getBuildNumber()
                            + " completed with a result of " + ((BuildCompletedEvent) event).getBuildState()).toUpperCase());
        }
    }

    @Override
    public Class[] getHandledEventClasses() {

        return new Class[]{
                BuildCompletedEvent.class
        };
    }

    @Required
    public void setMessager(Messager messager) {
        this.messager = messager;
    }
}

Check out https://github.com/jbdoto/sherlock-bamboo to see the full project.

This widget could not be displayed.

Does your custom spring-components.xml work when you use it in a standalone application on Spring 2.0 ?

After a little trial here, no...turns out the Rabbit AMQP stuff depends on Spring Core 3.0.x.

Can I get around this by using some clever OSGi tricks?

Thanks for your help.

As far as I understand, you need a simple "put" operation on that queue, nothing fancy. I think your best bet is to write it in plain Java using Rabbit's classes, without Spring - it shouldn't take much more than 20-50 lines.

Actually...I'm not totally tied to the Spring AMQP thing. I just got the standalone to work trashing the Spring AMQP stuff and using the Rabbit-provided libraries.

I'll be all set if I can perform injection on the BuildStateListener class, if possible.

Suggest an answer

Log in or Sign up to answer
Community showcase
Posted Thursday in United States

Local Atlassian Research Workshop opportunity on Sep. 28th

We're looking for participants for another workshop at Atlassian! We need Jira admins who have interesting custom workflows, issue views, or boards. Think you have a story to sh...

55 views 0 0
View post

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you