Start - Publikationen - Wissen - TOGAF - Impressum -

Outbound Resource Adapter


Um die Implementierung eins Outbound Resource Adapter zu verstehen sei hier noch einmal das Zusammenspiel zwischen

  • ConnectionManager (serverseitig implementiert): choreographiert die Abläufe
  • ConnectionFactory: liefert die Application Handle einer Connection
  • ManagedConnectionFactory: liefert die gepoolten, physikalischen ManagedConnections
zusammengefasst. Dabei werden die Feinheiten von RMI, mit denen die clientseitige Connection zum Remote Handle auf die serverseitig verwaltete Connection organisiert wird, vernachlässigt. Man stelle sich zum Beispiel vor, die Clientkomponente und die Serverkomponente laufen in der gleichen JVM. Am besten strukturiert man die Abläufe entlang des Lebenszyklus einer ManagedConnection-Instanz:
  1. Application ruft getConnection: ConnectionFactory.getConnection ruft (von Spec gefordert) ConnectionManager.allocateConnection:
    1. erzeugt eine Instanz einer Connection (und ruft den no-arg Konstruktor der deklarierten Implementierung)
    2. holt ein Set aller freien ManagedConnections aus dem Pool
    3. ruft ManagedConnectionFactory.matchManagedConnections mit diesem Set
    4. nimmt dann entweder die dabei identifizierte Instanz oder ruft ManagedConnectionFactory.createManagedConnection auf (im Falle von create meldet sich dann noch der ConnectionManager als Listener an der ManagedConnection an)
    5. ruft an dieser ManagedConnection associateConnection mit der erzeugten Connection, in ManagedConnection.associateConnection kann dann in der Connection die ManagedConnection verdrahtet werden, was die Connection in die Lage versetzt, Funktionsaufrufe an die ManagedConnection weiter zu reichen
  2. Application ruft Methode an Connection: die Connection nutzt die verdrahtete ManagedConnection Instanz und ruft geeignete fachliche Methoden
  3. Application ruft Methode close: Weiterleitung an ManagedConnection und damit
    1. Aufräumen aller Ressourcen in Bezug auf die Assoziation zwischen Connection und ManagedConnection
    2. Aufruf der angemeldeten Listener mit ConnectionEvent.CONNECTION_CLOSED: ConnectionManager ruft ManagedConnection.cleanup, ManagedConnection kommt zurück in den Pool
  4. Container beseitigt ManagedConnection Instanz aus dem Pool: Container ruft ManagedConnection.destroy, alle Ressourcen der physikalischen Connection werden nun freigegeben und die ManagedConnection wird verworfen

Beispielimplementierung


Die hier behandelte Beispielimplementierung simuliert ein "Hello World" antwortendes EIS. In der Praxis wird die Kommunikation mit dem EIS typischerweise über eine Java API gekapselt. Ein Outbound Resource Adapter besteht mindestens aus zwei Interfaces, sieben Implementierungsklassen und einem Delpoyment Deskriptor:

Artefakt Bedeutung
HelloWorldConnectionFactory und HelloWorldConnectionFactoryImpl Die Definition der Komponente genutzten ConnectionFactory und deren Implementierung.
HelloWorldConnection und HelloWorldConnectionImpl Die Schnittstelle des ConnectionHandle und dessen Implementierung. Ein ConnectionHandle repräsentiert eine EIS Connection aus Komponentensicht.
HelloWorldConnectionRequestInfoImpl Die Implementierungen von javax.resource.spi.ConnectionRequestInfo. Kapselt alle benutzerspezifischen Informationen einer Connection aus Komponentensicht.
ManagedConnectionImpl Die Implementierungen von javax.resource.spi.ManagedConnection. Instanzen repräsentieren eine physikalische Connection zu einer Ressource. Ihr Lebenszyklus ist vom Container gesteuert, dieser ruft entsprechende Callbacks der Instanz.
ManagedConnectionFactoryImpl Die Implementierungen von javax.resource.spi.ManagedConnectionFactory. Repräsentiert eine vom Container gesteuerte Instanz zur Erzeugung von ManagedConnections. Hier sind Methoden implementiert, die den Container beim Connection Pooling unterstützen und hier holt sich der Container die ConnectionFactoryImpl Instanz für JNDI Initialisierung.
DefaultConnectionManager Die Implementierungen von javax.resource.spi.ConnectionManager für die Nutzung des Resource Adapters in einer nicht vom Container gesteuerten Umgebung. In einer Java EE Umgebung wird eine ConnectionManager Instanz vom Container bereitgestellt. Dieser Artikel befasst sich nicht mit diesem Thema.
ManagedConnectionMetaDataImpl Die Implementierungen von javax.resource.spi.ManagedConnectionMetaData kapselt adapterspezifische Informationen.
ra.xml Der Deploymentdescriptor des Adapters.

Nutzersicht


Nutzer eines Outbound Resource Adapters sind immer die Komponenten eines Java EE Containers (Servlets, Beans) und damit, zumindest potenziell, remote Komponente. Zunächst holt sich die Komponente eine HelloWorldConnectionFactory vom JNDI. Damit kann sie dann, vermittelt über die Connectionpool Funktionen des Containers ConnectionHandles erzeugen, nutzen und wieder freigeben:

InitialContext ictx = new InitialContext();
Context rootctx = (Context) ictx.lookup("java:comp/env");
:
Object home = rootctx.lookup("eis/helloworld");
HelloWorldConnectionFactory factory = (HelloWorldConnectionFactory) PortableRemoteObject.narrow(home, HelloWorldConnectionFactory.class);
:
HelloWorldConnection connection = factory.getConnection();
connection.helloWorldCall();
connection.close();
eine Variante mit Parametern sähe so aus:
:
HelloWorldConnectionRequestInfo info = new HelloWorldConnectionRequestInfo();
info.setMessage("duke");
HelloWorldConnection connection = factory.getConnection(info);
connection.helloWorldCall();
connection.close();
Das ConnectionHandle Interface deklariert alle fachlichen Methoden. Eine close-Methode ist dabei von der Spezifikation gefordert, sie darf beliebige Ausnahmen werfen.
public interface HelloWorldConnection {
 /**
  * Eine close Methode muss laut Spec angeboten werden.
  * Sie darf auch Exceptions werfen. 
  */
  void close();
/** * Das hier ist das Dummy für eine fachliche Methode. * @return Ergebnis des Aufrufs */ [*String helloWorldCall();*] }

Bei der Implementierung von HelloWorldConnection werden sämtliche Aufrufe zwingend an die zugeteilte ManagedConnection Instanz weitergereicht:

final class HelloWorldConnectionImpl implements HelloWorldConnection {
  /**
   * Ein Connection Handle darf nur mit genau einer Managed Connection
   * assoziiert sein - deshalb ein Feld dafür
   */
  private ManagedConnectionImpl managedConnection = null;
  /**
   * Injiziert die mit diesem Connection Handle verknüpfte {@link ManagedConnection}.
   * 
   * Im Rahmen des Connection Sharing kann auch null übergeben werden, 
   * das signalisiert, dass das ConnectionHandle vorübergehend nicht
   * mit einer {@link ManagedConnection} assoziiert ist. Das ConnectionHandle
   * muss aber intakt bleiben, da zu einem späteren Zeitpunkt die
   * alte Assoziation wieder hergestellt wird. 
   * @param managedConnection
   */
  void setManagedConnetion(ManagedConnectionImpl managedConnection) {
    this.managedConnection = managedConnection;
  }
  /**
   * Nach Benutzung von der Komponente zu rufen, damit die ManagedConnection
   * anderen Komponenten zur Verfügung stehen kann.
   * @see com.eis.hw.HelloWorldConnection#close()
   */
  public void close() {
    if (this.managedConnection != null) {
      this.managedConnection.closeHandle(this);
    } 
  }
  /**
   * @throws ResourceException 
   * @see com.eis.hw.HelloWorldConnection#helloWorldCall()
   */
  public String helloWorldCall() throws ResourceException {
    if (this.managedConnection != null) {
      return this.managedConnection.doHelloWorldCall();
    }
    throw new ResourceException("Missing ManagedConnection (Timeout?!)");
  }
}
Das ConnectionHandle besorgt sich die Komponente über eine Factory, im Beispiel kann man sich ein ConnectionHandle wahlweise mit oder ohne Parameter besorgen:
public interface HelloWorldConnectionFactory {
 /**
  * Eine Forderungen aus der Spezifikation.
  *   
  * @return ein ConnectionHandle
  * @throws ResourceException
  */
  HelloWorldConnection getConnection() throws ResourceException;
 /**
  * Gibt ein ConnectionHandle mit der Übergabe von 
  * Requestparametern in Form von ConnectionRequestInfo
  * 
  * @param cri Aufrufparameter
  * @return ein ConnectionHandle
  * @throws ResourceException
  */
  HelloWorldConnection getConnection(HelloWorldConnectionRequestInfo cri) throws ResourceException;
}
Die Implememtierung der Factory delegiert die Beschaffung des ConnectionHandle an einen Connection Manager. Der Connection Manager wird dabei vom Server bereitgestellt und organisiert die Verwaltung der Connectionpools. Die Factory muss Serializable und Referenceable implementieren. Sie existiert containerseitig, nutzende Komponenten werden mit einer Proxyinstanz aus dem JNDI versorgt.
final class HelloWorldConnectionFactoryImpl 
  implements HelloWorldConnectionFactory, Serializable, Referenceable {
 /**
  * Die UID der Klasse. 
  */
  private static final long serialVersionUID = 2285452178032105898L;
  //
  final ConnectionManager cm;
  final ManagedConnectionFactory mcf;
 /**
  * Dieser Konstruktor wird im Rahmen der Server-Initialisierung
  * gerufen, dabei werden auch Proxyinstanzen in den JNDI abgelegt.
  * 
  * @param cm vom Server bereitgestellter ConnectionManager 
  * @param mcf die ManagedConnectionFactory Implementierung,
  * mit ihr wird der ConnectionManager ein ConnectionHandle bereitstellten. 
  */
  HelloWorldConnectionFactoryImpl(final ConnectionManager cm, final ManagedConnectionFactory mcf) {
    this.cm = cm;
    this.mcf = mcf;
  }
 /**
  * @see com.eis.hw.HelloWorldConnectionFactory#getConnection()
  */
  public HelloWorldConnection getConnection() throws ResourceException {
    // Alternativ: hier eine Standard-ConnectionRequestInfo erzeugen
    HelloWorldConnectionRequestInfo cri = null; 		
    return (HelloWorldConnection) getConnection(cri);
  }
 /**
  * getConnection erzeugt ein ConnectionHandle indem es dessen
  * delegiert die Implementierung an den ConnectionManager. 
  * 
  * Die ResourceException kommt von allocateConnection, ist aber nicht zwingend zu werfen.
  * @see com.eis.hw.HelloWorldConnectionFactory#getConnection(javax.resource.spi.ConnectionRequestInfo)
  */
  public HelloWorldConnection getConnection(final HelloWorldConnectionRequestInfo cri)
  throws ResourceException {
    return (HelloWorldConnection) this.cm.allocateConnection(this.mcf, cri);
  }
  /**
   * Stores the provided reference.
   */
  private Reference reference = null;
  /**
   * Set the reference.
   */
  public final void setReference(final Reference reference) {
    this.reference = reference;
  }
  /**
   * Get the stored reference.
   */
  public final Reference getReference() throws NamingException {
    return this.reference;
  }
}
Zur Übergabe von Parametern wird die Bean konforme Klasse HelloWorldConnectionRequestInfo implements ConnectionRequestInfo benutzt. Die korrekte Implementierung von hashCode und equals ist enorm wichtig, da der Container unter anderem mit diesen Implementierungen seine Pools verwaltet.

Containersicht


Die ManagedConnection Implementierung repräsentiert eine physikalische Verbindung zum EIS. Die nötigen Ressourcen werden bei der Instanziierung dieser Klasse belegt und solange gehalten, bis der Container destroy() ruft. Es sind Instanzen der ManagedConnection, die der Container in seinen Connection Pools organisiert.

final class ManagedConnectionImpl implements ManagedConnection {
	/**
	 * Für die Arbeit mit dem EIS sind oft Credentials (user, password)
	 * notwendig, die mit diesem Subject weitergereicht werden.
	 * 
	 * Die Subject-Instanz wird vom Container bereitgestellt. 
	 * SSO Funktionalität kann hiermit realisiert werden.
	 * 
	 * @see Subject
	 */
	private final Subject subject;
	/**
	 * Für die Arbeit mit dem EIS sind oft initiale Parameter bei
	 * der Etablierung der EIS Connection nötig. Diese werden
	 * hier abgelegt.
	 */
	private final HelloWorldConnectionRequestInfo info;
	/**
	 * ManagedConnection - Konstruktor.
	 * 
	 * Hier erfolgt die physikalische Connection zum EIS. Sie
	 * wird solange gehalten und belegt diese Ressourcen, bis
	 * destroy gerufen wird.
	 * 
	 * @param mcf Die ManagedConnectionFactory Instanz, hier können
	 * die Konfigurationsparameter des Deployments abgefragt werden.
	 * @param subject Die User Credentials
	 * @param info Die ConnectionRequestInfo
	 * @param defaultLogWriter der LogWriter der ManagedConenctionFactory
	 */
	ManagedConnectionImpl(final ManagedConnectionFactoryImpl mcf,
			final Subject subject, 
			final HelloWorldConnectionRequestInfo info,
			final PrintWriter defaultLogWriter) {
		// Subject und info legen die Identität dieser Instanz fest:
		this.subject = subject;
		this.info = info;
		this.logWriter = defaultLogWriter;
		/*
		 * hier Code, der physikalische Ressource zur Arbeit
		 * mit dem EIS allociert. Diese Resourcen werden bis
		 * zum Aufruf von destroy gehalten. Zur Etablierung der
		 * Ressourcen werden potenziell auch die Konfigurations-
		 * parameter der ManagedConnectionFactory verwendet:
		 */ 
		String hostURL = mcf.getHostURL();
		/*
		 * Jetzt kann die physikalische Connection aufgebaut werden.
		 * Falls dazu ein Username aus dem Subject zu holen ist, dann
		 * in getUserName implementieren - dort wird es auch von
		 * ManagedConnectionMetaDataImpl benutzt.
		 */
		this.logWriter.print(this + " created: ");
		this.logWriter.print("Host URL is: " + hostURL);
	}
	/**
	 * Das momentan mit dieser {@link ManagedConnection} assoziierte ConnectionHandle 
	 */
	private HelloWorldConnectionImpl associatedHandle = null; 
	/**
	 * Der Server kann im Rahmen des Connection Sharings sich jederzeit die 
	 * Assoziation eines Connection Handles zu dieser Managed Connection verbiegen 
	 * und ruft zu diesem Zweck diese Methode.
	 */
	public void associateConnection(Object connection) throws ResourceException {
		disassociateConnection();
		HelloWorldConnectionImpl connectionHandle = (HelloWorldConnectionImpl) connection;
		connectionHandle.setManagedConnetion(this);
		this.associatedHandle = connectionHandle;
	}
	/**
	 * Kappt die Verbindung zwischen dieser Instanz und dem 
	 * gerade assoziierten ConnectionHandle.
	 */
	private void disassociateConnection() {
		if (this.associatedHandle != null) {
			this.associatedHandle.setManagedConnetion(null);
			this.associatedHandle = null;
		}		
	}
	/**
	 * Der Container entschließt sich, eine ManagedConnection aus dem Pool zu
	 * entfernen und ruft diese Methode damit alle physikalischen Resourcen
	 * freigegeben werden können.
	 * 
	 * Wird regulär gerufen, wenn die konfigurierte max-idle Zeit
	 * überschritten wird.
	 */
	public void destroy() throws ResourceException {
		this.logWriter.print(this + " destroy");
		// zunächst ein cleanup
		cleanup();
		// dann Code, der alle physikalischen Ressourcen freigibt 
	}
	/**
	 * Wird ausschließlich vom Container gerufen, diese Instanz wird 
	 * gleich in den Pool befördert, der Zustand der physikalischen 
	 * Connection muss intakt bleiben.
	 * 
	 * Wird regulär gerufen, wenn das assoziierten ConnectionHandle
	 * die Connection schließt (und dieses Signal ordnungsgemäß
	 * an den Container weitergereicht wird, siehe
	 * {@link #closeHandle(HelloWorldConnection)}.
	 */
	public void cleanup() throws ResourceException {
		this.logWriter.print(this + " cleanup");
		// löse die Assoziation wenn nicht schon geschehen:
		disassociateConnection();
	}
	/**
	 * Wird im Zusammenhang mit {@link ConnectionManager#allocateConnection(ManagedConnectionFactory, ConnectionRequestInfo)}
	 * gerufen und liefert ein Connection Handle das mit dieser Instanz assoziiert ist.
	 * 
	 * Der Container kann im Rahmen des Connection Sharings mehr als einmal
	 * diese Methode aufrufen, auch wenn die das erste ConnectionHandle noch
	 * nicht die close-Methode gerufen hat.
	 */
	public Object getConnection(final Subject subject,
			final ConnectionRequestInfo connectionRequestInfo)
			throws ResourceException {
		this.logWriter.print(this + " getConnection");
		HelloWorldConnection connection = new HelloWorldConnectionImpl();
		associateConnection(connection);
		/* connectionRequestInfo und subject können hier bei Bedarf
		 * für Reconnect etc. verwendet werden (Auffrischen der
		 * physikalischen Connection) 
		 */ 
		return connection;
	}
        //
	public XAResource getXAResource() throws ResourceException {
		throw new ResourceException("XA transaction not supported");
	}
        //
	public LocalTransaction getLocalTransaction() throws ResourceException {
		throw new ResourceException("Local transaction not supported");
	}
	/**
	 * Der Default LogWriter wird von der ManagedConectionFactoryImpl
	 * bereitgestellt - er kann aber vom Server auch gesetzt werden...
	 */
	private PrintWriter logWriter = null;
        //
	public PrintWriter getLogWriter() throws ResourceException {
		return this.logWriter;
	}
        //
	public void setLogWriter(PrintWriter logWriter) throws ResourceException {
		this.logWriter = logWriter;
	}
        //
	public ManagedConnectionMetaData getMetaData() throws ResourceException {
		return new ManagedConnectionMetaDataImpl(this);
	}
	/**
	 * Implementierung der EIS Logik.
	 * 
	 * Diese Implementierung erfolgt mittels der im Konstruktor
	 * akquirierten EIS Ressourcen und muss potentiell auch nebenläufig
	 * funktionieren.
	 * 
	 * @return Ein EIS Ergebnis
	 */
	String doHelloWorldCall() {
		//
		if (this.info == null) {
			return "Hello World";
		}
		String message = this.info.getMessage();
		// So kann man einen Fehlerverhalten testen
		if ("ERROR".equals(message)) {
			errorOccured();
		}
		return "Hello " + message + "!";		
	}	
	/**
	 * Ein Connection Handle signalisiert, dass es geschlossen wird.
	 *
	 * Der Container hat sich als Listener angemeldet und wird unter 
	 * zuhilfenahme dieses Events seine Pools verwalten. 
	 * 
	 * @param connection
	 */
	void closeHandle(HelloWorldConnection connection) {
		this.logWriter.print(this + " closeHandle: ");
		disassociateConnection();
		for (ConnectionEventListener listener : this.listenerList) {
			ConnectionEvent event = new ConnectionEvent(this,
					ConnectionEvent.CONNECTION_CLOSED);
			event.setConnectionHandle(connection);
			listener.connectionClosed(event);
		}
	}
	/**
	 * Alle Listener (der Container!) werden benachrichtigt, dass ein Fehler passiert ist. 
	 */
	private void errorOccured() {
		this.logWriter.print(this + " error: ");
		for (ConnectionEventListener listener : this.listenerList) {
			ConnectionEvent event = new ConnectionEvent(this,
					ConnectionEvent.CONNECTION_ERROR_OCCURRED);
			listener.connectionClosed(event);
		}
	}
        //
	private final List<ConnectionEventListener> listenerList = new LinkedList<ConnectionEventListener>();
	//
	public void addConnectionEventListener(ConnectionEventListener listener) {
		this.listenerList.add(listener);
	}
	//
	public void removeConnectionEventListener(ConnectionEventListener listener) {
		this.listenerList.remove(listener);
	}
	/**
	 * Beispielhafte Implementierung zur Unterstützung von 
	 * {@link ManagedConnectionFactory#matchManagedConnections}.
	 * 
	 * this.subject und this.info charakterisieren die Identität dieser
	 * ManagedConnection Instanz - zur Implementierung von matches
	 * sollten aber nur diejenigen Informationen verwendet werden,
	 * die tatsächlich bei der Etablierung der physikalischen
	 * Verbindung zum EIS gebraucht werden.
	 * 
	 * @param subject
	 * @param info
	 * @return 
	 */
	boolean matches(final Subject subject, final ConnectionRequestInfo info) {
		this.logWriter.print(this + " matches: ");
		if (info == null) {
			if (this.info != null) {
				return false;
			}
		} else if (!info.equals(this.info)) {
			return false;
		}
		if (subject == null) {
			if (this.subject != null) {
				return false;
			}
		} else if (!subject.equals(this.subject)) {
			return false;
		}
		return true;
	}
	/**
	 * Ermittelt aus dem Subject den Usernamen.
	 * 
	 * @return Der Username für den Aufbau der Verbindung.
	 */
	String getUserName() {
		if (this.subject != null) {
		  this.subject.getPrincipals();
 		  //... mehr Code
		}
		return null; // dummy
	}
	/**
	 * In Section 16.4 der Spec ist gefordert, dass die Standard equals 
	 * Implementierung NICHT überschrieben wird ("no two Java objects are 
	 * considerd equals").
	 * 
	 * {@link #matches(Subject, ConnectionRequestInfo)}.
	 */ 	
	@Override
	public final boolean equals(Object obj) {
		// this implementation must not be changed
		return super.equals(obj);
	}
	/**
	 * In Section 16.4 der Spec ist gefordert, dass die Standard equals 
	 * Implementierung NICHT überschrieben wird ("no two Java objects are 
	 * considerd equals"), damit muss auch {@link #hashCode()} bleiben.
	 */ 	
	@Override
	public final int hashCode() {
		// this implementation must not be changed
		return super.hashCode();
	}
}

Bei der Implementierung einer eigenen ManagedConnection sind anzupassen:

  • Im Konstruktor erfolgt der Aufbau einer physikalischen Verbindung zum EIS und wird gehalten.
  • Die fachlichen Methoden des EIS müssen ausimplementiert werden.
  • destroy und gegebenenfalls cleanup ist sinnvoll zu implementieren.
  • getConnection muss gegebenenfalls ein Aufrischen der Connection berücksichtigen.
  • getUserName oder eine andere Auswertung der Credentials ist gegebenenfalls zu implementieren.

Mit einer ManagedConnectionFactory sucht der Container nach passenden ManagedConnections im Pool. Wird er nicht fündig erzeugt er mit ihrer Hilfe neue ManagedConnection Instanzen. Zusätzlich erzeugt der Container mit ihr den JNDI Eintrag der ConnectionHandleFactory. Die Klasse benötigt einen parameterlosen Konstruktor und passende Bean Setter für die im Deployment Deskriptor verwendeten Parameter.

public final class ManagedConnectionFactoryImpl implements ManagedConnectionFactory {
 /**
  * Die UID der Klasse.
  */
  private static final long serialVersionUID = 6902822490236740333L;
 /**
  * Wird mit dem Inhalt der config-property aus der ra.xml belegt.
  */
  private String hostURL = null;
 /**
  * Setzt einen Parameter
  * 
  * @param hostURL
  */
  public void setHostURL(String hostURL) {
    this.hostURL = hostURL;
  }
 /**
  * Liefert einen Parameter
  * 
  * @return
  */
  String getHostURL() {
    return this.hostURL;
  }
 /**
  * In Containerumgebungen bei der Initialisierung des ResourceAdapters
  * gerufen (der ConnectionManager wird bereitgestellt)
  */
  public Object createConnectionFactory(ConnectionManager cm) throws ResourceException {
    return new HelloWorldConnectionFactoryImpl(cm, this);
  }
 /**
  * wird in Standalone Szenarien gerufen, der ResourceAdapter stellt die
  * Implementierung des ConnectionManager
  */
  public Object createConnectionFactory() throws ResourceException {
    return createConnectionFactory(new DefaultConnectionManager());
  }
 /**
  * Der Container hat sich entschlossen, eine neue physikalische Connection
  * zu instanziieren (zB keine freie Instanz im Pool).
  * 
  * @see javax.resource.spi.ManagedConnectionFactory#createManagedConnection(javax.security.auth.Subject,
  *      javax.resource.spi.ConnectionRequestInfo)
  */
  public ManagedConnection createManagedConnection(Subject subject, ConnectionRequestInfo info) 
  throws ResourceException {
    if (info == null || info instanceof HelloWorldConnectionRequestInfo) {
      return new ManagedConnectionImpl(this, subject, (HelloWorldConnectionRequestInfo) info, getLogWriter());
    }
    throw new ResourceException("Unknown ConnectionRequestInfo type: " + info.getClass().getName());
  }
  /**
   * Der Container versucht mit Hilfe dieses Aufrufs eine geeigeneten Instanz im Pool zu finden.
   *  
   * Der eigentliche Vergleich wird aber an die
   * {@link ManagedConnectionImpl#matches(Subject, ConnectionRequestInfo)}
   * delegiert.
   * 
   * Wenn Pooling nicht unterstützt werden kann, muss hier eine NotSupportedException geworfen werden.
   */
  public ManagedConnection matchManagedConnections(final Set connectionSet, 
  final Subject subject, final ConnectionRequestInfo info) throws ResourceException {
    for (final Iterator iter = connectionSet.iterator(); iter.hasNext();) {
      Object obj = iter.next();
      if (obj instanceof ManagedConnectionImpl) {
        ManagedConnectionImpl managedConnection = (ManagedConnectionImpl) obj;
        // am besten der Vergleich wird von den ManagedConnectionImpl selbst vorgenommen
        if (managedConnection.matches(subject, info)) {
          return managedConnection;
        }
      }
    }
   /*
    * das ist das Signal an den Container, dass nichts gefunden wurde:
    */
    return null;
  }
 /**
  * Die Implementierung berücksichtigt alle Konfigurationsparameter dieser
  * Factory-Instanz - falls in einem Container mehr als einmal der gleiche
  * ResourceAdapter deployed wird (mit unterschiedlichen Konfigurationen)
  * wird der Container mit dieser Implementierung seine Pools strukturieren.
  */
  @Override
  public int hashCode() {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + ((hostURL == null) ? 0 : hostURL.hashCode());
    return result;
  }
 /**
  * Eine equals Implementierung konform zu hashCode
  */
  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    final ManagedConnectionFactoryImpl other = (ManagedConnectionFactoryImpl) obj;
    if (hostURL == null) {
      if (other.hostURL != null) return false;
    } else if (!hostURL.equals(other.hostURL)) return false;
    return true;
  }
 /**
  * Der Container regelt die Assoziation mit einem Logwriter.
  */
  private PrintWriter logWriter = null;
  //
  public PrintWriter getLogWriter() throws ResourceException {
    return this.logWriter;
  }
  // 
  public void setLogWriter(PrintWriter logWriter) throws ResourceException {
    this.logWriter = logWriter;
  }
}
Bei der Implementierung einer eigenen ManagedConnectionFactory sind lediglich alle Parameter, die mit den Einträgen des Deploymentdeskriptors gesetzt werden können, als Beanparameter bereitzustellen. Die Implementierung von equals und hashCode muss auf dieser Basis erfolgen, der Container verwaltet seine Pools unter anderem damit. Die ManagedConnectionMetaData Implementierung versorgt den Container mit statischen Eigenschaften des Outbound Resource Adapters. Die Implementierungen sind entsprechend anzupassen. Ein Defaultimplementierung des ConnectionManagers für ein Deployment des Adapters in einer nicht Java EE Umgebung ist für einen vollständigen Resource Adapter zwingend erforderlich (hier in Pseudocode):
public class DefaultConnectionManager implements ConnectionManager {
 /**
  * Die UID der Klasse. 
  */
  private static final long serialVersionUID = 1589810599645876875L;
  //
  public Object allocateConnection(final ManagedConnectionFactory mcf,
  final ConnectionRequestInfo cri) throws ResourceException {	
   /*
    * Einfachste Implementierung für stand alone Anwendungen.   
    * 
    * - Beschaffung und Pooling von ManagedConnectionImpl Instanzen aus Sicht
    *   einer JVM Instanz
    * - Verwaltung des Pool unter Nutzung von 
    *   ManagedConnectionFactory.matchManagedConnections
    * - Erzeugung von ConnectionHandles mit ManagedConnectionImpl
    */
    ManagedConnection mc = mcf.createManagedConnection(null, cri);
    return mc.getConnection(null, cri);
  }
}

Deployment


Der Deploymentdeskriptor des Adapters ist weitestgehend selbsterklärend:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE connector PUBLIC '-//Sun Microsystems, Inc.//DTD Connector 1.0//EN' 'http://java.sun.org/j2ee/dtds/connector_1_0.dtd'>
<connector>
  <display-name>Hello World Adapter</display-name>
  <vendor-name>nn</vendor-name>
  <spec-version>1.0</spec-version>
  <eis-type>Prop</eis-type>
  <version>1.0</version>
  <resourceadapter>
    <managedconnectionfactory-class>com.eis.hw.ManagedConnectionFactoryImpl</managedconnectionfactory-class>
    <connectionfactory-interface>com.eis.hw.HelloWorldConnectionFactory</connectionfactory-interface>
    <connectionfactory-impl-class>com.eis.hw.HelloWorldConnectionFactoryImpl</connectionfactory-impl-class>
    <connection-interface>com.eis.hw.HelloWorldConnection</connection-interface>
    <connection-impl-class>com.eis.hw.HelloWorldConnectionImpl</connection-impl-class>
    <!-- Der TX-Kontrakt wird in diesem Artikel nicht beleuchtet -->
    <transaction-support>NoTransaction</transaction-support>
    <!-- Die passenden Setter werden an der ManagedConnectionFactory Implementierung gerufen -->
    <config-property>   
      <config-property-name>HostURL</config-property-name>
      <config-property-type>java.lang.String</config-property-type>
      <config-property-value>127.0.0.1</config-property-value>
    </config-property>
    <authentication-mechanism>
      <authentication-mechanism-type>BasicPassword</authentication-mechanism-type>
      <credential-interface>javax.resource.security.PasswordCredential</credential-interface>
    </authentication-mechanism>
    <reauthentication-support>false</reauthentication-support>
  </resourceadapter>
</connector>
Ein Resource Adapter kann in einem Server auf zwei Arten deployed werden:
  • stand alone: Der Resource Adapter wird unabhängig von anderen Java EE Modulen deployed. Er steht allen Java EE Modulen des Servers zur Verfügung.
  • bundled: Der Resource Adapter wird in einer Enterprise Application (also in einem .ear-Archiv) deployed. Er steht allen Java EE Modulen dieser Enterprise Application zur Verfügung.
Für einen einfachen Test wird der Resource Adapter stand alone deployed. Das erfolgt serverspezifisch und ist hier am Beispiel des Sun Java System Application Server (GlassFish) demonstriert.
  1. Der Resource Adapter und eine nutzende Komponente in Form einer Minimal-Webanwendung werden als Java EE Module deployed
  2. Ein Connection Pool für den Resource Adapter wird eingerichtet.
  3. Ein JNDI Eintrag wird mit diesem Connection Pool verknüpft.

Transaktionale Outbound Resource Adapter


Ein Outbound Resource Adapter kann als transaktionale Ressource deklariert werden. Dazu wird im Deploymentdeskriptor

<transaction-support>LocalTransaction</transaction-support>
oder
<transaction-support>XATransaction</transaction-support>
angegeben und es ist eine der Methoden getLocalTransaction() respektive getXAResource() zu implementieren.

Lokale Transaktionen unterstützen kein two phase commit, sind dadurch weniger isoliert aber einfacher zu implementieren. Der Container ruft lediglich die Methoden begin und commit / rollback im Kontext der klammernden Transaktion. Wichtig ist hierbei den Container (der sich als ConnectionEventListener angemeldet hat) über die erfolgreiche Abarbeitung der Methoden zu informieren. XA Transaktionen (manchmal auch als globale Transaktionen bezeichnet) unterstützen two phase commit. Die dadurch erreichte Isolierung ist besser als die lokaler Transaktionen, die Implementierung wesentlich anspruchsvoller. Ob überhaupt und welche Transaktionalität zum Einsatz kommt bestimmt letztlich das angebundene System und dessen API.

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved