Durabilità delle Topic
Una proprietà interessante, impostabile con @ActivationConfigProperty
, è la durabilità per una Topic. Nel funzionamento publish and subscribe, di default, una copia del messaggio è inviata a tutti i subscriber connessi. Se un subscriber non dovesse essere connesso nella fase di ricezione di un messaggio, lo perderebbe inevitabilmente.
Impostando la durabilità su Durable
, il subscriber non connesso riceverà il messaggio nel momento in cui effettua la connessione. Questo grazie al fatto che il MOM
conserva per lui una copia del messaggio in quanto registrato come subscriber durevole. Affinché un MDB sia un subscriber durevole, possiamo utilizzare l'attributo activationConfig
nel seguente modo:
activationConfig={
@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(
propertyName="destinationLookup",
propertyValue="java:jboss/exported/jms/topic/ShippingTopic"),
@ActivationConfigProperty(
propertyName="subscriptionDurability",
propertyValue="Durable")
}
Prima di definire un caso di test nella classe IntegrationTestCase
per l'MDB creato, disabilitiamo l'autenticazione del MOM utilizzato da JBoss Wildfly per evitare errori. A tal fine individuiamo il tag Hornetq
nel file standalone-full.xml
e impostiamo il tag security-enabled
su false
:
<hornetq-server>
<journal-file-size>102400</journal-file-size>
<security-enabled>false</security-enabled>
.....
JUnit test e connessione JMS
Possiamo ora definire un JUnit test che effettua una connessione JMS, costruisce un produttore di messaggi per ShippingQueue
e invia un messaggio testuale:
....
private static final String JMS_CONNECTION_FACTORY_JNDI_NAME="/jms/RemoteConnectionFactory";
private static final String JMS_QUEUE_JNDI_NAME="/jms/queue/ShippingQueue";
....
@Test
public void testShippingMDB() throws NamingException, JMSException {
ConnectionFactory connectionFactory = (ConnectionFactory)
namingContext.lookup(JMS_CONNECTION_FACTORY_JNDI_NAME);
Queue shippingQueue = (Queue)namingContext.lookup(JMS_QUEUE_JNDI_NAME);
Connection connect = connectionFactory.createConnection();
Session session = connect.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(shippingQueue);
TextMessage textMsg = session.createTextMessage();
textMsg.setText("Order message!");
producer.send(textMsg);
connect.close();
}
Esaminiamo il codice. Un oggetto ConnectionFactory
incapsula le informazioni di configurazione per il collegamento al MOM, nel nostro caso Hornetq
di JBoss. Oltre al collegamento al MOM dobbiamo recuperare la Queue sulla quale inviare il messaggio. Con questi oggetti possiamo aprire una connessione JMS, creare una sessione, un produttore di messaggi (in questo caso testuali) e infine inviare un messaggio.
Per ragioni di efficienza l'oggetto javax.jms.Connection
rappresenta una connessione thread-safe verso il MOM, questo significa che una connessione JMS può essere condivisa.
Utilizzando il metodo createSession()
di Connection
, otteniamo un oggetto javax.jsm.Session
che è invece un task a singolo thread necessario per poter inviare e ricevere messaggi.
Il primo parametro di createSession()
specifica se la sessione è transazionale o meno. In caso di valore true
, l'effettivo invio del messaggio avviene sulla chiamata del metodo commit()
o alla chiusura della sessione. In caso di valore false
il messaggio viene inviato prima possibile.
Il secondo parametro indica la modalità di acknowledge, cioè il consenso alla rimozione del messaggio dalla Queue. In generale il messaggio non viene rimosso fino a quando il consumer non lo abbia ricevuto dando il consenso alla rimozione. La modalità Session.AUTO_ACKNOWLEDGE
prevede la rimozione del messaggio non appena ricevuto dal consumer
.
Non possiamo inviare messaggi attraverso un oggetto Session
, ma abbiamo bisogno di ottenere un produttore di messaggi javax.jms.MessageProducer
, creare un particolare tipo di messaggio, ad esempio javax.jms.TextMessage
, ed infine inviarlo con il metodo send()
.
MDB e interfacce senza metodi
A partire dalla versione EJB 3.2 gli MDB possono implementare interfacce senza metodi. Questa caratteristica permette di esporre tutti i metodi pubblici del Bean e delle classi eredidate ad eccezione di Object
. In pratica possiamo fare in modo che un MDB riceva messaggi da più canali eseguendo un particolare metodo per un messaggio ricevuto da un canale. Vediamo la versione EJB 3.2 del bean shipping processor
che chiamiamo ShippingProcessor2
:
package it.html.progetto1.mdb.ejb32;
import javax.ejb.MessageDriven;
import org.jboss.ejb3.annotation.ResourceAdapter;
import it.html.progetto1.mdbconnect.NoMethodsListenerIntf;
@MessageDriven(
name="ShippingProcessor2"
)
@ResourceAdapter(value="Progetto1Ear.ear#Progetto1Connector.rar")
public class ShippingProcessor2 implements NoMethodsListenerIntf {
public void order1(String message){
System.out.println("Order1 invoked:"+message);
}
public void order2(String message){
System.out.println("Order2 invoked:"+message);
}
}
L'annotation @ResourceAdapter
è propria di JBoss e consente di specificare un particolare resource adapter per l'MDB. Il resource adapter che dobbiamo implementare è un connettore JCA che ascolta su diverse code e, attraverso l'uso della reflection, è in grado di invocare order1()
o order2()
.
Attraverso il menù "File" di JBoss Developer Studio creiamo un Connector Project di nome
Progetto1Connector
e lo inseriamo nel Build Path del progetto ProgettoEjb1
. Aggiungiamo inoltre Progetto1Connector
alle proprietà Project References
e Deployment Assembly
del progetto Progetto1Ear
.
In questo modo il connettore verrà installato come modulo Progetto1Connector.rar
del pacchetto applicativo Progetto1Ear.ear
. In Progetto1Connector
, nel source folder connectorModule
, definiamo il package it.html.progetto1.mdbconnect
; al suo interno iniziamo con il definire l'interfaccia senza metodi implementata dall'MDB:
package it.html.progetto1.mdbconnect;
public interface NoMethodsListenerIntf {}
E la classe che definisce un ActivationSpec
per il collegamento ad una Queue e il recupero di messaggi:
package it.html.progetto1.mdbconnect;
import java.io.Serializable;
import javax.resource.ResourceException;
import javax.resource.spi.Activation;
import javax.resource.spi.ActivationSpec;
import javax.resource.spi.InvalidPropertyException;
import javax.resource.spi.ResourceAdapter;
@Activation(
messageListeners = {NoMethodsListenerIntf.class}
)
public class MdbEventSpecification implements ActivationSpec, Serializable {
private static final long serialVersionUID = 6335290773122081421L;
private ResourceAdapter resourceAdapter;
@Override
public ResourceAdapter getResourceAdapter() {
return resourceAdapter;
}
@Override
public void setResourceAdapter(ResourceAdapter resourceAdapter) throws ResourceException {
this.resourceAdapter = resourceAdapter;
}
@Override
public void validate() throws InvalidPropertyException {
}
}