Archiv der Kategorie: glassfish

Embedded Glassfish – und der Fisch schwimmt!°°°

Der folgende Artikel ist Teil meines Buches, das Gerade im Entstehen ist und den Arbeitstitel – „Hands on Software“ trägt.

Embedded Glassfish

Die Verwendung von Eclipse WTP ist mit Sicherheit Geschmackssache. Mir ist das ganze System zu fehlerhaft und zu intransparent. Zu oft schlagen Deployments fehl, WTP deployt kaputte Artefakte in den Applicationserver oder Redeployments sind unvollständig.

Im folgenden wird daher ein anderer Ansatz vorgestellt, den ich bevorzuge. Der Glassfish-Applicationserver bietet die Möglichkeit, auch Embedded, also direkt aus dem Code heraus gestartet zu werden.

Zunächst jedoch bauen wir uns eine sehr einfache Webapplikation. Diese muss nicht mehr beinhalten als ein einfaches Servlet.


@WebServlet(
urlPatterns = { "/" })
public class EffectiveServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// TODO Auto-generated method stub
super.doGet(req, resp);
resp.getWriter().write("Hello!!");
resp.flushBuffer();
}
}

Damit dieses Servlet kompiliert werden kann, muss die Java-EE-API im Classpath vorhanden sein. Naiver weise deklariere ich folgende Dependency in meinem Gradle- bzw. Maven-Buildfile.

 providedCompile "javax:javaee-api:6.0@jar"

oder als Maven-Dependency

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>6.0</version>
    <scope>provided</scope>
</dependency></pre>

Der embedded Glassfish lässt sich sehr einfach starten.

Hierzu erstellt man eine Klasse GlassfishStart.java im Ordner src/test/java.
Es handelt sich hierbei zwar nicht um einen Test, durch die Platzierung in src/test/java kann jedoch die Klasse innerhalb der IDE verwendet werden, ist aber nicht Teil des generierten War-Files.

public class GlassfishStart {

        public static void main(String[] args) throws Exception {
            GlassFishProperties gfProp = new GlassFishProperties();
            gfProp.setPort("http-listener", 9090);

            GlassFish glassfish = GlassFishRuntime.bootstrap().newGlassFish(gfProp);
            glassfish.start();
        }
}

Durch diese 4 Zeilen wird ein kompletter Glassfish-Applicationserver auf Port 9090 aus dem Code heraus gestartet.

Hierfür benötigt man zwingend die Embedded-Glassfish Klassen im Classpath.
Folgende Dependency im gradle-buildfile.

testCompile "org.glassfish.extras:glassfish-embedded-all:3.1.1"

oder als Maven-Dependency


<dependency>
<groupId>org.glassfish.extras</groupId>
<artifactId>glassfish-embedded-all</artifactId>
<version>3.1.1</version>
<scope>test</scope>
</dependency>

Durch den Scope testCompile sind die benötigten Klassen zwar in der IDE verfügbar, nicht jedoch zur Laufzeit im war-File.

Wenn man die Klasse startet ist die Enttäuschung jedoch erstmal groß. Folgende nichtssagende Exception erscheint in den Logs:

Exception in thread "main" java.lang.ClassFormatError:
Absent Code attribute in method that is not native or abstract in class file javax/validation/constraints/Pattern$Flag

Kennen Sie die Lösung bereits?

Die Lösung ist komplexer zu finden als gedacht. Das Problem ist folgende Abhängigkeit im Klassenpfad, die ich bereits zuvor definiert hatte.

 providedCompile "javax:javaee-api:6.0@jar"

Die javax:javaee-api ist die von Oracle bereitgestellte API für die Java EE Spezifikiation. Diese API besteht lediglich aus den als Schnittstellen deklarierten Klassen. Alle Klassen, die als Implementierung deklariert sind wurden entfernt. Dies schließt sowohl abstrakte Klassen als auch konkrete Implementierungen mit ein. Mit Hilfe dieser Abhängigkeit kann der Code zwar kompiliert, nicht jedoch zur Laufzeit ausgeführt werden. Wird dies trotzdem versucht, resultiert dies genau in der obigen Fehlermeldung.

Ist der Fehler gefunden ist die Lösung trivial. Statt der Abhängigkeit auf die von Oracle bereitgestellte API verwendet M. die Glassfish-eigene API. Hierfür ersetze ich die problematische Dependency mit der folgenden.

providedCompile 'org.glassfish:javaee-api:3.1'

oder als Maven Dependency

<dependency>
  <groupId>org.glassfish</groupId>
  <artifactId>javaee-api</artifactId>
  <version>3.1</version>
</dependency>

Beim nächsten Start erhalte ich wenigstens eine andere Fehlermeldung als zuvor.

java.lang.IllegalStateException: Provider already mapped glassfish:jsf:faces-servlet

Dies ist weniger schwierig zu lösen als das letzte Problem. Ein Blick auf den Classpath in Eclipse bringt folgendes zu Tage.

Zusammen mit der zuvor deklarierten Embedded-Glassfish Dependency sind die Glassfish-Klassen doppelt im Classpath vorhanden und werden auch doppelt registriert. Das ist zuviel für den Glassfish und er quittiert den Dienst. Diese Bibliothek wurde durch WTP automatisch in den Classpath eingefügt, lässt sich aber einfach über den Build Path entfernen.

Jetzt endlich lässt sich der Embedded-Glassfish starten.

Was jetzt noch getan werden muss ist das Deployable korrekt zu konfigurieren, da die Sourcen hierfür über das komplette Projekt verteilt sind.
Hierfür bietet die Glassfish-API das ScatteredArchive. Mit einem ScatteredArchive kann ein “virtuelles” War-File zur Laufzeit aufgebaut werden. Der Code hierfür kann so aussehen:

Deployer deployer = glassfish.getDeployer();
ScatteredArchive archive = new ScatteredArchive(appName, ScatteredArchive.Type.WAR);
//kompilierte Klassen hinzufügen.
            archive.addClassPath(new File("build", "classes/main"));
            deployer.deploy(archive.toURI());

Über archive.addClassPath können beliebige Dateien in den Classpath aufgenommen werden und stehen somit zur Laufzeit zur Verfügung.
Die Applikation ist nach dem Start direkt unter http://localhost:9090/effective erreichbar. Der Kontext-Root-Pfad kann als Konstruktor des ScatteredArchive angegeben werden und wurde hier mit dem Parameter appName befüllt.

HowTo – Glassfish 3.1 – Property Files „OnStartup“

Hallo zusammen,

der Glassfish in der Version 3.1 ist verfügbar und ich möchte mir gerne die Mühe machen, mir das Ganze ein wenig genauer anzuschauen. Für diesen Zweck möchte ich in nächster Zeit einige Mini-HowTos hier veröffentlichen, die es mir erlauben, noch effizienter mit dem AS zu arbeiten.

Ein Use-Case der mich immer schon interessiert hat ist, wie kann ich es schaffen, Property-Files zur Laufzeit über JNDI zur Verfügung zu stellen.

Nehmen wir folgenden Use-Case an:

Wir arbeiten mit dem Glassfish-Server, verwenden aber „unter der Haube“ einige Backend-Systeme die über Webservices zur Verfügung gestellt werden.

Was müssen wir mindestens wissen, damit wir mit diesen Systemen arbeiten können?

  1. Wir brauchen die URL des EndPoints
  2. Wir brauchen evtl. Usernamen und Passwort

Üblicherweise kann man für die tägliche Entwicklung unterscheiden zwischen Produktiv-Systemen und Entwicklungssystemen. Wir müssen also zusätzlich eine Möglichkeit bereitstellen, wie wir umgebungsabhängig einstellen können, gegen welche Systeme wir uns verbinden.

Das MIttel der Wahl wenn ich gefragt werde sind die guten alten Property-Files.

Wie aber stelle ich diese möglichst komfortabel zur Verfügung?

Idealerweise wäre es so, dass ich beim Startup des Application Servers alle Property Files in einem Verzeichnis lade und per JNDI zur Verfügung stelle. Ist noch so einfach realisiertbar? DOCH!

Ich habe hierfür mal ein kleines Projekt aufgesetzt, natürlich alles Open Source und frei verwendbar.

Das Ganze ist gehostet bei GitHub.
Auschecken geht so:

git clone git@github.com:dilgerma/Glassfish-Property-Loader-.git

Hier der passende Source Code:

 


public class PropertyLifeCycleListener implements LifecycleListener {

 @Override
 public void handleEvent(LifecycleEvent event)
 throws ServerLifecycleException {

 if (LifecycleEvent.STARTUP_EVENT == event.getEventType()) {
 // Folder in which property files are located, folder must be
 // located under ${glassfish-domain-directory}/config
 File file = new File("properties");

 if (!file.exists()) {
 // file not available
 return;
 }

 if (!file.isDirectory()) {
 // file is not a directory
 return;
 }

 try {

 InitialContext context = new InitialContext();

 for (File property : file.listFiles()) {

 if (property.isFile()
 && property.getName().endsWith("properties")) {
 // found a property file, loading
 Properties props = new Properties();
 props.load(new FileInputStream(property));
 // binding property file in jndi
 // name example is "properties/myProperty.properties"
 context.rebind("properties/" + property.getName(),
 props);
 }
 }
 } catch (Exception ex) {
 // Logging Exception to console.
 ex.printStackTrace();
 }
 }
 }

}

Der Glassfish AS bietet uns die Möglichkeit, für jeden LifeCycle Event (INIT, STARTUP, READY, TIMEOUT, TERMINATION) einen Hook zu implementieren, in dem wir bestimmte Sachen machen können, beispielsweise Property-Files laden beim Startup.

Die Properties müssten hierfür im ${glassfish-home}/domains/domain1 „config“ Verzeichnis liegen, und hier genau im Verzeichnis „properties“.

Bauen lässt sich das Ganze so:

 mvn clean install -Dglassfish.home=/home/dilgerma/development/appserv/glassfish3.1/glassfish

Das Glassfish-Home Verzeichnis muss man angeben, damit wir die appserv-rt.jar bekommen, um Zugriff auf die APIs des Glassfish zu haben.
Anschliessend kopieren wir das Ganze einfach in das lib Verzeichnis des Glassfish:

cp target/propertyloader-0.1-SNAPSHOT.jar /home/dilgerma/development/appserv/glassfish3.1/glassfish/lib

Natürlich müssen wir dem Server noch sagen, was wir ihm da gerade gegeben haben:

Einfach im Navigationsbaum (Admin-Web-Console, localhost:4848 unter LifeCycleModules) ein neues LifeCycleModule anlegen und hier die zuvor erzeugte LifeCycleListener-Klasse angeben.

Jetzt noch den AS einmal durchstarten.

Ab jetzt kann auf jedes PropertyFile im angegeben Verzeichnis (per Default properties) über folgende Annotation in jedem Servlet etc. zugegriffen werden:

@Resource(name="properties/myProperty")
private Properties myProperty;