LDAP JCA

Java Connector für LDAP Directories

Hintergrund

Zur Erhöhung der Wiederverwendbarkeit von Programmcode sollte man es tunlichst vermeiden, laufzeitabhängige Werte im Code zu hinterlegen. Dazu gehören solche Daten wie Benutzernamen, Kennwörter, Dateinamen oder z.B. auch die Konfiguration der Verbindung zu einem Informationssystem. Insbesondere in Enterprise-Umgebungen geht man in der Regel noch einen Schritt weiter und lagert solche Verbindungen in einen Pool aus, so dass zur Laufzeit das Programm den Pool über einen JNDI-Lookup mit Hilfe eines entsprechenden Namens (parametrisiert) finden und darauf zugreifen kann, aber unabhängig von der Konfiguration des Pools ist. Der Pool wird wiederum so konfiguriert, dass er Verbindungen zu einem spezifischen Backend vorhalten kann.

Durch dieses Design ist es z.B. zur Laufzeit möglich, die Konfiguration eines Pools für eine Anwendung völlig transparent so zu ändern, so dass er auf ein anderes Informationssystem verweist, ohne dass die auf den Pool zugreifende Anwendung umkonfiguriert werden muss.

Diese Vorgehensweise ist z.B. beim Zugriff auf Datenbanken üblich; JDBC-Treiber implementieren in der Regel die benötigten Schnittstellen, so dass die Verbindungen entsprechend vorgehalten werden können. Bei JNDI-Datenquellen, also Namens- und Verzeichnisdiensten, scheint dieses Vorgehen allerdings nur sehr selten Anwendung zu finden; jedenfalls ergab eine entsprechende Suche meinerseits keine verwertbaren Spuren. Um dieses Vorgehen beim Zugriff auf ein LDAP-Verzeichnis anwenden zu können, wurde daher ein Connector gemäß der J2EE Connector Architecture (Version 1.0) geschrieben, mit dem entsprechende Verbindungen von einem Application Server verwaltet und Enterprise Applications zur Verfügung gestellt werden können. Der Connector wurde im Rahmen eines Projekts erfolgreich mit JBoss in den Versionen 4.0 und 4.02 verwendet.

Beschreibung des Connectors

Unterschiede zu JDBC-Datenquellen

Im Gegensatz zu „traditionellen“ Datenquellen, die über JDBC auf Datenbanken zugreifen, handelt es sich bei der Connection um eine Implementierung des Interface javax.naming.ldap.LdapContext (analog zu java.sql.Connection bzw. javax.sql.PooledConnection bei JDBC). Da der Zugriff auf Verzeichnisdienste in Java standardisiert ist, wird auch kein spezifischer Treiber benötigt; zwar ist die Verwendung eines auf ein Produkt spezialisierten Treibers über die allgemeine Konfiguration der Provider im JDK durchaus möglich, dieser Connector verwendet aber den Standard-Provider des JDK.

Ein weiterer gravierender Unterschied besteht darin, dass Transaktionen nicht unterstützt werden, da LDAP Transaktionen nicht unterstützt.

Konfiguration des Connectors

Der Connector hat drei einstellbare Attribute, mit denen die Verbindung zum Dienst eindeutig festgelegt wird:

Wie an der Erläuterung des Host Parameters erkennbar ist, unterstützt der Connector derzeit keine Verbindungen über SSL/TLS.

Verwendung des Connectors

Die Verwendung des Connectors wird am Beispiel des Aufbaus einer Datenquelle im JBoss Application Server sowie dem Zugriff darauf aus einer Entity-Bean heraus gezeigt.

JBoss-Konfiguration

Die Konfiguration des Pools in JBoss erfolgt durch eine XML-Datei im Unterverzeichnis deploy des Servers. Damit der Server automatisch erkennt, dass mit einer solchen Datei eine Datenquelle konfiguriert wird, muss der Name per Konvention auf -ds.xml enden; die Datei heißt daher ldap-ds.xml.

In der gezeigten Konfiguration werden maximal 20 Verbindungen mit dem LDAP-Server auf localhost (an Port 389) mit dem Benutzernamen cn=Admin, o=Konzern und dem Kennwort secret durch den Pool vorgehalten. Der Pool ist innerhalb des Application Server unter dem Namen LDAPDS verfügbar.

<?xml version="1.0" encoding="UTF-8"?>

  <!-- ==================================================================== -->
  <!-- LDAP JCA connector setup for JBOSS                                   -->
  <!-- ==================================================================== -->

<connection-factories>


  <no-tx-connection-factory>

    <jndi-name>LDAPDS</jndi-name>


        <!-- this is old jboss style -->
    <adapter-display-name>LDAP JCA Connector</adapter-display-name>

        <!-- this is the new way to go -->
    <rar-name>ldapjca.rar</rar-name>
    <connection-definition>de.alexanderlindhorst.jca.ldap.LDAPConnectionFactory</connection-definition>

    <config-property name="Host" type="java.lang.String">localhost</config-property>

    <config-property name="User" type="java.lang.String">cn=Admin,o=Konzern</config-property>

    <config-property name="Password" type="java.lang.String">secret</config-property>


    <min-pool-size>5</min-pool-size>
    <max-pool-size>20</max-pool-size>
        <blocking-timeout-millis>1000</blocking-timeout-millis>
        <idle-timeout-minutes>30</idle-timeout-minutes>
  </no-tx-connection-factory>

</connection-factories>

Zugriff aus Enterprise-Bean

Der beispielhafte Zugriffe erfolgt aus einer Entity-Bean mit BMP, die Einträge aus einem Verzeichnis (statt aus einer Datenbank) kapselt. Der Code-Auszug zeigt die Implementierung der ejbFindByPrimaryKey Methode; sie greift auf den Pool zu und holt sich eine Connection (LDAPContext), setzt die Sucheinstellungen und durchsucht das Verzeichnis nach dem Primary Key, bei dem es sich um einen DN handelt. Wird der Eintrag gefunden, wird er nach den Regeln des Namespace formatiert und zurückgegeben; ansonsten wird eine FinderException ausgelöst.

    public String ejbFindByPrimaryKey(String primaryKey)
                               throws FinderException {
        //Note: primary key=dn
        String name = null;

        try {
            //load from ldap backend - env entry "LDAP"
            InitialContext ctx = new InitialContext();

				//"java:comp/env/LDAP" is mapped to "java:LDAPDS" via
				//settings in jboss.xml
            DirContext dctx = ((LDAPConnectionFactory) ctx.lookup(
                                       "java:comp/env/LDAP")).getConnection();

            DirContext c = (DirContext) dctx.lookup(primaryKey);

            if (c == null) {
                throw new ObjectNotFoundException("No such object: " + dn);
            }


            //make the real name persistent
            name = c.getNameInNamespace().toString();

            dctx.close();
        } catch (ObjectNotFoundException o) {
            throw new FinderException(o.getMessage());
        } catch (Exception e) {
            throw new EJBException(e);
        }

        return name;
    }

Download