Home    | Software    | Articoli    | Tips'n Tricks    | Contattaci    | Supportaci  
Home arrow Articoli arrow Utilizzare OpenJMS da PHP

Utilizzare OpenJMS da PHP

Introduzione

 

Recentemente mi sono trovato su un progetto in cui era richiesta comunicazione affidabile tra piattaforme eterogenee. Avendo già lavorato su JMS, dovevo trovare un modo di esporne i servizi all'interfaccia web(un applicativo in PHP). Qualche tempo prima mi ero imbattuto in XFire, e non vedevo l'ora di trovare l'occasione per utilizzarlo. Questa era l'occasione.
Lo so, SOAP a volte è una sofferenza, ma XFire ne rende l'uso semplice, e PHP lo supporta bene(utilizzeremo la libreria nusoap).
In questo articolo, farò un'introduzione su come sia semplice unire OpenJMS,XFire e PHP per ottener un sistema di messagistica generico e immediatamente utilizzabile.
Se qualcuno sarà interessato, seguiranno ulteriori articoli al riguardo.

Panoramica
Per rendere il tutto il più semplice possibile, non scriverò una riga in più di quelle strettamente necessarie allo scopo, quindi ecco poche parole sulle tecnologie implicate:

JMS (java message service) è: "uno standard di messaggistica che permette a componenti applicative basate su piattaforma Java 2 Enterprise Edition, di creare, inviare, ricevere e leggere messaggi. Permette una comunicazione distribuita che è flessibile, affidabile e asincrona "(ref.).
Ciò che ci serve è un metodo che permetta a tecnologie non java(in questo caso PHP) di accedere alle funzionalità esposte da JMS. Questa volta useremo SOAP.

SOAP(simple object access protocol) è :un protocollo per lo scambio di messaggi in formato XML su rete, normalmente utilizzante HTTP. SOAP forma lo strato fondamentale dei servizi web, fornendo un base messaggistica utilizzabile da livelli più astratti. SOAP può essere utilizzato per facilitare uno schema architetturale orientato ai servizi"(ref.).

Piattaforme utilizzate


L'implementazione JMS che utilizzeremo è OpenJMS. Esistono molte altre implementazioni altrettanto valide; personalmente trovo che OpenJMS sia una buona scelta, è open source e facile da usare.
L'esternalizzazione dei servizi SOAP sarà a cura di XFire, che semplifica drammaticamente l'esposizione perfino di semplici oggetti java(POJO).
PHP rappresenterà la nostra interfaccia web. La parte PHP si occuperà solo di effettuare le chiamate SOAP, per cui è molto semplice adattare il tutto ad altri linguaggi che abbiano almeno un'estensione SOAP(praticamente qualsiasi linguaggio).

Prima di iniziare a scrivere codice
La prima cosa da fare è scaricare sia OpenJMS(qui) che XFire(qui). Di XFire ci servono solo le librerie, ma di OpenJMS dobbiamo anche far partire il server. Niente di più semplice, non dobbiamo configurare nulla. Decomprimiamo la distribuzione di OpenJMS e facciamolo partire(startup.sh o startup.bat) dalla sua directory bin.


Il codice di interfacciamento a OpenJMS
Dobbiamo inviare/ricevere messaggi da/a una Queu(coda)/Topic(argomento). Abbiamo 4 combinazioni, 2 per leggere, 2 per scrivere.
Prima definiamo l'interfaccia:

   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:}


Da notare che:
1) Lavoriamo solo su messaggi di testo(JMS ne espone anche altri tipi)
2) Non ci occupiamo di gestire le eccezioni qui. Le eccezioni verranno magicamente incapsulate da XFire e mandate al chiamante nella risposta soap.
3) L'interfaccia serve a XFire per costruire il file wsdl SOAP e il servizio.

Ora implementiamo la classe concreta:
   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:

Qui inizializziamo una connessione e una sessione con il server OpenJMS attivo in locale sulla porta 3035.
La URL utilizzata alla riga 20 dipende dalla configurazione di OpenJMS. Stiamo usando il default, ma teoricamente OpenJMS potrebbe risiedere su una macchina diversa, su una porta diversa, e potrebbe addirittura usare un protocollo diverso(es. RMI):
Usiamo oggetti generici per: ConnectionFactory, Connection e Session. Esistono oggetti specifici per Queue e Topic, ma in questo caso preferisco semplificare le cose.
Alla riga 25, il primo parametro(false) definisce che non utilizzeremo sessioni transazionali.

  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:

Il codice di cui sopra implementa i 4 metodi che verranno esposti come metodi SOAP.
Come si nota, la lettura dei messaggi è demandata al metodo generico "getMsg"(vedere sotto), dato che la sessione generica creata può essere usata sia per Topic che per Queue, per cui i metodi generici devono solo creare il corretto MessageConsumer.
Stessa cosa per inviare messaggi, questa volta creando un MessageProducer e passandolo alla funzione generica "postMsg".
In "getMsgFromTopic" usiamo un MessageConsumer di istanza perchè un DurableSubscriber è valido a livello di sessione, quindi ne tentiamo la creazione e se esiste già un'eccezione viene lanciata (e ignorata). Non molto elegante....

  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:}


Questi sono i metodi generici di cui parlavamo. Leggono e inviano messaggi. In entrambi i casi un TextMessage(ricevuto o inviato) viene restituito al chiamante.
In "getMsg" usiamo un array di TextMessage come dato di ritorno per rendere la funzione compatibile con eventuali metodi di lettura di messaggi multipli.

E' un'implementazione molto base,mancano una marea di cose e altre andrebbero considerate(finalizzazione/chiusura di sessione/connessione etc.), ma può già considerarsi un buon punto di partenza.
Andiamo avanti con la parte di XFire.

 

Esporre il servizio tramite XFire
La semplice classe seguente è tutto quello che serve per esporre la nostra interfaccia OpenJMSService come servizio SOAP tramite 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 ci permette di evitare il fastidio di configurare un motore di servlet, perchè ne contiene uno interno(jetty); il codice si auto descrive abbastanza; dopo aver creato un oggetto XFire tramite una Factory, vi registriamo un servizio(il nostro OpenJMSService), e facciamo partire il server http/servlet interno. Tutto qui !!!!!
E' sufficiente mettere nella classpath i jars di OpenJSM e XFire richiesti e lanciare la nostra classe Main. Main.java, OpenJMSService.java and OpenJMSServiceImpl.java devono risiedere nella stessa direcory(si può cambiare a piacimento).

Ora possiamo chiamare il nostro servizio da qualsiasi client SOAP. Vediamo come fare in PHP.

 

Accedere ai servizi SOAP in PHP
Prima di tutto, da una shell di sistema lanciamo l'interfaccia grafica amministrativa di OpenJMS(admin.sh o admin.bat in bin) e creiamo una Queue con nome "myqueue" e un topic con nome "mytopic" contenente un Consumer di nome "cons1".
La semplice classe seguente è sufficiente per accedere da PHP al nostro servizio OpenJMSService.

   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 è una eccellente libreria SOAP per PHP. PHP5 include delle sue classi. Potete usarle, se preferite, con poche modifiche.
Dopo aver creato l'oggetto soap, ne chiediamo il proxy, e chiamiamo i metodi esposti. Otterremo un array contenente gli oggetti di ritorno(può anche essere un oggetto di errore).

Ecco un semplice esmpio di utilizzo:

   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:?>


La URL passata al costruttore di OpenJms4PHP è quella del server XFire(localhost, port 8191, il default).

Conclusioni
Con un'interfaccia, 2 piccole classi Java e una di PHP, abbiamo costruito le fondamenta(incomplete) per un sistema di messaggistica di facilissimo utilizzo.
Se siete interessati ad ultertiori articoli sull'argomento, Indirizzo e-mail protetto dal bots spam , deve abilitare Javascript per vederlo .

 

 

 

 
< Prec.   Pros. >

  Articles RSS feed

Articoli pił recenti
Software pił recenti
   
designed by allmambo.com