Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need clarification on JMS vs ActiveMQ bean/resource configuration

There appears to be some inconsistency on how to use JMS resources, and setting up activationConfig with proper @ActivationConfigProperty on a @MessageDriven annotation.

First, here is my resource config (glassfish-resources.xml, but translatable to other deployment descriptors). This is applied to Glassfish (asadmin add-resources glassfish-resources.xml) along with the ActiveMQ Resource Adapter:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>

    <resource-adapter-config name="activemq-rar" 
                             thread-pool-ids="thread-pool-1" 
                             resource-adapter-name="activemq-rar">
        <property name="ServerUrl" value="tcp://localhost:61616"/>
        <property name="UserName" value="admin"/>
        <property name="Password" value="admin"/>
        <property name="UseInboundSession" value="false"/>
    </resource-adapter-config>
    <admin-object-resource enabled="true" 
                           jndi-name="jms/queue/myApp" 
                           object-type="user" 
                           res-adapter="activemq-rar" 
                           res-type="javax.jms.Queue">
        <description>MyApp JMS Queue</description>
        <property name="Name" value="myAppAMQ"/>
        <property name="PhysicalName" value="myAppAMQ"/>     
    </admin-object-resource>
    <connector-resource enabled="true" 
                        jndi-name="jms/factory/myApp" 
                        object-type="user" 
                        pool-name="jms/factoryPool/myApp">
        <description>MyApp Connection Factory</description>
        <property name="Name" value="myAppFactory"/>
    </connector-resource>
    <connector-connection-pool associate-with-thread="false" 
                               connection-creation-retry-attempts="0" 
                               connection-creation-retry-interval-in-seconds="10" 
                               connection-definition-name="javax.jms.QueueConnectionFactory" 
                               connection-leak-reclaim="false" 
                               connection-leak-timeout-in-seconds="0" 
                               fail-all-connections="false" 
                               idle-timeout-in-seconds="300" 
                               is-connection-validation-required="false" 
                               lazy-connection-association="false" 
                               lazy-connection-enlistment="false" 
                               match-connections="true" 
                               max-connection-usage-count="0" 
                               max-pool-size="32" 
                               max-wait-time-in-millis="60000" 
                               name="jms/factoryPool/myApp" 
                               ping="false" 
                               pool-resize-quantity="2" 
                               pooling="true" 
                               resource-adapter-name="activemq-rar" 
                               steady-pool-size="8" 
                               validate-atmost-once-period-in-seconds="0"/>
</resources>

Here is my message provider bean. You'll notice that JNDI names are found and the ActiveMQ resources are used without error, the message sent to the proper queue:

@Stateless
@LocalBean
public class ServicesHandlerBean {

    @Resource(mappedName = "jms/queue/myApp")
    private Queue queue;
    @Resource(mappedName = "jms/factory/myApp")
    private ConnectionFactory factory;

    public void sendJMSMessage(MessageConfig messageData) throws JMSException {
        Connection connection = null;
        Session session = null;
        try {
            connection = factory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            MessageProducer messageProducer = session.createProducer(queue);
            messageProducer.send(createJMSMessage(session, messageData));
        } finally {
            if (session != null) {
                try {
                    session.close();
                } catch (JMSException e) {
                    Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot close session", e);
                }
            }
            if (connection != null) {
                connection.close();
            }
        }
    }
}

The confusion begins when defining a @MessageDriven bean. The following which uses mappedName throws an exception:

@MessageDriven(mappedName = "jms/queue/myApp")
public class MessageBean implements MessageListener

Warning: RAR8000 : The method setName is not present in the class : org.apache.activemq.command.ActiveMQQueue Warning: RAR7097: No setter method present for the property Name in the class org.apache.activemq.command.ActiveMQQueue Info: visiting unvisited references Info: visiting unvisited references Warning: RAR8501: Exception during endpoint activation for ra [ activemq-rar ], activationSpecClass [ org.apache.activemq.ra.ActiveMQActivationSpec ] : javax.resource.ResourceException: Unknown destination type: null Severe: MDB00017: [InvoiceProductionMessageBean]: Exception in creating message-driven bean container: [java.lang.Exception] Severe: java.lang.Exception

I'm forced to define my MDB as such:

@MessageDriven(
        activationConfig = {
            @ActivationConfigProperty(propertyName = "connectionFactoryLookup", propertyValue = "jms/factory/myApp"),
            @ActivationConfigProperty(propertyName = "destination", propertyValue = "myAppAMQ"),
            @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = " JMSType = 'TypeA' "),
            @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue")
        }
)
public class MessageBean implements MessageListener

AND I need to supply a glassfish-ejb-jar.xml telling the container to use the ActiveMQ resource, otherwise I get a java.lang.ClassCastException:

Warning: RAR8501: Exception during endpoint activation for ra [ jmsra ], activationSpecClass [ com.sun.messaging.jms.ra.ActivationSpec ] : java.lang.ClassCastException: org.apache.activemq.ra.ActiveMQConnectionFactory cannot be cast to com.sun.messaging.jms.ra.DirectConnectionFactory Severe: MDB00017: [MessageBean]: Exception in creating message-driven bean container: [java.lang.Exception] Severe: java.lang.Exception

glassfish-ejb-jar.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-ejb-jar PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 EJB 3.1//EN" "http://glassfish.org/dtds/glassfish-ejb-jar_3_1-1.dtd">
<glassfish-ejb-jar>
    <enterprise-beans>
        <ejb>
            <ejb-name>MessageBean</ejb-name>
            <mdb-resource-adapter>
                <resource-adapter-mid>activemq-rar</resource-adapter-mid>
            </mdb-resource-adapter>
        </ejb>
    </enterprise-beans>
</glassfish-ejb-jar>

So, there seems to be some inconsistencies between how a producer can use a resource (JNDI), and how a consumer does (XML + @ActivationConfigProperty). Also, the EE7 ActivationConfigProperty properties don't appear to work. For instance, using destinationLookup doesn't lookup the destination, and I'm forced to use ActiveMQ's destination property.

ActiveMQ lists the following Activation Spec Properties:

acknowledgeMode (The JMS Acknowledgement mode to use. Valid values are: Auto-acknowledge or Dups-ok-acknowledge)

clientId (The JMS Client ID to use (only really required for durable topics))

destinationType (The type of destination; a queue or topic)

destination (The destination name (queue or topic name))

enableBatch (Used to enable transaction batching for increased performance)

maxMessagesPerBatch (The number of messages per transaction batch)

maxMessagesPerSessions (This is actually the prefetch size for the subscription. (Yes, badly named).)

maxSessions (The maximum number of concurrent sessions to use)

messageSelector (The JMS Message Selector to use on the subscription to perform content based routing filtering the messages)

noLocal (Only required for topic subscriptions; indicates if locally published messages should be included in the subscription or not)

password (The password for the JMS connection)

subscriptionDurability (Whether or not a durable (topic) subscription is required. Valid values are: Durable or NonDurable)

subscriptionName (The name of the durable subscriber. Only used for durable topics and combined with the clientID to uniquely identify the durable topic subscription)

userName (The user for the JMS connection)

useRAManagedTransaction (Typically, a resource adapter delivers messages to an endpoint which is managed by a container. Normally, this container likes to be the one that wants to control the transaction that the inbound message is being delivered on. But sometimes, you want to deliver to a simpler container system that will not be controlling the inbound transaction. In these cases, if you set useRAManagedTransaction to true, the resource adapter will commit the transaction if no exception was generated from the MessageListener and rollback if an exception is thrown.)

initialRedeliveryDelay (The delay before redeliveries start. Also configurable on the ResourceAdapter)

maximumRedeliveries (The maximum number of redeliveries or -1 for no maximum. Also configurable on the ResourceAdapter)

redeliveryBackOffMultiplier (The multiplier to use if exponential back off is enabled. Also configurable on the ResourceAdapter)

redeliveryUseExponentialBackOff (To enable exponential backoff. Also configurable on the ResourceAdapter useJndi no false when true, use destination as a jndi name)

Java EE7 spec lists the following Activation Spec Properties:

acknowledgeMode (This property is used to specify the JMS acknowledgement mode for the message delivery when bean-managed transaction demarcation is used. Its values are Auto_acknowledge or Dups_ok_acknowledge. If this property is not specified, JMS AUTO_ACKNOWLEDGE semantics are assumed.

messageSelector (This property is used to specify the JMS message selector to be used in determining which messages a JMS message driven bean is to receive)

destinationType (This property is used to specify whether the message driven bean is intended to be used with a queue or a topic. The value must be either javax.jms.Queue or javax.jms.Topic.)

destinationLookup (This property is used to specify the JMS queue or topic from which a JMS message-driven bean is to receive messages.)

connectionFactoryLookup (This property is used to specify the JMS connection factory that will be used to connect to the JMS provider from which a JMS message-driven bean is to receive messages.)

subscriptionDurability (If the message driven bean is intended to be used with a topic, this property may be used to indicate whether a durable or non-durable subscription should be used. The value of this property must be either Durable or NonDurable)

subscriptionName (This property is used to specify the name of the durable subscription if the message-driven bean is intended to be used with a Topic, and the bean provider has indicated that a durable subscription should be used.)

clientId (This property is used to specify the JMS client identifier that will be used when connecting to the JMS provider from which a JMS message-driven bean is to receive messages. If this property is not specified then the client identifier will be left unset.)

What is the proper way to use an ActiveMQ resource in both a producer and consumer with only @Inject points and jndi lookup? I'd like to avoid the glassfish-ejb-jar.xml and defining the queue name with an @ActivationConfigProperty.

like image 518
John Manko Avatar asked Sep 27 '22 22:09

John Manko


1 Answers

Yes, every application server does things a bit differently. More importantly, they do it differently not on how you configure it - that part is simple, but on the runtime behavior when you expect from the JMS server an SLA such as ordered message processing - even in the case of failure.

For example, if you have a business critical process wheren Message 2 can only be processed after Message 1. And your message 1 fails, and you want to be retried, but you also have configured a redelivery delay of 200 ms. Some application servers, will by default think: message 1 failed, I retry it in 200 ms, jumpt to next message ... And kabum, business process is dead because your expecation of ordered message consumption was just violated.

Normally, good JMS servers offer the ability to configure it in such a way that you can meet your required SLA ... but it is tricky.

As a rule, you should configured on your MDB through annotations, any property that is cross cutingly working accross multiple application servers. Normally, the JNDI naming can work - but it is tricky, because the JNDI is highly container dependent. Properties such as: - Activation Propertu: destinationType = javax.jms.Topic

This is quite quite standard, so you can just put it through annotation.

But then when you come to the tricky aspects, such as specifying the connection factory to connect to the destination. Or should you allow the JMS server to batch read N messages at once, or do you want to force it one-at a time, etc... This is highly dependent on your container, and you will want to configure this not through annotation by by the ejb deployment descriptor.

For example, in weblogic, you would want to use: weblogic-ejb-jar.xml To fine tune thing such as JNDI name to access the queue, max-beans-in-free-pool etc...

In Wildfly, where ActiveMQ is used, you would want ot use the: jboss-ejb3.xml

Deployment descriptor to do this.

So, through annotations - you should the greates common denominator of cross cutingly equivalent metadata across container. In the deployment descriptor, you enrich the configuration with the lacking metadata.

The good application servers, will always be doing a merge process where they combine the Metadata on the MDB with the metadata on the deployment descriptor. And when there is a collision, they take over the configuration on the deployment descriptor.

And so on.

So somethings, you really need to adjust pert container in the container supported deployment descriptor. In your java code, you should only keep the metadata that is cross cutingly compatible.

And finally, getting the exact JMS behavior for message handling accross different application servers that use different JMS server implementations ... is quite quite tricky.

Unless you have the basic scneario that does not care about ordered processing, you have multiple MDBs running in parallel on a queue because there is not happens before relationship... then it is trivial to get a slopy configuration to work.

like image 66
99Sono Avatar answered Sep 30 '22 08:09

99Sono