Introduzione
Nello scorso articolo (vedi [INT1]) sono state presentate le tre semplici applicazioni enterprise che saranno oggetto di prove d'integrazione.
In questo articolo si parlerà dell'integrazione a livello d'interfaccia utente e a livello di Object/RPC.
Integrazione interfaccia utente
Mediante l'integrazione a livello di User Interface si fa in modo che applicazioni indipendenti tra loro presentino la medesima interfaccia utente.
Questo tipo di approcio è consigliabile laddove non si necessiti distinguere da quale applicazione provengano i dati e quindi risulti sufficiente una integrazione a "grana grossa" lato front-end.
Vediamo alcuni esempi pratici utilizzando le applicazioni A, B e C.
Una possibile modalità d'integrazione consiste nell'integrare le Applicazioni sviluppando un nuovo controller con il compito di invocare le risorse appartenenti all'Applicazione A, B o C, a seconda della richiesta dell'utente.
A tale scopo si sviluppa una nuova pagina HTML che prevede il passaggio di un parametro di nome "DESTINAZIONE" il cui valore discrimina l'applicazione che si vuole utilizzare:
<HTML> . . . . <FORM NAME="F1" METHOD=GET ACTION="<%=request.getContextPath()%>/ServiceDispatcher.jsp"> <input type="submit" name="DESTINAZIONE" value="APPLICATION_A"> </FORM> . . . . <FORM NAME="F1" METHOD=GET ACTION="<%=request.getContextPath()%>/ServiceDispatcher.jsp"> <input type="submit" name="DESTINAZIONE" value="APPLICATION_B"> </FORM> . . . . <FORM NAME="F1" METHOD=GET ACTION="<%=request.getContextPath()%>/ServiceDispatcher.jsp"> <input type="submit" name="DESTINAZIONE" value="APPLICATION_C"> </FORM> . . . .
Il nuovo controller (nel nostro caso una semplice JSP di nome ServiceDispatcher.jsp) ha il compito di effettuare la corretta invocazione della risorsa richiesta.
Il ServiceDispatcher provvede a leggere il valore del parametro DESTINAZIONE:
<% String action=request.getParameter("APPLICAZIONE");
e nel caso si riferisca all'applicazione A, verrà invocata la pagina concat.jsp mediante il metodo forward del RequestDispatcher ottenuto dal ServletContext (l'oggetto implicito application che fa riferimento alla web application corrente).
if(action.equals("APPLICATION_A")){
RequestDispatcher rd = application.getRequestDispatcher("/jsp/concat.jsp");
rd.forward(request, response);
}
Nel caso dell'Applicazione B o C si utilizza il metodo Response.sendRedirect() che redireziona il client ad una nuova URL indicata nella Response (da notare come l'URL che compare nella navigation bar del browser sia quello reimpostato dal server).
else if(action.equals("APPLICATION_B")){
response.sendRedirect("http://manowar:7001/app_b/jsp/wordlength.jsp");
}
else if(action.equals("APPLICATION_C")){
response.sendRedirect(http://manowar:80/app_c/asp/wordlength.asp");
}
. . . . .
%>
Essendo le applicazioni A e B tecnologicamente omogenee (J2EE), si potrebbe installare la web application dell'applicazione B sul Servlet Engine dell'applicazione A (nel nostro caso Tomcat) ed avere la possibilità di ottimizzare l'invocazione delle risorse dell'applicazione B.
Infatti in questo caso il dispatcher potrebbe ottenere il riferimento al ServletContext della WebApp B, richiedere il RequestDispatcher relativo alla risorsa d'interesse, ed effettuare l'opportuno forward senza dovere ricorrere al sendRedirect.
<%
. . .
else if(action.equals("APPLICATION_B")){
ServletContext sc = application.getContext("/app_b");
RequestDispatcher rd = sc.getRequestDispatcher("/jsp/wordlength.jsp");
rd.forward(request,response);
}
. . . . .
%>
Creato il dispatcher delle due applicazioni, si deve procedere ad uniformare il look & feel dell'applicazione integrata (app. B) a quella integrante (app. A).
Tali modifiche possono variare da semplici modifiche di rendering grafico (modifiche di size, font, layout della pagina) fino a "pesanti" e impegnative modifiche di fogli di stile, java script, action, ...; è bene quindi valutare a priori il costo di questa operazione.
Nel nostro semplice esempio il tutto si risolve modificando il tipo e la dimensione dei font e cambiando la pagina template d'intestazione.
Nel caso in cui le due differenti WebApp da integrare si trovino sullo stesso Web Container è possibile "puntare" alle risorse dell'applicazione A all'interno delle pagine JSP dell'applicazione B come nel seguente esempio:
<%
ServletContext sc = application.getContext("/app_a");
RequestDispatcher rd = sc.getRequestDispatcher("/jsp/header.jsp");
rd.include(request,response);
%>
Questo approcio richiede l'impegno di effettuare il "porting" della presentation dell'applicazione B sull'application Server dell'applicazione A permettendo però di avere vantaggi come l'utilizzo di pagine template, il comportamento omogeneo su differenti browser e la possibilità di condividere la sessione HTTP.
Concludiamo con un'altra possibile modalità d'integrazione a livello d'interfaccia che prevede l'utilizzo dei frameset HTML. In questo modo è possibile presentare un documento con delle form indipendenti tra loro che "puntano" ai Server delle applicazioni d'interesse.
Il vantaggio di questo approcio è la facile implementazione e il "basso costo" di realizzazione limitandosi però ad una integrazione "superficiale" a livello di front-end. Possibili difficoltà a cui si andrebbe incontro sono la gestione del focus del frame, la sincronizzazione delle frames, la modalità di passaggio dati da un frame all'altro, l'apertura di una connessione per ogni frame, le dimensioni dipendenti dalla scrollbar, le differente implementazione a secondo del browser, ...
Nell'esempio sotto riportato si visualizzano in un'unica pagina tre diversi frame ciascuno dei quali visualizza la pagina iniziale delle Applicazioni A, B e C:
<html>
. . .
<frameset rows="*,*,*">
<frame name="primo" src="/eai/jsp/concat.jsp">
<frame name="secondo"
src="http://localhost:7001/wls_webApp/jsp/wordlength.jsp">
<frame name="terzo" src="http://localhost/myiis/asp/wordlength.asp">
</frameset>
</html>
Object/RPC integration
L'integrazione Object/RPC prevede di affrontare l'integrazione a livello di oggetti distribuiti (EJB, CORBA, DCOM, ...), cioè mediante l'invocazione RPC dei metodi di business degli oggetti remoti.
Per integrare maggiormente l'applicazione A e l'applicazione B è possibile consentire l'invocazione diretta del servizio di calcolo della lunghezza della stringa da parte dell'applicazione A, senza dovere essere obbligati a navigare tra le pagine JSP dell'applicazione B.
Si vuole quindi che una volta inserite due stringhe da concatenare avvenga un'interazione diretta tra l'Applicazione A ed il servizio di calcolo lunghezza stringa.
In pratica si desidera che la pagina concatExecute.jsp invochi direttamente sia la concatenazione delle stringhe (l'EJB ConcatBean installato su JBoss) che il calcolo della lunghezza della stringa concatenata (EJB CalculatorBean installato su Weblogic Server 7).
Vediamo le modifiche che bisogna apportare alle classi dell'applicazione A al fine di permettere l'interazione diretta con l'EJB Calculator dell'Applicazione B.
Innanzitutto bisogna "potenziare" la classe MyBusinessDelegate per potere essere in grado di invocare il metodo getLength() del servizio Calculator.
Si aggiunge quindi un nuovo attributo alla classe MyBusinessDelegate che altro non è che il reference remoto all'EJB Calculator.
public class MergeBusinessDelegate{
private Concat concat = null;
private Calculator calculator = null;
Nel costruttore si aggiunge la localizzazione dell'EJB Calculator:
public MergeBusinessDelegate() throws BusinessDelegateException {
try {
CalculatorHome homeCalculator = (CalculatorHome)
MergeServiceLocator.getInstance().
getHome(Defines.CALCULATOR_SERVICE);
this.calculator = homeCalculator.create();
...
e si aggiunge un nuovo metodo per permettere l'invocazione del metodo Calculator.getLength():
public int getLength(String param) throws BusinessDelegateException {
try {
return this.calculator.getLength(param);
} catch (RemoteException re) {
throw new BusinessDelegateException(re);
}
}
Le problematiche di localizzazione sono demandate alla classe MyServiceLocator che va opportunamente configurata per localizzare l'EJB Calculator installato su WLS.
Per fare questo è sufficiente aggiungere una nuova configurazione nel file `
<<MOKA_INTEGRATION>>
/config/locator.xml` che popolerà lo slot 1 dell'array businessServices della classe MyServiceLocator specificando la classe ContextFactory di Weblogic (weblogic.jndi.WLInitialContextFactory) e l'URL del server (t3://<SERVER>:7001):
Per fare questo è sufficiente aggiungere una nuova configurazione nel file <<MOKA_INTEGRATION>>/config/locator.xml che popolerà lo slot 1 dell'array businessServices della classe MyServiceLocator specificando la classe ContextFactory di Weblogic (weblogic.jndi.WLInitialContextFactory) e l'URL del server (t3://<SERVER>:7001):
<void index="1">
<object class="it.mokabyte.eai.my.locator.ServiceConfig">
<void property="homeClass">
<class>it.mokabyte.eai.other.ejb.CalculatorHome</class>
</void>
<void property="initialContextFactory">
<string>weblogic.jndi.WLInitialContextFactory</string>
</void>
<void property="jndiName">
<string>MokaCalculator</string>
</void>
<void property="providerUrl">
<string>t3://localhost:7001</string>
</void>
</object>
</void>
e aggiungere la relativa costante che identifica l'ID del servizio:
public class Defines {
/** Service ID dell'EJB Calculator installato su Weblogic */
public final static int CALCULATOR_SERVICE = 1;
Per potere utilizzare l'EJB Calculator bisogna inserire gli opportuni JAR nella directory <TOMCATHOME>\webapps\appa\WEB-INF\lib.
Oltre quindi ai JAR che permettono la comunicazione tra Tomcat e l'EJB CalculatorBean installato su JBoss (jboss.jar, jboss-j2ee.jar, jnpserver.jar ed il jar mokaejbintegration.jar) bisogna inserire i JAR per comunicare con Weblogic (weblogic.jar) e gli stub dell'EJB (ejbwlscalculator.jar).
Con Tomcat 3, il JDK1.3 e WLS 6, il weblogic.jar doveva essere inserito in TOMCAT\lib e non in WEB-INF\lib della WebApp per problemi di classloader della versione 3 di Tomcat. Con Tomcat 4, il JDK 1.3 e WLS 6 il tutto funziona correttamente posizionando weblogic.jar nella WebApp\WEB-INF\lib.
Con Tomcat 4, il JDK1.4 e WLS 7, inserendo weblogic.jar in WebApp\WEB-INF\lib si ha un conflitto di classi tra le classi di Weblogic e quelle del JDK1.4. In fase di startup di Tomcat si ottiene una ServletException causata da una ClassCastException generata dalla classe javax.xml.parsers.DocumentBuilderFactory.
Se invece si posiziona il weblogic.jar in TOMCAT\common\lib le classi del jar non vengono caricate per un bug delle prime versioni di Tomcat 4 (sia con la versione 4.01 che con la 4.03):
javax.naming.NoInitialContextException:
Cannot instantiate class: weblogic.jndi.WLInitialContextFactory. Root exception
is java.lang.ClassNotFoundException: weblogic.jndi.WLInitialContextFactory
at
org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader
.java:1307)...
Dati questi problemi, come è possibile procedere?
Per il funzionamento su Tomcat 4.01 e 4.03 si può "alleggerire" il weblogic.jar provvedendo a includere i package strettamente necessari per comunicare con il protocollo T3 o togliendo quantomeno le classi di conflitto con il JDK 1.4 (javax/xml e org/xml) e rinpacchettando il tutto in un nuovo jar. A questo punto il jar ottenuto può essere messo in webApp\WEB-INF\lib senza problemi di conflitti tra classi.
Se invece si vuole utilizzare il weblogic.jar "ufficiale" bisogna inserire il jar nella directory common\lib di Tomcat utilizzando release di Tomcat 4 più recenti.
Nella versione 4.1.18 di Tomcat il weblogic.jar di WLS 7 funziona correttamente se viene inserito nella Tomcat\common\lib ma non viene caricato correttamente se inserito nella webApp\WEB-INF\lib. Il jar contenente la Home e Remote interface ed i relativi stub del Calculator EJB si possono inserire nella directory WEB-INF\lib senza problemi di caricamento.
Una volta inserite le stringhe da concatenare nella pagina concat.jsp dell'Applicazione A, si otterrà come risultato sia la concatenazione che il calcolo della lunghezza della stringa concatenata:
<%
String resultConcat = null;
String resultLength = null;
try {
MyBusinessDelegate proxy = new MyBusinessDelegate();
resultConcat = proxy.getConcat((String)request.getParameter("FIRST_STRING"),
(String)request.getParameter("SECOND_STRING"));
resultLength = "" + proxy.getLength(resultConcat);
} catch(Exception e){
resultLength = "-1";
}
%>
per poi visualizzare il risultato:
<table width="400" border="0">
<tr>
<td>
Il risultato è <b><%= resultConcat %></b> di lunghezza <b>
<%= resultLength %></b>
</td>
</tr>
</table>
Abbiamo visto come permettere all'applicazione A di interagire oltre che con il servizio di concatenazione (l'EJB ConcatBean installato su JBoss) anche con il servizio di calcolo della lunghezza della stringa (l'EJB CalculatorBean installato su WLS) in modo diretto facendo convivere l'ORB di JBoss con quello di Weblogic.
In questo modo i due servizi sono invocati mediante un'"unica" navigazione, quella dell'applicazione A.
I due servizi sono invocati dal Business Delegate all'interno del Presentation tier dell'applicazione A dato che i servizi restituiscono dati primitivi e non hanno bisogno di nessuna logica di business aggiuntiva né di elaborazioni dati (adattamento, trasformazione, normalizzazione, ...).
In casi più "reali" capiterà che il servizio da integrare restituisca un proprio object model, (cioè dei propri Value Object - vedi [MOKA_VO]) che quindi occorre trasformare nei Value Object del dominio integrante. Inoltre è possibile che una volta ottenuti i dati si debbano effettuare elaborazioni di business aggiuntive.
E' quindi opportuno portare tale logica in un EJB Facade sul business tier esponendo un'interfaccia di business uniforme ed un modello dati omogeneo.
Per fare questo è opportuno sviluppare un EJB di "facciata" che si occupa di invocare i servizi di concatenazione e di calcolo di lunghezza e di implementare l'eventuale logica di business aggiuntiva (nel semplice esempio proposto si antepone alla stringa concatenata la data di esecuzione del servizio) e le eventuali operazioni di gestione dei dati (nel nostro semplice caso si trasforma l'intero in un Integer).
public class FacadeBean implements SessionBean {
. . .
public ResultOM getResult(String a, String b) throws RemoteException {
try{
ResultOM result = new ResultOM();
// Richiedo la concatenazione all'EJB Concat installato su JBoss
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
properties.put(Context.PROVIDER_URL, "jnp://localhost:1099");
Context ctx = new InitialContext(properties);
Object ref = ctx.lookup("java:comp/env/ejb/Concat");
ConcatHome home = (ConcatHome)
PortableRemoteObject.narrow(ref,ConcatHome.class);
Concat concat = home.create();
String strResult = concat.concat(a,b);
// Richiedo il calcolo della lunghezza della stringa all'EJB
// Calculator installato su WLS
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.WLInitialContextFactory");
properties.put(Context.PROVIDER_URL, "t3://localhost:7001");
Context ctx = new InitialContext(properties);
Object ref = ctx.lookup("MokaCalculatorWLS");
CalculatorHome home = (CalculatorHome)
PortableRemoteObject.narrow(ref,CalculatorHome.class);
Calculator calc = home.create();
Integer iLength=calc.getLength(strResult);
// Eseguo la logica di business "aggiuntiva"
result.setConcat(new java.util.Date().toString()+":"+strResult);
result.setLength(new Integer(iResult));
// Ritorno il risultato
return result;
}
catch(Exception e){
...
}
}
}
In questo modo il Business Delegate vede un unico punto d'accesso al Business tier (l'EJB Facade installato su JBoss) e deve conoscere un unico object model (il ValueObject di classe ResultOM):
...
import it.mokabyte.eai.my.ejb.FacadeHome;
import it.mokabyte.eai.my.ejb.Facade;
public class MyBusinessDelegate{
private Facade facade = null;
. . . . .
public ResultOM getResult(String param1,String param2)
throws BusinessDelegateException {
try {
FacadeHome home =(FacadeHome)
MyServiceLocator.getHome(Defines.FACADE_SERVICE);
this.facade= home.create();
return this.facade.getResult(param1,param2);
...
Per il corretto funzionamento di quest'ultimo scenario presentato bisogna inserire weblogic.jar e il jar delle interfacce Home e Remote ed i relativi stub del Calculator EJB in <JBOSS_HOME>\server\all\lib; in Tomcat WebApp\WEB-INF\lib invece sono necessari i jar per comunicare con JBoss e le interfacce Home e Remote dell'EJB Facade.
Operazioni di "convivenza" di ORB non sono da sottovalutare. Si pensi ad esempio se al posto di Weblogic avessimo dovuto comunicare con un EJB installato su WAS (Websphere Application Server), l'Application Server di IBM: le cose si complicherebbero.
Websphere utilizza il JDK IBM che non è compatibile con quello SUN. Al fine di permettere la interoperabilità tra il JDK SUN di Tomcat e/o JBoss e quello IBM di WAS, bisogna installare il Pluggable Application Client [PAC] di IBM. L'installazione del PAC comporta essenzialmente l'inclusione di 18 jar (10 MB da inserire nel classpath), la configurazione di una dozzina di proprietà di sistema e la modifica del file di policy di sicurezza del JRE/JDK.
Si può intuire come l'utilizzo di tale ORB diventa più complesso o quantomeno più "delicato".
Conclusioni
In questo articolo si è visto come è possibile integrare le due applicazioni J2EE (A, B) a livello Object/RPC e tutte e tre le applicazioni (A, B, C) a livello d'interfaccia utente.
Nel prossimo articolo si vedrà come è possibile sfruttare l'interoperabilità offerta dal protocollo IIOP per fare comunicare client CORBA con EJB e client Java con CORBA Server.
Allegati
Gli esempi sono disponibili in allegato (jea-2_samples.zip).
Bibliografia
- [INT1] S.Rossini: Integrazione di applicazioni Enterprise (I) MokaByte N.71 - Aprile 2003
- [MOKA_VO] S.Rossini: "Value Object", Mokabyte N.69 - Dicembre 2002
- [MOKA_FAC] S.Rossini: "SessionFacade", Mokabyte N.64 - Giugno 2002
- [MOKA_ADP] L.Bettini: "Il pattern Adapter", Mokabyte N.33 - Settembre 1999
- [PAC] http://www7b.software.ibm.com/wsdd/downloads/pluggableclient.html
