Home
 

Talking Stomp with PHP to ActiveMQ

Talking Stomp with PHP to ActiveMQ

Lance Hendrix

Abstract

I recently was required to integrate ActiveMQ with both PHP and Java. As a result, I also found the need to use the MapMessage format, but spent considerable time trying to get the three technologies to interoperate correctly. I was able to find some information on-line, but most of it was incorrect, probably due to being outdated. In this article, I will show you how I was able to get the two technologies to interoperate and the details of my solution. While the information in this article is rather specific to PHP, implementors of other languages might find this useful as a basis for how to integrate via Stomp to ActiveMQ through a (JMS) MapMessage as I provide significant detail as to the various aspects of the solution.


Warning

The original form of this article was created several years ago and the technologies have (thankfully) progressed and improved since then (I just love ASF) to the point where it is much easier to integrate the technologies than I had originally encountered. I have still included the main article with regards to the original technologies in case anyone wants or needs to use older technologies, but I have (as of March 28, 2012) updated the document with the current technologies and software releases available that make the configuraiton and integration of the technologies much easier. It might be, at this point of the maturity of the technologies, safe to say that the only significant portion of this article anymore is to help the reader understand how to correctly form the message in PHP. There might actually be other libraries or technologies that might make the PHP message creation easier, but for now if you are looking for a minimal installation and configuration, this might still be valuable.

1. Updated Article (with more recently released software)

This is an updated version of the original article that I created to document how to send a Java "MapMessage" from PHP via ActiveMQ to Java. I had quite a bit of difficulty at that time due to several issues such as complications with getting the technology configured as well as having run into some "quirks" with how the original version of the libraries "worked". The final issue that I ran into was that constructing the message appropriately in PHP was not trivial.

Luckily for all of us, most of the challenges that I encountered in my original quest are largely gone with the current versions of PHP and ActiveMQ (detailed below). It does still appear that the construction of the data structure from PHP is still somewhat tricky, so this article mostly demonstrates this as well as documents the libraries I used and how to receive the message on the Java side.

It might also be interesting to note, that I use the Stomp protocol from PHP to ActiveMQ, but I am using the standard messaging libraries (JMS) against the default ActiveMQ protocol from ActiveMQ to the Java code. That is, I am not using Stomp to talk to/from ActiveMQ from Java.

1.1. Background

In the older article (following this update article), I had a need to send a message from PHP to ActiveMQ and then I wanted to retrieve that message in a Java client and be able to process it there. There are several articles about how to do that out on the web; however, at the time of the original article, I ran into two issues. The first issue, which seems to be addressed in recent versions of the software involved, had to do with configuration and dependencies on libraries that seemed to be required at the time (I was not able to get it to work otherwise). These issues, thankfully seemed to be resolved with recent versions of ActiveMQ and PHP.

The second issue I ran into was in figuring out how to construct the message properly from the PHP side to send to ActiveMQ so that it was handled/transformed properly into a Java MapMessage for use on the Java client/receiving side. The code presented here still works (I verified recently, as of 2012/04/13), but I have seen other simpler examples on the web that I need to test to see if the PHP code can be simplified; however, I do know that as of the versions of the software detailed here that this PHP code does still work, I am just not sure if it is necessary to be this complex anymore.

1.2. Source of Documentation

Information on ActiveMQ can be found at http://activemq.apache.org.

Information on using stomp with ActiveMQ can be found at http://activemq.apache.org/stomp.html.

Information on the Stomp PHP client I used can be found at http://stomp.fusesource.org/. (I am not sure what the difference is between the page on fusessource.org and the client at http://stomp.github.com/).

1.3. Requirements and Version Info

Using the most recent versions of PHP, ActiveMQ, and the PHP Stomp Libraries (call out which one) makes this significantly easier to do these days regarding the installation and configuration. Now the most significant piece to getting this all to work would seem to be constructing the PHP data structure.

The version of PHP I used in the most recent testing/verification/examples is PHP 5.3.8.

The version of ActiveMQ I am using is 5.5.1.

The Stomp PHP library I am using is the Stomp PHP 1.0.0 from http://stomp.fusesource.org/download.html. This site also has some examples of creating a MapMessage that are much simpler than what I have done below, so I will have to test those out one of these days to see if this code can be simplified or determine why their examples are different from what I found was required to send a Java MapMessage.

1.4. Solution

The solution presented below sends a message from PHP via Stomp to ActiveMQ and it is then read by a Java client using the standard ActiveMQ connection (not Stomp). I have presented both the PHP code and the Java code required on both sides of the communication here.

1.4.1. ActiveMQ Configuration and Startup

The installation instructions provided on the ActiveMQ installation ("Getting Started") page is the source for installation and initial configuration of ActiveMQ for your particular OS/installation . These are pretty good and comprehensive and should get you up and running with ActiveMQ. The only change that needs to be made to the standard configuration is also documented on the Stomp installation page.

The only challenge you might have, if you are not fairly familiar with ActiveMQ configuration is understanding how to enable Stomp by finding and editing the right file in your ActiveMQ installation. The file you are looking for, from a clean/default installation, and assuming you have made no other changes to the default configuration, is activemq.xml and this is located in the {activemq base install dir}/config/activemq.xml. The entirety of my file is included in the program listing below:

  1|<!--
  2|    Licensed to the Apache Software Foundation (ASF) under one or more
  3|    contributor license agreements.  See the NOTICE file distributed with
  4|    this work for additional information regarding copyright ownership.
  5|    The ASF licenses this file to You under the Apache License, Version 2.0
  6|    (the "License"); you may not use this file except in compliance with
  7|    the License.  You may obtain a copy of the License at
  8|   
  9|    http://www.apache.org/licenses/LICENSE-2.0
 10|   
 11|    Unless required by applicable law or agreed to in writing, software
 12|    distributed under the License is distributed on an "AS IS" BASIS,
 13|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14|    See the License for the specific language governing permissions and
 15|    limitations under the License.
 16|-->
 17|<!-- START SNIPPET: example -->
 18|<beans
 19|  xmlns="http://www.springframework.org/schema/beans"
 20|  xmlns:amq="http://activemq.apache.org/schema/core"
 21|  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 22|  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 23|  http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
 24|
 25|    <!-- Allows us to use system properties as variables in this configuration file -->
 26|    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
 27|        <property name="locations">
 28|            <value>file:${activemq.base}/conf/credentials.properties</value>
 29|        </property>      
 30|    </bean>
 31|
 32|    <!-- 
 33|        The <broker> element is used to configure the ActiveMQ broker. 
 34|    -->
 35|    <broker xmlns="http://activemq.apache.org/schema/core" 
 36|            brokerName="localhost" dataDirectory="${activemq.base}/data" 
 37|            destroyApplicationContextOnStop="true">
 38| 
 39|        <!--
 40|			For better performances use VM cursor and small memory limit.
 41|			For more information, see:
 42|            
 43|            http://activemq.apache.org/message-cursors.html
 44|            
 45|            Also, if your producer is "hanging", it's probably due to producer flow control.
 46|            For more information, see:
 47|            http://activemq.apache.org/producer-flow-control.html
 48|        -->
 49|              
 50|        <destinationPolicy>
 51|            <policyMap>
 52|              <policyEntries>
 53|                <policyEntry topic=">" producerFlowControl="true" memoryLimit="1mb">
 54|                  <pendingSubscriberPolicy>
 55|                    <vmCursor />
 56|                  </pendingSubscriberPolicy>
 57|                </policyEntry>
 58|                <policyEntry queue=">" producerFlowControl="true" memoryLimit="1mb">
 59|                  <!-- Use VM cursor for better latency
 60|                       For more information, see:
 61|                       
 62|                       http://activemq.apache.org/message-cursors.html
 63|                       
 64|                  <pendingQueuePolicy>
 65|                    <vmQueueCursor/>
 66|                  </pendingQueuePolicy>
 67|                  -->
 68|                </policyEntry>
 69|              </policyEntries>
 70|            </policyMap>
 71|        </destinationPolicy> 
 72| 
 73|        
 74|        <!-- 
 75|            The managementContext is used to configure how ActiveMQ is exposed in 
 76|            JMX. By default, ActiveMQ uses the MBean server that is started by 
 77|            the JVM. For more information, see: 
 78|            
 79|            http://activemq.apache.org/jmx.html 
 80|        -->
 81|        <managementContext>
 82|            <managementContext createConnector="false"/>
 83|        </managementContext>
 84|
 85|        <!-- 
 86|            Configure message persistence for the broker. The default persistence
 87|            mechanism is the KahaDB store (identified by the kahaDB tag). 
 88|            For more information, see: 
 89|            
 90|            http://activemq.apache.org/persistence.html 
 91|        -->
 92|        <persistenceAdapter>
 93|            <kahaDB directory="${activemq.base}/data/kahadb"/>
 94|        </persistenceAdapter>
 95|        
 96|        
 97|          <!--
 98|            The systemUsage controls the maximum amount of space the broker will 
 99|            use before slowing down producers. For more information, see:
100|            
101|            http://activemq.apache.org/producer-flow-control.html
102|             
103|        <systemUsage>
104|            <systemUsage>
105|                <memoryUsage>
106|                    <memoryUsage limit="20 mb"/>
107|                </memoryUsage>
108|                <storeUsage>
109|                    <storeUsage limit="1 gb"/>
110|                </storeUsage>
111|                <tempUsage>
112|                    <tempUsage limit="100 mb"/>
113|                </tempUsage>
114|            </systemUsage>
115|        </systemUsage>
116|		-->
117|		  
118|        <!-- 
119|            The transport connectors expose ActiveMQ over a given protocol to
120|            clients and other brokers. For more information, see: 
121|            
122|            http://activemq.apache.org/configuring-transports.html 
123|        -->
124|        <transportConnectors>
125|            <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/>
126|            <transportConnector name="stomp" uri="stomp://localhost:61613"/>
127|        </transportConnectors>
128|
129|    </broker>
130|
131|    <!-- 
132|        Enable web consoles, REST and Ajax APIs and demos
133|        
134|        Take a look at ${ACTIVEMQ_HOME}/conf/jetty.xml for more details 
135|    -->
136|    <import resource="jetty.xml"/>
137|    
138|</beans>
139|<!-- END SNIPPET: example -->

As you can see, I have made no changes to the default ActiveMQ installation except for the addition of line 126. You will also notice that this sets up the port number that we will need to use later in the PHP code. The Java code, since we are using default ports, does not require this configuration.

1.4.2. From PHP to Java ActiveMQMapMessage

This section documents how to construct a message in PHP that can be read as an ActiveMQMapMessage in Java.

1.4.2.1. PHP Client (Sender)

Provide documentation on the PHP client... (things have changed since I originally wrote the article! (for the better))

  1|<?php
  2|    require_once("stomp.php");
  3|    require_once("Stomp/Message/Map.php");
  4|    
  5|    $body = array('map' => array('entry' => array(
  6|        array('string' => array('contentName', 'Partner Resources')),
  7|        array('string' => array('contentHREF', '<a href="http://somewhere.com/Partners/Resources/">PartnerResources</a>')),
  8|        array('string' => array('contentId', '10034')),
  9|        array('string' => array('author', 'Lance Hendrix')),
 10|        array('string' => array('authorEmail', 'lance@lancehendrix.com')),
 11|        array('string' => array('content', 'These are the partners we should be using...')),
 12|        array('string' => array('contentType', 'blog')),
 13|        array(array('string' => 'id', 'int' => 1234))
 14|    )));
 15|    
 16|    $mapMessage = new StompMessageMap($body);
 17|            
 18|    print_r($mapMessage);
 19|    
 20|    $stompCon = new Stomp('tcp://localhost:61613');
 21|    $stompCon->connect();
 22|    $stompCon->send('/queue/notifications.content', $mapMessage);
 23|    $stompCon->disconnect();
 24|?>

I think most will be interested in lines 5-14 above which is where the data structure is constructed in PHP to properly allow translation by ActiveMQ into the Java MapMessage format. Most of the rest of the program is pretty standard and their are many examples of how to connect and send string messages via Stomp to ActiveMQ. More complicated messages; however, or examples of how to send more complicated messages seem to be harder to find.

One item to note, this structure created correstponds to string keys and string values for all except the final entry in the map, which is demonstrated on line 13, where I insert an entry that has a string key but an integer for the value.

One item that I now recognize from implementing the reverse scenario (see below for a Java to PHP example), is that this structure in the message above constructs a "map" that has both the key and value pair as strings. If you want to send something other than strings for both the key and value, you have to construct the individual KvP entry a little differently. For an insight into how to do this, see the output from the PHP client receiver below. I may do some additional work and document this explicitly here in teh future.

1.4.2.2. Java Client (Receiver)

Not much has changed here, but there are some changes it appears to the ActiveMQ APIs... Be sure and give credit to originator http://www.javablogging.com/simple-guide-to-java-message-service-jms-using-activemq/

  1|import javax.jms.*;
  2|import org.apache.activemq.ActiveMQConnection;
  3|import org.apache.activemq.ActiveMQConnectionFactory;
  4|import org.apache.activemq.command.ActiveMQMapMessage;
  5|
  6|public class activeMQClientStompTest {
  7|    // URL of the JMS server
  8|    private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
  9|
 10|    // Name of the queue we will receive messages from
 11|    private static String subject = "notifications.content";
 12|
 13|    public static void main(String[] args) throws JMSException {
 14|        // Getting JMS connection from the server
 15|        ConnectionFactory connectionFactory
 16|            = new ActiveMQConnectionFactory(url);
 17|        Connection connection = connectionFactory.createConnection();
 18|        connection.start();
 19|
 20|        // Creating session for sending messages
 21|        Session session = connection.createSession(false,
 22|            Session.AUTO_ACKNOWLEDGE);
 23|
 24|        // Getting the queue 'TESTQUEUE'
 25|        Destination destination = session.createQueue(subject);
 26|
 27|        // MessageConsumer is used for receiving (consuming) messages
 28|        MessageConsumer consumer = session.createConsumer(destination);
 29|
 30|        // Here we receive the message.
 31|        // By default this call is blocking, which means it will wait
 32|        // for a message to arrive on the queue.
 33|        Message message = consumer.receive();
 34|
 35|        // There are many types of Message and TextMessage
 36|        // is just one of them. Producer sent us a TextMessage
 37|        // so we must cast to it to get access to its .getText()
 38|        // method.
 39|        if (message instanceof TextMessage) {
 40|            TextMessage textMessage = (TextMessage) message;
 41|            System.out.println("Received message '"
 42|                + textMessage.getText() + "'");
 43|        }
 44|        else if(message instanceof ActiveMQMapMessage)
 45|        {
 46|        	ActiveMQMapMessage AMQMessage = (ActiveMQMapMessage) message;
 47|        	System.out.println("Received author: '" + AMQMessage.getString("author") + "'");
 48|        	System.out.println("Receied (int) id: '" + AMQMessage.getInt("id") + "'");
 49|        	System.out.println("Received message '" + AMQMessage.toString() + "'");
 50|        }
 51|        connection.close();
 52|    }
 53|}

You can generally find other examples of how to receive messages from ActiveMQ and also various message types on the web, therefore the only unique part of this program is really line 47 where I pull out one of the key value pairs (KvP) named "author" from the MapMessage. This is actually pretty straight forward. One item I really need to check is pulling out non-string data from the map, but my assumption is that as long as the value you are asking for in a numerical format is valid (and can be converted or cast from string to number) that you shouldn't have any issues, but I will work to verify that sometime in the future.

Note

I do still need to point out that I had to link (reference or find the correct Java term) to the appropriate libraries in order to get this to work. I am certain that anybody with basic Java skills (and mine are barely that in many areas) should not have an issue with the JMS or the ActiveMQ dependencies, but it took me a few minutes myself to find the right libraries/dependencies to get it to compile and run...

1.4.3. From Java ActiveMQMapMessage to PHP

While my initial need to figure out PHP Stomp integration with Java with a PHP sender and a Java receiver, I also thought it bore further investigation on sending a MapMessage from Java to PHP and see what the message looks like. This section provides an example of how to construct and send a Java ActiveMQMapMessage and receive and access the details of this message on the PHP side (receiver).

1.4.3.1. Java Client (Sender)

The example below demonstrates how to construct a ActiveMQMapMessage in a Java client and send that to ActiveMQ.

  1|import javax.jms.*;
  2|import org.apache.activemq.ActiveMQConnection;
  3|import org.apache.activemq.ActiveMQConnectionFactory;
  4|import org.apache.activemq.command.ActiveMQMapMessage;
  5|
  6|public class activeMQClientStompTest {
  7|    // URL of the JMS server
  8|    private static String url = ActiveMQConnection.DEFAULT_BROKER_URL;
  9|
 10|    // Name of the queue we will receive messages from
 11|    private static String subject = "notifications.content";
 12|
 13|    public static void main(String[] args) throws JMSException 
 14|    {
 15|        // Getting JMS connection from the server
 16|        ConnectionFactory connectionFactory
 17|            = new ActiveMQConnectionFactory(url);
 18|        Connection connection = connectionFactory.createConnection();
 19|        connection.start();
 20|
 21|        // Creating session for sending messages
 22|        Session session = connection.createSession(false,
 23|            Session.AUTO_ACKNOWLEDGE);
 24|
 25|        // Getting the queue 'TESTQUEUE'
 26|        Destination destination = session.createQueue(subject);
 27|        
 28|    	// MessageProducer is used for sending messages (as opposed
 29|        // to MessageConsumer which is used for receiving them)
 30|        MessageProducer producer = session.createProducer(destination);
 31|
 32|        // We will send a small text message saying 'Hello' in Japanese
 33|        MapMessage message = session.createMapMessage();
 34|        
 35|        message.setString("author", "lance");
 36|        message.setInt("id", 1);
 37|        message.setString("forum", "general");
 38|        message.setDouble("money", 10.0);
 39|
 40|        // Here we are sending the message!
 41|        producer.send(message);
 42|        System.out.println("Sent message '" + message.toString() + "'"); 
 43|
 44|        connection.close();
 45|    }
 46|}
1.4.3.2. PHP Client (Receiver)

The example below demonstrates how to receive in PHP the map message we constructed in Java above and do some basic decoding of the message.

  1|<?php
  2|    require_once("stomp.php");
  3|    
  4|    $stompCon = new Stomp('tcp://localhost:61613');
  5|    $stompCon->connect();
  6|    $stompCon->subscribe("/queue/notifications.content", array('transformation' => 'jms-map-json'));
  7|
  8|    $msg = $stompCon->readFrame();
  9|
 10|    if ( $msg != null) {
 11|        echo "Received array: "; 
 12|        print_r($msg->map);
 13|        // mark the message as received in the queue
 14|        //$stompCon->ack($msg);
 15|    } else {
 16|        echo "Failed to receive a message\n";
 17|    }
 18|    $stompCon->disconnect();
 19|
 20|    echo "Array index value:";
 21|    print_r($msg->map["map"]["entry"][0]["string"]);
 22|    echo "\n";
 23|?>
 24|

One item to note in the above program, I have commented out line 14 which means that the message is never acknowledged and once the connection is closed, ActiveMQ won't remove the message from the queue. This allowed me to run the PHP program over and over without having to add a new message with the Java client for testing purposes. In actual use, you will want to "ack" the message to ActiveMQ to let it know that you have received the message and that the message can be removed from the queue.

One item that is a little distressing is that accessing the actual data in the map constructed from Java is not especially straightforward and quite (at least to me) convuluted. I will see if I can find any libraries available for PHP that might make accessing the data a little easier. My preference would be to simply access the records using a simple associative array (by KvP)...

When the above PHP code is run with the Java sender sending a message above, you get the following results:

  1|Received array: Array
  2|(
  3|    [map] => Array
  4|        (
  5|            [entry] => Array
  6|                (
  7|                    [0] => Array
  8|                        (
  9|                            [string] => id
 10|                            [int] => 1
 11|                        )
 12|
 13|                    [1] => Array
 14|                        (
 15|                            [string] => money
 16|                            [double] => 10
 17|                        )
 18|
 19|                    [2] => Array
 20|                        (
 21|                            [string] => Array
 22|                                (
 23|                                    [0] => author
 24|                                    [1] => lance
 25|                                )
 26|
 27|                        )
 28|
 29|                    [3] => Array
 30|                        (
 31|                            [string] => Array
 32|                                (
 33|                                    [0] => forum
 34|                                    [1] => general
 35|                                )
 36|
 37|                        )
 38|
 39|                )
 40|
 41|        )
 42|
 43|)
 44|Array index value:id

This is obviously not a JSON string; however, even if you take the JSON string "{"map":{"entry":[{"string":"id","int":1},{"string":"money","double":10},{"string":["author","lance"]},{"string":["forum","general"]}]}}" and use Zend_Json::decode, you will get the exact same structure, so this must be about the only way to structure the message in PHP for that particular map structure. When I have managed to send JSON encoded strings from PHP, they are received on the Java side as Strings and not as maps. I will have to keep working on trying to figure out what is going on.

One item to note from this example is that the structure of the "entry" array is different with both elements within the KvP are strings and when they are of differing types. The example of PHP to Java shows how to construct the "map" where both the Key (K) and the Value (V) in the KvP are strings, while this example where I constructed the MapMessage in Java seems to give a clue about how to send KvP where the value is not necessarily a string. I may work on augmenting the first example above to do more than just simple stirng Keys and string Values.

One other interesting item to note is that the order of the KvPs are not necessarily the same as they were "put" into the map in the Java code...

2. Original (Deprecated) Article

I may use some of this in the new article with the updated technologies, but I also wanted to preserve the original article as there are quite a few links to it and also because it receives quite a bit of traffic. However, the new article (section) above covers the same integration with more recent version of the technologies and is quite a bit easier to accomplish than what was (seemed?) to be required with I originally wrote this article

2.1. Background

On a recent project, I was developing a number of backend components that we had decided to write in Java and leverage ActiveMQ as the transport between the PHP frontend application and the Java backend. Initially, we were using simple messages with only a single parameter; however, as the requirements developed and the capabilities of what was being built was reviewed by the users, they of course did what users do and started requesting additional functionality that placed additional requirements on our messaging infrastructure.

While evaluating moving from simple text messages with a single parameter to a more complex messaging infrastructure, we considered a number of options to include XML formatted messages, custom formated messages, etc. Eventually, we decided to use the MapMessage format as it was supported by ActiveMQ and JMS (on the Java side) and also because the Stomp library we were using also supported creation of MapMessage objects. This all seemed well and good and I sat to undertake what I thought would be a simple task. I was wrong. Two days and a great deal of frustration later, I finally had a solution that leveraged MapMessages from PHP to Java using ActiveMQ; however, in order to implement a solution that worked, I finally had to resort to digging through the ActiveMQ code and reverse engineering the solution.

The intention of this article is hopefully to document this integration for others so that you hopefully don't have to spend so much time on what should have been a simple integration effort. There are probably other ways of accomplishing this same integration and I look forward to feedback regarding other experiences and solutions, but if you are looking to leverage ActiveMQ MapMessages from PHP to/from another platform, hopefully this document will help you in your efforts.

2.2. Other Documentation Sources

Warning

Many of the links that were in this section are no longer valid and I have removed the hyperlinks, but left the bullet items with text indicating where they used to reside. You may want to review this section in the newer article as I have worked to find more recent links to this information.

My journey started with the ActiveMQ documentation and a number of other sources. While I either did not find the information required because I wasn't looking in the right places or just overlooked the relevant information because I didn't know what to look for, I think it is important to provide these sources, as I am sure relevant sources will eventually be updated to catch up with the current version of the implementation or the authors have not had the opportunity to complete more recent documentation and publish. Either way, these sources should be considered and since they are developed by the authors of their respective software used in this implementation, they should be considered the definitive source for information; however, while I used these sources to find information regarding potential capabilities, the solution presented here differs (IMHO) significantly from what is published on these sites. I also plan to work with the authors of each of these sites to offer my assistance in updating their information as it would be better for the community for each of their respective sites to contain the definitive sources of this information, as it is natural to "go to the source" for documentation of systems. In this regard, I hope this document is a "stop gap" measure while the authors either complete or update their sites and if they feel so inclined, they are welcome to my information.

As another "in their defence", if it had not been for the documentation of these sites, I most likely would not have even undertaken the endeavor. Additionally, as the implementation of the solution differs from the documentation, it must be noted that each software component does work and works well, just not (precisely) in the way that the documentation specifies. Additionally, these sources are also referenced throughout this documentation in order to provide better context as to the relevance of the source.

  • The first source would of course be the ActiveMQ documentation and API documentation, as well as documentation on the Stomp support within ActiveMQ.

    • ActiveMQ Home Page

    • ActiveMQ Stomp Documentation

    • ActiveMQ API Documentation: this used to be located at "http://activemq.apache.org/maven/activemq-core/apidocs/", but now returns an HTTP 404. I will work to try to find where it has been relocated.

  • Another key source of information is the PHP Stomp library documentation from codehaus.org (the links here now redirect to "http://stomp.github.com/" although the codehaus.org domain is still alive).

    • Stomp home on codehaus.org (now looks to be hosted on "github")

    • Stomp supported client list: This used to be located at "http://stomp.codehaus.org/Clients" but this now returns an HTTP 404. I will work to try to find where it has been relocated.

    • PHP Stomp client home: This used to be located at "http://stomp.codehaus.org/PHP" but this now returns an HTTP 404. I will work to try to find where it has been relocated.

2.3. Requirements and Version Info

This document focuses on ActiveMQ version 5.1. One of the issues faced by the author was that the implementation for version 5.1 is different from previous versions, to include significant differences from version 5.0 of ActiveMQ. For this reason, in order to get the examples, code, and integration to work as described in this document, you must be using Active MQ version 5.1 (or potentially greater/newer). Other versions of the system components may or many not make a significant difference, but I will enumerate the versions that were used for the development of the system and for the further testing that went into this document.

  • ActiveMQ 5.1+

  • JRE/JDK 1.5

  • PHP 5 (5.2.5)

  • PHP Stomp Library (1.0, but see below)

In addition to the above components, I also used both JUnit (3.0) and the PHP Simple Test framework for quite a bit of my testing, although these components are not required for integration and production use; however, both of these components can assist greatly with testing and exploration of this and similar concepts and I make significant use of both during development and testing of any system that I develop. We are also using Apache as our web server and mod_php within Apache, again, at least to the extent of my knowledge, this should not have any impact on the implementation of the solution and if you are using a different web server or PHP execution environment (such as CLI or fast_cgi, etc), it should not make a difference.

2.3.1. PHP

I will admit from the onset that I am only a mediocre PHP developer. I have only been using PHP for a couple of months and would not have had the pleasure of the experience if it had not been for this opportunity. I do find PHP interesting and in the hands of a good PHP developer, it is quite powerful and a solution for quickly developing a web based application. For this installation, the relevant aspects of the PHP installation are that we are using PHP 5. The relevant information from running "phpinfo.php" is:

  • PHP Version 5.2.5

  • build 6001

  • Build Date Nov 8 2007 23:18:08

  • Server API Apache 2.0

  • PHP API 20041225

  • Zend Extension 220060519

  • PHP Extension 20060613

  • This program makes use of the Zend Scripting Language Engine: Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies with Zend Debugger v5.2.12, Copyright (c) 1999-2007, by Zend Technologies

While not significant, as you can see, we are using the Zend Framework. About the only difference (that I know of) for this solution is that it provides for autoloading of the PHP classes such that we don't have to "require_once" each PHP file/class we are using, but I am sure seasoned PHP developers will already know this and adjust accordingly. For the examples below, I believe I have removed the dependency for everything other than the Stomp PHP library, which I have copied into the project directory (just to make it even more simple/bullet proof).

2.3.2. PHP Stomp Library

We are using the stomp library as recommended on the ActiveMQ stomp information page. This ActiveMQ reference suggests going to "the Stomp site" which is at codehaus.org. Once there, under the "Software" section on the left hand navigation pane, there is a link to "clients" that takes you to a list of clients, one of those clients is PHP, among quite a few others. The (this link, is no longer valid and as such, I have removed the hyperlink, but for completeness and for posterities sake, I have left the textual address) "http://stomp.codehaus.org/PHP" PHP client page is where you can download the PHP Stomp client that I am using. The exact file name of the client that I use in this article is "stompcli-php4-1.0-SNAPSHOT.tar.gz" which I assume is a 1.0 version of the client, but the word "snapshot" in the title gives me concern regarding the amount of change or if the "version 1.0" is relaiable at this point; however, the client does work, although for our specific environment we made a few very minor changes, specifically we are not using the PEAR PHP libraries, so at least one minor change in that regard was made, but I will publish the details of our changes (with "diff") at the end of this article, most likely in an appendix.

2.3.3. ActiveMQ

The version of ActiveMQ that you choose to use for implementing Stomp makes a significant difference, especially with regard to MapMessages and the Stomp protocol. One of the issues that I ran into when trying to implement this integration scenario was that the documentation that I consulted did not even seem to be correct for version 5.0. I am not certain, but I suspect the documentation was for an earlier version or an early version of 5.1 that has since radically changed. None-the-less, the solution documented here will not work with version 5.0 as there are significant changes, especially to the supported "transformation" capabilities that seem to be needed for successful PHP -> Stomp -> ActiveMQ -> JMS MapMessage. I will provide more information as I work you through the specifics of the solution, just ensure that you are using ActiveMQ 5.1 (or potentially later, but 5.1 is the current "development version"). I do know that this is not the "current stable build" of ActiveMQ, but we are successfully using 5.1 in production and aside from the requirements for Stomp and MapMessage, there are a number of fixes that are integrated into 5.1 that make it almost obligatory to use over 5.0.

2.3.4. Java JRE/JDK

While I have used a number of JDK/JRE versions for testing this solution and have a number of them installed on my development machine, the consistent version of the JRE/JDK across all environments is JRE 1.5.0_15. I have also been using recent versions of JRE 1.6. I have not tested the solution with JRE versions prior to 1.5, but other than potential issues with differences in the JMS specification, this should not be an issue. As you can see, I am not using a J2EE environment as such, but I am leveraging the JMS interface, although I am using the ActiveMQ provided libraries rather than any specific J2EE implementation. More information can be found later regarding the library dependencies for the implemented solution as there are a few beyond the provided ActiveMQ jars, as also specified in the ActiveMQ documentation.

2.4. Solution Overview

For the impatient among us, I will first provide the complete solution, which will be detailed in this section. In subsequent sections, I will break down the solution to a more detailed level as well as providing insights into how I solved the problem, which may assist others seeking to implement a similar solution but using a different technology, such as PERL or Python. The solution will be presented in three subsections corresponding to the PHP client side, the ActiveMQ requisite setup and deviations from the standard setup, and the Java/JMS server side.

2.4.1. PHP Client Implementation

The most challenging aspect of the solution was getting the PHP client implementation correct. Specifically, it was figuring out how to properly send a message to ActiveMQ that instructed ActiveMQ to transform the message from (Stomp) JSON to (JMS) MapMessage format. More (and probably more accurate) information can be found on the JSON website. The PHP code required to do this follows:

<?php
    require_once("stomp.php");
    
    $body = array('map' => array('entry' => array(
        array('string' => array('contentName', 'Partner Resources')),
        array('string' => array('contentHREF', '<a href="http://somewhere.com/Partners/Resources/">PartnerResources</a>')),
        array('string' => array('contentId', '10034')),
        array('string' => array('author', 'Lance Hendrix')),
        array('string' => array('authorEmail', 'lance@lancehendrix.com')),
        array('string' => array('content', 'These are the partners we should be using...')),
        array('string' => array('contentType', 'blog')))
    ));
    
    $header = array();
    $header['transformation'] = 'jms-map-json';
    
    $mapMessage = new MapMessage($body, $header);
            
    print_r($mapMessage);
    
    $stompCon = new StompConnection('tcp://localhost');
    $stompCon->connect();
    $stompCon->send('/queue/notifications.content', $mapMessage);
    $stompCon->disconnect();
?>

The above represents a test case using the PHP Simple Test framework that I used to debug and validate the PHP -> Stomp -> ActiveMQ -> JMS -> Java solution that I required. I will explain the code in detail with significant emphasis placed on the array structure used and the header attribute. The header attribute took me the longest time to figure out and once that was in place, then it took me awhile to figure out the correct structure (the nested array) to allow ActiveMQ to "deserialize" the message correctly on the server side for constructing the MapMessage.

2.4.1.1. Message Body (Array Structure)

The $body = array(...) is probably the most interesting portion of this code. How I arrived at this as the way to get a MapMessage created for the Java server side is detailed in a later section, for now, just know that it was more or less reverse engineered by reading the ActiveMQ source code and then creating a number of Java programs to simulate what is happening "under the covers" in ActiveMQ. I then had to do some testing to figure out how to get the correct structure created in PHP. What should have been simple is complicated, IMHO, mostly by the translation between Java and PHP.

I will not claim to be anything near an expert in JSON or PHP, but I will attempt to walk you through what I think is happening and why and how the above implemented message body works. The details of JSON can be found elsewhere, so I won't go into a deep explanation of that protocol, but essentially, it is a way of "serializing" objects, usually from Java script and also, it seems, usually related to AJAX. While PHP implements "Objects" (regardless of the controversy, let's just assume), and while there might be some way to better represent this structure with PHP objects (if someone knows how, please send me an email and let me know how to better represent this implementation), the only way I could figure out to ensure that the message was constructed properly was to use a set of nested associative arrays in PHP.

First, let me provide a "print_r" of "$mapMessage" which includes a string representation of the JSON encoded message body. I have added line breaks and formatting (indentation) to the "[body]" element for readability:

MapMessage Object
(
    [map] => 
    [command] => SEND
    [headers] => Array
        (
            [transformation] => jms-map-json
            [amq-msg-type] => MapMessage
        )

    [body] => {"map":
                {"entry":
                    [
                        {"string":["contentName","Partner Resources"]},
                        {"string":["contentHREF","<a href=\"http:\/\/somewhere.com\/Partners\/Resources\/\">PartnerResources<\/a>"]},
                        {"string":["contentId","10034"]},
                        {"string":["author","Lance Hendrix"]},
                        {"string":["authorEmail","lance@lancehendrix.com"]},
                        {"string":["content","These are the partners we should be using..."]},
                        {"string":["contentType","blog"]}
                    ]
                }
              }
)

Again, I have added line breaks and indentation to the "[body]" section for readability and for "effect".

Attempting to (loosely) translate what is (should be) going on into JSON, the outer associative array "array('map' =>" translates into the "map": identifier or (seems in JSON "speak) that we have an object of type "Map". This make some sense, as we are trying to create a MapMessage, which uses a (java.util) Map (interface) as it's implementation, which in turn contains "entries". In JSON notation, the object name is followed by a colon ":" and the colon is followed by the value of the object. The value of this object is an array of "entry" objects that are in turn composed of arrays of (pairs of) strings. The each of the "string": elements translates into a "value key pair" (Map.Entry<K,V>). Therefore, this represents a simple map object with entries represented as "key, value" pairs so that the first string following "string": (in this case, "contentName") represents the key, with the (string) value following the comma (",") representing the value for the corresponding key.

That is, I have now represented a simple Java Map object that looks would be declared like "Map<String, String>", which is what it appears that ActiveMQ/JMS want to see on the backend. I had no requirement to represent a more complex message that a single level "key, value" pair message where I would retrieve the values on the backend based on a set of (agreed upon) key values that the server side expects to receive in the message.

It seems possible, although I did not attempt it, to better represent this communication through a well defined interface or "message class" and given the technology used in the "transform" logic (as specified in the header, more information in the next section) that more complex objects or "real" message objects could be serialized from PHP (either through a better "true" class representation or the associative array structure based on the above) to Java that would rely on a more formal "contract"; however, given the time I had already spent on this project and the need to get it delivered to the client, once I had the associative array/MapMessage worked out, and I am comfortable (at the moment) with the implementation, I did not take the exercise any further.

Just for the sake of completeness, if you were to browse a queue with the above message using the built in we admin functionality in ActiveMQ (say by using "http://localhost:8161/admin/"), the message body would look like the following. Note that I have again done some slight reformatting for readability by adding line breaks and indentation.

{
  contentName=Partner Resources, 
  contentId=10034, content=These are the partners we should be using..., 
  authorEmail=lance@lancehendrix.com, 
  contentHREF=<a href="http://somewhere.com/Partners/Resources/">PartnerResources</a>, 
  contentType=blog, author=Lance Hendrix
}

You will note that the above is definitely not in JSON format. ActiveMQ has already translated the message from JSON and is rendering the message in a common format for us. More information on the details can be found in the "How the Solution Was Discovered" section below where I will dive into the ActiveMQ source code and provide more details around the "magic" of the "transformation" header property/attribute.

2.4.1.2. Message Header

As you can see from the "print_r" of the message body from the "Message Body" section above, there are two properties/attributes that have been set on the message. The "transformation" message header property is obviously set by me explicitly in the code where I am creating and sending the message. The second property "amq-msg-type" is set by the PHP Stomp Library and when I initially started using it, I had assumed that this was all that was needed to be able to send a PHP Stomp Map Message to ActiveMQ; however, in reality, it appears that this property is not required as the implementation works without it (I tested and verified this against the versions and components listed above). However, without the "transformation" property, the message is accepted by ActiveMQ, but is received by the client as an ("uninterpreted") TextMessage, which would require you to parse and deserialize the message yourself, which defeats the whole purpose of using a MapMessage in the first place.

More information on the "transformation" header property can be found later and it appears that the ActiveMQ documentation on the stomp page (look in the "Message Transformation" section for reference) is incorrect (in their defence, it is probably just outdated and has not been updated with the recent changes to ActiveMQ 5.1, but the code as documented doesn't appear to work even with 5.0, as I attempted to do so and failed, but upon digging into the details of the code and the ActiveMQ jar file, I discovered that it was not implemented as documented in 5.0, please see the ActiveMQ site for more information).

2.4.1.3. Everything Else

The remainder of the items in the PHP file are rather uninteresting, as they are either well know, well documented elsewhere, or rather trivial (in comparison) to figure out with the existing resources on the net. Specifically, the series of calls to connect and send a stomp message are well documented on the (this link now returns an HTTP 404, however for posterities sake, I have left the textual reference to the address but removed the hyperlink) "http://stomp.codehaus.org/PHP" PHP Stomp Client library page. The connection strings and the specific URLs for accessing queues and topics is well documented on the ActiveMQ Stomp reference page on the ActiveMQ site.

2.4.2. ActiveMQ Middleware

The configuration of ActiveMQ is rather straightforward and follows the documentation provided by ActiveMQ quite well. The only area that may cause issues is well documented in the ActiveMQ documentation and that is regarding external dependencies required due to the "transformation" request in the message header. I will detail both the configuration of ActiveMQ as well as the resolution of these dependencies in the next two sections.

2.4.2.1. Configuration

Luckily, ActiveMQ (at least version 5.0 and 5.1) come configured with the stomp connector enabled by default right "out of the box". Therefore, the default installation of ActiveMQ should not require any extra configuration in order to be able to leverage this documentation or Stomp in general; however, there are some "gotchas" regarding external dependencies as documented in the following section.

2.4.2.2. External Dependencies

One issue that also tripped me up for a little while on this implementation is the need for additional external libraries that are not part of the ActiveMQ 5.1 distribution/installation. This curious, because, based on the "tar ball" for the 5.0 installation, it appears that these dependencies were included. I assume the ActiveMQ maintainers decided for good reasons not to include these third party dependencies into their distribution and I can definitely understand why they would not want to. These dependencies are also called out on the ActiveMQ Stomp reference (just below the Java example code is a box shaded blue with "dependencies" as the title that is the information you should be looking for) page, but I will go through them with you in a little more detail to ensure you understand how to acquire these dependencies and ensure they are properly installed so that ActiveMQ can use them to transform your JSON Stomp messages to JMS MapMessages. In other sections, I will describe how you can inspect the ActiveMQ jar files and source code to verify both the external dependencies as well as the supported transformation types.

The ActiveMQ 5.0 distribution contains an xstream-1.1.2.jar in the "lib/optional" directory, but don't use it! (I already tried and it doesn't work) You need to go to the XStream website as indicated on the ActiveMQ Stomp reference page. I am using a jar named "xstream-1.3.jar" that I downloaded as a binary distribution from the XStream download page. Once you have downloaded the distribution, extract it into a directory, but all you need is the "xtream-1.3.jar" file from the "lib" directory where ever you extracted the distribution. I copied only the "xstream-1.3.jar" file into the "lib" directory of my ActiveMQ installation. However, we still have to get the Jettison library.

The ActiveMQ Stomp reference page also has a reference to the Jettison site where you can obtain the additional Jettison jar file required for JSON transformations. Download the "binary" distribution from the Jettison download site, which is a single jar file, "jettison-1.0.jar". You might be tempted to try to use the jettison jar file that is included in the XStream distribution that we downloaded above, but don't, as it didn't work for me. You need the 1.0 file that is on the Jettison site. As with the XStream jar file, just copy the "jettison-1.0.jar" file into the "lib" directory of your ActiveMQ installation and you should be all set.

2.4.3. Java Server Side

Receiving the message on the Java side is rather trivial and straightforward, once the client side is sending the message correctly and the additional external dependencies are installed in the ActiveMQ middleware layer; however, for completeness of the example and for those less familiar with Java or JMS, the following solution highlights how to setup a message listener and receive and process an incoming MapMessage from ActiveMQ in Java. The ActiveMQ documentation is quite good in this regard and you should consult the ActiveMQ documentation for any questions or issues regarding leveraging ActiveMQ from Java. Any perceived (unnecessary) complexity of the Java solution, I can assure you, is introduced as a result of the JMS APIs and not by the authors of ActiveMQ.

2.4.3.1. Message Listener

The following code demonstrates a message listener class that listens for messages on a queue.

package com.webOnyx.macteam.messaging;

import javax.jms.Connection;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.apache.activemq.command.ActiveMQMapMessage;

public class ContentListener implements MessageListener 
{
    public void onMessage(Message message) 
    {
        try
        {
            if (message instanceof MapMessage)
            {
                ActiveMQMapMessage notification = (ActiveMQMapMessage)message;
                String contentId = notification.getString("contentId");
            }  
            else
            {
                System.out.println("It seems the notification message was not in map message format...");
            }
        }
        catch (JMSException e) 
        {
            super.handleJmsException(e);
        } 
        finally
        {
            System.out.println();
        }
    }
}
2.4.3.2. Message Processing

To be documented... As it has been several years, I am not quite sure what I had intended here, but I am working on revising and updating the document, so stay tuned...

2.5. How The Solutions Was Discovered

Since it has been several years since I worked on this (I am working to update this document, so stay tuned although hopefully I don't have to dig so deep this time), I don't remember the specifics of what I had to do to find the problem, but I do remember that I did quite a bit of debugging on the ActiveMQ source code. That is, I brought the source up in Eclipse and set break points through the code until I dug my way into where the transformation were happening to see the data structures and how it was handling the layouts. This is how I ended up hitting on the structure required from PHP in order to make the java MemMap work appropriately.

If I end up having to dig into this again, I will work to try and document the specifics of what I found and where I was looking to figure it out.