MokaByte 68 - 9mbre 2002 
Java Connector Architecture
III parte: CCI la pratica
di
G.Morello e
S. Rossini
Per accedere da Java ai Resource Adapter, l'architettura JCA mette a disposizioni delle API standard denominate CCI (Common Client Interface). In questo articolo vedremo un'implementazione pratica delle API CCI, presentando il MokaConnnector, un resource adapter che si connette ad una risorsa EIS rappresentata da un file testuale. Il MokaConnector permette a componenti J2EE un accesso standard mediante le API CCI descritte in [JCA2]. Ovviamente per scopi didattici, il MokaConnector non si concentra sulle problematiche specifiche dell'accesso a risorse EIS complesse, ma gestisce un semplice file testuale

Le classi CCI del MokaConnector
Il MokaConnector espone un'interfaccia CCI compliant. Implementando quindi le interfacce CCI evidenziate dalla tabella seguente, saremo in grado di permettere l'accesso medianti API CCI al MokaConnector.



Figura 1 (clicca sull'immagine per ingrandirla)

 

 

La classe MokaCciConnectionFactory
La classe MokaCciConnectionFactory è il factory che permette la creazione delle connessioni.
Oltre ad implementare l'interfaccia CCI javax.resource.cci.ConnectionFactory deve anche implementare le interfacce java.io.Serializable (per essere serializzabile) e javax.resource.Referenceable (per supportare la registrazione presso un contesto JNDI).
La classe dell'esempio MokaCciConnectionFactory ha quindi la seguente firma :

public class MokaCciConnectionFactory implements
javax.resource.cci.ConnectionFactory,
javax.resource.Referenceable,
java.io.Serializable {

Il costruttore della classe MokaCciConnectionFactory riceve in ingresso due oggetti appartenenti al system contract di Connection Management [JMS1]: l'oggetto managed connection factory (implementa l'interfaccia javax.resource.spi.ManagedConnectionFactory) e l'oggetto ConnectionManager (implementa l'interfaccia javax.resource.spi.ConnectionManager)

public MokaCciConnectionFactory(ManagedConnectionFactory mcf,                                 ConnectionManager cm){
  this.managedConnectionFactory = mcf;
  this.connectionManager = cm;
}

Un componente J2EE per ottenere la connessione con l'EIS deve invocare il metodo getConnection() dell'oggetto MokaCciConnectionFactory.
Il resource adapter demanda la responsabilità della gestione del connetion pool all'Application Server. Il metodo getConnection invoca quindi a sua volta il metodo allocateConnection() del ConnectionManager per ottenere una connessione prelevata dal Connection pool dell'Application Server.
Nel caso del MokaConnector il metodo getConnection() si aspetta in ingresso un oggetto di classe MokaConnectionRequestInfo in cui vengono specificati username e password per potere accedere al file:

public Connection getConnection(ConnectionSpec properties)
                  throws ResourceException {
 
ConnectionRequestInfo info = new MokaConnectionRequestInfo(…);
  return (javax.resource.cci.Connection)
  this.cm.allocateConnection(this.managedConnectionFactory,info);
}

 

La classe MokaCciConnection
E' il riferimento alla connessione con il sottosistema EIS.
public class MokaCciConnection implements javax.resource.cci.Connection {

Ad ogni oggetto javax.resource.cci.Connection è associato un oggetto javax.resource.spi.ManagedConnection (system contract di Connection Management) che gestisce direttamente la risorsa EIS.

// riferimento all'oggetto managed connection
private MokaManagedConnection managedConnection;
// costruttore
public MokaCciConnection (MokaManagedConnection mc) {
this.managedConnection = mc;
}

I metodi principali di questa classe permettono al componente J2EE (il JCA client) le seguenti operazioni :

createInteraction(): crea un oggetto Interaction per permettere di interagire con la risorsa EIS

public Interaction createInteraction() throws ResourceException {
return new MokaCciInteraction(this);
}

getLocalTransaction(): crea un oggetto per gestire in modo applicativo le transazioni con l'EIS

public LocalTransaction getLocalTransaction()throws ResourceException {
throw new ResourceException("#MokaConnector#: NO Trx
                             management for file system !");
}

close(): permette di chiudere la connessione conl'EIS

public void close() throws ResourceException {
if (this.managedConnection != null) {
this.managedConnection.close();
}
}


La classe MokaCciConnectionMetaData
Implementando l'interfaccia javax.resource.cci.ConnectionMetaData è possibile specificare i dati descrittivi (metadata) della connessione atttiva.
Nell'esempio del MokaConnector i metadata sono rappresentati da nome e versione del ResourceAdapter e dalla user name dell'utente collegato alla risorsa EIS.

public class MokaCciConnectionMetaData implements javax.resource.cci.ConnectionMetaData {
  public MokaCciConnectionMetaData() {}
  public String getEISProductName(){
    return MokaCciResourceAdapterMetaData.MOKA_RA_NAME;
  }
  public String getEISProductVersion(){
    return MokaCciResourceAdapterMetaData.MOKA_RA_VERSION;
  }
  public String getUserName() {
    return "anonymous user";
  }
}

 

La classe MokaCciConnectionSpec
L'oggetto ConnectionSpec permette di specificare le proprietà specifiche delle risorsa EIS al fine di ottenere la connessione in modo appropriato.
Nel caso del MokaConnector tale classe permette di specificare la user name e la password nella richiesta di connessione all'EIS:

public class MokaCciConnectionSpec implements javax.resource.cci.ConnectionSpec {
  private String user;
  private String password;
  public MokaCciConnectionSpec(String user, String password) {
    this.user = user;
    this.password = password;
  }
  public String getUser() {
    return user;
  }
  public String getPassword() {
    return password;
  }
}


La classe MokaCciResourceAdapterMetaData
Questa classe fornisce informazioni sulle caratteristiche del Resource Adapter. E' ottenibile invocando il metodo getMetaData sull'oggetto ConnectionFactory.
Da tale classe è possibile ottenere informazioni puramente descrittive come:

  • il nome del resource adapter
    public String getAdapterName(){return this.MOKA_RA_NAME;}

  • la descrizione del connettore
    public String getAdapterShortDescription(){return "MokaConnector:
    a simple RA to access system files";}


  • il nome del produttore
    public String getAdapterVendorName(){return "Mokabyte INC";}

  • la versione
    public String getAdapterVersion(){return this.MOKA_RA_VERSION;}

  • la versione JCA supportata
    public String getSpecVersion(){return "SUN JCA 1.0";}

ma anche informazioni utili per l'operatività e l'utilizzo da parte del client J2EE come:

  • il supporto delle transazioni locali (il MokaConnector non implementa l'interfaccia LocalTransaction e quindi deve restituire false)
    public boolean supportsLocalTransactionDemarcation(){return false;}

  • le classi utilizzabili per interagire con la risorsa EIS
    public java.lang.String[] getInteractionSpecsSupported(){
    String[] res = { it.mokabyte.mokajca.ra.MokaCciInteractionSpec.class.getName() };
    return res;
    }

  • il supporto per l'interazione mediante il metodo Record execute(InteractionSpec ispec, Record input)
    public boolean supportsExecuteWithInputRecordOnly(){return true;}

  • l supporto per l'interazione mediante il metodo boolean execute(InteractionSpec ispec, Record input, Record output)
    public boolean supportsExecuteWithInputAndOutputRecord(){return false;}

La classe MokaCciInteractionSpec
Contiene le proprietà necessarie per interagire con la risorsa EIS ed è utilizzata dall'oggetto Interaction. Come il ConnectionSpec, anche l'InteractionSpec è di fatto un Java Bean.
La classe MokaCciInteractionSpec incapsula le informazioni relative all'operazione che si vuole eseguire sul file (lettura/scrittura) ed il numero di righe del file nel caso di lettura.

public class MokaCciInteractionSpec implements InteractionSpec,
                                    java.io.Serializable {
private String functionName;
private int numOfLines;
<< metodi get e set >>

 

La classe MokaCciInteraction
Un ogetto MokaCciInteraction viene costruito a partire dall'oggetto MokaCciConnection e utilizza la classe MokaCciInteractionSpec per indicare l'operazione da eseguire ed i relativi parametri.

public class MokaCciInteraction implements Interaction {
// riferimento all'oggetto connection
private javax.resource.cci.Connection connection;

// Costruttore
public MokaCciInteraction(javax.resource.cci.Connection con) {
this.connection = con;
}

La classe mette a disposzione il metodo execute per interagire con la risorsa EIS. Nel caso del MokaConnector il metodo boolean execute (InteractionSpec ispec, Record input, Record output) non è supportato.

public boolean execute (InteractionSpec ispec, Record input, Record output)
               throws ResourceException {
throw new ResourceException("# boolean execute (InteractionSpec ispec,Record in,Record output) #NOT#
SUPPORTED by MokaConnector #");
}


Si noti come coerentemente a questa scelta il metodo supportsExecuteWithInputAndOutputRecord della classe MokaCciResourceAdapterMetaData ritorni false.

Il MokaConnector permette di interagire con la risorsa EIS con il solo metodo Record execute (InteractionSpec ispec, Record input). Si noti anche in questo caso la coerente implementazione del metodo supportsExecuteWithInputRecordOnly() della classe MokaCciResourceAdapterMetaData che ritorna true.
Risulta quindi fondamentale capire come questo metodo consente di interagire con il file.

public Record execute(InteractionSpec ispec,Record input)
              throws ResourceException {
  if (ispec == null ||(!(ispec instanceof MokaCciInteractionSpec))) {
    throw new ResourceException(…);
  }
  String procName = ((MokaCciInteractionSpec)ispec).getFunctionName();
  int numLines = ((MokaCciInteractionSpec)ispec).getNumOfLines();
  IndexedRecord output = new MokaCciIndexedRecord();
  return this.exec(procName,numLines,input,output);
}

Una volta controllata la coerenza dei parametri d'ingresso, vengono letti i valori specifici dell'interazione EIS e viene invocato il metodo privato exec

private Record exec(String procName,int numLines, Record input,Record output)throws ResourceException{

La prima operazione eseguita è quella di ricavare la risorsa EIS passando dal reference CCI della connessione all'oggetto ManagedConnection (la connessione a livello SPI); sulla connessione viene poi invocato il metodo getFile

java.io.File file = ((MokaCciConnection)this.connection).
                     getManagedConnection().getFile();

è necessario poi inizializzare opportunamente il Record output per contenere il risultato dell'interazione

IndexedRecord oRec = null;
if(output instanceof IndexedRecord) {
  oRec = (IndexedRecord)output;
}
else {
  oRec = new MokaCciIndexedRecord();
}

nel caso in cui si richieda un'operazione di lettura del file la stringa procName conterrà il valore "READ"

if(procName.equals("READ")) {

se il file non esiste, viene lanciata una ResourceException

if(!file.exists())
throw new ResourceException(…);

altrimenti si provvede a leggere o l'intero file (se il parametro del numero delle righe da leggere è -1) creando lo stream di lettura

BufferedReader br = new BufferedReader(new FileReader(file));
if(numLines == -1) { // read all file required
while(br.ready()) {
String temp = br.readLine();
oRec.add(temp);
}
}

o un numero specifico di righe richiesto

else {
for(int i=0; i<numLines && (br.ready()); ++i) {
String temp = br.readLine();
oRec.add(temp);
}
}

alla fine dell'operazione di lettura viene chiuso lo stream
br.close();

Nel caso invece si richieda un'operazione di scrittura
else if(procName.equals("WRITE")){

viene verificata la compatibilità della classe del parametro input con il MokaConnector

IndexedRecord iRec = null;
if(!(input instanceof IndexedRecord) ) {
  throw new ResourceException(…);
}
iRec = (IndexedRecord) input;

viene poi creato lo stream di scrittura

PrintWriter pw = new PrintWriter(new BufferedWriter(
new FileWriter(file.getAbsolutePath(),file.exists())));

ogni elemento contenuto nell'oggetto input record sarà scritto sul file

for(int i=0; i<iRec.size(); ++i) {
  pw.println(iRec.get(i));
  pw.flush();
}

alla fine dell'operazione di lettura viene chiuso lo stream

pw.close();

e viene restituito il Record di output

return oRec;
// conn.close();
} catch(Exception ex) {
throw new ResourceException(ex.getMessage());
}


La classe MokaCciIndexedRecord
E' la classe che rappresenta i dati di input ed output del MokaConnector.

public class MokaCciIndexedRecord implements javax.resource.cci.IndexedRecord {
private String recordName;
private String description;
private Vector indexedRecord;
<< metodi get, set e di wrapping del Vector >>


La classe MokaCciRecord
E' la classe factory degli oggetti record di classe MokaCciIndexedRecord.

public class MokaCciRecordFactory implements javax.resource.cci.RecordFactory{
  public MokaCciRecordFactory () {}
  public MappedRecord createMappedRecord(String recordName)
                      throws ResourceException {
    throw new ResourceException("MappedRecord not supported");
  }

    public IndexedRecord createIndexedRecord(String recordName)
                       throws ResourceException {
    return new MokaCciIndexedRecord(recordName);
  }
}

 

L'EJB TesterBean e l'utilizzo del MokaConnector
Andiamo ora ad analizzare un esempio d'uso delle interfacce implementate. Nello scenario in esame, un componente J2EE (un EJB Stateful) accedererà al MokaConnector attraverso le API CCI.


Figura 2

 

La home interface
La home interface espone un unico metodo di create:

public interface TesterHome extends EJBHome {
public Tester create() throws RemoteException, CreateException;
}


La remote interface

La remote interface pubblica i metodi necessari per utilizzare il connettore. Il metodo performRead permette di leggere un numero arbitrario di righe del file (-1 per leggere l'intero file). Il metodo performWrite() permette di scrivere il contenuto dell'oggetto ListModel sul file(una semplice classe Wrapper di una collection Java).

public interface Tester extends EJBObject {
  public void performWrite(ListModel vals)throws RemoteException,
                                                 BusinessException;
  public ListModel performRead(int num) throws RemoteException,
                                               
BusinessException;
}


Figura 3

 

 

L'EJB TesterBean
All'interno del metodo ejbCreate si inizializzano le proprietà connectionFactory econnection dell'EJB

public class TesterBean implements SessionBean{

  // il nome JNDI del MokaConnector
  private static final String JNDI_NAME_EIS =                               "java:comp/env/ra/CCI_MOKA_EIS";
  
  
// username per utilizzare la risorsa EIS
  private static final String JNDI_NAME_USR =
                              "java:comp/env/username";
  
  // password per accedere alla risorsa EIS
  private static final String JNDI_NAME_PWD =
                              "java:comp/env/password";

  // Il ConnectionFactory
  private ConnectionFactory connectionFactory= null;

  //La Connessione con la risorsa EIS
  private Connection connection= null;

  public void ejbCreate() throws CreateException{
    log.debug("TesterBean.ejbCreate : called ...");
    try{
      // Recupero del ConnectionFactory
      this.connectionFactory = this.getCCIConnectionFactory();

      // si ottiene la connessione alla risorsa EIS
      this.connection = this.getCCIConnection(this.connectionFactory);

    }
    catch(NamingException ne) {
      throw new CreateException(ne.getMessage());
    }
    catch(ResourceException re) {
      throw new CreateException(re.getMessage());
    }
  }

Il metodo getCCIConnectionFactory istanzia un JNDI Context ed effettua l'opportuna lookup ed il relativo downcast del ConnectionFactory.

private ConnectionFactory getCCIConnectionFactory() throws NamingException{
  Context ic = new InitialContext();
  ConnectionFactory cf = (ConnectionFactory) ic.lookup(this.JNDI_NAME_EIS);
  return cf;
}

Il metodo getCCIConnection effettua la lookup dei parametri username e password necessari a reistanziare un oggetto MokaCciConnectionSpec. Mediante questo è possibile invocare il metodo getConnection sull'oggetto ConnectionFactory ricevuto in ingresso.


private Connection getCCIConnection(ConnectionFactory cf)
                   throws NamingException,ResourceException {
  Context ic = new InitialContext();
  String user = (String) ic.lookup(this.JNDI_NAME_USR);
  String password = (String) ic.lookup(this.JNDI_NAME_PWD);
  MokaCciConnectionSpec spec;
  spec = new MokaCciConnectionSpec(user, password);
  Connection con = cf.getConnection(spec);
  return con;
}


I metodi ejbRemove ed ejbPassivate provvedono alla chiusura della connessione invocando il metodo closeCCIConnection

this.closeCCIConnection(this.connection);

che di fatto si limita ad invocare il metodo close sull'oggetto connection ricevuto come paramtro d'ingresso

con.close();

Nel metodo ejbActivate viene nuovamente ottenuta una connessione

this.connection = this.getCCIConnection(this.connectionFactory);

Vediamo ora i metodi che permettono di leggere e scrivere sulla risorsa EIS. Il metodo performRead permette di leggere dalla risorsa EIS

public ListModel performRead(int num) throws BusinessException {
ListModel result = new ListModel();
try {


si crea l'oggetto Interaction

Interaction ix = this.connection.createInteraction();

Si specifica l'intenzione di effettuare un'operazione di lettura

MokaCciInteractionSpec iSpec = new MokaCciInteractionSpec();
iSpec.setFunctionName("READ");

specificando il numero di righe che si è interessati a leggere

iSpec.setNumOfLines(num);

si crea un oggetto RecordFactory

RecordFactory rf = this.connectionFactory.getRecordFactory();

un oggetto IndexedRecord

IndexedRecord iRec = rf.createIndexedRecord("InputRecord");

si invoca il metodo execute memorizzando l'opportuno output

Record oRec = ix.execute(iSpec, iRec);

si cicla sul Record di output

Iterator iterator = ((IndexedRecord)oRec).iterator();
int count = 0;
while(iterator.hasNext()) {
  Object obj = iterator.next();
  ++count;
  popolando il ListModel da restituire
  result.add("" + obj);
}

si ritorna il ListModel contenente il risultato dell'interazione con la risorsa EIS

return result;
}

Il metodo performWrite permette invece di scrivere sulla risorsa EIS

public void performWrite(ListModel vals) throws BusinessException {
try {
  Interaction ix = this.connection.createInteraction();
  MokaCciInteractionSpec iSpec = new MokaCciInteractionSpec();
  log.debug("setFunctionName to WRITE ...");


si specifica l'intenzione di effettuare un'operazione di lettura

  iSpec.setFunctionName("WRITE");
  RecordFactory rf = this.connectionFactory.getRecordFactory();
  IndexedRecord iRec = rf.createIndexedRecord("InputRecord");

si valorizza l'indexed record con i dati del ListModel ricevuto come parametro d'ingresso

  for(int i=0; i<vals.size(); ++i) {
    iRec.add(vals.elementAt(i));
  }

e infine si invoca il metodo execute

  ix.execute(iSpec, iRec);
 }catch(ResourceException ex) { … }
}

Il client EJB
E' un'applicazione Java standalone che invoca i metodi dell'EJB per testare il funzionamento del Mokaconnector.
Una volta ottenuto il reference della remote interface

Context ctx = new InitialContext();
Object ref = ctx.lookup(EJB_JNDI_NAME);
home = (TesterHome)
PortableRemoteObject.narrow(ref, TesterHome.class);
obj = home.create();
si effettuano le prove di lettura invocando il metodo di business performRead
System.out.println(obj.performRead(1));
System.out.println(obj.performRead(3));
// leggo tutto il file e di scrittura mediante
// l'invocazione del metodo di business performWrite

System.out.println(obj.performRead(-1));
String values[] = { "uno","due","tre" };
obj.performWrite(new ListModel(values));
alla fine del test si rimuove l'EJB
obj.remove();

 

Conclusioni
In questo articolo è stato analizzato un'esempio di implementazione delle API CCI per il MokaConnector.
Il mese prossimo si inizieranno a studiare i sytem contracts e la relativa implementazione nel MokaConnector.
Bibliografia e riferimenti
[JCA1] G.Morello - S.Rossini: "Java Connector Architecture", Mokabyte N.66 - Settembre 2002
[JCA2] G.Morello - S.Rossini: "Java Connector Architecture", Mokabyte N.67 - Ottobre 2002
[JCA] http://java.sun.com/j2ee/connector/
[SPEC] J2EE Connector Architecture Specification, http://java.sun.com/j2ee/download.html#connectorspec
[
CCI1] Beth Stearns: Using the J2EETM Connector Architecture Common Client Interface, http://java.sun.com,Aprile 2001
[BSTW] David Marks: J2EE Connector Architecture Brings Business Systems to the web,
http://developer.java.sun.com/developer/technicalArticles/J2EE/connectorclient/resourceadapter.html
[JW2] Dirk Reinshagen: Connect the enterprise with the JCA, Part 2,JavaWorld Febbraio 2002
[JROD] Jennifer Rodoni: The Java 2 Enterprise Edition Connector Architecture's Resource Adapter, http://developer.java.sun.com/developer/technicalArticles/J2EE/connectorclient/resourceadapter.html
[JTUT] J2EE Tutorial, http://java.sun.com/j2ee/tutorial/download.html : esample of black box resource adapter for J2EE reference implementation.
[JBOSS] http://www.jboss.org

 

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it