Beispiel: Stateless Session Bean


Inhalt:

Anlegen der Enterprise Application
Anlegen der GeometricModelBean
Anlegen des WebClient
WebClient ohne Injection
Server einrichten
Anlegen des Application Clients
Ausführen des Application Clients
Application Client ohne Injection
Export des Workspace
Re-Import
Ohne Annotations

Beispiel für eine Stateless Session Bean, auf die per Webclient und mittels Application Client zugegriffen wird.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet man am Ende dieses Dokuments): Stateless.ear

Aufbau des Beispieles


a) Bean-Klasse mit Local und Remote-Interfaces
b) Webclient: JSP-Seite
c) WebClient: Servlet
d) App-Client


Anlegen der Application

Schritt 1: Über "File" -> "New" -> "Other..." gelangen wir in den Wizard zum Hinzufügen von neuen Komponenten. wir wählen im Zweig "Java EE" ein "Enterprise Application Project".
Erzeugen einer Application (Schritt 1)
Schritt 2: In dem erscheinenden Dialog geben wir dem Projekt einen Namen und wählen den JBoss 5.0-Server aus. Die "EAR version" bleibt auf "5.0"
Erzeugen einer Application (Schritt 2)
Auf "Next" klicken.
Schritt 3: Hier könnten wir direkt die einzelnen Modulprojekte zufügen. Deshalb klicken wir auf "New Module" und wählen im erscheinenden Dialog die Module "Application client module", "EJB module" und "Web module".
Erzeugen einer Application (Schritt 3)
Das Ergebnis sehen wir hier:
Erzeugen einer Application (Schritt 4)
Die Checkbox "Create Deployment Descriptor" wird nicht gesetzt.

Man klickt auf "Finish".

Beim ersten Aufruf erscheint die Abfrage ob wir zur "Java EE perspective" wechseln wollen. Diese bejahen wir.
JavaEE perspective

Jetzt sieht man folgende Hierarchie im Project Explorer:
Erzeugte JEE-Hierarchie


Anlegen der GeometricModelBean

Schritt 1: Im Project Explorer den Knoten "StatelessEJB" wählen. Rechtsklick, im Contextmenü den Punkt "New" -> "Session Bean" wählen.
Stateless Session Bean
Schritt 2: Wir vergeben einen Namespace und einen Bean-Namen. Da wir Local- und Remote-Interfaces haben wollen, setzen wir die entsprechenden Checkboxen.

ACHTUNG: Namenskonvention: Die eigentlichen Implementierung der Bean hat den Zusatz "Bean", das Local Interface hat den Zusatz "Local", das Remote Interface den Zusatz "Remote" (teilweise gibt es auch die Konvention, dem Remote Interface keinerlei Namenszusatz zu geben). WTP kommt mit der Konvention "Bean-Klasse hat Zusatz Bean" nicht so ganz zurecht und packt diesen Zusatz in die Namen der Interfaces. Deshalb wieder entfernen!

Stateless Session Bean
Im nächsten Schritt wird alles auf den Defaults belassen:
Stateless Session Bean

Schritt 3: Vorbereiten der Business-Methoden:
Die beiden Business-Methoden werfen eine eigene Exception. Diese wird dem Projekt so zugefügt: Das Package "de.fhw.swtvertiefung.knauf.stateless" im Folder "StatelessEJB\ejbModule" im Projekt "StatelessEJB" auswählen. Rechtsklick, "New" -> "Class". Eine Klasse namens "InvalidParameterException" zufügen, die von java.lang.Exception abgeleitet ist.
  public class InvalidParameterException extends Exception
  {
    private static final long serialVersionUID = 1L;
    
    public InvalidParameterException(String str_Message)
    {
      super(str_Message);
    }
  } 

Schritt 4: Jetzt legen wir die Business-Methoden "computeCuboidVolume" und "computeCuboidSurface" in der GeometricModelBean an. Die gesamte Klasse sieht so aus:
  public class GeometricModelBean implements GeometricModel, GeometricModelLocal
  {  
    protected static final Logger logger = Logger.getLogger(GeometricModelBean.class.getName());
  
    public GeometricModelBean()
    {
    }
  
    public double computeCuboidVolume(double a, double b, double c) throws InvalidParameterException
    {
      logger.info("computeCuboidVolume with a = " + a + ", b = " + b + ", c = " + c);
      if (a <= 0 || b <= 0 || c <= 0)
        throw new InvalidParameterException("Side length <= 0");
  
      return a * b * c;
    }
  
    public double computeCuboidSurface(double a, double b, double c) throws InvalidParameterException
    {
      logger.info("computeCuboidSurface with a = " + a + ", b = " + b + ", c = " + c);
      if (a <= 0 || b <= 0 || c <= 0)
        throw new InvalidParameterException("Side length <= 0");
  
      return 2 * (a * b + b * c + c * a);
    }
  } 


Schritt 5: Wir fügen das Local- und Remote-Interface der Bean zu. Dazu können wir uns die Refactoring-Möglichkeiten von Eclipse zu Nutze machen. Im Project Explorer die Bean-Klasse wählen, Rechtsklick, "Refactor", "Pull up..." wählen.
Refactor
Zuerst schieben wir die beiden Methoden ins Local Interface, indem wir es auswählen und die zwei Methoden markieren.
Refactor
Im nächsten Schritt gibt es nix zu ändern.
Refactor

Das gleiche wiederholen wir, um die Methoden ins Remote Interface "GeometricModelRemote" zu schieben.


Anlegen des Webclients

Modul-Abhängigkeiten
Zuerst müssen wir eine Referenz auf das EJB-Projekt zufügen, damit wir die Interfaces der Bean verwenden können: Auf "StatelessWeb" einen Rechtsklick ausführen und in die "Properties" gehen. Im Punkt "Java EE Module Dependencies" aktivieren wir die Referenz auf "StatelessEJB.jar".
Java EE Module Dependencies
JSP anlegen
Den Ordner "StatelessWeb" -> "WebContent" auswählen und per Rechtsklick eine JSP zufügen.
Neue JSP (1)
Im ersten Schritt des Assistenten der JSP den Namen "GeometricModelTest.jsp" geben.
Neue JSP (2)
In Schritt 2 verwenden wir das vorgeschlagene Template.
Neue JSP (3)
Jetzt noch auf "Finish" klicken und den JSP-Code einbauen. Die vollständige JSP-Seite sieht so aus:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"
    import="javax.ejb.EJB,de.fhw.komponentenarchitekturen.knauf.stateless.*"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>GeometricModelTest</title>
</head>
<body>
<H1>Test for the GeometricModel stateless session bean</H1>
<%!
  //EJB Interfaces have to be declared in the "page variable" block.
  @EJB()
  GeometricModelRemote geometricModel;

  @EJB(mappedName="Stateless/GeometricModelBean/local")
  GeometricModelLocal geometricModelLocal;
%>

<%
//Ist es der erste Aufruf oder wurde auf "Berechnen" geklickt.
if (request.getParameter("submit") == null)
{
  //Erster Aufruf: Nur Formular ausgeben.
}
else
{
  // Zweiter bis n-ter Aufruf: Berechnen.

  double dblA = Double.parseDouble(request.getParameter("a") );
  double dblB = Double.parseDouble(request.getParameter("b") );
  double dblC = Double.parseDouble(request.getParameter("c") );

  ///////////////////////////////////////////////////////////////
  // Berechnung über Remote-Interface.

  double dblVolume = geometricModel.computeCuboidVolume( dblA, dblB, dblC);
  double dblSurface = geometricModel.computeCuboidSurface( dblA, dblB, dblC);

  %>

  Volume = <%=dblVolume%>, Surface= <%=dblSurface%>

  <br>And now with the Local Interface:
  <%
  dblVolume = geometricModelLocal.computeCuboidVolume( dblA, dblB, dblC);
  dblSurface = geometricModelLocal.computeCuboidSurface( dblA, dblB, dblC);

  %>

  Volume = <%=dblVolume%>, Surface = <%=dblSurface%>
  <br><br><br>
<%
}
%>
<form action="GeometricModelTest.jsp" method="post">
  a = <input type="text" name="a" /> <br>
  b = <input type="text" name="b" /> <br>
  c = <input type="text" name="c" /> <br>

  <input type="submit" name="submit" value="submit" />
</form>

</BODY>
</html> 
ACHTUNG 1: Die Injection in JSP-Seiten ist nicht Teil des JavaEE-Standards, sondern wohl eine JBoss-spezifische Erweiterung.

Zitat aus der JSP-2.1-Spezifikation, Kapitel 7.1.11:
"Finally, note that injection is not supported on JSP pages or tag files. This is because all the information represented by injection annotations needs to be known at deployment time. If an annotation is included in a JSP page or tag file, it won't be seen at deployment time (unless the JSP was being precompiled during deployment)."

ACHTUNG 2: Die Injection des Local Interfaces klappte bei mir nicht, wenn nur die @EJB-Annotation verwendet wurde, das führte zu folgender Exception (aus der Server-Konsole):
21:46:31,421 ERROR [FieldBeanProperty] failed to set value Proxy to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 implementing [interface org.jboss.ejb3.proxy.intf.EjbProxy, interface de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote, interface org.jboss.ejb3.proxy.intf.SessionProxy] on field de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal org.apache.jsp.GeometricModelTest_jsp.geometricModelLocal
java.lang.IllegalArgumentException: Can not set de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal field org.apache.jsp.GeometricModelTest_jsp.geometricModelLocal to $Proxy245
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
	at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(Unknown Source)
	at sun.reflect.UnsafeObjectFieldAccessorImpl.set(Unknown Source)
	at java.lang.reflect.Field.set(Unknown Source)
	at org.jboss.injection.lang.reflect.FieldBeanProperty.set(FieldBeanProperty.java:74)
	at org.jboss.injection.JndiPropertyInjector.inject(JndiPropertyInjector.java:119)
	at org.jboss.web.tomcat.service.TomcatInjectionContainer.processInjectors(TomcatInjectionContainer.java:361)
	at org.jboss.web.tomcat.service.TomcatInjectionContainer.processAnnotations(TomcatInjectionContainer.java:404)
	at org.jboss.web.tomcat.service.TomcatInjectionContainer.newInstance(TomcatInjectionContainer.java:261)
	at org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:145)
	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:324)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:322)
Lösung des ganzen: für das Local Interface muss ein Attribut "mappedName" angegeben werden! Dieser setzt sich zusammen aus "EAR-Name/Bean-Name/local" (siehe Abschnitt "JNDIView").


Servlet anlegen:
Wir gehen auf "StatelessWeb" und erstellen mit Rechtsklick -> "New" -> "Servlet" ein neues Servlet. Package und Name sollten so aussehen:
Neues Servlet (1)
Im nächsten Schritt wird eine URL angegeben unter der das Servlet später angesprochen werden soll. Ich habe den Default "/GeometricModelServlet" entfernt und durch "/servlet/GeometricModelServlet" ersetzt. Diese Angabe finden wir in "web.xml" wieder.
Neues Servlet (2)
In Schritt 3 belassen wir alles auf den Defaults.

Der Code des Servlets sieht so aus:
package de.fhw.komponentenarchitekturen.knauf.stateless;

import java.io.IOException;
import java.io.PrintWriter;

import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Client-Servlet for the GeometricModeBean.
 * It does the same actions as the JSP page.
 *
 */
public class GeometricModelServlet extends HttpServlet
{

  /**Remote interface for Bean @see GeometricModelBean is injected by the server.
   */
  @EJB
  private GeometricModelRemote geometricModel;

  /**Local interface for Bean @see GeometricModelBean is injected by the server.
   */
  @EJB
  private GeometricModelLocal geometricModelLocal;

  /**Serialisierungs-ID. Nur zugefügt da Eclipse sonst meckert.
   */
  private static final long serialVersionUID = 1L;

  /**Konstruktor. Hier passiert nichts.
   * @see javax.servlet.http.HttpServlet#HttpServlet()
   */
  public GeometricModelServlet()
  {
    super();
  }

  /**Verarbeiten einer HTTP-Anfrage per GET-Request.
   * Wird direkt weitergeleitet an doPost.
   * GET-Requests erfolgen nur beim ersten Aufrufen der Seite.
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
   *      response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    this.doPost(request, response);
  }

  /**Process a HTTP-Post-Request.
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
   *      response)
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    // This printwriter is used for outputting the response HTML:
    PrintWriter printWriter = response.getWriter();

    try
    {
      // Page header:
      printWriter.write("<HTML>\r\n" + "<HEAD>\r\n" + "<TITLE>GeometricModelTest</TITLE>\r\n" + 
          " <meta http-equiv=\"content-type\" content=\"text/html; charset=Windows-1252\">\r\n" + 
          "</HEAD>\r\n"+
          "<BODY>\r\n" + 
          "<H1>Test for the GeometricModel stateless session bean</H1>\r\n");

      // Should only the form be shown or is this a submitted form and we have
      // to calculate ?
      if (request.getParameter("submit") == null)
      {
        // First call to this page: just create the form.
      }
      else
      {
        // Page was submitted: do some calculations.

        double dblA = Double.parseDouble(request.getParameter("a"));
        double dblB = Double.parseDouble(request.getParameter("b"));
        double dblC = Double.parseDouble(request.getParameter("c"));

        // /////////////////////////////////////////////////////////////
        // Calculate by using the remote interface:
        
        double dblVolume = this.geometricModel.computeCuboidVolume(dblA, dblB, dblC);
        double dblSurface = this.geometricModel.computeCuboidSurface(dblA, dblB, dblC);

        printWriter.write("Volume = " + dblVolume + " Surface= " + dblSurface + "\r\n"
            + "<br>And now with the Local Interface: <br>\r\n");

        // /////////////////////////////////////////////////////////////
        // Calculate by using the local interface:

        dblVolume = this.geometricModelLocal.computeCuboidVolume(dblA, dblB, dblC);
        dblSurface = this.geometricModelLocal.computeCuboidSurface(dblA, dblB, dblC);

        printWriter.write("Volume = " + dblVolume + ", Surface = " + dblSurface + "\r\n" + "<br><br><br>\r\n");
      }

      // Write the form:
      printWriter.write("<form action=\"./GeometricModelServlet\" method=\"post\"> \r\n" +
          " a = <input type=\"text\" name=\"a\" /> <br> \r\n" +
          " b = <input type=\"text\" name=\"b\" /> <br> \r\n" +
          " c = <input type=\"text\" name=\"c\" /> <br> \r\n" +
          " <input type=\"submit\" name=\"submit\" value=\"submit\" /> \r\n" + 
          "</form> \r\n" + 
          "</BODY> \r\n" +
          "</HTML> \r\n");
    }
    catch (Exception ex)
    {
      // Output in Servlet-Log:
      this.log("An error has occured: " + ex.getMessage(), ex);
      printWriter.write("An error has occured: " + ex.getMessage());
    }

    // Aufräumen:
    printWriter.flush();
    printWriter.close();
  }
}

Es wird dieser Eintrag in "web.xml" erzeugt:
	<servlet>
		<description></description>
		<display-name>GeometricModelServlet</display-name>
		<servlet-name>GeometricModelServlet</servlet-name>
		<servlet-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>GeometricModelServlet</servlet-name>
		<url-pattern>/servlet/GeometricModelServlet</url-pattern>
	</servlet-mapping> 
Im "Project Explorer" sieht das so aus:
Neues Servlet (3)


Web Client ohne Injection

Sofern man aus eigener Kraft einen JNDI-Lookup durchführen möchte, ist folgendes zu ändern:
a) Einen javax.naming.InitialContext über den Default-Konstruktor erzeugen, einen Lookup auf eine in den "Environment Naming Context" (ENC) des JNDI gebundene Referenz auf das Remote bzw. Local Interface durchführen, und anschließend den so geholten Proxy auf das auf dem Server liegende Objekt in ein Objekt umwandeln, mit dem wir arbeiten können. Ein Remote Interface muss über PortableRemoteObject.narrow geholt werden, beim Local Interface reicht ein Typecast.
        InitialContext initialContext = new InitialContext();

        Object objGeometricModel = initialContext.lookup ("java:comp/env/ejb/GeometricModel");

        GeometricModelRemote geometricModel = (GeometricModelRemote) PortableRemoteObject.narrow(objGeometricModel, GeometricModelRemote.class);
        
        ...berechnen über Remote Interface...

        objGeometricModel = initialContext.lookup("java:comp/env/ejb/GeometricModelLocal");

        GeometricModelLocal geometricModelLocal = (GeometricModelLocal) objGeometricModel;

        ...berechnen über Local Interface...

b) in "web.xml" muss eine EJB-Referenz deklariert werden:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<display-name>StatelessWeb</display-name>
	...
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
	</ejb-ref>
	<ejb-local-ref>
		<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<local>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</local>
	</ejb-local-ref>
</web-app>

c) Ein neuer JBoss-spezifischer Deployment-Deskriptor "jboss-web.xml" kommt hinzu:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
    "-//JBoss//DTD Web Application 5.0//EN"
    "http://www.jboss.org/j2ee/dtd/jboss-web_5_0.dtd">
<jboss-web>
	<context-root>StatelessWeb</context-root>

	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<jndi-name>Stateless/GeometricModelBean/remote</jndi-name>
	</ejb-ref>
	<ejb-local-ref>
		<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
		<local-jndi-name>Stateless/GeometricModelBean/local</local-jndi-name>
	</ejb-local-ref>
</jboss-web>

Anmerkung: hier wäre auch ein direkter Lookup auf den globalen JNDI-Namen der Bean möglich gewesen:
Object objRemote = initialContext.lookup("Stateless/GeometricModelBean/remote");
Dies hätte die EJB-Referenzen in "web.xml" und "jboss-web.xml" gespart, wäre aber längst nicht so sauber gewesen.




Server einrichten

Das Webprojekt auswählen, Rechtsklick -> "Run As..." -> "Run on Server" wählen. Im Assistenten wählen wir den JBoss-Server.
Falls wir den Server bereits angelegt haben (dies ist außerdem die Default-Option beim Definieren der Server-Runtime), dann wählen wir hier "Choose an existing server" und wählen den Server aus, ansonsten "Manually define a new server").
Server anlegen (1)
In Schritt 2 belassen wir die Defaults. In Schritt 3 sollte unsere Anwendung bereits ausgewählt sein.
Server anlegen (2)

Wir klicken auf "Finish". Die Enterprise Application wird auf den JBoss deployed und der Server wird gestartet.
Falls das ohne Fehlermeldungen klappt erscheint ein integriertes Browserfenster für die URL
http://localhost:8080/StatelessWeb/.
Diese zeigt allerdings den Fehler "Seite nicht gefunden" an. Dies liegt daran dass unser Projekt keine Index-Seite definiert (mehr dazu im nächsten Beispiel).
Webclient testen
Wir hängen also unsere Seite "GeometricModelTest.jsp" and und können endlich Berechnungen ausführen.
Den neu angelegten Server erreichen wir über die Karteikarte "Servers" im unteren Bereich der Anwendung. Nachdem wir die Anwendung jetzt zum ersten Mal deployed haben können wir sie in Zukunft nach einer Änderung aktualisieren indem wir den Server auswählen und im Contextmenü "Publish" wählen. Kurz nach einem Publish sollte auf der Karteikarte "Console", auf der die JBoss-Ausgabe landet, eine Ausgabe über ein Neu-Laden der Anwendung auftauchen.
Publish
Falls das Bedürfnis besteht, das Logging meiner Beispielanwendung in eine Datei umzuleiten: Eine Anleitung wie das Logging für den JBoss zu konfigurieren ist findet sich hier.

Automatisches Publish abschalten:
Leider hat Eclipse die Default-Einstellung, die Anwendung regelmäßig automatisch auf zu publishen. Das ist unnötig und kostet nur Laufzeit, deshalb wird es abgeschaltet. Im Fenster "Servers" bei gestopptem Server Rechtsklick auf den Server => "Open" wählen.
Open Server
In der Sektion "Publishing" wird "Never publish automatically" aktiviert.
Wichtig: Anschließend die Einstellungen speichern!
Never publish automatically

Troubleshooting:
Eventuell kommt beim Serverstart folgende Fehlermeldung:
Timeout
Lösung: In den Server-Eigenschaften den Timeout auf einen höheren Wert setzen.
Timeout


Anlegen des Application Clients

Im Project Explorer "StatelessClient" auswählen. Rechtsklick, und dann "New" -> "Class" wählen.
Application Client, Schritt 1
Wie im Webclient muss hier eine Abhängigkeit zum EJB-Projekt definiert werden (Rechtsklick auf "StatelessClient", "Properties" wählen). Man wählt unter "Java EE Module Dependencies" das Bean-JAR aus.
Application Client, Schritt 2

Der Client bekommt die EJB per Injection geschenkt, d.h. der Code ist minimal einfach:

Im folgenden der gesamte Code des Clients:
  public class GeometricModelApplicationClient
  {
    @EJB()
    public static GeometricModelRemote geometricModel;
  
    public static void main(String[] args)
    {
      try
      {
        double dblVolume = geometricModel.computeCuboidVolume(10, 5, 7);
        double dblSurface = geometricModel.computeCuboidSurface(10, 5, 7);
  
        System.out.println("Calculated volume: " + dblVolume + ", surface: " + dblSurface);
      }
      catch (Exception ex)
      {
        ex.printStackTrace();
      }
    }
  } 

Die Datei "application-client.xml" kann auf dem Default bleiben:
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
	<display-name>StatelessClient</display-name>
</application-client>

Für den JBoss ist ein Deployment-Deskriptor namens "jboss-client.xml" nötig. Der hier spezifizierte "jndi-name" (des Clients) wird weiter unten noch sehr wichtig:
	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE jboss-client PUBLIC
		"-//JBoss//DTD Application Client 5.0//EN"
		"http://www.jboss.org/j2ee/dtd/jboss-client_5_0.dtd">
	<jboss-client>
		<jndi-name>StatelessClient</jndi-name>
	</jboss-client> 

Jetzt noch die Klasse mit der "main"-Methode in "StatelessClient\appClientModule\META-INF\MANIFEST.MF" eintragen:
Manifest-Version: 1.0
Class-Path: StatelessEJB.jar
Main-Class: Main-Class: de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient
Die von Eclipse geniererte Dummy-Klasse "Main.java" (für die uns diverse JavaDoc-Warnungen ausgespuckt werden) können wir übrigens gefahrlos löschen.


Ausführen des Application Clients

Da ich hier Injection benutze, wird es richtig gemein kompliziert :-(
Die Quelle des Folgenden ist übrigens diese Seite:
http://www.jboss.org/community/docs/DOC-12835

Manueller Start:
Sobald die komplette Enterprise Application auf den Server deployed wurde können wir die JAR-Datei des Application-Clients mit einem simplen Packprogramm aus "Stateless.ear" klauen. Alternativ dazu finden wir sie im Verzeichnis ".metadata\.plugins\org.eclipse.wst.server.core\tmp0\Stateless" unseres Workspaces (dieses Verzeichnis ist erst vorhanden nachdem wir die Anwendung das erste Mal auf den Server deployed haben).

So sieht die Kommandozeile zum Start aus (verwendet einige Umgebungsvariablen, damit der eigentliche Befehl übersichtlicher ist, im eigentlichen java-Aufruf dürfen keine Zeilenumbrüche sein!) :
set JBOSS_HOME=c:\temp\jboss-5.0.1.GA
set JAVA_HOME=C:\Program Files (x86)\Java\jdk1.6.0_12
set PATH=%JAVA_HOME%\bin;%PATH%

"%JAVA_HOME%\bin\java" -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099 -classpath .;"%JBOSS_HOME%\client\jbossall-client.jar;%JBOSS_HOME%\client\jboss-metadata.jar;%JBOSS_HOME%\client\log4j.jar;%JBOSS_HOME%\lib\jboss-classloader.jar;%JBOSS_HOME%\lib\jboss-classloading-spi.jar;%JBOSS_HOME%\lib\jboss-classloading-vfs.jar;%JBOSS_HOME%\lib\jboss-classloading.jar;%JBOSS_HOME%\lib\jboss-dependency.jar;%JBOSS_HOME%\lib\jboss-reflect.jar;%JBOSS_HOME%\lib\jboss-kernel.jar;%JBOSS_HOME%\lib\jboss-xml-binding.jar;%JBOSS_HOME%\lib\jboss-vfs.jar;%JBOSS_HOME%\server\default\lib\jboss-ejb3-core.jar" org.jboss.client.AppClientMain -jbossclient de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient -launchers org.jboss.ejb3.client.ClientLauncher -j2ee.clientName StatelessClient


Zur Erklärung:
Anmerkung 1:
Obiger Classpath in der Kommandozeile wird deutlich einfacher, wenn man Wildcards benutzt:

...
"%JAVA_HOME%\bin\java" -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099 -classpath .;"%JBOSS_HOME%\client\*;%JBOSS_HOME%\lib\*;%JBOSS_HOME%\server\default\lib\jboss-ejb3-core.jar" org.jboss.client.AppClientMain -jbossclient de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient -launchers org.jboss.ejb3.client.ClientLauncher -j2ee.clientName StatelessClient


Hier ist nur eine einzige JAR-Datei aus der Serverkonfiguration explizit referenziert, der Rest geht über Wildcards.

Anmerkung 2:
Für JBoss 6 ist folgender Aufruf nötig:

...
"%JAVA_HOME%\bin\java" -Dlog4j.configuration=log4.properties -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099 -classpath .;"%JBOSS_HOME%\client\*;%JBOSS_HOME%\lib\*;%JBOSS_HOME%\common\lib\jboss-ejb3-vfs-spi.jar;" org.jboss.client.AppClientMain -jbossclient de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient -launchers org.jboss.ejb3.client.ClientLauncher -j2ee.clientName StatelessClient


Im Vergleich zu JBoss 5 ist hier "common\lib\jboss-ejb3-vfs-spi.jar" nötig.


Ausführen aus Eclipse heraus:
Ein simples Ausführen ist leider nicht möglich, da wir sehr viel einzustellen haben. Deshalb gehen wir im Menü "Run" auf "Run Configurations...", dort in der Rubrik "Java Application" klicken wir auf "New launch configuration" (Toolbar-Button links).
Wir geben der Konfiguration einen Namen (z.B. "GeometricModelApplicationClient"), und wählen als Project unseren "StatelessClient" aus.
Die "Main Class" setzen wir auf org.jboss.client.AppClientMain
Application Client starten (1)
Auf dem Karteireiter "Arguments" wird folgendes geändert:
Die "Program arguments" lauten:
-jbossclient de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient -launchers org.jboss.ejb3.client.ClientLauncher -j2ee.clientName StatelessClient


Die "VM Arguments" enthalten die JNDI-Initialisierung:
-Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099
Application Client starten (2)

Nach dem Klick auf "Run" läuft unsere Anwendung los und gibt etwas auf der Konsole aus. Parallel dazu gibt es in der JBoss-Konsole ein paar Ausgaben über die Bean-Methoden-Aufrufe. Leider sieht man immer nur dasjenige der beiden Konsolenfenster, in das die letzte Ausgabe erfolgte. Zwischen diesen Fenstern können wir umschalten über einen Button in der Fenster-Toolbar:
Application Client-Ausgabe

Troubleshooting
Falls die Anwendung ohne jegliche Meldung scheinbar erfolgreich beendet wird, empfiehlt es sich, die Debugging-Ausgabe des Client-Launcers über Log4j zu aktivieren. Am einfachsten geht das, indem im StatelessClient-Projekt, Unterverzeichnis "appClientModule" eine Datei "log4j.properties" angelegt wird mit diesem Inhalt:
log4j.rootLogger=DEBUG, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%t][%c] - <%m>%n
Jetzt wird sehr viel auf der Konsole ausgegeben, u.a. auch die JNDI-Bindings für unseren Client:
2008-10-21 21:17:16,171 INFO [main][org.jboss.ejb3.client.ClientContainer] - <Client ENC(StatelessClient):
 +- UserTransaction[link -> UserTransaction] (class: javax.naming.LinkRef)
 +- metaData (class: org.jboss.metadata.client.jboss.JBossClientMetaData)
 +- env (class: org.jnp.interfaces.NamingContext)
 |   +- geometricModel[link -> Stateless/GeometricModelBean/remote-de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote] (class: javax.naming.LinkRef)
 +- classPathEntries (class: java.util.ArrayList)
>
Falls das nur zu zwei Zeilen mit Log4j-Fehlermeldungen führt, sollte die Log4j-Debuggingausgabe eingeschaltet werden. Dazu ein "VM Argument" -Dlog4j.debug zufügen.

Application Client ohne Injection

Sofern man aus eigener Kraft einen JNDI-Lookup durchführen möchte, ist folgendes zu ändern:
a) Mittels eines java.util.Properties-Objekts die JNDI-Anbindung selbst initialisieren. Einen javax.naming.InitialContext mit diesen Properties erzeugen, einen Lookup auf eine in den "Environment Naming Context" (ENC) des JNDI gebundene Referenz auf das Remote Interface durchführen, und anschließend den so geholten Proxy auf das auf dem Server liegende Objekt in ein Objekt umwandeln, mit dem wir arbeiten können.
        Properties props = new Properties();
        props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
        props.setProperty(Context.URL_PKG_PREFIXES, "org.jboss.naming.client");
        props.setProperty(Context.PROVIDER_URL, "jnp://localhost:1099");
        props.setProperty("j2ee.clientName", "StatelessClient");
        
        InitialContext initialContext = new InitialContext(props);
        Object objRemote = initialContext.lookup("java:comp/env/ejb/GeometricModel");
        GeometricModelRemote geometricModel = (GeometricModelRemote) PortableRemoteObject.narrow(objRemote, GeometricModelRemote.class);

b) in "application-client.xml" muss eine EJB-Referenz deklariert werden:
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
	<display-name>StatelessClient</display-name>
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
	</ejb-ref>
</application-client>

c) "jboss-client.xml" muss so aussehen:
	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE jboss-client PUBLIC
		"-//JBoss//DTD Application Client 5.0//EN"
		"http://www.jboss.org/j2ee/dtd/jboss-client_5_0.dtd">
	<jboss-client>
		<jndi-name>StatelessClient</jndi-name>
		<ejb-ref>
			<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
			<jndi-name>Stateless/GeometricModelBean/remote</jndi-name>
		</ejb-ref>
	</jboss-client>
Jetzt kann man den Client sogar direkt aus Eclipse per Rechtsklick "Run As" => "Java Application" starten.

Anmerkung: hier wäre auch ein direkter Lookup auf den globalen JNDI-Namen der Bean möglich gewesen:
Object objRemote = initialContext.lookup("Stateless/GeometricModelBean/remote");
Dies hätte die EJB-Referenzen in "application-client.xml" und "jboss-client.xml" gespart, wäre aber längst nicht so sauber gewesen.


Export des Workspace

Um den kompletten Workspace so zu exportieren, dass er auf einem anderen Rechner wieder importiert werden kann, im Menü "File" den Punkt "Export..." wählen. Als "export destination" wählen wir "EAR file".
Export, Schritt 1
Im nächsten Schritt die zu exportierende EAR-Anwendung "Stateless" auswählen und eine Zieldatei für den Export angeben. Ich würde empfehlen diese Datei genauso zu benennen wie die EAR-Anwendung da der Dateiname beim Import als Vorschlag für die neu zu erstellen EAR-Anwendung verwendet wird.
Wichtig: den Haken bei "Export source files" setzen !
Export, Schritt 2
Fertig !


Re-Import:

Falls wir das zu importierende Projekt bereits im Workspace haben dann sollten wir es vorher löschen.
Im Menü "File" ruft man "Import..." auf. Man wählt wiederum die Quelle "EAR file".
Import, Schritt 1
Im nächsten Schritt wählt man die Quelldatei (unsere eben exportierte EAR-Datei), den Namen der Ziel-EAR-Anwendung ("Stateless"), und den eben angelegten Target-Server.
Import, Schritt 2
Unsere Anwendung verfügt über keine Hilfsprojekte, wir können den nächsten Schritt also ignorieren.
Import, Schritt 3
Im letzten Schritt wählen wir die gewünschten Module aus.
Import, Schritt 4


Ohne Annotations

Alle Annotations des JavaEE5-Standards lassen sich komplett durch Deployment-Deskriptoren ersetzen.

Die Deployment-Deskriptoren gemäß JavaEE-Standard (also ohne JBoss-spezifisches) kann man sich durch Eclipse generieren lassen.
Schritt 1: Enterprise Application
Beim Erzeugen der Enterprise Application müssen wir den Haken "Generate Deployment Descriptor" setzen. Achtung: trotz Setzen dieses Hakens haben EJB- und ApplicationClient-Modul keinen Deployment Deskriptor, hier ist die Web Tools Platform unvollständig.
New EAR application project
Schritt 2: Deployment-Deskriptoren
Jetzt fügen wir die Standard-Deskriptoren für EJB-Projekt und ApplicationClient zu. Dazu Rechtsklick auf das EJB-Projekt, "Java EE Tools" => "Generate Deployment Descriptor Stub" aufrufen. Das erzeugt eine Datei "ejb-jar.xml".
Analog für den Application Client.
Generate Deployment Descriptor Stub

Deployment-Deskriptoren im EJB-Projekt
Damit das Beispiel funktioniert, muss "ejb-jar.xml" im EJB-Projekt so geändert werden:
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar id="ejb-jar_ID" version="3.0"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
	<display-name>StatelessEJB</display-name>
	<enterprise-beans>
		<session>
			<description><![CDATA[This is the Stateless SessionBean "GeometricModel",
				which might be used for simple geometric calculations.]]>
			</description>
			<display-name>GeometricModelBean</display-name>
			<ejb-name>GeometricModelBean</ejb-name>
			<business-local>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</business-local>
			<business-remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</business-remote>
			<ejb-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelBean</ejb-class>
			<session-type>Stateless</session-type>
		</session>
	</enterprise-beans>
</ejb-jar> 
Pflichtangaben gibt es eigentlich keine mehr, da alles durch Annotations gesteuert werden kann. Wird ein Element "session" eingefügt dann muss allerdings zumindest ein "ejb-name" angegeben werden.

Schieben wir das Projekt auf den JBoss scheint dieser intern die Annotations an die Klassen zu basteln (in "server\default\log\server.log" zu erkennen):
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation javax.ejb.Stateless to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.StatelessImpl@7fd33a
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) Adding @Local interface de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal as specified in metadata
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) Adding @Remote interface de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote as specified in metadata
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation javax.ejb.Local to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.LocalImpl@4b862
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation javax.ejb.Remote to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.RemoteImpl@1e52bfd
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.SecurityDomain to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 SecurityDomainImpl[value=, unauthenticatedPrincipal=anonymous]
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.LocalBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.LocalBindingImpl@ab190c
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.LocalHomeBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.LocalHomeBindingImpl@f97188
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.RemoteHomeBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 [RemoteHomeBindingImpl:, jndi=Stateless/GeometricModelBean/home]
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.RemoteBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 [RemoteBindingImpl:, jndi=Stateless/GeometricModelBean/remote, stack=, bindUrl=, proxyFactory=RemoteProxyFactory, constructionPoint=org.jboss.ejb3.Ejb3DescriptorHandler.addRemoteJndiAnnotations(Ejb3DescriptorHandler.java:1783)]
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(12)-127.0.0.1) Adding org.jboss.ejb3.annotation.RemoteBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3: [RemoteBindingImpl:, jndi=Stateless/GeometricModelBean/remote, stack=, bindUrl=, proxyFactory=RemoteProxyFactory, constructionPoint=org.jboss.ejb3.Ejb3DescriptorHandler.addRemoteJndiAnnotations(Ejb3DescriptorHandler.java:1783)]
2008-10-17 21:46:41,000 DEBUG [org.jboss.ejb3.Ejb3AnnotationHandler] (RMI TCP Connection(12)-127.0.0.1) found EJB3: ejbName=GeometricModelBean, class=de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelBean, type=STATELESS

Durch Angabe des "ejb-name" können wir steuern unter welchem Namen JBoss die Bean im Default ins JNDI hängt ("Stateless/GeometricModelBean/local" und "Stateless/GeometricModelBean/remote", fett markiert ist der "ejb-name"). Der Name der Bean-Klasse, also "GeometricModelBean", ist gleichzeitig der Default wenn kein EJB-Name angegeben ist.

Wollen wir mehr Kontrolle über den globalen JNDI-Namen dann können wir eine Datei "jboss.xml" ins EJB-Projekt hängen.
Beispiel:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC
	"-//JBoss//DTD JBOSS 5.0//EN"
	"http://www.jboss.org/j2ee/dtd/jboss_5_0.dtd">
<jboss>
	<enterprise-beans>
		<session>
			<ejb-name>GeometricModelBean</ejb-name>
			<jndi-name>Keks</jndi-name>
			<local-jndi-name>KeksLocal</local-jndi-name>
		</session>
	</enterprise-beans>
</jboss> 
Das führt zu dieser Ausgabe in "server\default\log\server.log":

2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation javax.ejb.Stateless to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.StatelessImpl@2b25b8
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) Adding @Local interface de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal as specified in metadata
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) Adding @Remote interface de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote as specified in metadata
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation javax.ejb.Local to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.LocalImpl@f214f6
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation javax.ejb.Remote to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.RemoteImpl@13e3faf
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.SecurityDomain to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 SecurityDomainImpl[value=, unauthenticatedPrincipal=anonymous]
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.LocalBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.LocalBindingImpl@1c70928
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.LocalHomeBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 org.jboss.ejb3.annotation.impl.LocalHomeBindingImpl@2a533e
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.RemoteHomeBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 [RemoteHomeBindingImpl:, jndi=Stateless/GeometricModelBean/home]
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) adding class annotation org.jboss.ejb3.annotation.RemoteBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 [RemoteBindingImpl:, jndi=Keks, stack=, bindUrl=, proxyFactory=RemoteProxyFactory, constructionPoint=org.jboss.ejb3.Ejb3DescriptorHandler.addRemoteJndiAnnotations(Ejb3DescriptorHandler.java:1783)]
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] (RMI TCP Connection(9)-127.0.0.1) Adding org.jboss.ejb3.annotation.RemoteBinding to jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3: [RemoteBindingImpl:, jndi=Keks, stack=, bindUrl=, proxyFactory=RemoteProxyFactory, constructionPoint=org.jboss.ejb3.Ejb3DescriptorHandler.addRemoteJndiAnnotations(Ejb3DescriptorHandler.java:1783)]
2008-10-17 21:41:09,609 DEBUG [org.jboss.ejb3.Ejb3AnnotationHandler] (RMI TCP Connection(9)-127.0.0.1) found EJB3: ejbName=GeometricModelBean, class=de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelBean, type=STATELESS
Es werden also noch zusätzliche Annotations angelegt. Der Meldung kann man übrigens entnehmen dass diese JNDI-Bindungen (die über keine Standard-JavaEE-Annotation realisierbar sind) durch eine JBoss-spezifische Annotation org.jboss.annotation.ejb.RemoteBinding realisierbar wären ! Damit würde unsere Bean allerdings endgültig die Containerunabhängigkeit verlassen, da der Code selbst ohne eine Chance auf Änderung an den JBoss getackert wäre.

Wichtig ist dass wir jetzt die EJB-Referenzen im Web- und ApplicationClient-Projekt anpassen, denn die Bean gibt es unter dem dort deklarierten Namen nicht mehr.

Deployment-Deskriptoren im Web-Projekt
Im Servlet können EJBs auch in der Annotation-freien Variante durch Injection gesetzt werden. Für JSPs ist dies leider nicht unterstützt (siehe Einschub im Annotation-Beispiel: kein Bestandteil des offiziellen Standards). Deshalb muss auf der JSP ein JNDI-Lookup durchgeführt werden.

"web.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<display-name>StatelessWeb</display-name>
	<servlet>
		<description>
		</description>
		<display-name>GeometricModelServlet</display-name>
		<servlet-name>GeometricModelServlet</servlet-name>
		<servlet-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>GeometricModelServlet</servlet-name>
		<url-pattern>/servlet/GeometricModelServlet</url-pattern>
	</servlet-mapping>
	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
		<welcome-file>index.htm</welcome-file>
		<welcome-file>index.jsp</welcome-file>
		<welcome-file>default.html</welcome-file>
		<welcome-file>default.htm</welcome-file>
		<welcome-file>default.jsp</welcome-file>
	</welcome-file-list>
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
		<injection-target>
			<injection-target-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</injection-target-class>
			<injection-target-name>geometricModel</injection-target-name>
		</injection-target>
	</ejb-ref>
	<ejb-local-ref>
		<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<local>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</local>
		<injection-target>
			<injection-target-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelServlet</injection-target-class>
			<injection-target-name>geometricModelLocal</injection-target-name>
		</injection-target>
	</ejb-local-ref>
</web-app>
Der JBoss-spezifische Deskriptor heißt hier "jboss-web.xml". Er ist nötig, um die EJB-Referenzen an echte Beans zu binden und sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
    "-//JBoss//DTD Web Application 5.0//EN"
    "http://www.jboss.org/j2ee/dtd/jboss-web_5_0.dtd">
<jboss-web>
	<context-root>StatelessWeb</context-root>

	<!-- EJB References -->
	<!-- Ohne diesen Eintrag ergibt das Deploy der Anwendung einen Server-Fehler:
		"org.jboss.deployment.DeploymentException: Error during deploy; - nested throwable: (javax.naming.NamingException: ejb-ref: ejb/GeometricModel, no ejb-link in web.xml and no jndi-name in jboss-web.xml)"-->
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<jndi-name>Keks</jndi-name>
	</ejb-ref>

	<!-- EJB Local References -->
	<ejb-local-ref>
		<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
		<local-jndi-name>KeksLocal</local-jndi-name>
	</ejb-local-ref>
</jboss-web>

Deployment-Deskriptoren im Application-Client-Projekt
Der Standard-Deskriptor "application-client.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
	<display-name>StatelessClient</display-name>
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<remote>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</remote>
		<injection-target>
			<injection-target-class>de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient</injection-target-class>
			<injection-target-name>geometricModel</injection-target-name>
		</injection-target>
	</ejb-ref>
</application-client>

Der JBoss-spezifische Deskriptor "jboss-client.xml" aus dem Application-Client-Projekt:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC
	"-//JBoss//DTD Application Client 5.0//EN"
	"http://www.jboss.org/j2ee/dtd/jboss-client_5_0.dtd">
<jboss-client>
	<jndi-name>StatelessClient</jndi-name>
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<jndi-name>Keks</jndi-name>
	</ejb-ref>
</jboss-client> 

Die JNDI-View liefert diese Ausgabe:
JNDI-View


Die modifizierte Version des Projekts (einschließlich des geänderten JNDI-Namens "Keks") gibt es hier:
StatelessNoAnnotation.ear.
Beim Import sollte der "EAR name" von "StatelessNoAnnotation" auf "Stateless" geändert werden.

ACHTUNG: Dieses Projekt kann nicht neben dem obigen Stateless-Beispiel existieren !
Auf Bean-Ebene wäre dies zwar problemlos möglich da die gleiche Bean-Klasse an unterschiedliche JNDI-Namen gebunden wird. Allerdings wird zweimal der gleiche Environment Naming Context für die Application Clients erzeugt, und das gibt beim Deploy eine Fehlermeldung.



Stand 22.03.2011
Historie:
17.10.2008: Erstellt aus Vorsemester-Version, angepaßt an JBoss 5.0 und Eclipse 3.4
21.10.2008: Jetzt mit EJB-Injection im Application Client
22.10.2008: "Publish automatically" abgeschaltet, Troubleshooting Server-Start-Timeout, Beispiel "No Annotations" enthielt falsch benannten Deployment-Descriptor.
26.10.2008: Beispiel "No annotations" ist jetzt korrekt, Abschnitt "WebClient ohne Injection" zugefügt.
30.10.2008: Falsche "jboss-web.xml" im Abschnitt "WebClient ohne Injection" korrigiert.
05.11.2008: Beispiel "No annotation": falsche Injection-Annotations aus Servlet gelöscht.
04.03.2009: Injection-Aufruf: doppelte JAR entfernt und Leerzeichen aus Zeile entfernt, JBoss 5.0.1GA und Java 1.6.0_12
08.03.2009: Client-Start mit Injection: VM-Argument "Context.URL_PKG_PREFIXES" auf "org.jboss.naming.client" geändert
11.05.2009: Client-Start mit Injection: VM-Argument "Context.URL_PKG_PREFIXES" entfernt, weil unnötig.
22.03.2011: Client-Start mit Injection: Variante "Classpath mit Wildcards" und Classpath für JBoss 6 zugefügt.