Beispiel: Message Driven Bean auf WildFly 10

Inhalt:

Der richtige Server
JMS-Standardkonfiguration
Anlegen der Message Driven Bean "Message"
MDB goes Server
Vorbereiten des Applicationclients
Anlegen des Applicationclients
Ausführen des Applicationclients
Mit Injection
Ohne Annotations
JavaEE7-Features

Beispiel für eine Message Driven Bean, die ihre Messages per Application Client erhält. Dieses Beispiel funktioniert ab WildFly 10, auch wenn Screenshots und Links in diesem Dokument zu WildFly 19 gehören.
Hier gibt es das Projekt zum Download (dies ist ein EAR-Export, die Importanleitung findet man im Stateless-Beispiel): Message.ear

Aufbau des Beispieles

a) Message Driven Bean, die an einer Queue hängt.
b) Application Client der die Nachrichten abschickt.

Die relevante Doku für dieses Beispiel findet sich: https://docs.wildfly.org/19/Admin_Guide.html#Messaging
Und das Beispiel aus den Quickstart-Guides (diese sind auch über die WildFly-Downloadseite als Gesamtpaket zu finden): https://github.com/wildfly/quickstart/tree/10.x/helloworld-jms

Das Beispiel besteht aus einem "EAR Application Project" mit dem Namen "Message", einem EJB-Projekt mit einer Message Driven Bean und einem Application Client-Projekt.

Der richtige Server

Die Default-Konfiguration von WildFly 10 im Standalone-Modus aus der Datei "standalone.xml" stellt das sogenannte "Web Profile" dar, das keine JMS-Komponenten enthält. Deployed man das Beispiel auf einem Server im Standalone-Modus, erhält man solche Fehler:
20:32:29,986 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-3) MSC000001: Failed to start service jboss.deployment.subunit."Message.ear"."MessageEJB.jar".
      PARSE: org.jboss.msc.service.StartException in service jboss.deployment.subunit."Message.ear"."MessageEJB.jar".
      PARSE: WFLYSRV0153: Failed to process phase PARSE of subdeployment "MessageEJB.jar" of deployment "Message.ear"
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:154)
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948)
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: org.jboss.msc.service.ServiceNotFoundException: Service service jboss.ejb.default-resource-adapter-name-service not found
	at org.jboss.msc.service.ServiceContainerImpl.getRequiredService(ServiceContainerImpl.java:669)
	at org.jboss.as.ejb3.deployment.processors.MessageDrivenComponentDescriptionFactory.getDefaultResourceAdapterName(MessageDrivenComponentDescriptionFactory.java:274)
	at org.jboss.as.ejb3.deployment.processors.MessageDrivenComponentDescriptionFactory.processMessageBeans(MessageDrivenComponentDescriptionFactory.java:154)
	at org.jboss.as.ejb3.deployment.processors.MessageDrivenComponentDescriptionFactory.processAnnotations(MessageDrivenComponentDescriptionFactory.java:81)
	at org.jboss.as.ejb3.deployment.processors.AnnotatedEJBComponentDescriptionDeploymentUnitProcessor.processAnnotations(AnnotatedEJBComponentDescriptionDeploymentUnitProcessor.java:57)
	at org.jboss.as.ejb3.deployment.processors.AbstractDeploymentUnitProcessor.deploy(AbstractDeploymentUnitProcessor.java:76)
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147)
	... 5 more
Man könnte "standalone.xml" um die Konfiguration für JMS erweitern. Einfacher ist es, das "Full Profile" aus "standalone-full.xml" zu verwenden.

Serverstart über Kommandozeile:
standalone.bat -c standalone-full.xml

Server in Eclipse:
Hier muss man eine komplett neue Serverdefinition anlegen:
Serverdefinition mit standalone-full.xml
Auch hier gibt man "standalone-full.xml" als Konfigurationsdatei an.


JMS-Standardkonfiguration

WildFly verwendet als Messaging-System "Apache ActiveMQ Artemis" (http://activemq.apache.org/artemis/).
In WildFly 8/9 wurde "HornetQ" verwendet. Dieses ist in "ActiveMQ" übergegangen. Auswirkung für die Benutzung: geänderte Konfigurationsdateien.

Im folgenden werden die relevanten Elemente der JMS-Konfiguration in "standalone-full.xml" erklärt.
Im Bereich "extensions" wird die JMS-Erweiterung der Konfigurationsdatei deklariert (ohne diese würde das Parsen der Element des Subsystems "messaging-activemq" fehlschlagen):
    <extensions>
        ...
        <extension module="org.wildfly.extension.messaging-activemq"/>
        ...
    </extensions>
Weiter unten wird diese Konfiguration als Subsystem verwendet. Dort wird ein "server" namens "default" definiert:
        <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0">
            <server name="default">
              ...
            <server>
        </subsystem>			
Die Schema-Definition für diesen Bereich findet man in der Datei "wildfly-messaging-activemq_1_0.xsd" (in "%WILDFLY_HOME%\docs\schema").

Infos zu diesem Element:
Bis WildFly 18 unter dieser URL zu finden: http://wildscribe.github.io/WildFly/10.0/subsystem/messaging-activemq/server/index.html.
Ab WildFly 19 hier zu finden: https://docs.wildfly.org/19/wildscribe/subsystem/messaging-activemq/server/index.html.

Aus der ActiveMQ-Doku: http://activemq.apache.org/artemis/docs/1.1.0/configuring-transports.html (diese Doku scheint allerdings nicht für das zu gelten, was in "standalone-full.xml" steckt, dort findet sich wohl eine leicht angepasste Variante der Konfiguration)

Die erste relevante Deklaration darin ist ein "connector":
                <http-connector name="http-connector" endpoint="http-acceptor" socket-binding="http"/>
                <http-connector name="http-connector-throughput" endpoint="http-acceptor-throughput" socket-binding="http">
                    <param name="batch-delay" value="50"/>
                </http-connector>
                <in-vm-connector name="in-vm" server-id="0"/>

Ein Connector wird von einem JMS-Client genutzt, um sich mit einem Server zu verbinden.

Der "in-vm-connector" wird verwendet, wenn innerhalb der aktuell laufenden VM eine Verbindung zu ActiveMQ aufgebaut werden soll.
Der "http-connector" führt ein Verbindung zu einem Remote-Server über HTTP durch.
Der "http-connector-throughput" führt ebenfalls eine HTTP-Verbindung zu einem Remote-Server durch und ist für erhöhten Durchsatz gedacht.
Die beiden HTTP-Connectoren verweisen auf ein "socket-binding" namens "http", in dem der Port (8080) des lokalen Hosts deklariert ist. Die Bindings werden weiter unten erklärt.

Will z.B. ein Servlet eine Nachricht an eine Message Queue im gleichen Server schicken, kann es den "in-vm-connector" nutzen, oder den HTTP-Connector, der gemäß obenstehender Konfiguration ebenfalls auf den lokalen Server weiterleitet.

Umgekehrt gibt es die "acceptors":
                <http-acceptor name="http-acceptor" http-listener="default"/>
                <http-acceptor name="http-acceptor-throughput" http-listener="default">
                    <param name="batch-delay" value="50"/>
                    <param name="direct-deliver" value="false"/>
                <in-vm-acceptor name="in-vm" server-id="0"/>
                </http-acceptor>
Hier geht es um den Empfang von Nachrichten: der "in-vm-acceptor" akzeptiert Nachrichten aus der gleichen virtuellen Maschine, die "http-acceptor" und "http-acceptor-throughput" empfangen Nachrichten über das Netzwerk, und zwar horchen sie an den im "http-listener" angegebenen HTTP-Listener.
Dieser wiederum ist im Subsystem "urn:jboss:domain:undertow:3.0" deklariert:
        <subsystem xmlns="urn:jboss:domain:undertow:3.0">
            <server name="default-server">
                <http-listener name="default" redirect-socket="https" socket-binding="http"/>
                <host name="default-host" alias="localhost">
                    ...
                </host>
            </server>
Dieser Listener verweist auf ein "socket-binding" namens "http", das schon oben beim HTTP-Connector gezeigt wurde.

Die folgenden "security-settings" definieren Berechtigungen.
                <security-setting name="#">
                    <role name="guest" 
                        delete-non-durable-queue="true" 
                        create-non-durable-queue="true" 
                        consume="true"
                        send="true"/>
                </security-setting>
Hier sind die Permissions für "send" und "consume" relevant: nur ein Benutzer, der sich in der deklarierten Rolle "guest" befindet, darf Nachrichten abschicken/empfangen. Über das "name"-Attribut wird definiert, für welche Queue oder welches Topic diese Deklaration gilt. Im obigen Ausschnitt ist "#" die Wildcard für "alle".

Jetzt folgende die "connection-factories":
                <connection-factory 
                    name="InVmConnectionFactory" 
                    entries="java:/ConnectionFactory" 
                    connectors="in-vm"/>
                <connection-factory 
                    name="RemoteConnectionFactory" 
                    entries="java:jboss/exported/jms/RemoteConnectionFactory" 
                    connectors="http-connector"/>
                <pooled-connection-factory 
                    name="activemq-ra" 
                    transaction="xa" 
                    entries="java:/JmsXA java:jboss/DefaultJMSConnectionFactory" 
                    connectors="in-vm"/

Für jeden der obigen Connectoren wird eine ConnectionFactory ins JNDI gebunden. Wichtig ist hier die "pooled-connection-factory" namens "activemq-ra". Diese bindet sich an den In-VM-Connector und wird von WildFly 10 unter dem JaveEE7-Standardnamen "java:comp/DefaultJMSConnectionFactory" ins JNDI gebunden! Im folgenden Beispiel wird die In-VM-Connectionfactory "java:/ConnectionFactory" genutzt.

Am Ende von "standalone-full.xml" folgen die Socket Bindings, die Ports benennen.
Im folgenden nur die in der ActiveMQ-Konfiguration erwähnten Ports:
    <socket-binding-group name="standard-sockets" ...>
        ...
        <socket-binding name="http" port="${jboss.http.port:8080}"/>
        ...
    </socket-binding-group>

Wenn man weiten oben einen Connector auf einen Remote Host deklariert hätte, dann müsste man hier wohl ein "outbound-socket-binding" mit Namen und Port des Remote Host deklarieren:
        <outbound-socket-binding name="myremotemessaging">
            <remote-destination host="otherhost" port="5445"/>
        </outbound-socket-binding>
Dieser Connector müsste im "socket-binding"-Attribut den Namen dieses Socket Binding, also "myremotemessaging", stehen haben. Ein Beispiel hierfür gibt es in der Konfiguration des JavaEE-ApplicationClient.

Die Connectoren der Standard-Konfiguration referenzieren alle nur "socket-binding"-Elemente. Ich nehme an, dass sie deshalb per Default mit dem deklarieren Port des Servers "localhost" kommunizieren.

Anlegen der Message Driven Bean "Message"

Über "New" -> "Other..." wählen wir "EJB" -> "Message-Driven Bean (3.x)" aus:
Message Driven Bean (1)
Die Klasse heißt "MessageBean" und liegt im Package "de.fhw.komponentenarchitekturen.knauf.mdb".
Als "Destination Name" wird ein Name angegeben, unter dem die zugehörige Queue ins Server-JNDI gebunden wird, hier "jms/queue/MessageBeanQueue". Falls wir keine Queue im Server konfigurieren (siehe weiter unten) wird hier beim Deploy eine neue angelegt.
Der "Destination Type" gibt an, ob es sich bei dieser Bean um eine Queue (Punkt-zu-Punkt-Verbindung, der Client schickt die Nachricht an einen bestimmten Empfänger, wie eine Telefonverbindung) oder ein Topic (es gibt eine unbekannte Anzahl von Empfängern, vergleichbar mit dem Radio-Senden) handelt.
Message Driven Bean
Im nächsten Schritt können wir alles bei den Defaults belassen.


Die generierte Klasse sieht so aus (bereinigt um Kommentare):
package de.fhw.komponentenarchitekturen.knauf.mdb;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;

@MessageDriven(
		activationConfig = { @ActivationConfigProperty(
				propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(
				propertyName = "destination", propertyValue = "jms/queue/MessageBeanQueue")
		}, 
		mappedName = "jms/queue/MessageBeanQueue")
public class MessageBean implements MessageListener
{
  public MessageBean()
  {
  }

  public void onMessage(Message message)
  {
  }
}
Anmerkung: das Attribut "mappedName" ist in meinem Beispielcode nicht enthalten, es geht also auch ohne.


In der Methode onMessage steckt die Implementierung der Bean:
  private static final Logger logger = Logger.getLogger ( MessageBean.class.getName() );

  public void onMessage(Message message)
  {
    try
    {
      MessageBean.logger.info("onMessage: Message vom Typ " + message.getClass().toString() + " erhalten");
      if (message instanceof TextMessage)
      {
        TextMessage textMessage = (TextMessage) message;
        MessageBean.logger.info("TextMessage enthält diesen Text: " + textMessage.getText() );
      }
      else
        MessageBean.logger.info("Sonstige Message. toString() = " + message.toString() );
    }
    catch (JMSException jex)
    {
      MessageBean.logger.log( Level.SEVERE, "Fehler beim Verarbeiten der Message: " + jex.getMessage(), jex );
      throw new EJBException ("Fehler beim Verarbeiten der Message: " + jex.getMessage(), jex );
    }
  }

Schon sind wir kurz davor die MDB auf den Server zu packen.


MDB goes Server

Anmerkung: scheinbar generiert JBoss automatisch eine Queue, wenn wir folgende Config-Schritte nicht durchführen!
Also deklarieren wir die Queue. Hierfür gibt es drei Möglichkeiten (sowie die Möglichkeit des automatischen Erzeugens):

Weitere Management-Optionen:
Anmerkung: die beiden folgenden Operationen scheinen mit WildFly 10 jeweils nichts mehr auszugeben!

Ausgabe aller Nachrichten, die die Queue abgearbeitet hat (leider ohne Ausgabe der Textinhalte):
[standalone@localhost:9990 /] jms-queue --queue-address=MessageBeanQueue list-messages
Eine Langform des gleichen Befehls (diese hat den Vorteil, dass man die Operationen wie "list-messages" per Code completion angezeigt bekommt):
[standalone@localhost:9990 /] /subsystem=messaging-activemq/server=default/jms-queue=MessageBeanQueue:list-messages

Anzahl der Nachrichten, die die Queue verarbeitet hat:
[standalone@localhost:9990 /] jms-queue --queue-address=MessageBeanQueue count-messages

Details über die Queue:
[standalone@localhost:9990 /] /subsystem=messaging-activemq/server=default/jms-queue=MessageBeanQueue:read-resource


Vorbereiten des Applicationclients

Schritt 1: Konfiguration
Die Konfiguration des JMS-Framework muss analog zu den Einträgen von "standalone-full.xml" auch in der Configdatei des Client-Container ("appclient.xml"). Tun wir das nicht, führt das zu diesem Fehler:
2015-11-05 20:59:48,158 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-8) WFLYSRV0027: Starting deployment of "Message.ear" (runtime-name: "Message.ear")
2015-11-05 20:59:48,252 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-6) WFLYSRV0207: Starting subdeployment (runtime-name: "MessageClient.jar")
2015-11-05 20:59:48,252 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-7) WFLYSRV0207: Starting subdeployment (runtime-name: "MessageEJB.jar")
2015-11-05 20:59:48,299 INFO  [org.jboss.as.ejb3.deployment] (MSC service thread 1-8) WFLYEJB0473: JNDI bindings for session bean named 'MessageBean' in deployment unit 'subdeployment "MessageEJB.jar" of deployment "Message.ear"' are as follows:


2015-11-05 20:59:48,314 INFO  [org.hibernate.validator.internal.util.Version] (MSC service thread 1-4) HV000001: Hibernate Validator 5.2.2.Final
2015-11-05 20:59:48,346 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-4) MSC000001: Failed to start service jboss.deployment.subunit."Message.ear"."MessageClient.jar".
  POST_MODULE: org.jboss.msc.service.StartException in service jboss.deployment.subunit."Message.ear"."MessageClient.jar".
  POST_MODULE: WFLYSRV0153: Failed to process phase POST_MODULE of subdeployment "MessageClient.jar" of deployment "Message.ear"
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:154) [wildfly-server-2.0.0.CR8.jar:2.0.0.CR8]
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1948) [jboss-msc-1.2.6.Final.jar:1.2.6.Final]
	at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1881) [jboss-msc-1.2.6.Final.jar:1.2.6.Final]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [rt.jar:1.8.0_65]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [rt.jar:1.8.0_65]
	at java.lang.Thread.run(Unknown Source) [rt.jar:1.8.0_65]
Caused by: org.jboss.as.server.deployment.DeploymentUnitProcessingException: WFLYEE0027: Could not load javax.jms.QueueConnectionFactory referenced in env-entry
	at org.jboss.as.ee.component.deployers.ResourceReferenceProcessor.getResourceRefEntries(ResourceReferenceProcessor.java:153) [wildfly-ee-10.0.0.CR4.jar:10.0.0.CR4]
	at org.jboss.as.ee.component.deployers.ResourceReferenceProcessor.processDescriptorEntries(ResourceReferenceProcessor.java:74) [wildfly-ee-10.0.0.CR4.jar:10.0.0.CR4]
	at org.jboss.as.ee.component.deployers.AbstractDeploymentDescriptorBindingsProcessor.deploy(AbstractDeploymentDescriptorBindingsProcessor.java:95) [wildfly-ee-10.0.0.CR4.jar:10.0.0.CR4]
	at org.jboss.as.server.deployment.DeploymentUnitPhaseService.start(DeploymentUnitPhaseService.java:147) [wildfly-server-2.0.0.CR8.jar:2.0.0.CR8]
	... 5 more
Caused by: java.lang.ClassNotFoundException: javax.jms.QueueConnectionFactory from [Module "deployment.Message.ear.MessageClient.jar:main" from Service Module Loader]
	at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:205) [jboss-modules.jar:1.4.4.Final]
	at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:455) [jboss-modules.jar:1.4.4.Final]
	at org.jboss.modules.ConcurrentClassLoader.performLoadClassChecked(ConcurrentClassLoader.java:404) [jboss-modules.jar:1.4.4.Final]
	at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:385) [jboss-modules.jar:1.4.4.Final]
	at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:130) [jboss-modules.jar:1.4.4.Final]
	at org.jboss.as.ee.component.deployers.ResourceReferenceProcessor.getResourceRefEntries(ResourceReferenceProcessor.java:151) [wildfly-ee-10.0.0.CR4.jar:10.0.0.CR4]
	... 8 more

2015-11-05 20:59:48,346 ERROR [org.jboss.as] (Controller Boot Thread) WFLYSRV0026: WildFly Full 10.0.0.CR4 (WildFly Core 2.0.0.CR8) started (with errors) in 1514ms - Started 176 of 182 services (3 services failed or missing dependencies, 18 services are lazy, passive or on-demand)
2015-11-05 20:59:48,361 ERROR [org.jboss.as.controller.management-operation] (Thread-40) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "Message.ear")]) - failure description: 
  {"WFLYCTL0080: Failed services" => {"jboss.deployment.subunit.\"Message.ear\".\"MessageClient.jar\".POST_MODULE" => "org.jboss.msc.service.StartException in service jboss.deployment.subunit.\"Message.ear\".\"MessageClient.jar\".
    POST_MODULE: WFLYSRV0153: Failed to process phase POST_MODULE of subdeployment \"MessageClient.jar\" of deployment \"Message.ear\"
    Caused by: org.jboss.as.server.deployment.DeploymentUnitProcessingException: WFLYEE0027: Could not load javax.jms.QueueConnectionFactory referenced in env-entry
    Caused by: java.lang.ClassNotFoundException: javax.jms.QueueConnectionFactory from [Module \"deployment.Message.ear.MessageClient.jar:main\" from Service Module Loader]"}}
2015-11-05 20:59:48,361 ERROR [org.jboss.as.server] (Thread-40) WFLYSRV0021: Deploy of deployment "Message.ear" was rolled back with the following failure message: 
{"WFLYCTL0080: Failed services" => {"jboss.deployment.subunit.\"Message.ear\".\"MessageClient.jar\".POST_MODULE" => "org.jboss.msc.service.StartException in service jboss.deployment.subunit.\"Message.ear\".\"MessageClient.jar\".
  POST_MODULE: WFLYSRV0153: Failed to process phase POST_MODULE of subdeployment \"MessageClient.jar\" of deployment \"Message.ear\"
    Caused by: org.jboss.as.server.deployment.DeploymentUnitProcessingException: WFLYEE0027: Could not load javax.jms.QueueConnectionFactory referenced in env-entry
    Caused by: java.lang.ClassNotFoundException: javax.jms.QueueConnectionFactory from [Module \"deployment.Message.ear.MessageClient.jar:main\" from Service Module Loader]"}}
2015-11-05 20:59:48,377 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-3) WFLYSRV0208: Stopped subdeployment (runtime-name: MessageEJB.jar) in 13ms
2015-11-05 20:59:48,377 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-6) WFLYSRV0208: Stopped subdeployment (runtime-name: MessageClient.jar) in 13ms
2015-11-05 20:59:48,377 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0028: Stopped deployment Message.ear (runtime-name: Message.ear) in 17ms
2015-11-05 20:59:48,377 INFO  [org.jboss.as.controller] (Thread-40) WFLYCTL0183: Service status report
WFLYCTL0184:    New missing/unsatisfied dependencies:
      service jboss.naming.context.java.module.Message.MessageEJB (missing) dependents: [service jboss.deployment.subunit."Message.ear"."MessageEJB.jar".INSTALL] 
WFLYCTL0186:   Services which failed to start:      service jboss.deployment.subunit."Message.ear"."MessageClient.jar".POST_MODULE
Also öffnen wir "%WILDFLY_HOME%\appclient\configuration\appclient.xml" und fügen folgendes hinzu:


WildFly 10 bis WildFly 23
Im Element "extensions" wird folgendes eingetragen:
    <extensions>
		...
		<extension module="org.wildfly.extension.messaging-activemq"/>
		...
    </extensions>
Im Element "profile" wird ein neues "subsystem" zugefügt:
        <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0">
            <server name="default">
                <security-setting name="#">
                    <role name="guest" delete-non-durable-queue="true" create-non-durable-queue="true" consume="true" send="true"/>
                </security-setting>
                <address-setting name="#" message-counter-history-day-limit="10" page-size-bytes="2097152" max-size-bytes="10485760" expiry-address="jms.queue.ExpiryQueue" dead-letter-address="jms.queue.DLQ"/>
                <http-connector name="http-connector" endpoint="http-acceptor" socket-binding="http"/>

                <!--
                <jms-queue name="MessageBeanQueue" entries="java:/jms/queue/MessageBeanQueue"/>
                -->
				
                <connection-factory name="RemoteConnectionFactory" entries="java:/ConnectionFactory" connectors="http-connector"/>
            </server>
        </subsystem>
Hier wird ein einziger Connector deklariert, da wir in der Anwendung nur eine Verbindung zu einem fernen Server aufbauen wollen. Deshalb nehmen wir als Typ des Connectors einen "http-connector", der das Socket Binding "http" verwendet (siehe nächster Schritt).
Die Elemente "security-settings" und "address-settings" habe ich aus einer Beispielconfig verwendet, ich weiß nicht, ob sie nötig sind.
Der JNDI-Name der "RemoteConnectionFactory" ("java:/ConnectionFactory") wird so von der Client-Anwendung benötigt.
Anmerkung: das Beispiel enthält einen auskommentierten Block, der die Queue deklariert. Das klappt natürlich nicht, wenn wie im Beispielcode nach Variante 3 die Queue durch die Config-Datei "knaufmq-activemq-jms.xml" als Teil des EJB-Deployment erzeugt wird!

Im Bereich "socket-binding-group" wird deklariert, zu welchem Server/Port sich die Queue verbinden soll. Der Name des Bindings entspricht dem beim Connector angegebenen "socket-binding"
    <socket-binding-group name="standard-sockets" default-interface="public">
      ...
      <outbound-socket-binding name="http">
        <remote-destination host="localhost" port="8080"/>
      </outbound-socket-binding>
    </socket-binding-group>
	

Besonderheit ist hier, dass wir ein "outbound-socket-binding" deklarieren, d.h. ein ausgehender Port inklusive Zielserver wird deklariert. Würden wir hier ein "socket-binding" deklarieren (so wie es die anderen Deklarationen in diesem Bereich tun), dann würde das vom "Connector" zwar auch als ausgehender Port genutzt, dieser würde sich aber nur mit "localhost" verbinden, statt mit einem fernen Server. Siehe z.B.
https://docs.wildfly.org/26/Developer_Guide.html#EJB_invocations_from_a_remote_server_instance


Ab WildFly 24
Obige Konfiguration führt beim Ausführen des Application Client zu folgendem Fehler:
19:37:42,961 ERROR [org.jboss.as.controller] (Controller Boot Thread) WFLYCTL0362: Capabilities required by resource '/subsystem=messaging-activemq/server=default' are not available:
    org.wildfly.security.legacy-security-domain.other; There are no known registration points which can provide this capability.
Ein Blick in "standalone-full.xml" von WildFly 26 (in WildFly 24 war dies noch nicht enthalten) verriet mir, dass das Subsystem "urn:jboss:domain:messaging-activemq:13.0" folgende Deklaration enthielt:

                <security elytron-domain="ApplicationDomain"/>

Also habe ich diese Zeile sowie Teile des Elytron-Subsystems aus der Server-Konfiguration übertragen (solange experimentiert, bis es fehlerfrei funktionierte):
Im Element "extensions" wird folgendes eingetragen:
    <extensions>
		...
		<extension module="org.wildfly.extension.messaging-activemq"/>
		<extension module="org.wildfly.extension.elytron"/>
		...
    </extensions>
Im Element "profile" wird im Subsystem "urn:jboss:domain:messaging-activemq" (man beachte die Versionsnummer - diese entstammt der WildFly 24-Config) das Element "security" zugefügt, das auf eine Security Domain "ApplicationDomain" verweist. Diese ist im Subsystem "urn:wildfly:elytron:14.0" definiert:
        <subsystem xmlns="urn:jboss:domain:messaging-activemq:13.0">
            <server name="default">
                <security elytron-domain="ApplicationDomain"/>
                
                <address-setting name="#" message-counter-history-day-limit="10" page-size-bytes="2097152" max-size-bytes="10485760" expiry-address="jms.queue.ExpiryQueue" dead-letter-address="jms.queue.DLQ"/>
                <http-connector name="http-connector" endpoint="http-acceptor" socket-binding="http"/>

                <!--
                <jms-queue name="MessageBeanQueue" entries="java:/jms/queue/MessageBeanQueue"/>
                -->
				
                <connection-factory name="RemoteConnectionFactory" entries="java:/ConnectionFactory" connectors="http-connector"/>
            </server>
        </subsystem>
		
        <!--Minimales Elytron-Subsystem-->
        <subsystem xmlns="urn:wildfly:elytron:14.0" >
            <security-domains>
                <security-domain name="ApplicationDomain"/>
            </security-domains>
        </subsystem>


Schritt 2: User hinzufügen:
Das Senden an eine Queue (liegt das hier nur daran, dass wir uns außerhalb des Server-Prozesses befinden?) ist verboten, ohne dass der User authentifiziert ist. Starten wir unseren Application Client ohne Anmeldung (also ohne Angabe eines Users beim Erzeugen der Queue-Connection: queueConnectionFactory.createQueueConnection();), kommt eine solche Exception im Client:
19:56:25,946 ERROR [stderr] (AWT-EventQueue-0) javax.jms.JMSSecurityException: AMQ229031: Unable to validate user from /127.0.0.1:50410. Username: null; SSL certificate subject DN: unavailable
19:56:25,947 ERROR [stderr] (AWT-EventQueue-0)  at org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.sendBlocking(ChannelImpl.java:464)
19:56:25,947 ERROR [stderr] (AWT-EventQueue-0)  at org.apache.activemq.artemis.core.protocol.core.impl.ChannelImpl.sendBlocking(ChannelImpl.java:358)
Also verwenden wir das Script "%JBOSS_HOME%\bin\add-user.bat", um einen neuen User anzulegen:

add user


Anlegen des Applicationclients

Der Applicationclient muss nicht die EJB-JARs referenzieren, da MessageDrivenBeans über keine Remote/Local-Interfaces verfügen.
Es wird neue Klasse vom Typ javax.swing.JFrame zugefügt.
Sie muss in "Manifest.mf" als Main-Class eingetragen werden.

Wichtig: es muss für das Beenden des Application Client die gleiche Logik implementiert werden, die im
Stateless-Beispiel implementiert ist ("defaultCloseOperation", Warten auf Schließen des Fensters).

Auf dem JFrame einen Button und ein Textfeld platzieren. Beim Button-Klick soll die Eingabe aus dem Textfeld an die MessageBean geschickt werden.
Der Code dazu sieht so aus:
  private void sendMessage()
  {
    try
    {
      InitialContext initialContext = new InitialContext();
      QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory) initialContext.lookup("java:comp/env/jms/MBConnectionFactory");

      //Die konfigurierte Queue holen:
      Queue queue = (Queue) initialContext.lookup("java:comp/env/jms/MBQueueRef");

      //Verbindung erzeugen:
      //Hier muss der User angegeben werden!
      QueueConnection queueConnection = queueConnectionFactory.createQueueConnection("tester", "test123!");
      QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
      QueueSender queueSender = queueSession.createSender(queue);

      //Senden der Nachricht:
      TextMessage textMessage = queueSession.createTextMessage();
      textMessage.setText(this.jTextFieldMessage.getText());

      queueSender.send(textMessage);

      //Alles sauber aufräumen:
      queueSender.close();
      queueSession.close();
      queueConnection.close();

      //Fertig.
      JOptionPane.showMessageDialog(this, "Nachricht wurde gesendet und sollte im Serverlog stehen !");
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }
An den InitialContext müssen keine Parameter übergeben werden.
Man holt sich die QueueConnectionFactory aus einem Environment Naming Context-Eintrag "java:comp/env/jms/MBConnectionFactory", der im Folgenden beschrieben wird.
Danach wird die Queue über "java:comp/env/jms/MBQueueRef" geholt.
Anschließend erzeugt man sich über die Connection Factory eine Connection, startet eine QueueSession und erzeugt daraus und der Ziel-Queue einen QueueSender. Über diesen schickt man die Textnachricht.

-Jetzt kommt die Hauptarbeit: die Resourcen-Referenzen müssen deklariert werden:
In "application-client.xml" wird folgendes eingefügt:
   <resource-ref>
      <res-ref-name>jms/MBConnectionFactory</res-ref-name>
      <res-type>javax.jms.QueueConnectionFactory</res-type>
      <res-auth>Container</res-auth>
   </resource-ref>
   <resource-ref>
      <res-ref-name>jms/MBQueueRef</res-ref-name>
      <res-type>javax.jms.Queue</res-type>
      <res-auth>Container</res-auth>
   </resource-ref>

Die eigentliche Verknüpfung mit den globalen JNDI-Namen der JBoss-Resourcen erfolgt über die Datei "jboss-client.xml". Wir könnten natürlich (wie in den bisherigen Beispielen) den XML-Inhalt händisch zufügen, aber um den WebTools-Plugin ein bisschen besser kennen zu lernen machen wir das hier über den umständlichen Weg:

Dazu Rechtsklick auf "META-INF" im Client-Projekt und "New" -> "Other..." wählen. Wir wählen "XML" -> "XML" aus.
jboss-client.xml (1)
Im ersten Schritt wird der Dateiname "jboss-client.xml" angegeben und des META-INF-Verzeichnis als Ziel gewählt.
jboss-client.xml (2)
Im nächsten Schritt wird die Option "Create XML file from a DTD file" gewählt.
jboss-client.xml (3)
Die DTD-Datei steckt im XML-Katalog, wir wählen sie aus:
jboss-client.xml (4)
Im letzten Schritt würde ich empfehlen, den Prefix für den Default-Namespace "jboss-client" über "Edit" zu kicken (sprich im Feld "Prefix" den Wert zu löschen"). Grund: ansonsten hätten wir vor jedem Element "jboss-client:" stehen, und das verringert die Lesbarkeit der "jboss-client.xml" eher:
jboss-client.xml (5)
Das erzeugt eine Rumpf-XML-Datei mit sauberer XSD-Deklaration. Dieser fügen wir noch das Attribut version="6.0" zu (optional)

Jetzt fügen wir die Resourcen-Referenzen zu. Wichtig ist dass die Elemente res-ref-name in "jboss-client.xml" den gleichen Wert haben wie die zugehörigen Elemente in "application-client.xml"!
<?xml version="1.0" encoding="UTF-8"?>
<jboss-client xmlns="http://www.jboss.com/xml/ns/javaee"
	xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xml="http://www.w3.org/XML/1998/namespace"
	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>MessageClient</jndi-name>
	<resource-ref>
		<res-ref-name>jms/MBConnectionFactory</res-ref-name>
		<jndi-name>java:/ConnectionFactory</jndi-name>
	</resource-ref>
	<resource-ref>
		<res-ref-name>jms/MBQueueRef</res-ref-name>
		<jndi-name>java:/jms/queue/MessageBeanQueue</jndi-name>
	</resource-ref>
</jboss-client>
Die ConnectionFactory wird an den Namen "java:/ConnectionFactory" gebunden. Wie dieser konfiguriert wird, sehen wir im nächsten Abschnitt.

Die Queue wird (siehe ebenfalls nächster Abschnitt) an "java:/jms/queue/MessageBeanQueue" gebunden. In der dort angegebenen Konfiguration ist der Queue-Name als "queue/MessageBeanQueue" deklariert, aber hier in "jboss-client.xml" müssen wir "java:/jms" voranstellen, weil alle Queues unter diesem Namen gebunden werden.


Ausführen des Applicationclients

Gestart wird die Anwendung durch diesen Aufruf:
%WILDFLY_HOME%\bin\appclient.bat Message.ear#MessageClient.jar


Achtung: die EAR-Dateien, die hier zum Download stehen, wurden mit Java 1.8 erstellt. Mit Java 17 (das erst ab WildFly 25 offiziell unterstützt wird) gab es folgende Fehlermeldung:

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private static final java.lang.Object javax.swing.JFrame.defaultLookAndFeelDecoratedKey accessible: module java.desktop does not \"opens javax.swing\" to unnamed module @f7cd57e"}

Ursache: es fehlt eine "module-info"-Datei in meinem Projekt (da mit Java 1.8 erstellt).
Lösung: Durch Setzen der Umgebungsvariable "JAVA_OPTS", die in "appclient.bat" ausgewertet wurd, kann dieses Problem umgangen werden:
set JAVA_OPTS=--add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED
%WILDFLY_HOME%\bin\appclient.bat Message.ear#MessageClient.jar 

In vorherigen Java-Versionen konnte man die JAVA_OPTS auf --illegal-access=permit setzen, aber dies wurde in Java 17 entfernt.


Mit Injection

Hier gibt es ein EAR-Projekt mit für die Injection vorbereitetem Client: MessageInjection.ear

Der Code des Clients sieht so aus (die injizierten Variablen müssen static sein und in der Main Class stecken):
  @Resource(name="jms/MBConnectionFactory")
  private static QueueConnectionFactory queueConnectionFactory;
  
  @Resource(name="jms/MBQueueRef")
  private static Queue queue;
Für Queue und QueueConnectionFactory greife ich auf einen Eintrag aus dem ENC zu (siehe letzter Abschnitt).

Hier hätte auch ein Binden an den globalen JNDI-Namen ohne Verwendung des ENC geklappt:
  @Resource(mappedName="ConnectionFactory")
  private static QueueConnectionFactory queueConnectionFactory;
  
  @Resource(mappedName="queue/MessageBeanQueue")  
  private static Queue queue;

Die Konfiguration des Environment Naming Context sieht so aus:

application-client.xml:
<?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>MessageClient</display-name>
   <resource-ref>
      <res-ref-name>jms/MBConnectionFactory</res-ref-name>
      <res-type>javax.jms.QueueConnectionFactory</res-type>
      <res-auth>Container</res-auth>
   </resource-ref>
   
   <message-destination-ref>
      <message-destination-ref-name>jms/MBQueueRef</message-destination-ref-name>
      <message-destination-type>javax.jms.Queue</message-destination-type>
   </message-destination-ref>
   
</application-client>
jboss-client.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-client xmlns="http://www.jboss.com/xml/ns/javaee"
  xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xml="http://www.w3.org/XML/1998/namespace"
  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>MessageClient</jndi-name>
  <resource-ref>
      <res-ref-name>jms/MBConnectionFactory</res-ref-name>
      <jndi-name>java:/ConnectionFactory</jndi-name>
  </resource-ref>
  
  <message-destination-ref>
      <message-destination-ref-name>jms/MBQueueRef</message-destination-ref-name>
      <jndi-name>java:/jms/queue/MessageBeanQueue</jndi-name>
  </message-destination-ref>
</jboss-client>
Wichtig ist, dass die Queue nicht als resource-ref eingetragen wird, sondern als message-destination-ref!


Ohne Annotations

"ejb-jar.xml" sieht so aus:
<?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>MessageEJB</display-name>
	<enterprise-beans>
	
		<message-driven>
			<display-name>MessageBean</display-name>
			<ejb-name>MessageBean</ejb-name>
			<ejb-class>de.fhw.komponentenarchitekturen.knauf.mdb.MessageBean</ejb-class>
			<messaging-type>javax.jms.MessageListener</messaging-type> 
			<transaction-type>Container</transaction-type>
			<message-destination-type>javax.jms.Queue</message-destination-type>
			<activation-config>
				<activation-config-property>
					<activation-config-property-name>destinationType</activation-config-property-name>
					<activation-config-property-value>javax.jms.Queue</activation-config-property-value>
				</activation-config-property>
				<activation-config-property>
					<activation-config-property-name>destination</activation-config-property-name>
					<activation-config-property-value>queue/MessageBeanQueue</activation-config-property-value>
				</activation-config-property>
			</activation-config>
		</message-driven>
	</enterprise-beans>
</ejb-jar> 
Die Elemente "messaging-type", "transaction-type" und "message-destination-type" sind scheinbar optional, die Anwendung hat jedenfalls auch ohne funktioniert.

Außerdem fügen wir eine Datei "jboss.xml" zu (obwohl dies für das Funktionieren der Anwendung nicht nötig wäre, hier nur der Vollständigkeit halber):
<?xml version="1.0" encoding="UTF-8"?>
<jboss 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_5_1.xsd"
	version="5.1">
	<enterprise-beans>
		<message-driven>
			<ejb-name>MessageBean</ejb-name>
		</message-driven>
	</enterprise-beans>
</jboss>

Auf Injection im Application Client habe ich hier verzichtet ;-)

Die modifizierte Version des Projekts gibt es hier:
MessageNoAnnotation.ear (es sollte beim Import in "Message" umbenannt werden)
ACHTUNG: Dieses Projekt kann nicht neben dem obigen Message-Beispiel existieren !


JavaEE7-Features

In JavaEE7 wurde die JMS-Spezifikation mit Version 2 deutlich erweitert.
Auf Seiten der EJB ist es jetzt möglich, die Queue per Annotation zu deklarieren, ohne Container-spezifische Deploymentdeskriptoren wie "xxx-activemq-jms.xml" oder ein Eintrag in die Server-Config ("standalone-full.xml").
Hierzu wird die Annotation javax.jms.JMSDestinationDefinition verwendet:
@MessageDriven (activationConfig=
  {
    @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
    @ActivationConfigProperty(propertyName="destination", propertyValue="java:app/jms/queue/MessageBeanQueue")
  })
@JMSDestinationDefinition(
    name = "java:app/jms/queue/MessageBeanQueue",
    interfaceName = "javax.jms.Queue",
    destinationName = "MessageBeanQueue"
  )
public class MessageBean implements MessageListener
}
Man beachte, dass die Queue hier ins private JNDI der Anwendung gebunden wird (siehe
https://community.jboss.org/thread/235447)

In der JNDIView findet man die Queue jetzt im privaten Bereich der Anwendung:
JNDIView
Entsprechend kann man sie über CLI auch nur ansprechen, wenn man die Anwendung angibt.
[standalone@localhost:9990 /] /deployment=Message.ear/subdeployment=MessageEJB.jar/subsystem=messaging-activemq/server=default/jms-queue=MessageBeanQueue:list-messages
{
    "outcome" => "success",
    "result" => []
}

Auch auf Client-Seite ergeben sich Vereinfachungen: man kann mit dem javax.jms.JMSContext arbeiten, der das Senden der Nachricht vereinfacht. Die ConnectionFactory und die Queue muss man allerdings wie gehabt aus dem JNDI holen.
      QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory) initialContext.lookup("java:comp/env/jms/MBConnectionFactory");

      Queue queue = (Queue) initialContext.lookup("java:comp/env/jms/MBQueueRef");
      
      JMSContext context = queueConnectionFactory.createContext("tester", "test123!");
      TextMessage textMessage = context.createTextMessage();
      textMessage.setText(this.jTextFieldMessage.getText());
      context.createProducer().send(queue, textMessage);
      
      context.close();
Der JMSContext wird hier über die javax.jms.ConnectionFactory geholt, dabei wird das Passwort übergeben.

Anmerkung: in einer serverseitigen Anwendung (Web oder EJB) könnte man sich den Context auch injizieren lassen, aber das geht in einem Application Client nicht (siehe https://jms-spec.java.net/2.0/apidocs/javax/jms/JMSContext.html: Applications running in the Java EE web and EJB containers may alternatively inject a JMSContext into their application using the @Inject annotation).

Anmerkung 2: JavaEE 7 definiert eine Connectionfactory unter dem Namen "java:comp/DefaultJMSConnectionFactory". Dies gilt aber nur für Anwendungen, die innerhalb des Server laufen. Im ApplicationClient muss man die JMS-Konfiguration über "appclient" selbst definieren, und dabei kann man die ConnectionFactory beliebig benennen.

In "jboss-client.xml" habe ich die Environment Naming Context-Eintrag an den "java:"-Bereich gebunden.
	<resource-ref>
		<res-ref-name>jms/MBQueueRef</res-ref-name>
		<jndi-name>java:/jms/queue/MessageBeanQueue</jndi-name>
	</resource-ref>


In "%WILDFLY_HOME%\appclient\configuration\appclient.xml" muss die MDB "MessageBeanQueue" ebenfalls an den Namen "java:/jms/queue/MessageBeanQueue" gebunden werden (einziger Unterschied zu obiger Konfiguration):
        <subsystem xmlns="urn:jboss:domain:messaging-activemq:1.0">
            <server name="default">
                <security-setting name="#">
                    <role name="guest" delete-non-durable-queue="true" create-non-durable-queue="true" consume="true" send="true"/>
                </security-setting>
                <address-setting name="#" message-counter-history-day-limit="10" page-size-bytes="2097152" max-size-bytes="10485760" expiry-address="jms.queue.ExpiryQueue" dead-letter-address="jms.queue.DLQ"/>
                <http-connector name="http-connector" endpoint="http-acceptor" socket-binding="http"/>
				
                <jms-queue name="MessageBeanQueue" entries="java:/jms/queue/MessageBeanQueue"/>
                
                <connection-factory name="RemoteConnectionFactory" entries="java:/ConnectionFactory" connectors="http-connector"/>
            </server>
        </subsystem>
Mein erster Ansatz war, auch im Client die Queue an "java:app/jms/queue/MessageBeanQueue" zu binden:
	<resource-ref>
		<res-ref-name>jms/MBQueueRef</res-ref-name>
		<jndi-name>java:app/jms/queue/MessageBeanQueue</jndi-name>
	</resource-ref>

Aber diese Variante funktioniert mit WildFly 10.0.0 und später (getestet bis WildFly 26) nicht. Siehe https://community.jboss.org/message/863696 und https://issues.jboss.org/browse/WFLY-3211
Es gibt diese Fehlermeldung:

20:23:10,912 ERROR [org.jboss.as.controller.management-operation] (Thread-42) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "Message.ear")]) - failure description: {
    "WFLYCTL0412: Required services that are not installed:" => ["jboss.naming.context.java.app.Message.jms.queue.MessageBeanQueue"],
    "WFLYCTL0180: Services with missing/unavailable dependencies" => ["jboss.naming.context.java.module.Message.MessageClient.env.jms.MBQueueRef is missing [jboss.naming.context.java.app.Message.jms.queue.MessageBeanQueue]"]
}
20:23:10,913 ERROR [org.jboss.as.server] (Thread-42) WFLYSRV0021: Deploy of deployment "Message.ear" was rolled back with the following failure message:
{
    "WFLYCTL0412: Required services that are not installed:" => ["jboss.naming.context.java.app.Message.jms.queue.MessageBeanQueue"],
    "WFLYCTL0180: Services with missing/unavailable dependencies" => ["jboss.naming.context.java.module.Message.MessageClient.env.jms.MBQueueRef is missing [jboss.naming.context.java.app.Message.jms.queue.MessageBeanQueue]"]
}


Mehr zu den JMS 2.0-Features (wobei ein Teil davon nicht für Application Clients zutrifft): http://www.mastertheboss.com/jboss-jms/jms-20-tutorial-on-wildfly-as

Die modifizierte Version des Projekts gibt es hier: MessageJavaEE7.ear (es sollte beim Import in "Message" umbenannt werden)
ACHTUNG: Dieses Projekt kann nicht neben dem obigen Message-Beispiel existieren !


Stand 30.12.2021
Historie:
07.11.2015: Erstellt aus WildFly 8-Beispiel und angepasst an WildFly 10
07.11.2017: zwei kleine Tippfehler korrigiert, JavaEE7-Features klappen auch mit WildFly 11 nicht.
22.04.2020: diverse WildFly-Links aktualisiert, Abschnitt "jboss-client_6_0.xsd zu XML-Katalog zufügen" entfernt, Beispiel "JavaEE7-Features": mit Workaround (?) lauffähig gemacht.
30.12.2021: geänderte Konfiguration in "appclient.xml" ab WildFly 24, Fehlermeldung beim Ausführen des Application Client mit Java 17