Home    | Software    | Articles    | Tips'n Tricks    | Contacts    | Support Us  
Home arrow Articles arrow Exposing OpenJMS to PHP

Exposing OpenJMS to PHP

Introduction

I recently got involved in a project where reliable communication between etherogenous platforms was required. Having already worked with JMS, I had to find a way to expose its services to the frontend(a PHP base web application). Some time before I had come across XFire, and had been looking for a chance to work on it. This was that chance.
I know SOAP can sometimes be cumbersome, but XFire really makes its use easy, and PHP well supports it( we'll use the nusoap library).
In this article I'll make an introduction on how to easily merge together OpenJMS,XFire and PHP to get a generic, immediately usable messaging system.
If someone will be interested in it, further articles will follow on this topic.


Overview
To make this as simple as possible, I'm going to cover not a line more than what is really needed to achieve our goal, so just a few words about the involved technologies.

JMS (java message service) is :"a messaging standard that allows application components based on the Java 2 Platform, Enterprise Edition (J2EE) to create,send, receive, and read messages. It enables distributed communication that is loosely coupled, reliable, and asynchronous"(ref.).
What we need is a way to allow non-java technologies(in our case PHP) to access the functionalities exposed by JMS. This time we'll use SOAP.

SOAP(simple object access protocol) is :a protocol for exchanging XML-based messages over a computer network, normally using HTTP. SOAP forms the foundation layer of the Web services stack, providing a basic messaging framework that more abstract layers can build on. SOAP can be used to facilitate a Service-Oriented architectural pattern "(ref.).

Used platforms


The JMS implementation we're going to use is OpenJMS. There are plenty of other valid JMS implementations; personally I find OpenJMS to be a good choice, is open source and easy to use .
SOAP services will be handled by XFire, which allows us to expose even POJOs as web services at a snap.
PHP will be our front end platform. The PHP part just handles soap calls, and it's quite easy to adapt this part for other languages with soap extensions(basically every language has at least one).

Before we start writing code
First thing to do is to download both OpenJMS(here) and XFire(here). From XFire we just need its libraries, but with OpenJMS we also need to start up the server. Nothing easier, we're not going to configure anything. Unzip OpenJMS distribution and just start it(startup.sh or startup.bat) from its bin directory.


Writing the OpenJms functionalities wrapper

We need to get/post a message from/to a queue/topic. We have 4 combination, 2 for getting, 2 for posting.
Let's first define an interface:

   1:import javax.jms.JMSException;
2:import javax.jms.TextMessage;
3:import javax.naming.NamingException;
4:
5:/**
6: *
7: * @author beanizer
8: */
9:public interface OpenJMSService {
10: public TextMessage[] getMsgFromQueue(String queue) throws NamingException,
JMSException;
11: public TextMessage postMsgToQueue(String queue,String msg) throws
NamingException,JMSException;
12:
13: public TextMessage[] getMsgFromTopic(String topic,String consumer) throws
NamingException,JMSException;
14: public TextMessage postMsgToTopic(String topic,String msg) throws
NamingException,JMSException;
15:
16:}


Note that:
1) We're going to work only on text messages (more message types are available through JMS)
2) We're not going to handle exceptions here. The thrown exceptions will be magically encapsulated by XFire and sent back to the client in the soap response
3) The interface will be used by XFire to build the SOAP wsdl and service.

It's now time to implement the concrete class:
   1:import java.util.Hashtable;
2:import javax.jms.*;
3:import javax.naming.Context;
4:import javax.naming.InitialContext;
5:import javax.naming.NamingException;
6:
7:/**
8: *
9: * @author beanizer
10: */
11:public class OpenJMSServiceImpl implements OpenJMSService{
12: Context context;
13: Session session;
14: Connection connection;
15: MessageConsumer receiver=null;
16: /** Creates a new instance of OpenJMSServiceImpl */
17: public OpenJMSServiceImpl() {
18: Hashtable properties = new Hashtable();
19: properties.put(Context.INITIAL_CONTEXT_FACTORY, "
org.exolab.jms.jndi.InitialContextFactory");
20: properties.put(Context.PROVIDER_URL, "tcp://localhost:3035/");
21: try {
22: context = new InitialContext(properties);
23: ConnectionFactory factory =(ConnectionFactory)context.lookup("ConnectionFactory");
24: connection = (Connection)factory.createConnection();
25: session = (Session)connection.createSession(false
, Session.AUTO_ACKNOWLEDGE);
26: connection.start();
27: } catch (Exception ex) {
28: ex.printStackTrace();
29: }
30:
31: }
32:

Here we initialize a connection and a session to the OpenJMS server running on localhost at port 3035.
The URL used on line 20 depends on the OpenJMS server configuration. We're using the default, but keep in mind that OpenJMS could be on a different machine, different port, and could even use a different protocol(i.e. RMI).
We're using generic items for : ConnectionFactory,Connection and Session. Specific items for both topics and queues exist, but in this case we're trying to simplify things.
On line 25, the first parameter(false) tells that we're not going to use a transactioned session.

  33:    public TextMessage[] getMsgFromTopic(String topicName,String consumer) throws
NamingException,JMSException{
34: Topic destination=(Topic) context.lookup(topicName);
35: try{
36: receiver=session.createDurableSubscriber(destination,consumer);
37: } catch(Exception ex){}
38: return (getMsg(receiver));
39: }
40: public javax.jms.TextMessage[] getMsgFromQueue(String queueName) throws
NamingException,JMSException{
41: Queue destination=(Queue) context.lookup(queueName);
42: MessageConsumer receiver=session.createConsumer(destination);
43: return (getMsg(receiver));
44:
45: }
46:
47:
48: public TextMessage postMsgToTopic(String topicName, String msg) throws
NamingException,JMSException{
49: Topic destination= (Topic)context.lookup(topicName);
50: MessageProducer sender = session.createProducer(destination);
51: return postMsg(sender,msg);
52: }
53:
54:
55: public TextMessage postMsgToQueue(String queueName, String msg) throws
NamingException,JMSException{
56: Queue destination= (Queue)context.lookup(queueName);
57: MessageProducer sender = session.createProducer(destination);
58: return postMsg(sender,msg);
59: }
60:

The above snippet implements the 4 methods that will be exposed as SOAP methods.
As you can see, message retrieving is demanded to the generic method "getMsg"(explained below), as the generic session created can be used by both topics and queues, so the specific methods just need to create a proper MessageConsumer.
Same thing for posting messages, a MessageProducer is created this time and passed to the generic function "postMsg".
On "getMsgFromTopic" we use an instance scoped MessageConsumer because a DurableSubscriber is valid at a session level, so we try to create it and if it already exists an (ignored) exception is thrown and trapped. Not very elegant...

  61:
62: private TextMessage postMsg(MessageProducer sender, String msg) throws
JMSException{
63: TextMessage message = session.createTextMessage(msg);
64: sender.send(message);
65: sender.close();
66: return message;
67: }
68:
69: private javax.jms.TextMessage[] getMsg(MessageConsumer receiver) throws
JMSException{
70: javax.jms.Message message=receiver.receive(100);
71: TextMessage text= (message instanceof TextMessage) ?
(TextMessage) message : null;
72: javax.jms.TextMessage[] msgs={text};
73: return (msgs);
74: }
75:
76:}


These are the generic methods we were talking about. They just post/retrieve a message. In both cases a text message(the received or the sent) is passed back to the caller.
On "getMsg" I'm using a TextMessage array as return type to make the function compatible with an eventual multi message retrieving method.

This is a very basic implementation, lots of things are missing and other should be considered(finalizing, closing sessions/connections etc.), but it's maybe good enough as a starting point.
Let's go on with the XFire section now.

Exposing the service via XFire

The following simple class is everything we need to expose our OpenJMSService interface as a SOAP service through XFire.

   1:
2:import org.codehaus.xfire.XFire;
3:import org.codehaus.xfire.XFireFactory;
4:import org.codehaus.xfire.client.XFireProxyFactory;
5:import org.codehaus.xfire.server.http.XFireHttpServer;
6:import org.codehaus.xfire.service.Service;
7:import org.codehaus.xfire.service.binding.ObjectServiceFactory;
8:import org.codehaus.xfire.service.invoker.BeanInvoker;
9:
10:/**
11: *
12: * @author beanizer
13: */
14:public class Main {
15: XFireHttpServer server;
16: public static void main(String[] args) {
17: Main starter = new Main();
18: try {
19: starter.start();
20: } catch (Exception ex) {
21: ex.printStackTrace();
22: }
23: }
24: public void start() throws Exception
25: {
26: // Create an XFire Service
27: ObjectServiceFactory serviceFactory = new ObjectServiceFactory();
28: XFire xfire = XFireFactory.newInstance().getXFire();
29:
30: Service service = serviceFactory.create(OpenJMSService.class);
31: service.setInvoker(new BeanInvoker(new OpenJMSServiceImpl()));
32: xfire.getServiceRegistry().register(service);
33:
34: // Start the HTTP server
35: server = new XFireHttpServer();
36: server.setPort(8191);
37: server.start();
38: }
39: public void stop() throws Exception
40: {
41: server.stop();
42: }
43:}


Xfire allows us to avoid the hassle of setting up a servlet engine, by providing an embedded http server(Jetty). The above code is pretty self-explaining; after creating an XFire object through a factory, we register a service (our OpenJMSService service) on it and just start the embedded http server. That's all!!!
Just put in your classpath the required XFire and OpenJMS jars and launch our Main class. Main.java, OpenJMSService.java and OpenJMSServiceImpl.java must reside in the same directory(you can arrange them differently in packages if you prefer).

Now we can call our service with any SOAP client. Let's see how to do it with PHP.

 

Accessing the SOAP service from PHP

First, from a system shell start OpenJMS admin GUI (admin.sh or admin.bat in bin) and create a queue with name "myqueue" and a topic with name "mytopic" containig a consumer named "cons1".
The following simple class is everything we need to access our OpenJMSService SOAP service from PHP.

   1:<?php
2: include_once("nusoap.php");
3:
4: class OpenJms4PHP{
5: var $proxy;
6: var $params;
7: var $sess;
8: var $client;
9:
10: function OpenJms4PHP($endpoint){
11: $this->client = new soapclient($endpoint,true);
12: $this->proxy = $this->client->getProxy();
13:
14: }
15: function PostMsgToQueue($queueName,$msg){
16: return $this->proxy->PostMsgToQueue(array(
"in0"=>$queueName,"in1"=>$msg));
17: }
18: function PostMsgToTopic($topicName,$msg){
19: return $this->proxy->PostMsgToTopic(array(
"in0"=>$topicName,"in1"=>$msg));
20: }
21: function getMsgFromTopic($topicName,$consumerName){
22: return $this->proxy->getMsgFromTopic(array(
"in0"=>$topicName,"in1"=>$consumerName));
23: }
24: function getMsgFromQueue($queueName){
25: return $this->proxy->getMsgFromQueue(array(
"in0"=>$queueName));
26: }
27:
28: }
29:?>

Nusoap is an excellent soap library for PHP. PHP5 includes its own soap classes. You can use them if you prefer, only minimal changes are needed.
After creating a soap object, we retrieve the proxy, and just call the exposed methods. We'll get back an array containing the returned object, which can also be an error.

Here a simple example of usage:

   1:<?php
2: include_once "OpenJms4PHP.php";
3: $jms=new OpenJms4PHP("http://127.0.0.1:8191/OpenJMSService?wsdl");
4: $result=$jms->PostMsgToTopic("mytopic","PHP2OpenJMSService test message");
5: echo "<pre>";
6: print_r($result);
7: echo "</pre>";
8:?>


The URL passed to the contructor of OpenJms4PHP is the XFire server one (on localhost and port 8191, the default).

Conclusions
With an interface, 2 small java classes and a PHP script, we've built the (still incomplete) root for a trivial to use messaging system.

Hasta la proxima.
 
< Prev   Next >

  Articles RSS feed

Latest Articles
Latest Software
   
designed by allmambo.com