Saturday, June 15, 2013

What's new in JMS 2 - Part 2 - An Event based Message Sender

One of the pipe dreams we had in Seam3 was to be able to create a variable JMS event router that could send and receive messages as if they were simple CDI events. Why did we want this? For one, it was rather difficult to build a JMS sender. lots of objects and exception handling, not much to do with these errors. It was beneficial to support this as this gave an easy way to handle asynchronous event processing with CDI, leveraging JMS. It should be simple, but there were some stumbling blocks we ran across.

  1. CDI events used a global firing process, not a first match or best match algorithm. If you had an observer method that was as generic as:

    public void handleObj(@Observes Object obj) { ... }

    Then every event fired would make it to this method.

  2. Creating specific bindings breaks dynamic event processing.

    In CDI, there are two ways to fire an event. The first is to use the Event interface, the other is to use a BeanManager.

    We got close. With a fairly convoluted way we could support static binding of events to JMS messages. However, we had no way to support the additional qualifiers that may accompany a message. Let's say we had the following qualifiers on our event:

    @SendTo("someQueue") @From(customer=1) @ReplyTo("someOtherQueue")

    We could use the SendTo to map the message to a Destination, however the extra annotations were not able to come along for the ride. This is because CDI didn't give access to these qualifiers, so any observer method was blind to its input.

  3. In bound message processing breaks Java EE specification requirements. Technically, you cannot start a message listener in Java EE; you instead need to setup an MDB. You could create a message listener implementation and allow developers to extend it for custom processing, or have it mapped in ejb-jar.xml but it doesn't allow this solution to be turn key.




To help fix the problems introduced in 1 and 2, along came the EventMetadata object, which helps out our observer methods. This class acts like an InjectionPoint but is specific to event observers.

Issue 3 remains an issue. Thankfully, one of the late additions in Java EE 7 was support for a Resource Adapter that was more dynamic in nature. We'll have to see if something can be

Now, with CDI 1.1 and JMS 2 we can easily build this outbound message sender with just about 25 lines of code. Behold

 public class SendToObserver {  
   @Inject  
   private JMSContext jmsContext;  
   @Resource  
   private Context context;  
   public void sendViaJms(@Observes @SendTo("") Serializable obj, EventMetadata metadata) {  
     SendTo st = getSendTo(metadata);  
     try {  
       Destination d = (Destination) context.lookup(st.value());  
       jmsContext.createProducer().setProperty("qualifiers", metadata.getQualifiers()).send(d, obj);  
     } catch (NamingException e) {  
       // TODO log something here please  
     }  
   }  
   public SendTo getSendTo(EventMetadata metadata) {  
     Set<Annotation> qualifiers = metadata.getQualifiers();  
     for (Annotation a : qualifiers) {  
       if (a instanceof SendTo) {  
         return (SendTo) a;  
       }  
     }  
     return null;  
   }  
 }  


Assuming that we have a SendTo qualifier defined as

 @Qualifier  
 @Target({ TYPE, METHOD, PARAMETER, FIELD })  
 @Retention(RUNTIME)  
 @Documented  
 public @interface SendTo {  
      @Nonbinding String value();  
 }  


So what happens in this observer? Simple. First, we're observing any Serializable object. So an injection point for this observer could be

 @Inject  
 @SendTo("someQueue")  
 @Important  
 private Event<Serializable> serializableEvent;  


Now, in CDI 1.1 the qualifiers (@SendTo("someQueue") and @Important) will be forwarded and found in the annotation set, returned in getQualifiers(). We can iterate through them to find the SendTo to identify where to send the message to. We can also take all of these qualifiers and send them as an attribute of the message; making them available for use on the receiver side. getSendTo processes the values to read in this destination.

Next, we use JMS2's JMSContext to simplify sending. Behind the scenes, this is using a JMSProducer via a builder pattern to set properties on the message and then send it to the destination. We don't need to create a message, it happens within the sender for us. The injection here can be Request scoped or Transaction scoped, whichever is appropriate here will be activated.

What was easily 1000 lines + in Seam JMS is now just about 25 lines (30 lines if you count the qualifier). This is a huge amount of code improvement. And now, sending JMS messages is really as easy as firing a CDI event.

Sunday, March 10, 2013

What's New in JMS 2 (Part 1)

This post is the first of many parts around the new features of JMS 2.

In this post we talk about some of the new features in the original APIs.  The biggest changes here are around the AutoCloseable interface and RuntimeExceptions.

Let's say we want to read some messages from a Queue, but maybe can't use an MDB or MessageListener to do the work.


public List readMessageContent() {
List messageBodies = new ArrayList<>();
try (Connection conn = connFactory.createConnection();
Session sess = conn.createSession();
MessageConsumer cons = sess.createConsumer(queue)) {
Message m = null;
while ((m = cons.receiveNoWait()) != null) {
if (m instanceof TextMessage) {
TextMessage tm = (TextMessage) m;
messageBodies.add(tm.getText());
m.acknowledge();
}
}
} catch (JMSException | JMSRuntimeException e) {

}
return messageBodies;
}

In our try area we can create a Connection, Session and Message Consumer in an AutoCloseable fashion.  We only want TextMessages so use an if to check the type.  We don't have to deal with any of the closing methods in the code and handle only the exception.  We also have a new JMSRuntimeException that can be caught, or you can let it throw further into the stack.

Digging through the JMS 2.0 javadocs, you'll notice a lot of use in AutoCloseable.  All of the Session interfaces have it now, all of the Connection interfaces as well.  All of the MessageConsumer equivalents (TopicSubscriber, QueueReceiver) and the MessageProducers (TopicPublisher, QueueSender) have it as well.  The new APIs (JMSContext and related) all have it too.  Whenever you create these objects in a try with resources statement they will automatically close for you when done.  This is a pretty powerful feature that matches standard use cases from today.

At this point, all you have to do is finish out your REST resource, and you can easily read all given messages in a single request.  Here's what it looks like:


@Path("/jms")
@Stateless
public class WhatsNewReader {

@Resource(mappedName = "jms/__defaultConnectionFactory")
private ConnectionFactory connFactory;

@Resource(mappedName = "jms/SomeQueue")
private Queue queue;

private Logger logger = Logger.getLogger(WhatsNewReader.class
.getCanonicalName());

public List readMessageContent() {
List messageBodies = new ArrayList<>();
logger.info("Reading.");
try (Connection conn = connFactory.createConnection();
Session sess = conn.createSession();
MessageConsumer cons = sess.createConsumer(queue)) {
logger.info("In the try.");
Message m = null;
while ((m = cons.receiveNoWait()) != null) {
logger.info("In the while.");
if (m instanceof TextMessage) {
TextMessage tm = (TextMessage) m;
messageBodies.add(tm.getText());
m.acknowledge();
}
logger.info("leaving iteration.");
}
} catch (JMSException | JMSRuntimeException e) {

}
return messageBodies;
}

@GET
@Produces("text/plain")
public String getMessages() {
List msgs = readMessageContent();
StringBuilder sb = new StringBuilder("Hello,");
for (String m : msgs) {
sb.append(m).append("\n");
}
return sb.toString();
}
}

Note that I have to use mappedName in my resources.  As far as I can remember, mappedName is supposed to be the product specific JNDI, however I was not able to use name to look these up.  Hopefully this gets corrected in a latest GF4 Build.



You can pull the code from this example, and the next few, from here on my github account: https://github.com/johnament/whats-new-jms2

Edit:

- Fixed JNDI look up issue/at least it's working in b80/b81
- Fixed my typo of receive vs receiveNoWait.

Sunday, January 20, 2013

The book has a cover image

Good news everyone!

I now have a cover image selected for the book, Arquillian Testing Guide.  It's going to be the 1964 World's Fair, home of the climactic battle from Men In Black.

Going with the photographer next weekend to take the pictures.  One step closer to getting a pre-order up.

Sunday, January 13, 2013

And then I wrote a book

I started writing a similar blog post to this around New Year's but after reading it a few times it didn't quite convey what I was hoping for.

If you've been living under a rock, I wrote a book.  Yes, I can finally say I wrote it.  First draft is completely in the publisher's hands and reviews going on.  So far I've done one chapter's final draft but have plenty more to do over the next six weeks or so.  Hopefully nothing major comes up.

The book's subject is Arquillian, the leading deployment automation and extension framework for java testing.  It pushed me to learn more about the tools and even question what I knew about how they worked. Overall, I think readers who are new to Arquillian or even automated testing will find it very useful.  It's being published by Packt, and it fits into their "Getting Started" area of books so it's not meant for more advanced users, but generally novices.

One thing I should point out is that I am being paid to write the book.  If you're not aware, I lost my mother about seven years ago to pancreatic cancer.  The Pancreas Multidisciplinary Cancer Team at Johns Hopkins takes donations so I've decided to donate a portion of the proceeds in my mother's name to the team.

Tuesday, November 20, 2012

LinkedIn Updates

Well, if it's not already known, I recently left Burlington Coat Factory and joined the team at Sparta Systems.  The most curious thing to note is the influx of LinkedIn views, connection requests and overall traffic.  I'm used recruiters pinging me occasionally on LinkedIn saying, roughly, "OMG I have the greatest job for you ever email me for details."  Then you email them and receive back jobs all over the state/rough conditions.  Worst yet, you do end up interviewing with these people and they can be completely disorganized to the point where the job is clearly too painful.

I will say I found LinkedIn to be a very useful tool during my job search.  I was able to research the people I was interviewing with before hand, what companies they came from and how they were related previously.  Also gave me a good idea about what they may be looking for in a new recruit.  Made a world of difference.

Sunday, August 26, 2012

Arquillian, WebLogic with a HIbernate Enabled WebApp

For some upcoming work related applications, I decided to give  a try to Arquillian and WebLogic. Our constraint though was to ensure that we're using Hibernate, since we get very odd results when using EclipseLink in our WebLogic apps.

We're targetting WebLogic 12c for new applications, which ships with tools like SLF4J, Weld in our app server.  Lots of libraries are already loaded in your application as a result:

- SLF4J
- Javassist
- Jersey
- Jackson/Jettison

As a result of all of these libraries being available, you can get some very unexpected results trying to drop Hibernate in to your application.  In my case I was using it as a JPA implementation.  This is what my persistence.xml looks like as a result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
    name="LOCALAPPS">
    org.hibernate.ejb.HibernatePersistence

      jdbc/LocalApps
      
          name="hibernate.hbm2ddl.auto" value="update"/>
          name="hibernate.transaction.jta.platform"
                value="org.hibernate.service.jta.platform.internal.WeblogicJtaPlatform" />
      
   

In this scenario, I was using a MySQL database, where the connection was available as jdbc/LocalApps.  You can see on line 6 that I have set my JPA provider to Hibernate.  I ensured that Hibernate was in my project using the following dependency:


org.hibernate
hibernate-entitymanager
4.1.5.SP1
javassist
org.javassist




I had to remove javassist from Hibernate's dependency since it will conflict with Weld.  If you don't, you will receive deployment failures.  The javassist version deployed with WebLogic is binary compatible with Hibernate 4.1.5.SP1.  If you do not make this change, CDI injection will not work with your application.

Next up, we have to deal with an AST/ANTLR issue.  Hibernate uses AST for query object reading.  When you create a query "select p from Person p" it uses AST to read these tokens.  WebLogic's version is not binary compatible, so you cannot simply exclude the library.  You have to include it in your application.  In order to allow WebLogic core and your application to operate, you'll need the following weblogic.xml in your WAR file

1
2
3
4
5
    
        true
    
 
Now you should be able to deploy your Hibernate Web App on WebLogic 12c.  Now if you're like me, you want to start testing this application.  You'll want to define the following Deployment method in yoru arquillian test case


File[] libs = DependencyResolvers.use(MavenDependencyResolver.class)
                .loadEffectivePom("pom.xml")
                 .importAllDependencies()
                .resolveAsFiles();
        WebArchive wa = ShrinkWrap.create(WebArchive.class,"foo.war")
            .addClasses(BasicEntity.class,BasicEntityDAO.class)
            .addAsLibraries(libs)
            .addAsWebInfResource("web/weblogic.xml","weblogic.xml")
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
            .addAsResource("META-INF/persistence.xml","META-INF/persistence.xml")
            ;


The first four lines tell ShrinkWrap to gather all dependencies in the pom file - this will include hibernate as well. Similar to what we have to do to build the real deployment, we need to add a few extra items.  We'll need the weblogic.xml used to deploy the application, as well as the persistence.xml.  Note that you must use addAsResource and include the file location.  In order to ease my testing, I added the following to my build section of the pom file:


src/test/resources
src/main/resources
src/main/webapp/WEB-INF
web


This will add these locations to your test classpath.  This should simplify gathering files.  This could also be used if you are working with ArquillianDrone to test your web app.  Hopefully with these tips, you can simplify any pains when using Arquillian and WebLogic.

The source code for this tutorial can be found here: https://github.com/johnament/tad-arquillian-weblogic-hibernate

Happy Testing!

Saturday, June 4, 2011

Seam JMS 3.0.0.Beta2 is now out!

Finally, Beta2 of the Seam JMS project is now available. This is mostly a minor improvement release:

1. Support for TopicBuilders and QueueBuilders, simple interfaces for sending and receiving messages with JMS. To use one, just inject a reference:

@Inject QueueBuilder queueBuilder;

Then, choose destinations and send messages:

queueBuilder.destination("jms/SomeQueue").send("This is my text");

No need for complicated objects or anything. In addition, the TopicBuilder supports Subtopics. Subtopics are based around message selectors, using a String property sm_jms_subtopic.

2. Changes in the mapping APIs. Instead of using @Routing(RouteType.INGRESS) or @Routing(RouteType.EGRESS) you can simply use @Inbound and @Outbound. I figure this will be easier to read and remember.

3. Some big documentation clean ups occurred, and I'm still working on some more.

Visit the module homepage here: http://sfwk.org/Seam3/JMSModule
Grab the download here: http://sourceforge.net/projects/jboss/files/Seam/JMS

Visit the Seam Community Forums with any questions, comments or issues!