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
Figura 1: il package javax.jbi
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.
Figura 2: Le interfaccie del package javax.jbi.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.
Figura 3: L'interfaccia javax.jbi.component.Bootstrap
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 <bootstrap-class-name>.
<bootstrap-class-name>it.mokabyte.jbi.SampleConcreteBootstrap</bootstrap-class-name>
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.
Figura 4: L'interfaccia javax.jbi.component.ComponentLifeCycle
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()).
Figura 5: L'interfaccia javax.jbi.component.Component
La classe concreta che implementa l'interfaccia Component
deve essere referenziata all'interno del deployment
descriptor JBI mediante il tag <component-class-name>.
Ad esempio:
<component-class-name>it.mokabyte.jbi.SampleConcreteComponent</component-class-name>
L'interfaccia
javax.jbi.component.ServiceUnitManager è invocata
dall''engine JBI durante il processo di deploy della
Service Unit correlata al Componente.
Figura 6: L'interfaccia javax.jbi.component.ServiceUnitManager
Il sequenze diagram che segue riassume quanto detto
fino ad ora.
Figura 7: Sequence diagram Componente JBI
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 (<component type="service-engine">)o
un Binding Component (<component type="binding-component">)
- L'elemento
identification contiene il nome del Componente mentre
<description> 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 [WP_DEVSE]). La
classe SampleConcreteBootstrap è la classe concreta
del componente che implementa l'interfaccia javax.jbi.component.Bootstrap:
public
class SampleConcreteBootstrap implements Bootstrap{
Figura 8: La classe concreta di 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{
Figura 9: La classe core del MokaServiceEngine
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{
Figura 10: Un esempio di classe concreta di 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("http://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 <task-result>SUCCESS</task-result>
oppure <task-result>FAILED</task-result>
a seconda dell'esito dell'operazione di deploy.
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.
Figura 11: Class diagram della classe Receiver
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
CLASS-PATH=.;%JBI_HOME%\appserver\jbi.jar;%SUNSERVER_HOME%\lib\j2ee.jar;sharedlib.jar
%JAVA_HOME%\bin\javac -classpath %CLASSPATH% -d ..\classes
it\mokabyte\jbi\*.java
Ottenuti
i .class provvediamo ad inserirli in un jar: di nome
mokasample_rt.jar:
%JAVA_HOME%\bin\jar
-cvf mokasample_rt.jar it
JBI.XML
Per
il deploy del componente dobbiamo creare il Deployment
Descriptor (DD) jbi.xml:
<?xml
version="1.0"?>
<jbi version="1.0" xmlns="http://java.sun.com/xml/ns/jbi"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<component type="service-engine">
<identification>
<name>MokaSampleServiceEngine</name>
<description>Moka Sample Service Engine.
</description>
</identification>
<component-class-name
description="Component Implementation">
it.mokabyte.jbi.SampleConcreteComponent</component-class-name>
<component-class-path>
<path-element>mokasample_rt.jar</path-element>
<path-element>sharedlib.jar</path-element>
</component-class-path>
<bootstrap-class-name>it.mokabyte.jbi.SampleConcreteBootstrap</bootstrap-class-name>
<bootstrap-class-path>
<path-element>mokasample_rt.jar</path-element>
<path-element>sharedlib.jar</path-element>
</bootstrap-class-path>
</component>
</jbi>
Ora
dobbiamo creare un nuovo archive jar contenente sia
il mokasample_rt.jar precedentemente creato, sia il
file file META-INF/jbi.xml.
%JAVA_HOME%\bin\jar
-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
Figura 12: il jar del Moka Componet
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:
install-component
<<MOKA_JBI>>\MokaComponent\MokaComponent.jar
A
questo punto è possibile interagire da linea
di commando con il tool di Managemnt JbiAdmin. Con
il comando list-service-engines è possbile verificare
i service engine installati.
>list-service-engines
================================
List of Service Engines
================================
Name : MokaSampleServiceEngine
State: 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 : MokaSampleServiceEngine
State: Started
--------------------------------
. . . . .
--------------------------------
Dai
log <<SUN_APPSERVER>>\Server\domains\domain1\logs\server.log
è possibile verificare che il componente è
in attesa sul canale in attesa di ricevere i messaggi:
.
. . . .
[#|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
[MOKA_JBI_I]
S. Rossini: Java Business Integration(I), Mokabyte N.
100 - Ottobre 2005
[JSR208_SPEC]: R. Ten-Hove, P. Walker: Java Business
Integration (JBI) 1.0 Final Release
http://www.jcp.org/en/jsr/detail?id=208
[JBI_HOME] http://java.sun.com/integration/
[WP_DEVSE] Sun White Paper: Developing a Service Engine
Component-
http://java.sun.com/integration/reference/techart/
[JBI_SDK1.0] http://java.sun.com/integration/download/
[JBI_SDK1.0_DOC] http://java.sun.com/integration/1.0/docs/sdk/
[JBI_JAVADOC] http://java.sun.com/integration/1.0/docs/sdk/api/index.html
|