Beispiel: Stateless Session Bean


Inhalt:

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

Für WildFly 8.2: 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 WildFly 8.2-Server aus. Die "EAR version" wird auf "6.0" geändert, auch wenn der Server JavaEE7 unterstützt.
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" (siehe übernächster Screenshot) 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 application.xml Deployment Descriptor" wird nicht gesetzt, das wäre erst für die Variante "
Ohne Annotations " relevant.

Man klickt auf "Finish".

Beim ersten Aufruf erscheint die Abfrage ob wir zur "Java EE perspective" wechseln wollen. Diese bejahen wir (Screenshot aus einer älteren Eclipse-Version).
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. Die Checkbox "No-interface view" ist per Default an und wird unchecked.

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:
  package de.fhw.komponentenarchitekturen.knauf.stateless;

  import java.util.logging.Logger;
  import javax.ejb.Stateless;

  @Stateless
  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
Anmerkung: Eclipse generiert uns jetzt in der Bean-Klasse die "@Override"-Annotation, um die Bean-Methode als "aus dem Interface stammend" zu markieren.

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 durchführen und in die "Properties" gehen. Im Punkt "Deployment Assembly" gehen wir auf den den Karteireiter "Manifest Entries" und klicken auf "Add...":
Deployment Assembly - add manifest entry (1)
Hier wählen wir das Projekt "StatelessEJB" aus:
Deployment Assembly - add manifest entry (2)
Das Ergebnis sieht so aus:
Deployment Assembly - add manifest entry (3)
Schauen wir uns die Datei "StatelessWeb\WebContent\META-INF\Manifest.mf", so sieht sie so aus:
Manifest-Version: 1.0
Class-Path: StatelessEJB.jar
Die zweite Zeile haben wir durch den letzten Schritt zugefügt.


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.*, javax.naming.InitialContext"%>
<!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();

  GeometricModelRemote geometricModel = (GeometricModelRemote) initialContext.lookup ("java:comp/env/ejb/GeometricModel");
  GeometricModelLocal geometricModelLocal = (GeometricModelLocal) initialContext.lookup ("java:comp/env/ejb/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:

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

In JBoss 5 klappte die Injection (mit Workarounds), aber JBoss 7.1 scheint dies nicht zu unterstützen.

"web.xml" anlegen
Wie man im obigen Beispiel sieht, habe ich mir die Remote-/Local-Interfaces der EJB aus dem JNDI geholt. Besonderheit dabei: ich bin den "sauberen" Weg gegangen, der die EJB nicht aus dem globalen JNDI holt, sondern dem Deployer der Anwendung die Möglichkeit läßt, den JNDI-Namen individuell zu konfigurieren und im "Environment Naming Context" bereitzustellen. Der ENC wird mittels des Deployment-Deskriptors "web.xml" konfiguriert.
Wir haben allerdings die Webanwendung ohne Deployment-Deskriptoren angelegt, da diese seit JavaEE6 (Servlet 3.0-Standard) optional sind. Den hier benötigten Deployment-Deskriptor "web.xml" können wir uns generieren lassen: im Projekt "StatelessWeb" einen Rechtsklick durchführen und den Menüpunkt "Generate Deployment Descriptor Stub" aufrufen:
Generate Deployment Descriptor Stub


Jetzt deklariert der Deployer der Anwendung (also wir ;-)) den Eintrag im "Environment Naming Context (ENC)". Schritt 1 davon erfolgt über "web.xml":
	<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>
Das Binden dieses JNDI-Namens an einen Eintrag im Server-JNDI erfolgt über einen JBoss-spezifischen Deployment-Deskriptor namens "jboss-web.xml", der an der gleichen Stelle wie "web.xml", also in "WebContent\WEB-INF", liegt. Er hat diesen Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-web_8_0.xsd"
	version="8.0">
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<jndi-name>java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</jndi-name>
	</ejb-ref>
	<ejb-local-ref>
		<ejb-ref-name>ejb/GeometricModelLocal</ejb-ref-name>
		<local-jndi-name>java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal</local-jndi-name>
	</ejb-local-ref>

</jboss-web>
In "web.xml" wird der Eintrag für den Environment Naming Context deklariert (z.B. "ejb/GeometricModel"). In "jboss-web.xml" erfolgt das Binden von "ejb/GeometricModel" an den globalen JNDI-Namen, also "java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote".

Hinweis:
Man könnte den JNDI-Lookup auch auf den globalen JNDI-Namen durchführen:
 GeometricModelRemote geometricModel = 
   (GeometricModelRemote) initialContext.lookup ("java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote");
Das hat aber den großen Nachteil, dass bei einer Änderung des globalen Namens alle Stellen, die auf diesen Namen zugreifen, angepaßt werden müssten. Und manchmal ändert sich in einer JBoss-Version die Konvention für den Aufbau des globalen Namens, in JBoss 6 und früher wäre der JNDI-Name "Stateless/GeometricModelBean/remote" gewesen - viel Spaß beim Anpassen von Code bei einer Migration.

Regeln für den Aufbau von JNDI-Namen:
Siehe
https://docs.wildfly.org/19/Developer_Guide.html#EJB_invocations_from_a_remote_client_using_JNDI (Abschnitt nach dem großen Codestück im Kapitel "Writing a remote client application for accessing and invoking the EJBs deployed on the server")

Für ein Remote Interface ist die Regel so:
ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>
Alternativ geht auch der Präfix "java:/global" statt "ejb:" (wie in meinem Beispiel geschehen)

Für local Interfaces muss man "java:/global" nehmen, "ejb:" ist hier nicht unterstützt (https://community.jboss.org/thread/215535).

Achtung:
An der genannten Schema Location ist "jboss-web_8_0.xsd" momentan nicht verfügbar. Diese Datei findet sich nur im JBoss-Verzeichnis ("docs/schema"). Der JBoss-Tools-Plugin fügt zum Glück diese Datei den Eclipse-"XML Catalog" zu, dadurch wäre eine Validierung möglich. Allerdings scheint es ein Problem im der Schema-Datei zu geben, weswegen sie nicht validiert (https://issues.jboss.org/browse/JBMETA-363)


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 muss man eine URL angegeben unter der das Servlet später angesprochen werden soll. In diesem Beispiel will ich das Servlet über Annotations steuern. Leider bietet Eclipse keinen Assistenten, um ein Servlet mit Annotations zu erzeugen, es werden Einträge in "web.xml" erzeugt. Deshalb müssen die Ergebnisse dieses Schritts nachträglich überarbeitet werden. Für dieses Beispiel können wir es beim Default "/GeometricModelServlet" belassen.
Würde man ein Servlet über "web.xml" deklarieren, müsste man in meinem Beispiel den Default "/GeometricModelServlet" durch "/servlet/GeometricModelServlet" ersetzen:
Neues Servlet (2)
In Schritt 3 belassen wir alles auf den Defaults.

Nacharbeiten: falls man bereits eine "web.xml" im Projekt hat, muss man "web.xml" mit dem "XML Editor" öffnen und löscht die Elemente "servlet" und "servlet-mapping" wieder raus, weil im Folgenden Annotations dafür verwendet werden:
<web-app ...>
  ...
  <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>/GeometricModelServlet</url-pattern>
  </servlet-mapping>
</web-app>
Hat man bisher noch keinen Deployment Deskriptor "web.xml" erzeugt, dann wurde das Servlet mit dieser Annotation versehen:
@WebServlet("/servlet/GeometricModelServlet")
public class GeometricModelServlet extends HttpServlet
{
  ...
Diese Annotation ersetzen wir durch eine ausführlichere Deklaration.


Der gesamte 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.annotation.WebServlet;
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.
 *
 */
@WebServlet(name="GeometricModelServlet", displayName="GeometricModelServlet",
  urlPatterns={"/servlet/GeometricModelServlet"})
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)
   */
  @Override
  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)
   */
  @Override
  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();
  }
}
Man beachte die Annotation @WebServlet, die die Informationen enthält, die uns der Assistent ansonsten in "web.xml" packt:

Aufrufbar ist das Servlet jetzt über diese URL: http://localhost:8080/StatelessWeb/servlet/GeometricModelServlet

Das Ergebnis sieht im Project Explorer so aus:
Struktur von StatelessWeb

Hinweis:
Wenn man im Servlet ohne Injection arbeiten will, führt man den JNDI-Lookup genauso aus wie in der JSP.


Server einrichten

Das Webprojekt auswählen, Rechtsklick -> "Run As..." -> "Run on Server" wählen. Im Assistenten wählen wir als "Type" den WildFly-8.x-Server und unter "Server runtime Environment" die von uns eingerichtete Server Runtime.
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:
Server anlegen (1a)
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" (JBoss 7.x) bzw. "Forbidden" (WildFly 8.x - diese Meldung sieht man allerdings nicht bei Verwendung des Eclipse-internen Browsers, da dies ein Internet Explorer ist, der im Standard die eigentliche Fehlermeldung unterdrückt) 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: http://localhost:8080/StatelessWeb/GeometricModelTest.jsp

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, ihn aufklappen, die Anwendung "Stateless" auswählen und im Contextmenü die Option "Full 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
Achtung: Auch auf der Ebene des Servers gibt es die Option "Publish" im Contextmenü. Diese hat bei mir aber nicht immer das gewünschte Ergebnis geliefert. Von einem "Incremental Publish" der Anwendung rate ich ebenfalls ab - lieber ein sauberes "Full Publish".


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


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 fügt unter "Deployment Assembly", Karteireiter "Manifest Entries" die JAR-Datei des EJB-Projekts hinzu.
Application Client, Schritt 2


Application Client mit Injection

Für JBoss 6 und früher: http://www.jboss.org/community/docs/DOC-12835

Für WildFly 8 und JBoss 7: 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();
      }
    }
  } 

Es ist keine weitere Konfiguration nötig!

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 generierte 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

Mehr Details siehe hier: https://docs.wildfly.org/19/Developer_Guide.html#Application_Client_Reference

Das Ausführen des Application Client geht leider nicht über Eclipse-Bordmittel, da das Ausführen eines Application Client unter JBoss ein Umfeld benötigt, das man grob als "Mini-JBoss-Server" bezeichnen kann.

Hinweis: es gibt einen Feature Request, um JBoss Tools entsprechend zu erweitern: https://issues.jboss.org/browse/JBIDE-11517

Hierzu benötigen wir die in den JBoss deployed Anwendung als ganzes irgendwo auf der Festplatte. Ideal wäre das JBoss-Verzeichnis "deployments", in dem die Daten schon liegen. Allerdings führen die JBoss Tools ein exploded Deployment durch, und ich habe es noch nicht geschafft, den folgenden Aufruf daraufhin anzupassen.

Deshalb müssen wir uns eine EAR-Datei unserer Anwendung exportieren. Dies geht wie im Kapitel Export des Workspace beschrieben, nur dass wir keine Quelldateien benötigen.
Aus dem Verzeichnis der EAR-Datei ruft man dann folgendes auf:
cd C:\temp\wildfly-8.2.0.Final\bin
.\appclient.bat x:\PfadZurEarDatei\Stateless.ear#StatelessClient.jar

Man beachte das "#", das für Java eine Datei innerhalb von "Stateless.ear" angibt.

Hinweis:
Ein auszuführender Application Client muss wohl immer Teil einer EAR-Datei sein, die außer der JAR-Datei mit dem Application Client auch eine EJB-Jar-Datei enthält.

Wenn man alles richtig gemacht hat, führt das zu dieser Ausgabe:
C:\Temp>C:\Temp\wildfly-8.2.0.Final\bin\appclient.bat c:\Temp\Stateless.ear#Stat
elessClient.jar
Calling "C:\Temp\wildfly-8.2.0.Final\bin\appclient.conf.bat"
JAVA_HOME is not set. Unexpected results may occur.
Set JAVA_HOME to the directory of your local JDK to avoid this message.
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=256M; sup
port was removed in 8.0
19:55:14,224 INFO  [org.jboss.modules] (main) JBoss Modules version 1.3.3.Final
19:55:14,427 INFO  [org.jboss.msc] (main) JBoss MSC version 1.2.2.Final
19:55:14,567 INFO  [org.jboss.as] (MSC service thread 1-3) JBAS015899: WildFly 8
.2.0.Final "Tweek" starting
19:55:17,063 INFO  [org.jboss.as.connector.subsystems.datasources] (ServerServic
e Thread Pool -- 8) JBAS010403: Deploying JDBC-compliant driver class org.h2.Dri
ver (version 1.3)
19:55:17,126 INFO  [org.jboss.as.naming] (ServerService Thread Pool -- 19) JBAS0
11800: Activating Naming Subsystem
19:55:17,141 WARN  [org.jboss.as.txn] (ServerService Thread Pool -- 21) JBAS0101
53: Node identifier property is set to the default value. Please make sure it is
 unique.
19:55:17,188 INFO  [org.jboss.as.webservices] (ServerService Thread Pool -- 22)
JBAS015537: Activating WebServices Extension
19:55:17,250 INFO  [org.jboss.as.jacorb] (ServerService Thread Pool -- 16) JBAS0
16300: Activating JacORB Subsystem
19:55:17,188 INFO  [org.jboss.as.security] (ServerService Thread Pool -- 20) JBA
S013171: Activating Security Subsystem
19:55:17,250 INFO  [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1
-1) JBAS010417: Started Driver service with driver-name = h2
19:55:17,250 INFO  [org.jboss.as.connector.logging] (MSC service thread 1-4) JBA
S010408: Starting JCA Subsystem (IronJacamar 1.1.9.Final)
19:55:17,344 INFO  [org.jboss.as.naming] (MSC service thread 1-1) JBAS011802: St
arting Naming Service
19:55:17,344 INFO  [org.jboss.as.security] (MSC service thread 1-3) JBAS013170:
Current PicketBox version=4.0.21.Final
19:55:18,374 INFO  [org.jboss.ws.common.management] (MSC service thread 1-1) JBW
S022052: Starting JBoss Web Services - Stack CXF Server 4.3.2.Final
19:55:18,717 WARN  [jacorb.codeset] (MSC service thread 1-2) Warning - unknown c
odeset (Cp1252) - defaulting to ISO-8859-1
19:55:18,888 INFO  [org.jboss.as.connector.subsystems.datasources] (MSC service
thread 1-1) JBAS010400: Bound data source [java:jboss/datasources/ExampleDS]
19:55:18,904 INFO  [org.jboss.as.jacorb] (MSC service thread 1-2) JBAS016330: CO
RBA ORB Service started
19:55:19,356 INFO  [org.jboss.as.jacorb] (MSC service thread 1-3) JBAS016328: CO
RBA Naming Service started
19:55:19,434 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8
.2.0.Final "Tweek" started in 6021ms - Started 118 of 120 services (9 services a
re lazy, passive or on-demand)
19:55:19,466 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-4) JBA
S015876: Starting deployment of "Stateless.ear" (runtime-name: "Stateless.ear")
19:55:19,824 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-4) JBA
S015973: Starting subdeployment (runtime-name: "StatelessClient.jar")
19:55:19,824 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) JBA
S015973: Starting subdeployment (runtime-name: "StatelessWeb.war")
19:55:19,824 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-3) JBA
S015973: Starting subdeployment (runtime-name: "StatelessEJB.jar")
19:55:20,043 INFO  [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeplo
ymentUnitProcessor] (MSC service thread 1-4) JNDI bindings for session bean name
d GeometricModelBean in deployment unit subdeployment "StatelessEJB.jar" of depl
oyment "Stateless.ear" are as follows:

        java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponenten
architekturen.knauf.stateless.GeometricModelRemote
        java:app/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen
.knauf.stateless.GeometricModelRemote
        java:module/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.sta
teless.GeometricModelRemote
        java:jboss/exported/Stateless/StatelessEJB/GeometricModelBean!de.fhw.kom
ponentenarchitekturen.knauf.stateless.GeometricModelRemote

19:55:20,339 INFO  [org.jboss.as.server] (Thread-30) JBAS018559: Deployed "State
less.ear" (runtime-name : "Stateless.ear")
19:55:20,370 INFO  [org.jboss.ejb.client] (Thread-38) JBoss EJB Client version 2
.0.1.Final
19:55:20,386 INFO  [org.xnio] (Thread-38) XNIO version 3.3.0.Final
19:55:20,417 INFO  [org.xnio.nio] (Thread-38) XNIO NIO Implementation Version 3.
3.0.Final
19:55:20,589 INFO  [org.jboss.remoting] (Thread-38) JBoss Remoting version 4.0.6
.Final
19:55:20,792 INFO  [org.jboss.ejb.client.remoting] (Remoting "endpoint" task-5)
EJBCLIENT000017: Received server version 2 and marshalling strategies [river]
19:55:20,807 INFO  [org.jboss.ejb.client.remoting] (Thread-38) EJBCLIENT000013:
Successful version handshake completed for receiver context EJBReceiverContext{c
lientContext=org.jboss.ejb.client.EJBClientContext@4f47f7e8, receiver=Remoting c
onnection EJB receiver [connection=Remoting connection <53e763e4>,channel=jboss.
ejb,nodename=turbotante]} on channel Channel ID d696c244 (outbound) of Remoting
connection 694fd236 to localhost/127.0.0.1:8080

19:55:21,172 INFO  [stdout] (Thread-38) Calculated volume: 350.0, surface: 310.0

19:55:21,203 INFO  [org.jboss.ejb.client.remoting] (Remoting "endpoint" task-10)
 EJBCLIENT000016: Channel Channel ID d696c244 (outbound) of Remoting connection
694fd236 to localhost/127.0.0.1:8080 can no longer process messages
19:55:21,281 INFO  [org.jboss.as.connector.subsystems.datasources] (MSC service
thread 1-1) JBAS010409: Unbound data source [java:jboss/datasources/ExampleDS]
19:55:21,297 INFO  [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1
-1) JBAS010418: Stopped Driver service with driver-name = h2
19:55:21,344 INFO  [org.hibernate.validator.internal.util.Version] (MSC service
thread 1-3) HV000001: Hibernate Validator 5.1.3.Final
19:55:21,484 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) JBA
S015974: Stopped subdeployment (runtime-name: StatelessEJB.jar) in 262ms
19:55:21,500 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-4) JBA
S015974: Stopped subdeployment (runtime-name: StatelessWeb.war) in 268ms
19:55:21,500 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) JBA
S015974: Stopped subdeployment (runtime-name: StatelessClient.jar) in 267ms
19:55:21,531 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-2) JBA
S015877: Stopped deployment Stateless.ear (runtime-name: Stateless.ear) in 296ms

19:55:21,546 INFO  [org.jboss.as] (MSC service thread 1-2) JBAS015950: WildFly 8
.2.0.Final "Tweek" stopped in 312ms
Und mittendrin steht die Konsolenausgabe unserer Anwendung:
Calculated volume: 350.0, surface: 310.0

Auf der Serverkonsole findet sich die EJB-seitige Debuggingausgabe.
Hinweis:
Im Beispiel hat der Application Client auf eine EJB im lokalen Server zugegriffen. Wenn man auf einen remote Server zugreifen möchte, geht das gemäß der Anleitung, die ich zu Beginn dieses Kapitels verlinkt hatte.
Allerdings hat der Aufruf "./appclient.sh --host=hostname myear.ear#appClient.jar" bei mir zu einer Fehlermeldung geführt.

java.lang.RuntimeException: org.jboss.remoting3.UnknownURISchemeException: No connection provider for URI scheme "localhost" is installed
        at org.jboss.as.appclient.service.LazyConnectionContextSelector.createConnection(LazyConnectionContextSelector.java:81)
        at org.jboss.as.appclient.service.LazyConnectionContextSelector.getCurrent(LazyConnectionContextSelector.java:93)
        at org.jboss.as.appclient.service.LazyConnectionContextSelector.getCurrent(LazyConnectionContextSelector.java:51)
        at org.jboss.ejb.client.EJBClientContext.getCurrent(EJBClientContext.java:253)
        at org.jboss.ejb.client.EJBClientContext.requireCurrent(EJBClientContext.java:263)
        at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:156)
        ...
Es klappt mit "%JBOSS_HOME\bin\appclient.bat --host=remote://hostname:4447 myear.ear#appClient.jar"

Ohne Angabe des Ports hing sich die Kommunikationsschicht scheinbar auf.

Bei WilfFly 8 ist folgender Aufruf nötig:
"%JBOSS_HOME\bin\appclient.bat --host=http-remoting://localhost:8080 myear.ear#appClient.jar"
(oder alternativ: "-H=http-remoting://localhost:8080")

Grund ist, dass in WildFly die Menge an offenen Ports reduziert wurde, und deshalb das "Http upgrade"-Verfahren (http://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header) verwendet wird, bei dem ein Http-Request an den Server geschickt wird, in dem ein Wechsel auf ein anderes Protokoll (über den gleichen Port) angefragt wird.

Mehr Infos im Bezug auf JBoss: http://planet.jboss.org/post/wildfly_8_0_0_alpha3_released_with_support_for_ejb_invocations_over_http




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!
Die Bedeutung der Option "Optimize for a specific server runtime" hat sich mir bisher nicht erschlossen, scheinbar hat sie zumindest für JBoss keine Auswirkung.
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 werden die zu importierenden Module angezeigt - es sollten per Default alle abgehakt sein.
Import, Schritt 4


Ohne Annotations

Alle Annotations des JavaEE6-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 application.xml 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".
Generate Deployment Descriptor Stub
Analog gehen wir für den Application Client vor.

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.1"
	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_1.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.


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.

Hinweis:
In vorherigen JBoss-Versionen gab es die Möglichkeit, einen abweichenden JNDI-Namen für die EJB anzugeben (über eine Datei "jboss.xml" oder eine JBoss-spezifische Annotation org.jboss.annotation.ejb.RemoteBinding). Diese Möglichkeit wurde in JBoss 7 ausgebaut (auch nicht durch den Nachfolger von "jboss.xml", eine Datei namens "jboss-ejb3.xml", machbar). Grund ist, dass frühere JavaEE-Standards die Implementierung der JNDI-Namen den Entwicklern von Anwendungscontainern überliesen, aber in JavaEE6 ein fester Namensstandard vorgegeben wurde. Siehe
http://stackoverflow.com/questions/12631769/how-to-set-specific-ejb-jndi-names-in-jboss-7-1-1 bzw. Kapitel 4.4 der EJB 3.1 Spezifikation (http://jcp.org/aboutJava/communityprocess/mrel/jsr318/index.html)


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="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/web-app_3_0.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>
Die Datei "jboss-web.xml" ist identisch zur obigen Variante mit Annotations und wird hier nicht nochmal gezeigt.

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="6"
	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_6.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 (scheinbar gibt es noch keine Version der XSD für WildFly 8, deshalb wird hier die JBoss 6-Variante verwendet):
<?xml version="1.0" encoding="UTF-8"?>
<jboss-client xmlns="http://www.jboss.com/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-client_6_0.xsd"
             version="6.0">
	<jndi-name>StatelessClient</jndi-name>
	<ejb-ref>
		<ejb-ref-name>ejb/GeometricModel</ejb-ref-name>
		<jndi-name>java:global/Stateless/StatelessEJB/GeometricModelBean!de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote</jndi-name>
	</ejb-ref>
</jboss-client> 


Die modifizierte Version des Projekts 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!



Stand 20.04.2020
Historie:
27.02.2013: Erstellt aus JBoss5-Version, angepaßt an JBoss 7.2 und Eclipse 4.1
05.05.2013: Deployment-Deskriptoren korrigiert (unnötige "xmlns"-Deklaration entfernt).
27.09.2013: WildFly 8: "http upgrade" als Protokoll für Angabe des Client-Host
12.02.2015: Anpassungen an WildFly 8
18.04.2020: Links auf die WildFly-Doku aktualisiert