Donnerstag, 16. Juli 2009

Checkliste JBoss-> Glassfish

Ich bin derzeit damit beschäftigt, meine JavaEE Anwendung soweit zu konfigurieren und zu testen, daß sie unter mehreren, führenden Applicationservern sowie Persistence Providern deployed werden kann. Mein Vorhaben war erfolgreich, stellte sich jedoch als anfangs etwas aufwändig heraus. Ich kann hier keinen vollständigen Bericht abliefern, was alles getan werden muß, um eine in JBoss laufende Applikation in Glassfish zum Deployen zu bekommen. Jede Anwendung ist an sich auch unterschiedlich. Aber eine kleine Checkliste möchte ich niemandem vorenthalten. Meine Anwendung verwendet dabei EJB3, JPA, Hibernate unter JBoss sowie Toplink unter Glassfish, JSF und Facelets.

Die einzige Konfigurationsdatei, an die man beim Deployen in einer anderen Umgebung noch anfassen muß in meinem Fall, ist die persistence.xml. Grund hierfür ist der Persistence Provider. Dieser wird von mir verwendet, um Tabellen automatisch generieren zu lassen.

Hibernate

Create:
<property name="hibernate.hbm2ddl.auto" value="update"/>

Drop & Create:
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>

Toplink

Create:
<property name="toplink.ddl-generation" value="create-tables"/>
<property name="toplink.ddl-generation.output-mode" value="both"/>

Drop & Create:
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>

EclipseLink

Create:
<property name="eclipselink.ddl-generation" value="create-tables"/>

Drop and create
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>

Eine beispielhafte persistence.xml könnte also so aussehen:


<?xml version="1.0" encoding="UTF-8"?>
<!--
(C) 2009 Frederik Mortensen
-->
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="yourPU" transaction-type="JTA">

<!-- GlassFish Configuration //-->
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/yourdb</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="toplink.ddl-generation" value="create-tables"/>
<property name="toplink.ddl-generation.output-mode" value="both"/>
</properties>
<!-- End of GlassFish configuration -->

<!-- JBoss Configuration -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/yourdb</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
<!-- End of JBoss Configuration -->

</persistence-unit>
</persistence>


Desweiteren muß für eine Unabhängigkeit von der Implementation des Application Servers dafür gesorgt werden, daß alle benötigten .JAR-Dateien vorhanden sind. JBoss liefert einige dieser .JARs bereits mit, die beim Deployen in GlassFish leider fehlen.

Auch über das Logging muß man sich Gedanken machen. Wer in JBoss log4J nutzte, wird feststellen, daß GlassFish hier mit dem in Java mitgelieferten Logging einen anderen Weg beschreitet. Mit einer gültigen log4j.xml sowie log4j.dtd im /src - Verzeichnis kann man jedoch weiterhin auf dieses Protokollierungsverfahren bauen. Anbei ein kleines Beispiel, wie dieses Logging integriert werden kann:

Log4j.dtd
Enthalten im Log4J-Bundle (evtl. also bereits auf Ihrer Festplatte):
Log4J v1.2

Log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<!--
(C) 2009: Frederik Mortensen
-->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

<appender name="yourappAppender" class="org.apache.log4j.DailyRollingFileAppender">
<param name="datePattern" value="'.'yyyy-MM-dd" />
<param name="file" value="logs/yourappWeb.log" />
<param name="Append" value="true" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{dd.MM.yyyy HH:mm:ss} %C: %m %n" />
</layout>
</appender>

<root>
<priority value="INFO" />
<appender-ref ref="yourappAppender" />
</root>

</log4j:configuration>


Hat man also dafür gesorgt, daß alle JAR-Dateien (commons-logging, log4j, RichFaces...) nun inkludiert sind und die persistence.xml angepasst wurde, sowie das Logging berücksichtig wurde, gibt es noch zwei größere Baustellen: Queries und Lookups.

Bei den EJBQueries ist darauf zu achten, daß sie wie in den Spezifikationen verwendet werden. Das heißt voll ausgeschrieben wie beispielsweise:
"SELECT alias FROM ENTITY alias WHERE..."
JBoss war da weniger restriktiv, da konnte man auch einmal nur mit der FROM clause beginnen, unter Umständen auch mal mit einem kleingeschriebenen "from" oder ähnlichem. Dies führt jedoch in Glassfish zu Problemen. Passt man seine Queries entsprechend an, laufen sie weiterhin in JBoss, aber nun auch in Glassfish.

Hier noch kurz die Unterschiede zwischen JBoss-Lookups und Glassfish-Lookups (ich arbeite daran, eine Lösung zu finden, die in JBoss und Glassfish einheitlich funktioniert). Ich habe den Lookup in eine Klasse EJBService ausgelagert:


package de.mortensenit.yourproject.web.util;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;

public class EJBService {

private static EJBService singleton;
private static Context ctx;

private EJBService() throws NamingException {

if (ctx == null) {
ctx = new InitialContext();
}
}

public static T lookup(Class ejbClassType, String name) {

if (singleton == null) {
try {
singleton = new EJBService();
} catch (NamingException ne) {
throw new RuntimeException(ne.getLocalizedMessage(), ne);
}
}

try {
InitialContext ctx = new InitialContext();
// JBoss: final Object object = ctx.lookup("yourapp/" + name);
// Glassfish: final Object object = ctx.lookup(ejbClassType.getName()); //Name of the interface
final Object object = ctx.lookup(ejbClassType.getName());

if (ejbClassType.isAssignableFrom(object.getClass())) {
return (T) object;
} else {
throw new RuntimeException("EJBService - Class found: " + object.getClass() + " cannot be assigned to type: " + ejbClassType);
}

} catch (NamingException e) {
throw new RuntimeException("EJBService - Unable to find ejb for " + ejbClassType.getName(), e);
}
}

}


Wer neu bei Glassfish ist, muß vor dem Deployen seiner Anwendung zuerst mal ein paar Dinge beachten. Es muß eine Domain erstellt werden, in der die Anwendung letztlich laufen wird. Es muß der JDBC-Treiber in diese Domain kopiert werden und ein Connection Pool angelegt werden. Wenn man also all die aufgezählten Punkte dieses Posts in einer Checkliste zusammenfassen möchte, kommt man in etwa hierzu:

- Anlegen der Domain (asadmin > create-domain --adminport 4848 yourapp)
- Kopieren von postgres/mysql... jdbc.jar in das yourdomain\lib\ext Verzeichnis
(Falls postgres genutzt wird, JDBC4 verwenden!)
- Erzeugen eines ConectionPools im JSF Admin Dialog des Glassfish AppServers (per Ping testen!)
- Erzeugen einer JDBC Connection, die den ConnectionPool benutzt (jdbc/yourapp)
- Wenn log4j verwendet wird, einbinden von log4j.dtd and log4j.xml (JBoss didnt need
- Ggf. Lookups anpassen
- persistence.xml anpassen auf den aktuellen Persistence Provider
- Falls man @mappedsuperclass verwendet hat, muß diese Annotation gelöscht und durch @Entity ersetzt werden, da @mappedsuperclass in Glassfish leider nicht funktioniert (derzeit).
- EJBQueries ggf. anpassen auf den JavaEE5 Standard, siehe oben
- Falls das Schema der Datenbanktabellen in JBoss durch Hibernate angelegt wurde, muß dieses evtl. bei früherer Verwendung von @mappedsuperclass gedroppt und neu angelegt werden. Allerdings sind die datenbankseitigen Unterschiede zwischen Hibernate und Toplink minimal.

Das wars. Viel Glück und bei Fragen wie immer einfach einen Kommentar hinterlassen ;)

Mehr Informationen zum Thema:
EclipseLink JPA
Logging Patterns

Sonntag, 12. Juli 2009

Dependency Injection in JSF

Neben dem klassischen Lookup gibt es in JavaEE5 die Verwendung von Annotations und der Dependency Injection (kurz DI). Diese DI wird auch in JSF unterstützt. So ist es in JBoss wie auch in Glassfish möglich, eine Session Bean per DI wie folgt injecten zu lassen:


//imports

class MyManagedBean {

@EJB private MySessionBeanLocalIF mySessionBean;

//some class using mySessionBean

}


Das erspart das Tippen des Lookups. Die einzigen Bedingungen sind dabei, daß die Variable weder static noch final sein darf. Nähere Informationen dazu gibt es auch in den JavaEE5 Specs:



Link zur Spezifikation, Abschnitt 5.2.3 Annotations and Injection

Weiterhin ist dabei zu beachten, daß managed Beans NICHT einfach per new-Operator instanziiert werden dürfen in diesem Fall. Hat man beispielsweise in einem Converter eine Methode, die über das managed Bean auf eine Session Bean zugreifen möchte, wird es zu einer NullpointerException kommen. Managed Beans holt man sich in diesem Fall also nicht per Instanziierung, sondern wie folgt:


ELContext elContext = FacesContext.getCurrentInstance().getELContext();
ELResolver elResolver = context.getApplication().getELResolver();
AccountManagementMB accountManagement = (AccountManagementMB) elResolver.getValue(elContext, null, "accountManagement");
return accountManagement.findCountryByName(value);


Dies führt dazu, daß die Dependency Injection berücksichtig wird und im managed Bean (hier beispielsweise AccountManagement) auf dessen Session Bean zugegriffen werden kann.

Sonntag, 5. Juli 2009

Missing dependencies - JBoss 5 Deployment

Ich habe gerade leider viel zu viel Zeit mit einem sehr unscheinbaren Fehler verbracht. Um diesen Ärger und die lange Fehlersuche anderen zu ersparen, poste ich hier einmal die Fehlermeldung und die dazugehörige Lösung, welche simpler nicht hätte sein können (wenn man es weiß):


2009-07-06 00:01:36,500 ERROR [org.jboss.system.server.profileservice.ProfileServiceBootstrap] (main) Failed to load profile: Summary of incomplete deployments (SEE PREVIOUS ERRORS FOR DETAILS):

DEPLOYMENTS MISSING DEPENDENCIES:
Deployment "jboss.j2ee:ear=myProject.ear,jar=myProjectCore.jar,name=MySessionBean,service=EJB3" is missing the following dependencies:
Dependency "&lsaquounknown ear="myProject.ear,jar="myProjectCore.jar,name="MySessionBean,service="EJB3">" (should be in state "Described", but is actually in state "** UNRESOLVED Demands 'persistence.unit:unitName=myProject.ear/myProjectCore.jar#myPersistenceUnitPU' **")
...
...
...
Deployment "persistence.unit:unitName=myProject.ear/myProjectCore.jar#myPersistenceUnitPU" is missing the following dependencies:
Dependency "jboss.jca:name=
java:/mypostgresdb
,service=DataSourceBinding" (should be in state "Create", but is actually in state "** NOT FOUND Depends on 'jboss.jca:name=
java:/mypostgresdb
,service=DataSourceBinding' **")

DEPLOYMENTS IN ERROR:
Deployment "&lsaquounknown ear="myProject.ear,jar="myProjectCore.jar,name="MySessionBean,service="EJB3">" is in error due to the following reason(s): ** UNRESOLVED Demands 'persistence.unit:unitName=myProject.ear/myProjectCore.jar#myPersistenceUnitPU' **
Deployment "jboss.jca:name=
java:/mypostgresdb
,service=DataSourceBinding" is in error due to the following reason(s): ** NOT FOUND Depends on 'jboss.jca:name=
java:/mypostgresdb
,service=DataSourceBinding' **


Des Rätsels Lösung hierbei war der Eintrag in der persistence.xml:


<jta-data-source>java:/mypostgresdb</jta-data-source>

versus

<jta-data-source>
java:/mypostgresdb
</jta-data-source>


Diese Datasource MUSS zwingend in einer Zeile erfolgen, ein Zeilenumbruch innerhalb des jta-data-source-Tags ist nicht möglich.

Freitag, 3. Juli 2009

Webservices mit EJB3 - Teil II

Mittlerweile konnte ich (mit ein Wenig Hilfe, Danke an dieser Stelle ;) ) feststellen, daß das Benutzen von Webservice-Clients in Java6 auch ohne Axis2 möglich ist. Auch den für Eclipse erwähnten Webservice-Client-Wizzard kann man ersetzen, indem man direkt die von diesem Wizzard aufgerufene EXE namens wsimport.exe aufruft, welche in JDK6 enthalten ist. Eine genauere, aber kompakte Beschreibung, wie man vorzugehen hat, findet sich auch hier:

http://www.vogella.de/articles/JavaWebservice/article.html#wsexample_client

Viel Spaß beim Ausprobieren.