This blog has moved to
http://blog.matthias-reining.com

Die bestehenden Artikel bleiben vorerst alle bei blogspot. Neue Artikel veröffentliche ich allerdings nur noch auf http://blog.matthias-reining.com

Samstag, 7. März 2009

EJBs und Ressourcen beim Aufruf einer „fremden“ Webapplikation per local Call

Der Titel ist vermutlich etwas unglücklich formuliert, weshalb ich hier nochmal die Ausgangslage etwas detailierter erläutere:

Ausganslage

Auf dem JBoss (Version 4.2.3) befindet sich eine EAR Applikation (MyApp.ear). In ihr enthalten sind einige Libs, ein EJB jar File (MyAppEJB.jar) und eine WAR Webapplikation (MyAppWeb.war). Die Webapplikation kennt das EJB jar (im Manifest bekannt gemacht) und kommuniziert mit den EJBs per local Aufrufen.

Um die Anwendung konfigurierbar zu halten arbeitet eine Methode innerhalb eines Stateless SessionBeans (SLSB) mit einer weiteren "normalen" Konfigurationsklasse, die ebenfalls in MyAppEJB.jar liegt. Die Hilfsklasse "Config" hat eine static Methode mit der ein Konfigurationsfile eingelesen wird. Dies passiert mit Hilfe der Apache API Commons Configuration (Configuration configuration = new PropertiesConfiguration( filePath ) ). Das Konfigurationsfile liegt innerhalb von MyAppEJB.jar im Verzeichnis META-INF (filePath = "META-INF/config.properties)

(Laut EJB Spec sollen keine Dateien vom Filesystem geladen werden bzw. es sollen keine Klassen aus java.io verwendet werden. Das File könnte in einer geclusterten Umgebung auf den unterschiedlichen Nodes unterschiedlich aussehen. Nachdem hier das File allerdings immer fix/ nicht änderbar im EAR bzw. EJB Archiv liegt, geht dies eigentlich in Ordnung. Später dazu mehr).

Soweit funktioniert hier auch alles!

Nun existiert eine weitere Webapplikation außerhalb des EARs (Bei der WebApp handelt es sich um eine Maintenance Anwendung, die auch getrennt deployed werden soll). Die Maintenance WebApp kommuniziert allerdings auch mit EJBs aus der oben beschriebenen Anwendung. Da die beiden Applikationen sich auf demselben Server befinden, findet die Kommunikation per local Interface statt (JNDI).

Problem

Falls nach dem Serverstart die Maintenance Webapp zuerst die EJB Methode aufruft, die wiederrum die Hilfsklasse "Config" aufruft und somit auch initialisiert kommt es zu folgender Fehlermeldung:

...
Caused by:
org.apache.commons.configuration.ConfigurationException: Cannot locate configuration source META-INF/config.properties
...

Die Reihenfolge ist in diesem Fall relevant, da die Konfiguration nur beim ersten Zugriff auf die Klasse initialisiert wird. Bis man das "Reihenfolgenproblem" lokalisiert hat, schaut es dummerweise so aus, als ob das Ganze manchmal geht, manchmal nicht… blöde Sache!

Hintergrund

PropertiesConfiguration versucht, falls es nirgends anders fündig wird, das File aus dem ClassPath zu lesen. In diesem Fall aus dem MyAppEJB JAR Archiv unter META-INF, was ja soweit auch korrekt ist.

Allerdings wie greift man auf ein File innerhalb eines JAR Files zu?

Thread.currentThread().getContextClassLoader.getResource(filePath)

Und genau so macht es die Apache Commons Configurations API.

Nachdem es sich um einen local Aufruf handelt, bleibt der ganze Aufruf-Stack logischerweise auch im gleichen Thread und hat somit auch den WAR Class Loader als ContextClassLoader.


 

Local Zugriff durch einer WebApplikation innerhalb des Enterprise Archives (EAR)

Bei einem Aufruf über MyAppWeb, das innerhalb von MyApp.ear liegt, funktioniert das Ganze; Die VM durchsucht den Classpath nach filePath (=META-INF/config.properties). Hier wird immer zuerst im Parent ClassLoader gesucht (dies passiert rekursiv).
Im folgendes diesen Prozess etwas ausführlicher:

Die Anfrage startet nach wie vor im Web Thread von MyAppWeb (der Threadname lautet bspw. etwa: http-127.0.0.1-8080-5; dabei wird der WAR Class Loader verwendet, Name = "WebappClassloader"). Bei der Such wird jetzt erst im Parent Classloader rekursiv gesucht:
WAR Class Loader à EJB Class Loader à Application Server Class Loader à System Class Loader à Extension Class Loader à Bootstrap Class Loader

Die Suche beginnt also im Bootstrap Class Loader – hier wird allerdings das File "META-INF/config.properties" nicht gefunden. Das Spiel geht dann so weiter bis zum EJB Class Loader. Hier wird die Classloader Resourcensuche fündig und liefert das gewünschte File als URL zurück. Die Konfiguration kann geladen werden!


 

Local Zugriff durch eine "selbstständigen" WebApplikation außerhalb des Enterprise Archives

Hier gilt das gleiche oben; bei einem Local Call befindet man sich im selben Thread. Der Classpath wird wieder rekursiv durchsucht. Dieser schaut hier wie folgt aus:
WAR Class Loader à Application Server Class Loader à System Class Loader à Extension Class Loader à Bootstrap Class Loader

In keinen der durchsuchten Classloader Resourcen wird das File "META-INF/config.properties" gefunden. Das File liegt ja auch im MyAppEJB.jar, dass hier überhaupt nicht durchsucht wird. Die Konfiguration kann nicht geladen werden!

Die Klassen "Config" wird vom AppServer bzw. vom EJB mit einen eigenen ClassLoader initialisiert (org.jboss.mx.loading.UnifiedClassLoader3@1be513c). Allerdings ist der ClassLoader vom aktuellen Thread (Thread.currentThread().getContextClassLoader) immer noch der WAR Class Loader (WebappClassLoader).


 

Problembehebung

Das Problem lässt sich auf verschieden Arten umgehen:

  • Die Apache Commons Configuration bietet auch eine Möglichkeit die Konfiguration aus der DB auszulesen (DatabaseConfiguration), so dass überhaupt keine Konfiguration in Form von Property-Files mehr notwendig ist.
  • Das Problem kann natürlich auch umgangen werden, in dem man dafür sorgt, dass die Konfiguration nach dem Start des Servers immer zuerst von der EAR Applikation initialisiert wird (funktioniert, ist aber nach Spec nicht wirklich sauber…)
  • Die Maintenance Applikation greift per remote Zugriff auf die EJBs zu. So ist sichergestellt, dass nicht der Maintenance Thread/ Classloader versucht das Konfigurationsfile zu laden.
  • Beim initialisieren von PropertiesConfiguration gibt man nicht den relativen Pfad (META-INF/config.properties) sondern gleich die URL innerhalb des EJB Archives (new PropertiesConfiguration(Config.class.getResource("/META-INF/config.properties")); )

In diesem Projekt habe ich den pragmatischen Ansatz gewählt und mich für die letzte Variante entschieden

1 Kommentar:

  1. Ich nutze Apache Commons Configuration und möchte die config-Datei extern ablegen. Dabei bekam ich immer ConfigurationExceptions (unter Tomcat). Die aufrufende Klasse ist im JAR im WAR. Doch wenn man weiss, wo Tomcat die Datei sucht, geht's:

    config = new XMLConfiguration("meine_config.xml");

    sucht die Datei an diesen Stellen:

    C:\Program Files\Apache\tomcat-5.5.28\bin\meine_config.xml
    C:\Documents and Settings\mein_user\meine_config.xml
    C:\Program Files\Apache\tomcat-5.5.28\webapps\webapp-name\WEB-INF\classes\meine_config.xml
    C:\Program Files\Apache\tomcat-5.5.28\common\classes\meine_config.xml
    C:\Program Files\Apache\tomcat-5.5.28\shared\classes\meine_config.xml

    AntwortenLöschen