I am trying to achieve a distributed transaction in Apache TomEE. In words, the flow is:
A message reader (i.e. a message driven bean) reads from a queue (1) and processes one message triggering:
one or more inserts/updates on a database (2)
sending a message to another queue (3)
Operations 1, 2, & 3 are all part of the same XA transaction controlled by TomEE. Therefore, under any circumstances they either all fail or all succeed.
tomee.xml
<?xml version="1.0" encoding="UTF-8"?>
<tomee>
this resource adapter is just necessary to tell tomee to not start internal ActiveMq instance
<Resource id="MyAdapter" type="ActiveMQResourceAdapter">
BrokerXmlConfig
ServerUrl tcp://fakehost:666
</Resource>
<Resource id="jms/MyIncomingConnFactory" type="javax.jms.ConnectionFactory" class-name="org.apache.activemq.ActiveMQXAConnectionFactory">
BrokerURL tcp://externalhost:61616?jms.redeliveryPolicy.maximumRedeliveries=0
</Resource>
<Resource id="jms/MyOutgoingConnFactory" type="javax.jms.ConnectionFactory" class-name="org.apache.activemq.ActiveMQXAConnectionFactory">
BrokerURL tcp://externalhost:61616?jms.redeliveryPolicy.maximumRedeliveries=0
</Resource>
<Resource id="jms/MyOutgoingQueue" class-name="org.apache.activemq.command.ActiveMQQueue">
PhysicalName MY_OUTGOING_QUEUE
</Resource>
<Resource id="jms/MyIncomingQueue" class-name="org.apache.activemq.command.ActiveMQQueue">
PhysicalName MY_INCOMING_QUEUE
</Resource>
<Resource id="jdbc/myDBXAPooled" type="DataSource">
XaDataSource myDBXA
DataSourceCreator dbcp
JtaManaged true
UserName TestUser
Password TestPassword
MaxWait 2000
ValidationQuery SELECT 1
MaxActive 15
</Resource>
<Resource id="myDBXA" type="XADataSource" class-name="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
Url jdbc:mysql://localhost:3306/test
User TestUser
Password TestPassword
</Resource>
</tomee>
Springconfig.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd">
<!-- <jee:jndi-lookup jndi-name="myDBXAPooled" id="myDatasource" resource-ref="true" /> -->
<jee:jndi-lookup jndi-name="jms/MyOutgoingConnFactory" id="myOutgoingConnFactory" resource-ref="true" />
<jee:jndi-lookup jndi-name="jms/MyIncomingConnFactory" id="myIncomingConnFactory" resource-ref="true" />
<jee:jndi-lookup jndi-name="jms/MyOutgoingQueue" id="myOutgoingQueue" resource-ref="true" />
<jee:jndi-lookup jndi-name="jms/MyIncomingQueue" id="myIncomingQueue" resource-ref="true" />
<jee:jndi-lookup jndi-name="jdbc/myDBXAPooled" id="myDatasource" resource-ref="true" />
<tx:jta-transaction-manager/>
<!-- <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> -->
<!-- the previous two ways of getting the transactionManager seems equivalent and both get Geronimo -->
</beans>
SpringConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.2.xsd">
<bean id="messageListener" class="com.test.MyListener">
<property name="connectionFactory" ref="myIncomingConnFactory" />
<property name="destination" ref="myIncomingQueue" />
<!-- <property name="sessionTransacted" value="true" /> -->
<property name="concurrentConsumers" value="1" />
<property name="maxConcurrentConsumers" value="6" />
<property name="messageListener" ref="myMessageProcessor" />
<property name="transactionManager" ref="transactionManager" />
<property name="taskExecutor" ref="msgListenersTaskExecutor" />
</bean>
<bean id="myMessageProcessor" class="com.test.MyMessageReceiver">
<property name="forwardConnectionFactory" ref="myOutgoingConnFactory" />
<property name="forwardQueue" ref="myOutgoingQueue" />
<property name="datasource" ref="myDatasource" />
</bean>
<bean id="msgListenersTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"/>
</beans>
MyMessageReceiver.java:
package com.test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class MyMessageReceiver implements MessageListener {
static Logger log = Logger.getLogger(MyMessageReceiver.class);
private ConnectionFactory forwardConnectionFactory;
private Queue forwardQueue;
private DataSource datasource;
public void setForwardConnectionFactory(ConnectionFactory connFactory) {
forwardConnectionFactory=connFactory;
}
public void setforwardQueue(Queue queue) {
forwardQueue=queue;
}
public void setDatasource(DataSource ds) {
datasource=ds;
}
#Override
#Transactional(propagation=Propagation.REQUIRED)
public void onMessage(Message message) {
log.info("************************************");
MyListener listener = (MyListener)SpringContext.getBean("messageListener");
listener.printInfo();
log.info("************************************");
TextMessage msg = (TextMessage) message;
String text = null;
try {
text = msg.getText();
if (text != null) log.info("MESSAGE RECEIVED: "+ text);
updateDB(text); // function call to update DB
sendMsg(text); // function call to publish messages to queue
System.out.println("****************Rollback");
// Throwing exception to rollback DB, Message should not be
// published and consumed message sent to a DLQ
//(Broker side DLQ configuration already done)
throw new RuntimeException();
//if (text!=null && text.indexOf("rollback")!=-1) throw new RuntimeException("Message content includes the word rollback");
} catch (Exception e) {
log.error("Rolling back the entire XA transaction");
log.error(e.getMessage());
throw new RuntimeException("Rolled back because of "+e.getMessage());
}
}
private void updateDB(String text) throws Exception {
Connection conn = null;
PreparedStatement ps = null;
try {
System.out.println("*******datasource "+datasource);
conn = datasource.getConnection();
System.out.println("*******conn "+conn.getMetaData().getUserName());
if (conn!=null) {
System.out.println("*******conn "+conn.getMetaData().getUserName());
ps = conn.prepareStatement("INSERT INTO MY_TABLE (name) VALUES(?)");
ps.setString(1, text);
ps.executeUpdate();
}
} catch (Exception e) {
throw e;
} finally {
if (ps!=null) {
try {
ps.close();
} catch (SQLException e) {
log.error(e.getMessage());
// do nothing
}
}
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
log.error(e.getMessage());
// do nothing
}
}
}
}
private void sendMsg(String msgToBeSent) throws Exception {
javax.jms.Connection conn = null;
Session session = null;
try {
System.out.println("*************forwardConnectionFactory"+forwardConnectionFactory);
conn = forwardConnectionFactory.createConnection();
session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
MessageProducer messageProducer = session.createProducer(forwardQueue);
TextMessage msg = session.createTextMessage(msgToBeSent);
messageProducer.send(msg);
} catch (Exception e) {
throw e;
} finally {
if (session != null) {
try {
session.close();
} catch (JMSException e) {
// do nothing
}
}
if (conn != null) {
try {
conn.close();
} catch (JMSException e) {
// do nothing
}
}
}
}
}
MyListener.java:
package com.test;
import javax.transaction.Status;
import javax.transaction.SystemException;
import org.apache.log4j.Logger;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import org.springframework.transaction.jta.JtaTransactionManager;
public class MyListener extends DefaultMessageListenerContainer {
static Logger log = Logger.getLogger(MyListener.class);
public void printInfo() {
try {
log.info("trans manager="+((JtaTransactionManager)this.getTransactionManager()).getTransactionManager()+","+((JtaTransactionManager)this.getTransactionManager()).getTransactionManager().getStatus()+", this.isSessionTransacted()="+this.isSessionTransacted());
log.info("STATUS_ACTIVE="+Status.STATUS_ACTIVE);
log.info("STATUS_COMMITTEDE="+Status.STATUS_COMMITTED);
log.info("STATUS_COMMITTING="+Status.STATUS_COMMITTING);
log.info("STATUS_MARKED_ROLLBACK="+Status.STATUS_MARKED_ROLLBACK);
log.info("STATUS_NO_TRANSACTION="+Status.STATUS_NO_TRANSACTION);
log.info("STATUS_PREPARED="+Status.STATUS_PREPARED);
log.info("STATUS_PREPARING="+Status.STATUS_PREPARING);
log.info("STATUS_ROLLEDBACK="+Status.STATUS_ROLLEDBACK);
log.info("STATUS_ROLLING_BACK="+Status.STATUS_ROLLING_BACK);
log.info("STATUS_UNKNOWN="+Status.STATUS_UNKNOWN);
} catch (SystemException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void forceRollback() {
try {
((JtaTransactionManager)this.getTransactionManager()).getTransactionManager().setRollbackOnly();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SystemException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
After updating the database and sending the message to the outgoing queue I am purposefully throwing a RuntimeException just to test the transaction rollback of both the database and the message broker.
All three operations are committed in case of success, but it only rolls back the database operation in case of failure, while the two JMS operations are committed anyway.
It could either be:
wrong settings in my tomee.xml (especially queue connection factories) or somewhere else
a bug??
I already spent quite much time fighting with the thing and searching for possible solutions.
It would be great to hear your opinion on this and, once again, advanced apologies if it turns out to be a mistake on my side.
I believe you need to use the ActiveMQ JCA resource adapter to ensure that connections are automatically enlisted into the XA transaction. Try this:
<tomee>
<Resource id="MyJmsResourceAdapter" type="ActiveMQResourceAdapter">
# Do not start the embedded ActiveMQ broker
BrokerXmlConfig =
ServerUrl = tcp://externalhost:61616?jms.redeliveryPolicy.maximumRedeliveries=0
</Resource>
<Resource id="jms/MyIncomingConnFactory" type="javax.jms.ConnectionFactory">
resourceAdapter = MyJmsResourceAdapter
transactionSupport = xa
</Resource>
<Resource id="jms/MyOutgoingConnFactory" type="javax.jms.ConnectionFactory">
resourceAdapter = MyJmsResourceAdapter
transactionSupport = xa
</Resource>
<Resource id="jms/MyOutgoingQueue" type="javax.jms.Queue"/>
<Resource id="jms/MyIncomingQueue" type="javax.jms.Queue"/>
<Resource id="jdbc/myDBXAPooled" type="DataSource">
XaDataSource myDBXA
DataSourceCreator dbcp
JtaManaged true
UserName TestUser
Password TestPassword
MaxWait 2000
ValidationQuery SELECT 1
MaxActive 15
</Resource>
<Resource id="myDBXA" type="XADataSource" class-name="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource">
Url jdbc:mysql://localhost:3306/test
User TestUser
Password TestPassword
</Resource>
</tomee>
Related
I downloaded latest ActiveMQ Artemis 2.10.1 (Windows 10, JDK 8) and can't get the address-settings to take affect. Reading the documentation (not much) on line I edited the broker.xml by adding:
<address-settings>
<address-setting match="BETATESTQ">
<dead-letter-address>BETATESTQ_DLQ</dead-letter-address>
<expiry-address>BETATESTQ_EXPIRY</expiry-address>
<redelivery-delay>30000</redelivery-delay>
<redelivery-delay-multiplier>1.5</redelivery-delay-multiplier>
<redelivery-collision-avoidance-factor>0.15</redelivery-collision-avoidance-factor>
<max-redelivery-delay>100000</max-redelivery-delay>
<max-delivery-attempts>999</max-delivery-attempts>
</address-setting>
</address-settings>
<addresses>
<address name="BETATESTQ_DLQ">
<anycast>
<queue name="BETATESTQ_DLQ" />
</anycast>
</address>
<address name="BETATESTQ_EXPIRY">
<anycast>
<queue name="BETATESTQ_EXPIRY" />
</anycast>
</address>
<address name="BETATESTQ">
<anycast>
<queue name="BETATESTQ" />
</anycast>
</address>
</addresses>
Everything else is default broker.xml when create a broker.
It seems to always use default values for redelivery-delay, max-redelivery-delay, max-delivery-attempts. It does read the dead letter and expiry values correctly. I can see the message getting retried in TomEE logs and shows up in Artemis console and moves to dead letter queue when done.
I am not passing in and delivery or retry data when put it on the queue initially (using bare minimal Java J2EE code).
How can I get it to not ignore redelivery-delay, max-redelivery-delay, max-delivery-attempts?
I connected to the queue with Apache TomEE (not using built-in ActiveMQ 5.x in TomEE but pointing it to ActiveMQ Artemis)
JMS listener:
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.*;
#MessageDriven(name = "BETATESTQ", activationConfig = {
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "BETATESTQ"),
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")
})
public class BetaTestQueueListener implements MessageListener, java.io.Serializable {
private static final long serialVersionUID = 1L;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message rcvMessage) {
System.out.println("omMessage throw runtime exception");
throw new RuntimeException("trigger retry");
}
}
12/20/19 - Working values I found for TomEE 8.0.0:
tomee.xml:
<?xml version="1.0" encoding="UTF-8"?>
<tomee>
<Resource id="artemis" class-name="org.apache.activemq.artemis.ra.ActiveMQResourceAdapter">
ConnectorClassName=org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
ConnectionParameters=host=127.0.0.1;port=61617;needClientAuth=false;sslEnabled=true;keyStorePath=../ssl/server_jks_keystore.jks;keyStorePassword=mypassword;trustStorePath=../ssl/client_jks_truststore.jks;trustStorePassword=mypassword;trustAll=true;verifyHost=false;wantClientAuth=false;needClientAuth=false;keyStoreProvider=JKS;trustSToreProvider=JKS
UserName=admin
Password=admin
JndiParams=java.naming.factory.initial=org.apache.openejb.core.OpenEJBInitialContextFactory
</Resource>
<Resource id="MyJmsConnectionFactory"
class-name="org.apache.activemq.artemis.jms.client.ActiveMQXAConnectionFactory"
constructor="uri,username,password"
type="javax.jms.ConnectionFactory">
uri=tcp://localhost:61617?needClientAuth=false;sslEnabled=true;keyStorePath=C:/apache-tomee-plus-8.0.0/ssl/server_jks_keystore.jks;keyStorePassword=mypassword;trustStorePath=C:/apache-tomee-plus-8.0.0/ssl/client_jks_truststore.jks;trustStorePassword=mypassword;trustAll=true;verifyHost=false;wantClientAuth=false;needClientAuth=false;keyStoreProvider=JKS;trustSToreProvider=JKS
username=admin
password=admin
TransactionSupport=xa
PoolMaxSize=20
PoolMinSize=0
</Resource>
<Resource id="BETATESTQ_DLQ"
class-name="org.apache.activemq.artemis.api.jms.ActiveMQJMSClient"
constructor="name"
factory-name="createQueue"
type="javax.jms.Queue">
name=BETATESTQ_DLQ
</Resource>
<Resource id="BETATESTQ"
class-name="org.apache.activemq.artemis.api.jms.ActiveMQJMSClient"
constructor="name"
factory-name="createQueue"
type="javax.jms.Queue">
name=BETATESTQ
</Resource>
<Container id="mdb" type="MESSAGE">
InstanceLimit = -1
ResourceAdapter = artemis
ActivationSpecClass = org.apache.activemq.artemis.ra.inflow.ActiveMQActivationSpec
</Container>
</tomee>
Java EE class to send JMS message:
import java.util.*;
import javax.jms.*;
import org.slf4j.*;
public final class JmsPublisherInstance2 implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(JmsPublisherInstance2.class);
public void send( String msg,
ConnectionFactory connectionFactory,
Queue queue ) throws Exception {
Session session = null;
MessageProducer producer = null;
TextMessage message = null;
Connection connection = null;
try {
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(true, Session.SESSION_TRANSACTED);
producer = session.createProducer(queue);
message = session.createTextMessage(msg);
producer.send(message);
session.commit();
} catch (Exception e) {
session.rollback();
LOG.error(e.getMessage(), e);
throw e;
} finally {
if (session!=null ) {
session.close();
}
if (connection!=null ) {
connection.close();
}
}
}
}
Java EE listener:
import java.io.*;
import javax.annotation.*;
import javax.ejb.*;
import javax.jms.*;
#MessageDriven ( name = "BetaTESTQMDB" , activationConfig = {
#ActivationConfigProperty(propertyName="destinationType", propertyValue = "javax.jms.Queue") ,
#ActivationConfigProperty(propertyName="destination", propertyValue = "BetaTESTQ"),
#ActivationConfigProperty(propertyName="maxSession", propertyValue = "5"),
#ActivationConfigProperty(propertyName="acknowledgeMode", propertyValue = "Auto-acknowledge")
})
public class BetaTestQueueListener implements MessageListener, java.io.Serializable {
private static final long serialVersionUID = 1L;
#Resource(name="MyJmsConnectionFactory")
private ConnectionFactory connectionFactory;
#Resource
private MessageDrivenContext mdbContext;
#Resource(name = "BETATESTQ")
private javax.jms.Queue betaTestQ;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message rcvMessage) {
try {
jmsInstance.send("test message", connectionFactory, betaTestQ);
} catch (Throwable t) {
t.printStackTrace();
mdbContext.setRollbackOnly();
}
}
}
It seems likely that you're using the OpenWire JMS client. This complicates matters because, as I understand it, the OpenWire JMS client implements redelivery in the client itself and those redelivery semantics are configured on the client side not the broker side. The broker doesn't have a chance to apply any kind of redelivery policy because the OpenWire client handles everything and doesn't inform the broker of the delivery failures. This documentation may help you configure redelivery for your OpenWire client.
That said, I recommend following this tutorial. It demonstrates how to integrate TomEE and ActiveMQ Artemis without building/deploying the JCA RA. This will allow you to use the Core JMS client rather than the OpenWire JMS client.
If you want to go the RA route you can build the ActiveMQ Artemis JCA RA by running mvn verify from the examples/features/sub-modules/artemis-ra-rar/ directory. The RA will be in the target directory named artemis-rar-<version>.rar.
In Production Environment,When I make Request for my web page.The Request show in apache's access logs,but not in configured my configured web logs.
My logback.xml is
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml" />
<jmxConfigurator />
<property name="CONSOLE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%15.15t{14}] %-40.40logger{0} : %m%n" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>/var/log/company_name/payments.log</file>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
And My controller is
#CrossOrigin
#RestController
#RequestMapping("/my/v1")
public class PaymentApi {
#RequestMapping(value = RestURIConstants.VOID_TRANSACTION,
method = RequestMethod.POST )
public #ResponseBody ResponseEntity<?> voidPayment(#RequestParam("request") String voidRequestJson,
HttpServletRequest httpServletRequest) {
//Code here.
}
#ExceptionHandler
void handleException(Exception e, HttpServletResponse response) throws IOException {
logger.error("Error in Request : " + new ObjectMapper().writeValueAsString(e));
if (e instanceof IllegalArgumentException) {
response.sendError(HttpStatus.BAD_REQUEST.value(), e.getMessage());
return;
}
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(), "an error occured while processing request");
}
}
I am making web Request using below code.
public static <T extends Response,S extends Request> T doPostRequest(S request,
Class<T> responseClass,
String urlString) throws ClientProtocolException, IOException{
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
logger.info("Sending Request " + objectMapper.writeValueAsString(request));
HttpEntity<String> entity = new HttpEntity<String>("request=" + objectMapper.writeValueAsString(request), headers);
ResponseEntity<String> response = null ;
response = restTemplate.exchange(urlString, HttpMethod.POST, entity, String.class);
logger.info("Response recieved " + response.toString());
if (response.getStatusCode() == HttpStatus.OK ||
response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
return objectMapper.readValue(response.getBody(), responseClass) ;
// gson.fromJson(response.getBody(), responseClass) ;
}else{
return objectMapper.readValue(response.getBody(), responseClass) ;
}
}
Same code is working for my local ,But in production it throws 400 Error and also there are no logs in payments.log file.
The Java version in production on my payment server is on 1.8.0_25 and server which is making these request are on 1.8.0_91.
I am unable to identify the reasons as there are no logs are present for this web request in payments.log
their is limit on header size on production.Because of that i am getting this issue and there are no logs in payments.log.
I’m developing a web app with JSF and Hibernate, and I’m facing a problem with Hibernate. At some point, I want to INSERT a row in a table. Just after that, I want to retrieve the 3 last inserted elements of the same table. The problem is that the INSERT is being made correctly (I checked that with a DB Client), but when I try to retrieve those 3 elements, the one I have just inserted is not returned by the SELECT statement. It seems like a kind of hibernate session problem, not reflecting the change when I make the SELECT. I attach the relevant part of the code:
hibernate.cfg.xml:
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/habana</property>
<property name="hibernate.connection.username">root</property>
<property name="connection.password"></property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="current_session_context_class">thread</property>
<property name="show_sql">true</property>
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.cache.use_query_cache">false</property>
<mapping class="org.blk.lahabana.model.ClaseMovimiento"/>
<mapping class="org.blk.lahabana.model.Devolucion"/>
<mapping class="org.blk.lahabana.model.DevolucionProducto"/>
<mapping class="org.blk.lahabana.model.DevolucionProductoId"/>
<mapping class="org.blk.lahabana.model.Evento"/>
<mapping class="org.blk.lahabana.model.Movimiento"/>
<mapping class="org.blk.lahabana.model.Pedido"/>
<mapping class="org.blk.lahabana.model.PedidoProducto"/>
<mapping class="org.blk.lahabana.model.PedidoProductoId"/>
<mapping class="org.blk.lahabana.model.Permiso"/>
<mapping class="org.blk.lahabana.model.Producto"/>
<mapping class="org.blk.lahabana.model.TipoProducto"/>
<mapping class="org.blk.lahabana.model.Trabajador"/>
<mapping class="org.blk.lahabana.model.Turno"/>
<mapping class="org.blk.lahabana.model.TurnoTrabajador"/>
<mapping class="org.blk.lahabana.model.TurnoTrabajadorId"/>
<mapping class="org.blk.lahabana.model.Usuario"/>
</session-factory>
</hibernate-configuration>
HibernateUtil:
public class HibernateUtil {
/** Logger for this class and subclasses */
protected static final Log logger = LogFactory.getLog(HibernateUtil.class);
private static SessionFactory sessionFactory;
static {
try {
sessionFactory = new AnnotationConfiguration().configure()
.buildSessionFactory();
} catch (Throwable ex) {
logger.error("Error al crear el sessionFactory de Hibernate",ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
// Alternatively, we could look up in JNDI here
return sessionFactory;
}
public static void closeSession() {
// Close caches and connection pools
getSessionFactory().close();
}
}
EventsController:
public String create(){
String view = "";
Integer id = eventosService.createEvent(evento);
if(id !=null){
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Evento Guardado", "El evento se ha guardado con éxito");
FacesContext.getCurrentInstance().addMessage(null, facesMsg);
}
else{
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Evento No Guardado", "Se ha producido un error al guardar el evento");
FacesContext.getCurrentInstance().addMessage(null, facesMsg);
}
view = dashboardController.input();
return view;
}
#Override
public Integer createEvent(Evento event) {
Integer result = null;
event.setEstado(Constants.EVENT_STATE_PLANED);
boolean saved = eventoDao.save(event);
if( saved ){
result = event.getId();
}
return result;
}
public boolean save(T entity) {
Session hbsession = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction transaction = null;
boolean result = false;
try {
transaction = hbsession.beginTransaction();
hbsession.save(entity);
hbsession.flush();
transaction.commit();
result = true;
} catch (HibernateException e) {
logger.error("Error al guardar una entidad entidad",e);
if(transaction != null){
transaction.rollback();
}
}
finally{
if(hbsession != null && hbsession.isOpen()){
hbsession.close();
}
}
return result;
}
DashboardController:
public String input(){
String view = "dashboard";
// Obtenemos los útimos eventos
List<Evento> events = eventosService.getLastEvents();
// Convertimos los eventos al VO para la vista
this.lastEvents = new ArrayList<EventoResumenVO>();
for (Evento evento : events) {
this.lastEvents.add(eventoResumenConverter.convertFromEvent(evento));
}
return view;
}
#Override
public List<Evento> getLastEvents() {
return eventoDao.findLastEvents(3);
}
#SuppressWarnings("unchecked")
#Override
public List<Evento> findLastEvents(Integer number) {
Session hbsession = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction transaction = null;
List<Evento> result = null;
try {
transaction = hbsession.beginTransaction();
Criteria criteria = hbsession.createCriteria(entityClass);
criteria.setFetchMode("movimientos", FetchMode.JOIN);
criteria.addOrder(Order.desc("fechaFin"));
criteria.setMaxResults(number.intValue());
result = criteria.list();
} catch (HibernateException e) {
logger.error("Error al recuperar la lista de entidades",e);
if(transaction != null){
transaction.rollback();
}
}
finally{
if(hbsession != null && hbsession.isOpen()){
hbsession.close();
}
}
return result;
}
Thanks for helping!
im trying to implement an integration test on RabbitMQ, so i have a setup of a published and basic consumer, to send and receive messages across a queue.
try
{
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername("guest");
factory.setPassword("guest");
factory.setHost("localhost");
factory.setPort(5672);
connection = factory.newConnection();
channel = connection.createChannel();
channel.queueDeclare(RPC_QUEUE_NAME_KEY, DURABLE, EXCLUSIVE, AUTO_DELETE, null);
channel.exchangeDeclare(RPC_EXCHANGE_NAME, "direct");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, RPC_EXCHANGE_NAME, RPC_QUEUE_NAME_KEY);
channel.basicQos(1);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, false, consumer);
System.out.println(" [x] Awaiting RPC requests");
while(true)
{
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
BasicProperties props = delivery.getProperties();
BasicProperties replyProps = new BasicProperties.Builder().correlationId(props.getCorrelationId()).build();
try
{
RetrieveUnencryptedCardsResponse response = null;
JAXBContext jaxbContext = JAXBContext.newInstance(RetrieveUnencryptedCardsRequest.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
StringReader reader = new StringReader(new String(delivery.getBody()));
JAXBElement<RetrieveUnencryptedCardsRequest> message = jaxbUnmarshaller.unmarshal(new StreamSource(reader), RetrieveUnencryptedCardsRequest.class);
response = retrieveUnencryptedCardsResponse(message);
JAXBContext jaxbMarshallingContext = JAXBContext.newInstance(RetrieveUnencryptedCardsResponse.class);
Marshaller jaxbMarshaller = jaxbMarshallingContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
ByteArrayOutputStream xmlStream = new ByteArrayOutputStream();
jaxbMarshaller.marshal(new JAXBElement<RetrieveUnencryptedCardsResponse>(_RetrieveUnencryptedCardsResponse_QNAME, RetrieveUnencryptedCardsResponse.class, null,
response), xmlStream);
data = xmlStream.toByteArray();
xmlStream.close();
}
catch(Exception e)
{
logger.error(e);
}
finally
{
channel.basicPublish("", props.getReplyTo(), replyProps, data);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
if(connection != null)
{
try
{
connection.close();
}
catch(Exception ignore)
{
}
}
}
}
The problem occurs when I try to receive the messages from the queue. i cant receive the first message, but i can from the second one.
i don't know if this might help:
<rabbit:queue id="account.amqp.default.reply.queue"
name="${vendor.account.amqp.default.reply.queue}" durable="${connector.fixed.queue.durable}"
auto-delete="false" exclusive="false">
<rabbit:queue-arguments>
<entry key="x-message-ttl">
<value type="java.lang.Long">${vendor.account.amqp.default.reply.ttl}</value>
</entry>
<entry key="x-ha-policy" value="${vendor.account.default.queue.ha.policy}" />
<entry key="x-ha-policy-params"
value="#{ T(org.springframework.util.CollectionUtils).arrayToList((T(org.springframework.util.StringUtils).commaDelimitedListToSet('${vendor.account.default.queue.ha.nodes}')).toArray())}" />
</rabbit:queue-arguments>
</rabbit:queue>
<rabbit:template id="pamRabbitTemplate"
connection-factory="connectionFactory" message-converter="accountMarshallingMessageConverter"
reply-timeout="${gateway.reply.timeout}" reply-queue="${vendor.account.amqp.default.reply.queue}">
<rabbit:reply-listener
concurrency="${vendor.account.amqp.default.reply.queue.consumers}" />
</rabbit:template>
<bean id="accountMarshallingMessageConverter"
class="org.springframework.amqp.support.converter.MarshallingMessageConverter">
<property name="marshaller" ref="fuelNotificationWsMarshaller" />
<property name="unmarshaller" ref="fuelNotificationWsMarshaller" />
</bean>
and in pamRabbitTemplate:
<int:channel id="channel.accounts.request" />
<int:channel id="channel.accounts.reply" />
<int:channel id="channel.accounts.outbound.request" />
<int:channel id="channel.accounts.outbound.reply" />
<int:channel id="channel.accounts.error" />
<int-amqp:outbound-gateway request-channel="channel.accounts.outbound.request"
reply-channel="channel.accounts.outbound.reply" amqp-template="pamRabbitTemplate"
exchange-name="${pam.exchange.service.account}" routing-key="${pam.routingkey.service.account}"
requires-reply="false" />
<int:gateway id="gateway.account"
service-interface="notification.fuelnotification.infrastructure.integration.RetrieveAccountsGateway"
error-channel="channel.error" default-reply-timeout="${gateway.reply.timeout}">
<int:method name="retrieveUnencryptedCards"
request-channel="channel.accounts.request" reply-channel="channel.accounts.reply" />
</int:gateway>
<int:transformer id="accountsReqTf" method="transform"
input-channel="channel.accounts.request" output-channel="channel.accounts.outbound.request">
<bean
class="notification.fuelnotification.infrastructure.transformer.common.AccountsRequestTransformer" />
</int:transformer>
<int:transformer id="accountResTf" method="transform"
input-channel="channel.accounts.outbound.reply" output-channel="channel.accounts.reply">
<bean
class="notification.fuelnotification.infrastructure.transformer.common.UnencryptedCardsResponseTransformer" />
</int:transformer>
<int:transformer id="errorLoggerTransformer"
method="logDetails" input-channel="channel.error">
<bean
class="notification.fuelnotification.infrastructure.config.amqp.ErrorChannelDetailLogger" />
</int:transformer>
I am able to write a java program using Rabbit Java API's doing the following:
Client sends message over Rabbit MQ exchange/queue with correlation Id (Say UUID - "348a07f5-8342-45ed-b40b-d44bfd9c4dde").
Server receives the message.
Server sends response message over Rabbit MQ exchange/queue with the same correlation Id - "348a07f5-8342-45ed-b40b-d44bfd9c4dde".
Client received the correlated message only in the same thread as 1.
Below is the Send.java and Recv.java using Rabbit APIs. I need help to convert this sample to use Spring AMQP integration especially receiving part on step 4. I am looking for something like receive method which can filter message using correlation Id.
Send.java:
import java.util.UUID;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class Send {
private final static String REQUEST_QUEUE = "REQUEST.QUEUE";
private final static String RESPONSE_QUEUE = "RESPONSE.QUEUE";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(REQUEST_QUEUE, false, false, false, null);
String message = "Hello World!";
String cslTransactionId = UUID.randomUUID().toString();
BasicProperties properties = (new BasicProperties.Builder())
.correlationId(cslTransactionId)
.replyTo(RESPONSE_QUEUE).build();
channel.basicPublish("", REQUEST_QUEUE, properties, message.getBytes());
System.out.println("Client Sent '" + message + "'");
Channel responseChannel = connection.createChannel();
responseChannel.queueDeclare(RESPONSE_QUEUE, false, false, false, null);
QueueingConsumer consumer = new QueueingConsumer(channel);
responseChannel.basicConsume(RESPONSE_QUEUE, false, consumer);
String correlationId = null;
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String responseMessage = new String(delivery.getBody());
correlationId = delivery.getProperties().getCorrelationId();
System.out.println("Correlation Id:" + correlationId);
if (correlationId.equals(cslTransactionId)) {
System.out.println("Client Received '" + responseMessage + "'");
responseChannel.basicAck(delivery.getEnvelope().getDeliveryTag(), true);
break;
}
}
channel.close();
connection.close();
}
}
Recv.java
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.AMQP.BasicProperties;
public class Recv {
private final static String REQUEST_QUEUE = "REQUEST.QUEUE";
private final static String RESPONSE_QUEUE = "RESPONSE.QUEUE";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(REQUEST_QUEUE, false, false, false, null);
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(REQUEST_QUEUE, true, consumer);
String correlationId = null;
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody());
correlationId = delivery.getProperties().getCorrelationId();
System.out.println("Correlation Id:" + correlationId);
System.out.println("Server Received '" + message + "'");
if (correlationId != null)
break;
}
String responseMsg = "Response Message";
Channel responseChannel = connection.createChannel();
responseChannel.queueDeclare(RESPONSE_QUEUE, false, false, false, null);
BasicProperties properties = (new BasicProperties.Builder())
.correlationId(correlationId).build();
channel.basicPublish("", RESPONSE_QUEUE, properties,responseMsg.getBytes());
System.out.println("Server Sent '" + responseMsg + "'");
channel.close();
connection.close();
}
}
After running the Java configuration provided by gary, I am trying to move the configuration to XML format for server side adding listener. Below is the XML configuration:
server.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:int-stream="http://www.springframework.org/schema/integration/stream"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean
id="serviceListenerContainer"
class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="queues" ref="requestQueue"/>
<property name="messageListener" ref="messageListenerAdaptor"/>
</bean>
<bean id="messageListenerAdaptor"
class="org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter">
<property name="delegate" ref="pojoListener" />
</bean>
<bean
id="pojoListener"
class="PojoListener"/>
<bean
id="replyListenerContainer"
class="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="queues" ref="replyQueue"/>
<property name="messageListener" ref="fixedReplyQRabbitTemplate"/>
</bean>
<!-- Infrastructure -->
<rabbit:connection-factory
id="connectionFactory"
host="localhost"
username="guest"
password="guest"
cache-mode="CHANNEL"
channel-cache-size="5"/>
<rabbit:template
id="fixedReplyQRabbitTemplate"
connection-factory="connectionFactory"
exchange="fdms.exchange"
routing-key="response.key"
reply-queue="RESPONSE.QUEUE">
<rabbit:reply-listener/>
</rabbit:template>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue id="requestQueue" name="REQUEST.QUEUE" />
<rabbit:queue id="replyQueue" name="RESPONSE.QUEUE" />
<rabbit:direct-exchange name="fdms.exchange" durable="true" auto-delete="false">
<rabbit:bindings>
<rabbit:binding queue="RESPONSE.QUEUE" key="response.key" />
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
SpringReceive.java
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringReceive {
/**
* #param args
*/
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("cslclient.xml");
SimpleMessageListenerContainer serviceListenerContainer = context.getBean("serviceListenerContainer", SimpleMessageListenerContainer.class);
serviceListenerContainer.start();
}
}
You can use RabbitTemplate.sendAndReceive() (or convertSendAndReceive()) with a reply listener container (Docs here); the template will take care of the correlation for you.
If you are using Spring Integration, use an outbound gateway with an appropriately configured rabbit template.