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

Sonntag, 27. Dezember 2009

ServiceLocator - Zugriff auf Stateless SessionBeans aus einem POJO (neues mit EJB 3.1)

Um von einem POJO auf einen Stateless SessionBean (SLSB) zugreifen zu können, gibt es keine Möglichkeit den SessionBean per Annotation á la @EJB zu injizieren. Das injizieren klappt nur bei gemanagten Klassen (Session Beans oder Servlets zum Beispiel).
Ähnliche Probleme gibt es allerdings auch bei einem "gemanagten" JSF Managed Bean (JSF2.0 bei der Verwendung des View-Scopes (@ViewScoped) und der Annotation @EJB).


Hier hilft das Service Locator Pattern weiter (siehe http://mr678.blogspot.com/2008/10/ejb3-und-servicelocator.html). Mit Hilfe dieses Patterns wird ein Session Bean lokalisiert.


In meinen Anwendungen möchte ich Öfters per Local Interface innerhalb der gleichen Applikation (WAR oder EAR File) einen SLSB nutzen. Bisher war der Zugriff über JNDI auf verschiedenen App Servern unterschiedlich. Am meisten hat mich bisher daran gestört, dass ich den Name des EAR Files hier immer hart codiert ist.
Mit der JEE6 Spec (EJB3.1) gibt es hier nur einen standardisierten Zugriff. Details hierzu sind in einem Blog von Mahesh Kannan von sun (siehe http://blogs.sun.com/MaheshKannan/entry/portable_global_jndi_names)


Kurzfassung:
Der Zugriff innerhalb der gleichen Applikation erfolgt wie folgt:


EAR File: java:app/<module-name>/<bean-name>!<fully-qualified-intf-name>
bzw.
WAR File: java:module/<bean-name>!<fully-qualified-intf-name>


Nachdem ich im Moment kleinere Applikation auf dem AppServer nur noch als WAR Modul baue, benötige man in diesem Fall überhaupt keine hart codierten Module- oder Applikationsnamen mehr. 
(Adam Bien hat hier sehr interessante Blog Einträge zum Thema Applikationen als WAR Module deployen; http://www.adam-bien.com/roller/abien/entry/is_java_ee_6_warhttp://www.adam-bien.com/roller/abien/entry/java_ee_6_kills_the)


Meine ServiceLocator Klasse zum Zugriff auf SLSBs innerhalb meines WAR Files schaut daher ganz einfach aus:



@SuppressWarnings("unchecked")
public static <T> T getService(Class<T> clazz) {
  try {
  InitialContext ctx = new InitialContext();
  String lookupPath = "java:module/"
           + clazz.getSimpleName() 
           + "!" + clazz.getName();

  return (T) ctx.lookup(lookupPath);
  } catch (NamingException e) {
  throw new ServiceLocatorException(e);
  }
}

Montag, 2. November 2009

Findbugs und Out of memory


Innerhalb meines Continous Integration Systems (hudson) werden per ANT auch potentielle Fehler per Findbugs ermittelt. Hierzu wird folgendes Target innerhalb des ANT Skriptes defniert:


name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask">
       refid="findbugs.class.path" />



name="run-findbugs">
      run findbugs (findbugs home: ${env.FINDBUGS_HOME})
       dir="${findbugs.report.dir}" />
       dir="${findbugs.report.dir}" />
       home="ext/findbugs-1.3.9-rc1" output="xml:withMessages" outputFile="${findbugs.report.dir}/findbugs_vger.xml">
             location="${build.jar.dir}" />
             refid="build.class.path" />
             path="${src.dir}" />
      


Allerdings tritt bei größeren Sourcecode Analysen ein Out of memory Fehler auf:
[findbugs] Executing findbugs from ant task
[findbugs] Running FindBugs...
[findbugs] Out of memory
[findbugs] Total memory: 66M
[findbugs]  free memory: 0M

Findbugs forkt den Prozess und macht eine neue JVM Instanz auf, weshalb eine Speichererhöhung innerhalb des Findbugs-Tags stattfinden muss:
  home="ext/findbugs-1.3.9-rc1" jvmargs="-server -Xmx256m" ...


Donnerstag, 17. September 2009

Eclipse – build.xml | No grammer constraints (DTD or XML schema) detected for the document

Ab eclipse ganymede kommt es im ANT Script (build.xml) zu folgende Fehlermeldunge „No grammer constraints (DTD or XML schema) detected for the document“
Folgende Zeile schafft hier abhilfe:


<!DOCTYPE xsl:stylesheet [&lz;!ENTITY bullet "•"> <!ENTITY nbsp " ">]>


Das ganze schaut dann im Context so aus:
<?xml version="1.0" encoding="UTF-8" standalone="yes" >
<!DOCTYPE xsl:stylesheet [&lz;!ENTITY bullet "•"> <!ENTITY nbsp " ">]>
<project …

Dienstag, 5. Mai 2009

Subversion (SVN) Einführung – Basis Know How

Ich bin gerade dabei, im Rahmen einer Projektübergabe, ein paar Kollegen Subversion etwas näher zu bringen (einige von Ihnen haben CVS Know How bzw. wenig Erfahrung mit VCS (Version Control Systems)).

Ich bin dabei zu folgendem Ergebnis gekommen:

Die freie Doku von O'Reilly ist einfach genial!

http://svnbook.red-bean.com/

bzw. http://svnbook.red-bean.com/en/1.5/svn-book.pdf oder http://svnbook.red-bean.com/nightly/en/svn-book.pdf

Dort ist eigentlich alles perfekt beschrieben und ich spar mir den großen "Rundumschlag" und kann die Übergabe auf ein paar Projektspezifika beschränken J

Dienstag, 28. April 2009

Hibernate Search indexBase Directory – relativer Pfad zum WebContainer

Wenn man den Hibernate Search Index im FileSystem ablegt (FSDirectoryProvider) wird dieser typischer Weise in der persistence.xml definiert (bei der Verwendung von JPA). Hier kann man allerdings nur einen relativen Pfad zum Index -Verzeichnis aus dem die Java Anwendung gestartet wurde angeben (bspw. TOMCAT_HOME$/bin) oder man gibt einen absoluten Pfad an.

Nachdem die persistence.xml im Normalfall Bestandteil der ausgelieferten Applikation ist, kommt ein absoluter Pfad eigentlich nicht in Frage (typisches Szenario: Entwicklung unter Windows - C:\app\luceneIdx; Livedeployment unter Linux - /usr/locale/app/luceneIdx).

Ein relativer Pfad ist allerdings auch nicht unbedingt schön - /usr/share/jboss/jboss-4.2.3GA/bin/luceneIdx…

Schön wäre meiner Meinung nach ein relativer Pfad zum AppServer wobei die Definition hierbei im Sourcecode liegt (XML Konfiguration gibt es wirklich schon zu genüge…)

Anbei einige Möglichkeiten den Pfad für Hibernate Search zu definieren:

Relativer Eintrag in der persistence.xml

<property name="hibernate.search.default.indexBase" value="luceneIdx" />

Nachteil: siehe Einleitung

Startparameter

-Dhibernate.search.default.indexBase=D:/luceneIdx

Beim Start der VM kann auch der Index angegeben werden; Hibernate Search verwendet dann diesen. So kann für jede Umgebung (Windows, Linux) ein absolutes Verzeichnis definiert

JBoss SystemProperties MBean

   <mbean code="org.jboss.varia.property.SystemPropertiesService"

        name="jboss.util:type=Service,name=SystemProperties">

    <!-- Load properties from each of the given comma separated URLs -->

    <attribute name="URLList">

        ./conf/lucene.properties

    </attribute>

   </mbean>

Lucene.properties

hibernate.search.default.indexBase=D:/luceneIdx3

Die Definition des Hibernate Search Index Directory findet nun in einem Property File statt (angesprochen über den JBoss System Properties MBean), dass für jede Serverumgebung unteschiedlich sein kann. Die Konfiguration liegt in der Applikation, das Property File kann auf jedem Server unterschiedlich sein.

Eigener DirectoryProvider

package com.nobiscum.provobis.search;

import java.io.File;
import java.util.Enumeration;
import java.util.Properties;

import org.hibernate.search.backend.configuration.MaskedProperty;
import org.hibernate.search.engine.SearchFactoryImplementor;
import org.hibernate.search.store.FSDirectoryProvider;

/**
* <p>
* This DirectoryProvider use the <i><jboss.server.data.dir>/luceneIdx</i> directory to store the
* Apache Lucene filesystem index.<br>
* For example the path looks like <i>&lt;JBOSS_HOME&gt;/server/provobis/data/luceneIdx</i>
* </p>
* <p>
* To use the FSRelativePathDirectoryProvider put following line in <code>persistence.xml</code><br>
*
* <pre>
* &lt;property name=&quot;hibernate.search.default.directory_provider&quot;
* value=&quot;com.nobiscum.provobis.search.FSRelativePathDirectoryProvider&quot; /&gt;
* </pre>
*
* </p>
*
* @author mre
* @see FSDirectoryProvider
*/
public class FSRelativePathDirectoryProvider extends FSDirectoryProvider {

@Override
public void initialize(String directoryProviderName, Properties properties,
SearchFactoryImplementor searchFactoryImplementor) {
// copy is necessary, see method description.
Properties p = copyProperties(properties);

// XXX AppServer dependency
String indexBase = System.getProperty("jboss.server.data.dir");
indexBase += File.separator + "luceneIdx";

p.put("indexBase", indexBase);
super.initialize(directoryProviderName, p, searchFactoryImplementor);
}

/**
* The <code>properties</code> parameter is not a {@link Properties} themselve, but rather a
* subclass ({@link MaskedProperty}. The {@link MaskedProperty} is read-only, less methods are
* implemented, the most throw an UnsupportedOperationException.
* <p>
* To manipulate the properties, a new <i>normal</i> {@link Properties} object is created. The
* values from the given properties are all copied into the new one.
*
* @param properties
* existing properties object
* @return new properties object, that contains all values from <code>properties</code>
*/
private Properties copyProperties(Properties properties) {
Properties newProperties = new Properties();
Enumeration<Object> keys = properties.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = properties.getProperty(key);
newProperties.put(key, value);
}
return newProperties;
}
}

Hier ist der Pfad relativ zum JBoss data Verzeichnis.

Eine schöne Lösung wie ich finde ;-)

Donnerstag, 2. April 2009

jQuery – Thickbox : Dialog Öffnen aus einem Dialog per HTTP POST

jQuery Thickbox unterstütz primär das Dialoghandling per HTTP GET. Dies ist auch verständlich, da die API ja aus der Slideshow Ecke kommt, bei der in der Regel neue Bilder per a href angefordert werden und in einer Dialogbox angezeigt werden.

Will man die API allerdings in einer Web Applikation zum Einsatz von web2.0 Dialogboxen (Layern) verwenden, kommt es des Öfteren vor, dass man Daten aus der Dialogbox posten möchte und anschließend eine andere Dialogbox anzeigen möchte.

Szenario:

Login Box à Link (a href) Passwort vergessen à Passwort vergessen Box à Email Adresse abfragen und and Server posten à Email mit neu generiertem Passwort verschickien à Login Box anzeigen.

Anbei der Code der auf dem Submit Button liegen muss.

$(document).ready(function() {
$("#btn_send").click(function() {
$.ajax({
type: "POST",
url: $('#rememberPasswordForm').attr('action')

                 + "?width=400",
dataType: "html",
data: $('#rememberPasswordForm').serialize(),
success: function(data){

            //set content to diabox and reinitalize thickbox
$("#TB_ajaxContent").html(data);
tb_position();
$("#TB_load").remove();
tb_init("#TB_ajaxContent a.thickbox");
$("#TB_window").css({display:"block"});
}
});
});
$(".btn_close").click(tb_remove);
});


 

Der interessante Teil ist hier die Callback Funktion (success: function(data)…). In ihr ist der Thickbox Code zum Darstellen der zurückgelieferten Seite untergebracht. Hier wird neben der Darstellung noch die Initialisierung übernommen, damit in der "neuen" Dialogbox auch die Thickbox Funktionen zur Verfügung stehen.

Donnerstag, 26. März 2009

Continuous Integration mit Hudson - Installationsanleitung (Standalone Konfiguration)

Continuous Integration mit Hudson: tolle Sache. Find ich richtig hübsch….

Um Hudson zu betreiben gibt es jede Menge verschieden Installations- und Konfigurationsmöglichkeiten: Standalone, innerhalb eines Webservers (Tomcat, JBoss, Glassfish), mit davor "geblendeten" Apache, usw.

Anbei eine kleine Anleitung wie ich Hudson eingerichtet habe.
Da ich den aktuellen Entwicklungssnapshot auf der gleichen VM betreibe wie Hudson, habe ich mich für die Standalone Variante von Hudson entschieden. Ich hoffe hierdurch Seiteneffekte zwischen dem Entwicklungsserver (hier JBoss) und Hudson zu reduzieren. Im gemeinsamen Betrieb unter JBoss erhielt ich bspw. PermGen Fehlermeldungen/ OutOfMemory Exceptions die ich so nicht sauber zuordnen konnte (zu ressourcenintensive Hudson Aktivitäten oder ein Speicherleak in der Applikation?).

Vorgaben

OS debian etch
JAVA ist installiert und im PATH

Eigenen User anlegen

i041:~# mkdir /data
i041:~# mkdir /data/hudson
i041:~# addgroup Hudson
Adding group `hudson' (GID 1002) ...
Done.
i041:~# useradd -g 1002 -d /data/hudson -s /bin/bash hudson
i041:~# chown hudson:hudson /data/hudson/

Bei der Useranlage (useradd) muss darauf geachtet werden, dass die GID (Option –g) der GID bei der Gruppenanlage entspricht.
Ggf. jetzt noch ein paar Einstellungen aus dem Lieblingsprofile kopieren, damit die Arbeit dann "schöner" von statten geht.

i041:~# cp /data/jboss/.bashrc /data/hudson/.
i041:~# chown -R hudson:hudson /data/hudson

Die folgenden Tätigkeiten sollten als Hudson User vorgenommen werden

i041:~# su hudson
hudson@i041:/root$ cd
hudson@i041:~$

Hudson Verzeichnis und Startskripte einrichten

hudson@i041:~$ mkdir bin
hudson@i041:~$ mkdir lib
hudson@i041:~$ mkdir log
hudson@i041:~$ cp /tmp/hudson.war lib/

Skript run.sh anlagen

hudson@i041:~/bin$ nano run.sh

#########################
CURRENT_USER=`whoami`
if [ $CURRENT_USER != "hudson" ]; then
echo "Run only valid as hudson user!... (not as $CURRENT_USER)"
exit -1;
fi

export HUDSON_HOME=/data/Hudson
export PATH="/usr/share/apache-ant/latest/bin:$PATH"
export FINDBUGS_HOME=$HUDSON_HOME/externals/findbugs-1.3.8

HTTP_PORT=8280
AJP13_PORT=8209
CONTROL_PORT=-1
LOGFILE=$HUDSON_HOME/log/hudson.log

nohup java -jar $HUDSON_HOME/lib/hudson.war \
--httpPort=$HTTP_PORT \
--ajp13Port=$AJP13_PORT \
--controlPort=$CONTROL_PORT \
> $LOGFILE 2>&1&

#########################

Skript shutdown.sh anlagen

hudson@i041:~/bin$ nano run.sh

#########################
#stop per kill. see http://winstone.sourceforge.net/#security

CURRENT_USER=`whoami`
if [ $CURRENT_USER != "hudson" ]; then
echo "Shutdown only valid as hudson user!... (not as $CURRENT_USER)"
exit -1;
fi

pid=`ps x | grep java | grep hudson | awk '{print $1}'`; kill $pid



#########################

hudson@i041:~/bin$ chmod u+x run.sh
hudson@i041:~/bin$ chmod u+x shutdown.sh

Der Hudson Server kann nun mit run.sh gestartet und mit shutdown.sh gestoppt werden. Damit sich der Hudson Server nicht mit dem JBoss auf der gleichen VM ins Gehege kommt sind die Webports entsprechend abgeändert (siehe Variable HTTP_PORT und AJP13_PORT).
Im Verzeichnis $HUDSON_HOME/log ist das log File enthalten. Falls der Server nicht hochfährt (bspw. ist eventuell der Webport bereits belegt) sollte dieses File auf alle Fälle geprüft werden!

[TODO: FindBugs Integration]

Mittwoch, 25. März 2009

JPA, Sequenzen (non-Id) und Oracle

Eine Sequenz per JPA abzufragen ist bekanntermaßen nicht so einfach – JPA 1.0 unterstützt nur Sequenzen bei der Genierung von IDs (@Id, @GeneratedValue). Eine Möglichkeit einen eindeutigen Wert trotzdem aus einer Sequenz zu bekommen ist natürlich per JPA Native Query (siehe bspw. http://stackoverflow.com/questions/277630/hibernate-jpa-sequence-non-id )

Nach kurzem googlen bekommt hier jede Menge Treffer die etwa wie folgt aussehen (für eine Oracle Sequenz):

Query query = em.createNativeQuery("select seq.nextval from dual ");
long nextNumber = new Long(((Number) query.getSingleResult()).longValue());

Leider funktioniert dies nicht (zumindest bei mir… L )

Es kommt hier immer die Fehlermeldung: ORA-00287: Sequence-Nummer hier nicht zulässig

Der Grund hierfür liegt daran, dass Hibernate (JPA unter JBoss), aus dem getSingleResult folgendes Statement baut:

select * from ( select profilenumber.nextval from dual ) where rownum <= 1 

Und dies ist leider kein korrektes SQL Statement, da hier eine Sequenz in der WHERE Klausel vorkommt…

Mit folgendem Java Code erhält man das gewünschte Ergebnis:

Query query = em.createNativeQuery("select seq.nextval from dual ");
long nextNumber = Long.valueOf(query.getResultList().get(0).toString());

Hier wird die überflüssige Select-Klammer um die eigentliche Sequenzabfrage in SQL weggelassen.


 

Donnerstag, 19. März 2009

Struts2 - Unchecked Exceptions loggen

Oft arbeitet man bei technischen Problemen oder Situation, die im Programmflow nicht vorkommen sollten, mit RuntimeExceptions (unchecked Exceptions). Ein paar Allgemeine Informationen rundum das Thema sind in folgenden Artikel schön dargestellt: http://www.roseindia.net/java/exceptions/exception.shtml.

Unchecked Exceptions müssen somit auch in der Struts Action nicht abgefangen werden. Das Struts Framework verarbeitet in diesem Fall die Exception.

Um in Struts ganz allgemein Exceptions abzufangen kann man in der struts.xml ein global exception mapping definieren. Dabei kann dann noch ein globaler result Typ eingestellt werden:

<global-results>
<result name="error">/WEB-INF/web/jsp/error.jsp</result>
</global-results>

<global-exception-mappings>
<exception-mapping exception="java.lang.Exception" result="error"/>
</global-exception-mappings>

Die error.jsp kann dann die Fehlermeldung bspw. wie folgt ausgeben:

...
<h1>ERROR</h1>
<s:actionerror/>
<p>
<s:property value="%{exception.message}"/>
</p>
<hr/>
<h3>Technical Details</h3>
<p>
<font style="font-size: 9px; color:gray">
<s:property value="%{exceptionStack}"/>
</font>
</p>
...

Die Exception wird dann allerdings nur im Client (Browser) dargestellt. Wichtig ist es aber natürlich auch, die Fehlermeldung in Logfiles zu dokumentieren um anschließend eine Ursachenforschung zu betreiben.

Um dies zentral vornehmen zu können bietet sich die globale error.jsp an. Mit folgenden Befehlen wird der Stracktrace mit Hilfe von log4j geloggt.

...
<%@page import="org.apache.log4j.Logger"%>
<s:set name="stackTrace" value="%{exceptionStack}" scope="page"/>
<%
String stackTrace = (String) pageContext.getAttribute("stackTrace");
Logger.getLogger(this.getClass()).error(stackTrace);
%>
...

In Variable exceptionStack auf dem ValueStack hat das Struts Framework den Stacktrace bereits reingeschrieben. Der restliche Code dürfte soweit selbsterklärend sein.

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

Freitag, 27. Februar 2009

Eclipse Server-Classpath bei keiner „default“ JBoss Server Configuration

Vielleicht habe ich auch nur irgendetwas in der IDE übersehen, die falschen Begriffe gegoogelt, keine Ahnung: Beim Umstellen der JBoss Server Konfiguration wird (bei mir) der Eclipse Classpath nicht korrekt gesetzt. Im folgenden mein keiner Workaround…

Folgende Eckdaten:

Eclipse Ganymede 3.4.1 (mit WTP)
JBoss 4.2.3 GA

Eigentlich wollte ich nur etwas mit JBoss Cache "herumexperimentieren", dazu mehr in einem anderen Blogeintrag. Allerdings sind die hierzu benötigten Libraries nur in der JBoss all Konfiguration enthalten (JBoss 4.2.3 hat standardmäßig minimal, default und all, wobei default, wie sollte es anders sein, die Standardeinstellung ist).

Schritt 1: Server Configuration | Runtime

Das umswitchen, in welchem Modus der Server gestartet wird, kann man in den Server Properties vornehmen. Hierzu einfach in Eclipse auf den Tab Server, JBoss selektieren und F3 drücken. Im Abschnitt Server Properites kann man neben Address, Port und JNDI Port auch die Server Configuration wählen.
Hier kann dann bspw. all ausgewählt werden (oder auch eine selbstdefinierte Konfiguration).

Schritt 2: Projekt Classpath

Anschließend ist allerdings der Classpath in den Eclipse Projekten (Bspw. Test (ß EAR Projekt), TestEJB, TestWeb) immer noch auf default. Die JBoss Libraries im Classpath verwenden immer noch die ‚alte' default Einstellung ($JBOSS_HOME/server/default/lib). Somit sind auch die weiteren Libraries nicht im Classpath verfügbar, wie etwas die JBoss Cache jar-Files.

Ich habe dies nur über folgenden Eingriff in das WTP JBoss Server Plugin hinbekommen:

Für die verschiedenen JBoss Versionen gibt es jeweils ein eigenes File unter

$ECLIPSE_HOME/org.eclipse.jst.server.generic.jboss_1.5.205.v200805140145/servers/

Bei JBoss 4.2.3 GA also jboss42.serverdef

Der Classpath lässt sich anpassen in dem man einfach in dem File alle /default/ mit /<neue Konfiguration>/ wie bspw /all/ ersetzt (eine Sicherungskopie schadet vor der Änderung sicherlich nicht ;-))

Anschließend Eclipse erneut starten, aber mit der Option –clean (hierzu in der Console in das Verzeichnis $ECLIPSE_HOME/bin wechseln und eclipse –clean aufrufen)


 

#############

Es ist natürlich auch Möglich einen komplett neuen Server anzulegen, so dass man bspw. zwischen den Servern JBoss 4.2.3(default) und JBoss 4.2.3(all) innerhalb der Eclipse Assistenten wählen kann. Habs ausprobiert: funzt. Hierzu muss das File plugin.xml und plugin.properties entsprechend angepasst werden.

Allerdings bin ich persönlich davon Fan pro Projekt nicht nur einen eigenen Workspace zu haben, sondern auch ein eigenes Eclipse. So können die Eclipse Plugins auf das Projekt zugeschnitten sein und man kommt bei neuen Projekten zwangsläufig mal in den Genuss einer neueren Eclipse Version.
Hat man pro Projekt ein eigenes Eclipse, kann man dort dann ja auch "wild" die orginal Konfiguration verändern – wie oben beschrieben ;-)

Sonntag, 25. Januar 2009

Struts2 – OGNLException „target is null for setProperty“ auf Stdout

Struts 2.1.6 GA endlich da…

Allerdings wird (immer noch, wie bereits in der Version 2.1.2) eine OGNL Exception ausgegeben wenn man versucht per OGNL (Object Graph Navigation Language) einen Wert zu setzen, der in einer Objekthierarchie liegt (bei der Verwendung des Interfaces ModelDriven).

JSP:

<s:textarea name="projectHistory.description" rows="10"></s:textarea>

StrutsAction:

public class ProfileAction extends BaseAction implements ModelDriven<ObjectA>, Preparable {
//...
@Override
public ObjectA getModel() {
return this.objectA;
}
}
public class ObjectA {
//setter and getter
private ProjectHistory projectHistory;
}
public class ProjectHistory {
//setter and getter
private String description;
}

Erfreulicherweise funktioniert alles! In diesem Beispiel wird der, auf der Website erfasste Wert, korrekt in das Feld objectAàprojectHistoryàdescription (getModel().getProjectHistory.setDescription(String description)) geschrieben.

Folgender Fehler wird dabei ausgegeben:

ognl.OgnlException: target is null for setProperty(null, "description", [Ljava.lang.String;@1a2f5b1)
    at ognl.OgnlRuntime.setProperty(OgnlRuntime.java:1651)
    at ognl.ASTProperty.setValueBody(ASTProperty.java:101)
...

expr: projectHistory.description val: [Ljava.lang.String;@1a2f5b1 context: ognl.OgnlContext@14d8bcda root:[com.xyz.ProfileAction@1b071c0, com.opensymphony.xwork2.DefaultTextProvider@fbf51d] value: [Ljava.lang.String;@1a2f5b1

und das auf StandartOut… (!)

Der Code hierfür liegt im XWork Projekt.

package com.opensymphony.xwork2.ognl;
...
public class OgnlValueStack implements ... {

public void setValue(String expr, Object value, boolean throwExceptionOnFailure) {
...
} catch (OgnlException e) {
if (throwExceptionOnFailure) {
e.printStackTrace(System.out);
System.out.println("expr: " + expr + " val: " + value + " context: " + context + " root:" + root + " value: " + value);
String msg = "Error setting expression '" + expr + "' with value '" + value + "'";
throw new XWorkException(msg, e);
} else {
...

Den Stacktrace auf System.out auszugeben und anschließend nochmal eine Info Meldung ist meines Erachtens nicht wirklich schön (und erst recht nicht in einer GA Version).

Klar: ist OpenSource, kann man selber korrigieren… werd ich jetzt auch mal angehen in meinem Projekt, da sonst das Logfile ziemlich hässlich ausschaut. Bevor ich hier mal bei der XWork Community nachfrage, will ich aber hier erst noch etwas tiefer in das Thema eintauchen – vielleicht macht's ja doch irgendwie Sinn und ich bin nur noch ganz dahinter gestiegen ;-)

Dienstag, 20. Januar 2009

Subversion: alte Versionen und Subclipse

Habe gerade ein älteres Projekt innerhalb von Eclipse in Bearbeitung… Ein paar Änderungen durchgeführt. Unit Tests angepasst. Seleniumtests ebenfalls angepasst… Allerdings habe ich das Selenium File mit TortoiseSVN innerhalb der SeleniumIDE commited. à Böses Faul, wie sich anschließend rausstellte…

Die EclipseIDE war passen auf das Projektkonfiguriert und auch mit dem Projekt gezippt eingecheckt (sehr schön ;-), konnte man sofort loslegen). TortoiseSVN ist natürlich recht aktuell auf meinem OS.

Als ich anschließend meine Sourcen in Eclipse einchecken wollte kam folgende Fehlermeldung:

svn: This client is too old to work with working copy... please get a newer Subversion client

Danach geht erstmal gar nichts mehr L. Ok: also neues Subclipse installieren.

Help -> Find and Install… -> Search for new features to install

New Remote Site…. -> URL: http://subclipse.tigris.org/update_1.4.x

Alte SVN Remote Site entfernen.

Finish … neu starten… strike!

Das Ganze hat allerdings auch einen Hacken: jetzt müssen alle Entwickler, die die IDE bereits ausgecheckt haben, auch das neue Subclipse installieren… Falls dies nicht gewünscht ist und mit der alten SVN Version weiter gearbeitet werden soll, sollte folgende Helfen (nicht ausprobiert!)

Ganzes Directory mit dem File, das mit dem neuen SVN eingecheckt wurde auf OS Ebene löschen (bei mir das SeleniumTest Verzeichnis). Anschließend mit einem alten SVN (bei mir innerhalb von Eclipse) das Verzeichnis komplett neu updaten/ auschecken.