Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Injecting Spring AmqpTemplate into HibernateEventListener Instance

Jeffrey Doto February 11, 2012

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

0 votes
Answer accepted
Jeffrey Doto February 25, 2012

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.

0 votes
Przemek Bruski
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 12, 2012

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

Jeffrey Doto February 12, 2012

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.

Przemek Bruski
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 12, 2012

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.

Jeffrey Doto February 12, 2012

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
TAGS
AUG Leaders

Atlassian Community Events