In questo articolo ci concentreremo sulle principali API del Component Framework JBI e svilupperemo un semplice esempio di Service Engine.
Nel precedente articolo (vedere [1]) si è introdotto JBI e si è parlato delle tipologie dei componenti (SE, BC), del sistema di messaging (NMR, DeliveryChannel, NormalizedMessage…) e della gestione del ciclo di vita del componente (Management).
Introduzione
Come è stato spiegato nel precedente articolo, JBI permette di installare componenti Service Engine (SE) e/o Binding Component (BC) con una modalità a plugin.
Per raggiungere questo livello di “system-level pluggability” l‘architettura JBI definisce un set standard di “contratti” da rispettare utilizzando le opportune API.
Il JBI Component Framework
Il Framework JBI è costituito da classi, identificate dal prefisso javax.jbi, suddivise in cinque package:
- javax.jbi
- javax.jbi.component
- javax.jbi.management
- javax.jbi.messaging
- javax.jbi.servicedesc
I contratti di Management sono stipulati dal componente JBI mediante le interfacce del package javax.management (AdminServiceMBean, ComponentLifeCycleMBean, DeploymentServiceMBean, LifeCycleMBean, …)
I contratti di Messaging sono siglati mediante le interfacce del package javax.jbi.messaging (DeliveryChannel, MessageExchange, NormalizedMessage, InOnly, InOut, …).
I rimanenti contratti vengono collettivamente indicati con il termine “Component Framework” e prevedono l‘utilizzo delle classi del package javax.component.
JBI fornisce un nuovo environment che si aggiunge agli ormai consolidati e blasonati J2SE e J2EE.
Gli oggetti di contesto dell‘environment JBI sono creati e gestiti in modo infrastrutturale analogamente a quanto accade ad esempio con gli EJB per gli oggetti SessionContext, EntityContext, …
Il JBI framework fornisce gli oggetti di contesto (InstallationContext, ComponentContext, …) attraverso i quali è possibile interagire con l‘environment JBI nella fase di installazione e durante la fase di esecuzione. Dagli oggetti di contesto è possibile accedere alle informazioni di deploy (es: il nome del componente, le classi che lo costutiscono, …) e alle risorse del sistema come ad esempio il Transaction Manager, gli MBean e gli endpoint (sia interni che esterni).
Vediamo ora un semplice esempio di componente JBI di tipo Service Engine il cui scopo è mettersi in attesa di ricevere dei messaggi per poi scriverne il contenuto su file.
Da notare come non vi sia alcuna differenza tra SE e BC a livello di API da utilizzare. L‘unica differenza è a livello di descriptor (META-INF/jbi.xml)dove il tag component type può assumere i valori “service-engine” o “binding-component”.
La vera differenza tra un BC e un SE è di come si sviluppa la gestione dei messaggi ricevuti dal NMR: il SE processa dei NormalizedMessage all‘interno dell‘environment JBI, mentre un BC usa uno specifico protocollo di comunicazione per comunicare con gli endpoint esterni.
La costruzione di un Componente JBI prevede di realizzare le classi concrete che implementano le opportune interfacce JBI.
Le interfacce del package javax.jbi.component
L‘interfaccia javax.jbi.component.Bootstrap è l‘interfaccia attraverso la quale il JBI engine interagisce con il componente durante la fase di installazione.
Nel metodo Bootstrap.init() viene fornita dal JBI environment, un‘istanza dell‘InstallationContext.
Mediante l‘interfaccia InstallationContext è possibile accedere all‘environment JBI per ottenere le risorse e le informazioni necessarie durante la fase di installazione e disinstallazione del componente. Ad esempio è possibile ottenere il nome del componente mediante il metodo getComponentClassName(), la classe concreta del componente con il metodo getComponentClassName() o la directory root di installazione invocando il metodo getInstallRoot().
La classe concreta di Bootstrap del componente deve essere referenziata all‘interno del deployment descriptor JBI mediante il tag
it.mokabyte.jbi.SampleConcreteBootstrap
L‘interfaccia ComponentLifecycle permette al JBI engine di controllare il ciclo di vita dl componente JBI (SE/BC) invocando gli opportuni metodi a seconda dell‘operazione richiesta dal tool di management.
Il Component context (javax.jbi.component.ComponentContext) è fornito al componente durante la fase di inizializzazione del componente nel metodo ComponentLifeCycle.init() e permette l‘accesso alle risorse dell‘environment JBI durante l‘esecuzione del componente. Ad esempio con il metodo getDeliveryChannel() è possibile ottenere un DeliveryChannel per inviare/ricevere messaggi, ottenere un logger invocando il metodo getLogger() o sapere gli endpoints (interni ed esterni) mediante i metodi getEndpoints() e getExternalEndpoints() nonchè attivare/disattivare i service enpoints forniti dal componente con i metodi activateEndpoint() e deactivateEndpoint().
Nei metodi start() e stop() si deve provvedere a fornire l‘opportuna logica per l‘avvio e l‘arresto del componente. Il metodo getExtensionMbeanName() deve restituire l‘MBean qualora il ciclo di vita del componente sia un‘estensione del ciclo di vita standard.
L‘interfaccia javax.jbi.component.Component permette al JBI engine al JBI environment di accedere alle parti “core” del Service Engine come ad esempio il ServiceUnitManager (metodo getServiceUnitManager()) e il ComponentLifeCycle (getLifeCycle()).
La classe concreta che implementa l‘interfaccia Component deve essere referenziata all‘interno del deployment descriptor JBI mediante il tag
Es:
it.mokabyte.jbi.SampleConcreteComponent
L‘interfaccia javax.jbi.component.ServiceUnitManager è invocata dall‘‘engine JBI durante il processo di deploy della Service Unit correlata al Componente.
Il sequenze diagram che segue riassume quanto detto fino ad ora.
Il package di un componente JBI
Il packaging di un componente JBI prevede la costruzione di un file archivio (.jar) contenente tutte le classi del componente ed i jar necessari per il suo corretto funzionamento.
All‘interno del file jar del componente JBI deve essere presente il file di deployment descriptor di nome jbi.xml all‘interno di una directory META-INF (META-INF/jbi.xml).
I principali elementi del file jbi.xml sono:
- jbi: è l‘elemento root del DD
- version: tale attributo specifica la versione JBI utilizzata
- type: è l‘elemento del tag component che specifica il tipo del componete, cioè indica se il componente è un Service Engine (
)o un Binding Component ( )
- L‘elemento identification contiene il nome del Componente mentre
riporta una descrizione testuale del componente
- component-class-name: indica la classe “core” del Componente, cioè la classe che implementa l‘interfaccia javax.jbi.component.Component
- component-class-path: indica la locazione delle classi del Componente
- bootstrap-class-name: indica la claase che dovrà essere usata in fase di installazione del componente. Questa classe deve implementare l‘interfaccia javax.jbi.component.Booststrap.
- bootstrap-class-path: indica la locazione della classe di Booststrap
Il MokaComponent: un esempio di Service Engine JBI
Vediamo ora un semplice esempio di Componente di tipo Service Engine (vedere [4]).
SampleConcreteBootstrap
La classe SampleConcreteBootstrap è la classe concreta del componente che implementa l‘interfaccia javax.jbi.component.Bootstrap:
public class SampleConcreteBootstrap implements Bootstrap{
All‘interno del metodo init() si memorizza l‘oggetto InstallationContext fornito dal JBI engine.
Mediante il Logger (ottenuto anch‘esso dal Context) si stampano le informazioni del componente come l‘installation root directory ed il nome a fini prettamente di debug.
public void init(InstallationContext installationcontext) throws JBIException {this.mContext = installationcontext;this.mLog = Logger.getLogger(getClass().getPackage().getName());this.mLog.info(this.getClass().getName() + ".init(): InstallRoot[" + this.mContext.getInstallRoot() + "]...");this.mLog.info(this.getClass().getName() + ".init(): ComponentName[" + this.mContext.getComponentName()+"]...");this.mLog.info(this.getClass().getName() + ".init(): ComponentClassName[" + this.mContext.getComponentClassName()+"]..."); ... ecc ...}
I metodi onInstall() on unInstall() devono contenere la logica necessaria rispettivamente nelle fase di installazione e di disinstallazione del componente.
Il metodo getExtensionMBeanName() deve restituire null se la fase di installazione deve essere gestita dall‘MBean standard infrastrutturale JBI, altrimenti un oggetto MBean ad hoc qualora si prevede un‘installazione custom.
public ObjectName getExtensionMBeanName(){return null;}
SampleConcreteComponent
La classe “core” del componente è la classe SampleConcreteComponent che implementa le interfaccie javax.jbi.component..Componwnt e javax.jbi.component.ComponentLifeCycle.
public class SampleConcreteComponent implements Component, ComponentLifeCycle{
Implementando l‘interfaccia ComponentLifeCycle è possibile sviluppare i metodi concreti che permettono all‘engine JBI di controllare il ciclo di vita del componente JBI (SE / BC) mediante l‘invocazione dei metodi:
- public void init(ComponentContext context) throws JBIException
- public void shutDown() throws JBIException
- public void start()throws JBIException
- public void stop() throws JBIException
- ObjectName getExtensionMBeanName
L‘interfaccia ComponentLifecycle permette al JBI engine di controllare il ciclo di vita dl componente JBI (SE/BC) invocando gli opportuni metodi a secondo dell‘operazione richiesta dal tool di management.
Nel metodo init() si memorizza il reference al ComponentContext, si crea un oggetto DeliveryChannel dal ComponentContext e si crea si crea un oggetto ServiceUnitManager.
public void init(ComponentContext jbiContext)throws JBIException{if(jbiContext == null){this.mLog.severe(this.getClass().getName() + ".init(): ComponentContext is NULL!");throw new JBIException("SampleConcreteComponentLifeCycle.init: ComponentContext is NULL!");}this.mContext = jbiContext;this.mChannel = this.mContext.getDeliveryChannel();this.mServiceUnitManager = new SampleConcreteServiceUnitManager(this.mContext);}
Da notare come il ComponentContext ricevuto come parametro nel metodo ComponentLifeCycle.init() sia diverso da quello disponibile in fase di bootstrap (bootstrap.init())
In fase di bootstrap è reso disponible un InstallationContext dal quale è possibile ottenere un component context mediante il metodo Installationcontext.getContext(). Da tale contesto è possibile invocare solamente i metodi getMBeanNames(), getMBeanServer(), getNamingContext() e getTransactionManager(); l‘invocazione degli altri metodi causerebbero un‘eccezione di tipo IllegalStateException. Il ComponentContext disponibile nel metodo ComponentLifeCycle.init() permette un accesso complete al contesto JBI essendo il componente già installato.
Nel metodo start() si crea un‘istanza dell‘oggetto del Componente in grado di elaborare i messaggi passandogli il DeliveryChannel creato nel metodo init() come parametro.
public void start()throws JBIException{try {this.mMsgReceiver = new SampleMessageReceiver(this.mChannel);Thread thread = new Thread(this.mMsgReceiver);thread.start();} catch (Exception e) { e.printStackTrace();}}
Nel metodo stop invece ci si occupa di fermare l‘elaborazione dell‘oggetto Receiver avviato nel metodo start().
public void stop()throws JBIException{this.mMsgReceiver.stopReceiving();}
Il metodo getExtensionMBeanName() deve restituire null se il ciclo di vita deve essere gestito dall‘MBean standard infrastrutturale JBI, altrimenti un oggetto MBean ad hoc qualora si prevede un ciclo di vita “esteso” ad hoc per il componente.
public ObjectName getExtensionMBeanName(){return null;}
Implementando l‘interfaccia Component bisogna sviluppare i seguenti metodi:
- public ComponentLifeCycle getLifeCycle()
- public ServiceUnitManager getServiceUnitManager()
- public Document getServiceDescription(ServiceEndpoint serviceendpoint)
- public boolean isExchangeWithConsumerOkay(ServiceEndpoint se, MessageExchange me)
- public boolean isExchangeWithProviderOkay(ServiceEndpoint se, MessageExchange me)
- public ServiceEndpoint resolveEndpointReference(DocumentFragment df)
L‘interfaccia javax.jbi.component.Component permette al JBI engine di accedere alle parti del Service Engine come ad esempio il ServiceUnitManager e il ComponentLifeCycle.
Nel metodo getLifeCycle() infatti bisogna restituire l‘istanza della classe del componente adibita a gestire il ciclo di vita.
public ComponentLifeCycle getLifeCycle(){return this;}
Nel metdo getServiceUnitManager() bisogna invece provvedere a restituire l‘istanza della classe del componente adibita a gestire il ciclo di vita che nel nostro esempio è la classe SampleConcreteServiceUnitManager.
public ServiceUnitManager getServiceUnitManager (){
return this.mServiceUnitManager;
}
SampleConcreteServiceUnitManager
La classe SampleConcreteServiceUnitManager è la classe del componente che implementa l‘interfaccia javax.jbi.component.ServiceUnitManager.
public class SampleConcreteServiceUnitManager implements ServiceUnitManager{
L‘oggetto SampleConcreteServiceUnitManager è costruito (SampleConcreteComponent.getServiceUnitManager()) e gestito in modo applicativo e non dal JBI container.
Il metodo ServiceUnitManager.init() ha lo scopo di inizializzare l‘associazione
Service Unit(-Component. Infatti una Service Unit (SU) può esistere solo se in presenza di un contesto di un componente in esecuzione.
Nel costruttore si procedere a memorizzare il riferimento al ComponentContext (passato dal metodo ComponentLifeCycle.init()) e a creare un oggetto di classe ManagementMessageBuilder (contenuta nel package sharedlib.jar).
public SampleConcreteServiceUnitManager(ComponentContext compCtx){this.mLog = Logger.getLogger(getClass().getPackage().getName());this.mContext = compCtx;this.mBuildManagementMessage = new ManagementMessageBuilderImpl();}
Nel metodo start() si “informa” il JBI Engine che è attivo un nuovo endpoint.
Questo si ottiene creando un oggetto di classe javax.xml.namespace.QName specificando i valori l‘URI dell‘enpoint ed il nome dell‘endpoint (che devono essere gli stessi presenti nel deployment descriptor)
public void start(String serviceUnitID)throws DeploymentException{ QName qn = new QName("https://www.mokabyte.it/mokase.wsdl","MokaService");
L‘attivazione dell‘endpoint avviene invocando il metodo activateEndpoint() sul Context precedentemente memorizzato nella proprietà mContenxt:
try { ServiceEndpoint ref = mContext.activateEndpoint(qn, "MokaEndpoint"); } catch (JBIException jbie) { jbie.printStackTrace(); throw new DeploymentException(jbie.getMessage()); }
Il metodo deploy viene invocato dall‘environment JBI all‘atto del deploy di un Service Unit correlate al componente.
Il metodo deve restituire un messaggio XML contenente l‘elemento
public String deploy(String serviceUnitID, String serviceUnitRootPath)throws DeploymentException{ComponentMessageHolder compMsgHolder = new ComponentMessageHolder("STATUS_MSG");compMsgHolder.setComponentName(this.mContext.getComponentName());compMsgHolder.setTaskName("deploy");compMsgHolder.setTaskResult("SUCCESS");try{ComponentMessageHolder.taskResult["+compMsgHolder.getTaskResult()+"]...");String retMsg=this.mBuildManagementMessage.buildComponentMessage(compMsgHolder);return retMsg;}catch(Exception e){throw new DeploymentException(this.getClass().getName() + ".deploy():" + e.getMessage());}}
SampleMessageReceiver
Questa classe è adibita alla gestione della ricezione dei messaggi.
Viene creata in modo applicativo e la sua creazione non è a carico di JBI bensì applicativo.
Questa classe agisce come ricevitore dei messaggi che arrivano dal JBI engine o dal Normalized Message Router.
Nel costruttore viene memorizzato il reference all‘oggetto DeliveryChannel passato in precedenza dal metodo ComponentLifeCycle. Start()
public SampleMessageReceiver(DeliveryChannel bc){System.out.println(this.getClass().getName() + "::SampleMessageReceiver(): DeliveryChannel["+bc+"]...");this.mChannel = bc;this.goOn=true;}
attraverso il quale sarà possibile ricevere i messaggi invocando il metodo MessageExchange.exchange().
Nel metodo run() (il corpo del thread) si entra in un loop effettuando il polling sul DeliveryChannel per verificare se è stato ricevuto o meno un messaggio invocando il metodo MessageExchange.exchange() ogni TIME_TO_WAIT secondi (private final int TIME_TO_WAIT=30000;)
public void run(){while(this.goOn){ try{ MessageExchange mExchange = mChannel.accept(TIME_TO_WAIT); if(mExchange != null){
In caso di ricezione del messaggio, lsi scrive il suo contenuto sul file /mokabyte/moka_jbi/log/mokalog_giorno-mese-anno_ora-minuti-secondi.
if(mExchange instanceof InOnly){InOnly in= (InOnly)mExchange;NormalizedMessage msg=in.getInMessage();Source source=msg.getContent();try{ SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy_HH-mm-ss"); Date d = Calendar.getInstance().getTime(); String dateString = formatter.format(d); File fileLog = new File("/mokabyte/moka_jbi/log/mokalog_" + dateString + ".log"); PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(fileLog))); Result output = new StreamResult (pw); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(source, output);}catch(Exception e){e.printStackTrace();} . . .
La classe mette inoltre a disposizione un metodo per terminare il thread valorizzando a false la variabile di condizione del loop.
public void stopReceiving(){this.goOn=false; System.out.println(this.getClass().getName() + ".run(): this.goOn set to FALSE !");}}
Finito lo sviluppo del componente, passiamo a compilare i file Java del Componente.
set CLASSPATH=.;%JBI_HOME%appserverjbi.jar;%SUNSERVER_HOME%libj2ee.jar;sharedlib.jar%JAVA_HOME% injavac -classpath %CLASSPATH% -d ..classes itmokabytejbi*.java
Ottenuti i .class provvediamo ad inserirli in un jar: di nome mokasample_rt.jar:
%JAVA_HOME% injar -cvf mokasample_rt.jar it
JBI.XML
Per il deploy del componente dobbiamo creare il Deployment Descriptor (DD) jbi.xml:
MokaSampleServiceEngine Moka Sample Service Engine. it.mokabyte.jbi.SampleConcreteComponent mokasample_rt.jar sharedlib.jar it.mokabyte.jbi.SampleConcreteBootstrap mokasample_rt.jar sharedlib.jar
Ora dobbiamo creare un nuovo archive jar contenente sia il mokasample_rt.jar precedentemen-te creato, sia il file file META-INF/jbi.xml.
%JAVA_HOME% injar -cvf moka_sample_jbicomponent.jar mokasample_rt.jar META-INF sharedlib.jar
Il moka_sample_jbicomponent.jar è quindi il jar del componente JBI costutito da:
- META-INF/jbi.xml
- mokasample_rt.jar
- sharedlib.jar
Installazione del componente
Ottenuto il package del componente procediamo a installare il componente.
Ad oggi l‘unico Application Server JBI-compliant è quello della SUN.
Il tool jbiadmin
Con l‘SDK JBI 1.0 è disponibile il tool jbiadmin, un tool da riga di comando che permette l‘installazione del componente e la gestione del suo ciclo di vita.
Il comando che permette l‘installazione del componente è: install-component [component jar]
Avviato l‘Application Server impartiamo al jbiadmin il seguente comando:
A questo punto è possibile interagire da linea di commando con il tool di Managemnt JbiAdmin.
install-component <>MokaComponentMokaComponent.jar
Con il comando list-service-engines è possibile verificare i service engine installati.
>list-service-engines================================List of Service Engines================================Name : MokaSampleServiceEngineState: Shutdown--------------------------------. . . . . --------------------------------
Il SE è stato installato ma è in stato Shutdown … facciamolo partire!
Sempre da jbiadmin, scriviamo il seguente comando start-component MokaSampleServiceEngine e verifichiamo che il componente sia effettivamente avviato:
>list-service-engines================================List of Service Engines================================Name : MokaSampleServiceEngineState: Started--------------------------------. . . . . --------------------------------
Dai log <
. . . . .
[#|2005-20-18T23:09:29.269+0200|INFO|sun-appserver-pe8.1_02|javax.enterprise.system.stream.out|_ThreadID=16;|it.mokabyte.jbi.SampleMessageReceiver.run(): ready to accept on Channel [com.sun.jbi.messaging.DeliveryChannelImpl@1d849ae] every[30000] msec ...|#]
[#|2005-20-18T23:09:29.269+0200|INFO|sun-appserver-pe8.1_02|javax.enterprise.system.stream.out|_ThreadID=16;|it.mokabyte.jbi.SampleMessageReceiver.run(): received MessageExchange [null] from DeliveryChannel at Tue Oct 18 22:09:29 CEST 2005|#]
[#|2005-20-18T23:09:29.269+0200|INFO|sun-appserver-pe8.1_02|javax.enterprise.system.stream.out|_ThreadID=16;|it.mokabyte.jbi.SampleMessageReceiver.run(): ready to accept on Channel [com.sun.jbi.messaging.DeliveryChannelImpl@1d849ae] every[30000] msec ...|#]. . . . .
Conclusioni
Abbiamo creato il nostro primo componente JBI!
Si tratta di un semplice Componente JBI Service Engine che si mette in attesa di ricevere messaggi per poi scriverli su file.
Nel prossimo articolo vedremo come testare il suo funzionamento costruendo una … Composite Application!
Bibliografia e riferimenti
[1] S. Rossini “Java Business Integration (I)” Mokabyte 100 Ottobre 2005
[2] R. Ten-Hove, P. Walker “Java Business Integration (JBI) 1.0 Final Release”
http://www.jcp.org/en/jsr/detail?id=208
[3] http://java.sun.com/integration/
[4] “Sun White Paper: Developing a Service Engine Component”
http://java.sun.com/integration/reference/techart/
[5] http://java.sun.com/integration/download/
[6] http://java.sun.com/integration/1.0/docs/sdk/
[7] http://java.sun.com/integration/1.0/docs/sdk/api/index.html