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
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.
Does your custom spring-components.xml work when you use it in a standalone application on Spring 2.0 ?
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.