Beispiel: Stateless Session Bean


Inhalt:

Anlegen der Enterprise Application
Anlegen der GeometricModelBean
Anlegen des WebClient
Server einrichten
Anlegen des Application Clients
Ausführen des Application Clients
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 "J2EE" 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 4.2.0-Server aus.
Erzeugen einer Application (Schritt 2)
Auf "Next" klicken.
Schritt 3: Hier belassen wir alles auf dem Default.
Erzeugen einer Application (Schritt 3)
Schritt 4: Hier könnten wir direkt die einzelnen Modulprojekte zufügen. Leider klappt das nicht, denn wir benötigen für den JBoss 4.2 zwingend Deployment-Deskriptoren, und durch einen Bug in Eclipse werden diese in den Unterprojekten über diesem Assistenten nicht erzeugt.
Also setzen wir nur den Haken bei "Generate Deployment Descriptor" und beenden den Assistenten.
Erzeugen einer Application (Schritt 4)

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

Wir fügen die einzelnen Module hinzu.
EJB-Projekt
Rechtsklick auf das Projekt "Stateless", und die Option "New" - "EJB Project" wählen.
New EJB Project (1)
Im erscheinden Dialog wird als Name "StatelessEJB" (Name des EAR-Projekts mit Zusatz "EJB" eingegeben). Als Server wird wiederum "JBoss 4.2" gewählt.
Wichtig ist, dass die Option "Add project to an EAR" gesetzt ist und in der Combobox unser eben erzeugtes EAR-Projekt gewählt ist.
New EJB Project (2)
Im nächsten Schritt belassen wir alles bei den Defaults.
New EJB Project (3)
Im letzten Schritt wird die Checkbox "Create an EJB client module..." zurückgesetzt, und dafür die Checkbox "Generate Deployment Descriptor" gesetzt.
New EJB Project (4)

Web-Projekt
Rechtsklick auf das Projekt "Stateless", und die Option "New" - "Dynamic Web Project" wählen.
Im erscheinden Dialog wird als Name "StatelessWeb" (Name des EAR-Projekts mit Zusatz "Web" eingegeben). Als Server wird wiederum "JBoss 4.2" gewählt.
Wichtig ist, dass die Option "Add project to an EAR" gesetzt ist und in der Combobox unser eben erzeugtes EAR-Projekt gewählt ist.
New Web Project (1)
Im nächsten Schritt belassen wir alles bei den Defaults.
New Web Project (2)
Im letzten Schritt stellen wir sicher dass die Checkbox "Generate Deployment Descriptor" gesetzt ist (das ist per Default der Fall)
New Web Project (3)

Application Client-Projekt
Rechtsklick auf das Projekt "Stateless", und die Option "New" - "Application Client Project" wählen.
Im erscheinden Dialog wird als Name "StatelessClient" (Name des EAR-Projekts mit Zusatz "Client" eingegeben). Als Server wird wiederum "JBoss 4.2" gewählt.
Wichtig ist, dass die Option "Add project to an EAR" gesetzt ist und in der Combobox unser eben erzeugtes EAR-Projekt gewählt ist.
New Application Client Project (1)
Im nächsten Schritt belassen wir alles bei den Defaults.
New Application Client Project (2)
Im letzten Schritt stellen wir sicher dass die Checkbox "Generate Deployment Descriptor" gesetzt ist (das ist per Default der Fall).
Die Checkbox "Create a default Main class" setzen wir zurück, da wir unsere Main Class sowieso per Hand anlegen.
New Application Client Project (3)

Das war es, der Project Explorer sieht so aus:
Project Explorer


Anlegen der GeometricModelBean

Die WebTools-Platform bietet zwar einen Assistenten für das Anlegen von Beans an, diese sind aber nach EJB-2.1-Standard aufgebaut und verwenden außerdem XDoclet zur Generierung der Deployment-Deskriptoren. Deshalb gehen wir hier zur Handarbeit über.
Schritt 1: Im Project Explorer den Knoten "StatelessEJB" wählen. Rechtsklick, im Contextmenü den Punkt "New" -> "Class" wählen.
Stateless Session Bean
Schritt 2: Wir vergeben einen Namespace und einen Bean-Namen. Den Haken "Generate Comments" setzen wir natürlich.
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", "Extract Interface..." wählen.
Refactor
Zuerst erzeugen wir das Remote Interface namens "GeometricModel". Wir wählen die zwei Methoden aus.
Refactor
Das gleiche wiederholen wir für das Local-Interface "GeometricModelLocal".

Schritt 6: Die Bean-Klasse soll diese beiden Interfaces implementieren (dies wird durch "Extract Interface" automatisch erledigt) und erhält außerdem die Annotation "Stateless".
@Stateless() 
public class GeometricModelBean implements GeometricModel, GeometricModelLocal
{
	... 
Das Remote-Interface bekommt die Annotation "Remote":
@Remote()
public interface GeometricModel
{
	... 
Das Local-Interface bekommt die Annotation "Local":
@Local()
public interface GeometricModelLocal
{
	... 


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 "J2EE Module Dependencies" aktivieren wir die Referenz auf "StatelessEJB.jar".
J2EE 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.rmi.PortableRemoteObject, javax.naming.InitialContext,de.fhw.swtvertiefung.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>
<%
InitialContext initialContext = new InitialContext();

//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.

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

  GeometricModel geometricModel = (GeometricModel) PortableRemoteObject.narrow(objGeometricModel, GeometricModel.class);
  
  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:
  <%
  objGeometricModel = initialContext.lookup ("java:comp/env/ejb/GeometricModelLocal");

  //Local Interfaces können direkt über Typecasts geholt werden.
  GeometricModelLocal geometricModelLocal = (GeometricModelLocal) objGeometricModel;

  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> 

EJB-Referenzen anlegen:
Obiger Codeausschnitt verwendet eine EJB-Referenz die im JNDI-Environment-Naming-Context (ENC) der Webanwendung abgelegt ist und auf die GeometricModelBean zeigt. Auf dem JBoss könnten wir die Bean zwar auch direkt über ihren JNDI-Namen ansprechen, aber das wäre unsauber.
Wir öffnen die Datei "StatelessWeb\WebContent\WEB-INF\web.xml" und fügen folgendes Stück nach dem Element "welcome-file-list" ein:
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<home>java.lang.Object</home>
		<remote>de.fhw.swtvertiefung.knauf.stateless.GeometricModel</remote>
	</ejb-ref>
	<ejb-local-ref>
		<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<local-home>java.lang.Object</local-home>
		<local>de.fhw.swtvertiefung.knauf.stateless.GeometricModelLocal</local>
	</ejb-local-ref> 
Wichtig ist dass wir ein "home"-Tag angeben, auch wenn Beans nach EJB3-Standard keine Home-Interfaces mehr kennen ! Der Grund ist dass JBoss ansonsten eine Fehlermeldung ausspuckt:
org.jboss.deployment.DeploymentException: Failed to parse WEB-INF/web.xml; - nested throwable: (org.jboss.deployment.DeploymentException: expected one local-home tag).
Das liegt wahrscheinlich daran dass der Web-Container noch auf J2EE-1.4-Stand ist.
Würden wir hier anderen Namen vergeben, könnten wir die EJB z.B. auch als "java:comp/env/ejb/EtwasKomplettAnderes" ansprechen.

Jetzt müssen wir noch den JBoss-spezifischen Teil der EJB-Referenz erzeugen (nämlich eine Verbindung zur tatsächlichen Bean im Container-JNDI). Dazu legen wir in "StatelessWeb\WEB-INF\" eine Datei "jboss-web.xml" an mit diesem Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
    "-//JBoss//DTD Web Application 4.2//EN"
    "http://www.jboss.org/j2ee/dtd/jboss-web_4_2.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> 
Der Eintrag "context-root" legt fest unter welcher URL wir die Anwendung später auf dem Server erreichen. Im obigen Beispiel also "http://www.meinserver.de/StatelessWeb/meineWebseite.html".
Hier wird der Name der EJB-Referenz mit einem EJB-Namen des Containers verbunden. Woher "Stateless/GeometricModelBean/remote" kommt werden wir weiter unten sehen.

Anmerkung: Gemäß JavaEE5-Spezifikation sollte es möglich sein eine EJB-Referenz per Injection zu erhalten (durch diese Variablendeklaration: @EJB GeometricModel geometricModel;). Dies funktioniert beim JBoss allerdings nicht weil es einen Servlet-Spezifikation-2.5-kompatiblen Container benötigt, und dies ist unterstützt der zugrundeliegende Tomcat 5.5 nicht.


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:
  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:
        InitialContext initialContext = new InitialContext();

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

        GeometricModel geometricModel = (GeometricModel) PortableRemoteObject.narrow(objGeometricModel, GeometricModel.class);
        
        double dblVolume = geometricModel.computeCuboidVolume(dblA, dblB, dblC);
        double dblSurface = geometricModel.computeCuboidSurface(dblA, dblB, dblC);

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

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

        //Local Interfaces can be fetched by direct typecast:
        GeometricModelLocal geometricModelLocal = (GeometricModelLocal) objGeometricModel;

        dblVolume = geometricModelLocal.computeCuboidVolume(dblA, dblB, dblC);
        dblSurface = 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();
  }

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
  {
    this.doPost(request, response);
  } 

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.swtvertiefung.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)


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 mit einem Verzeichnislisting der 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
Eine Anleitung wie das Logging für den JBoss zu konfigurieren ist findet sich hier.

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 "J2EE Module Dependencies" das Bean-JAR aus.
Application Client, Schritt 2
Jetzt den Code einfügen. Da wir uns im Gegensatz zur Webanwendung hier selbst um die Initialisierung der Clients-Umgebung (und damit des JNDI) kümmern müssen, müssen wir die Verbindungsinfos zum JNDI (für das Bean-Lookup) selbst anlegen. Dies geschieht indem ein Properties-Objekt angelegt wird.
Im folgenden der gesamte Code des Clients:
  public class GeometricModelApplicationClient
  {
    public static void main(String[] args)
    {
      try
      {
        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");
        GeometricModel geometricModel = (GeometricModel) PortableRemoteObject.narrow(objRemote, GeometricModel.class);
       
        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();
      }
    }
  } 
Wichtig ist hier dass wir die Property j2ee.clientName (JBoss-spezifisch) auf den Wert des Elements jndi-name in "jboss-client.xml" (siehe weiter unten) setzen !

Die EJB-Referenzen müssen im Deployment-Deskriptor des Clients deklariert werden. Dazu die Datei "application-client.xml" öffnen und folgendes einfügen:
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<ejb-ref-type>Session</ejb-ref-type>
		<home>java.lang.Object</home>
		<remote>de.fhw.swtvertiefung.knauf.stateless.GeometricModel</remote>
	</ejb-ref> 
Auch hier ist wieder wie im Webclient das sinnlose Item "home" nötig :-(.

Wie im Web-Projekt ist ein JBoss-spezifischer Deployment-Deskriptor nötig um die EJB-Referenz mit der Bean im Server-JNDI zu verbinden. Die Datei dazu heißt "jboss-client.xml" und sieht so aus:
	<?xml version="1.0" encoding="UTF-8"?>
	<!DOCTYPE jboss-client PUBLIC
		"-//JBoss//DTD Application Client 4.2//EN"
		"http://www.jboss.org/j2ee/dtd/jboss-client_4_2.dtd">
	<jboss-client>
		<jndi-name>StatelessClient</jndi-name>
		<!-- Connect the declared EJB reference to the JNDI-Name of the EJB: -->
		<ejb-ref>
			<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
			<jndi-name>Stateless/GeometricModelBean/remote</jndi-name>
		</ejb-ref>
	</jboss-client> 

Jetzt noch die Klasse mit der "main"-Methode in "StatelessClient\appClientModule\META-INF\MANIFEST.MF" eintragen und nicht mal der J2EE-Verifier hat etwas zu meckern:
Manifest-Version: 1.0
Class-Path: StatelessEJB.jar
Main-Class: de.fhw.swtvertiefung.knauf.stateless.GeometricModelApplica
 tionClient
Der Zeilenumbruch in der Zeile "Main-Class" gehört so, denn in einer Manifest-Datei darf eine Zeile maximal 72 Zeichen lang sein !

Ausführen des Application Clients

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). Für die Interfaces unserer Bean benötigen wir außerdem die EJB-JAR-Datei "StatelessEJB.jar".

So sieht die Kommandozeile zum Start aus:
java -cp c:\...\jboss-4.2.1.GA\client\jbossall-client.jar;c:\...\jboss-4.2.1.GA\client\jboss-ejb3-client.jar;c:\...\jboss-4.2.1.GA\client\jboss-aop-jdk50-client.jar;c:\...\jboss-4.2.1.GA\client\jboss-aspect-jdk50-client.jar;StatelessEJB.jar;StatelessClient.jar de.fhw.swtvertiefung.knauf.stateless.GeometricModelApplicationClient
Natürlich muss der Pfad zu den JBoss-Dateien angepaßt werden !

Wir benötigen hier insgesamt 4 weitere JBoss-Dateien.


Ausführen aus Eclipse heraus:
Wir wählen das Application-Client-Projekt aus und gehen im Menü "Run" auf "Open Run Dialog...". Der Dialog für die diversen Launch-Konfigurationen öffnet sich. Wir wählen links den Punkt "Java Application" und fügen durch Klick auf "New" (der unscheinbare Button links in der Toolbarleiste oben). eine neue Konfiguration zu. Die Konfiguration bekommt den Namen "StatelessClient", als MainClass wird natürlich "com.knauf.ejb.stateless.GeometricModelApplicationClient" gewählt. Auf der Registerkarte "Classpath" finden wir auch die benötigten JBoss-Libraries wieder.
Application Client starten
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. Zwischen diese Aufgaben können wir umschalten::
Application Client-Ausgabe


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 Zip-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 Datei "ejb-jar.xml" im EJB-Projekt muss 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>
			<remote>de.fhw.swtvertiefung.knauf.stateless.GeometricModel</remote>
			<local>de.fhw.swtvertiefung.knauf.stateless.GeometricModelLocal</local>
			<ejb-class>de.fhw.swtvertiefung.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):

2007-04-23 21:19:55,546 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation javax.ejb.Stateless to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.ejb.StatelessImpl@b65a68
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation javax.ejb.Remote to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.ejb.RemoteImpl@1d8c046
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation javax.ejb.Local to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.ejb.LocalImpl@1715510
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3AnnotationHandler] found EJB3: ejbName=GeometricModelBean, class=de.fhw.swtvertiefung.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 4.2//EN"
	"http://www.jboss.org/j2ee/dtd/jboss_4_2.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 auf der JBoss-Konsole:

2007-04-23 21:19:55,546 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation javax.ejb.Stateless to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.ejb.StatelessImpl@b65a68
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation javax.ejb.Remote to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.ejb.RemoteImpl@1d8c046
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation javax.ejb.Local to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.ejb.LocalImpl@1715510
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation org.jboss.annotation.ejb.LocalBinding to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean org.jboss.annotation.ejb.LocalBindingImpl@5eb748
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation org.jboss.annotation.ejb.RemoteBinding to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean [RemoteBindingImpl:, jndi=Keks, stack=, bindUrl=, proxyFactory=interface org.jboss.ejb3.remoting.RemoteProxyFactory]
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3DescriptorHandler] adding class annotation org.jboss.annotation.ejb.RemoteBinding to de.fhw.swtvertiefung.knauf.stateless.GeometricModelBean [RemoteBindingImpl:, jndi=Keks, stack=, bindUrl=, proxyFactory=interface org.jboss.ejb3.remoting.RemoteProxyFactory]
2007-04-23 21:19:55,562 DEBUG [org.jboss.ejb3.Ejb3AnnotationHandler] found EJB3: ejbName=GeometricModelBean, class=de.fhw.swtvertiefung.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.
Beispiel für "jboss-client.xml" aus dem Application-Client-Projekt:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC
	"-//JBoss//DTD Application Client 4.2//EN"
	"http://www.jboss.org/j2ee/dtd/jboss-client_4_2.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.
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 25.10.2007
Historie:
01.10.2007: Erstellt (Kopie der Sommersemester-2007-Version, nur Screenshots und Beschreibung des Projekt-Anlegens an Eclipse 3.3 angepaßt)
25.10.2007: Code des Servlets zugefügt. JavaDoc-Warnungen korrigiert.