I have a question about Message Driven Beans (MDB). Is there a way to generate these only at runtime? I want to provide a way in my backend to start or stop receiving messages. It should be controlled via a configuration entry in the database. When starting WildFly, it should also first check whether the MDBs can be started.
Is it Java EE compliant to do without an MDB and create the listeners manually?
I am currently using the following code
#MessageDriven(name = "MyMDB", activationConfig = {
#ActivationConfigProperty(propertyName = "maxSession", propertyValue = "2"),
#ActivationConfigProperty(propertyName = "destination", propertyValue = "java:/jms/queue/Test"),
#ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")})
public class JmsConsumer implements MessageListener {
#Override
public void onMessage(final Message msg) {
if (msg instanceof TextMessage) {
try {
final String text = ((TextMessage) msg).getText();
System.out.println("message: " + text + " (" + msg.getJMSRedelivered() + ")");
} catch (final JMSException e) {
e.printStackTrace();
}
}
}
}
Would this code also conform to Java EE?
#Singleton
#LocalBean
public class QueueWorkerManager {
private InitialContext initialContext = null;
private QueueConnectionFactory queueConnectionFactory = null;
private Queue queue = null;
private QueueConnection queueConnection = null;
private QueueSession queueSession = null;
private MessageConsumer consumer = null;
#PostConstruct
public void init() {
try {
this.initialContext = new InitialContext();
this.queueConnectionFactory = (QueueConnectionFactory) initialContext
.lookup("java:/ConnectionFactory");
this.queue = (Queue) initialContext.lookup(MyQueueSender.WORKER_QUEUE);
this.queueConnection = queueConnectionFactory.createQueueConnection();
this.queueSession = queueConnection.createQueueSession(false, Session.CLIENT_ACKNOWLEDGE);
this.consumer = queueSession.createConsumer(this.queue);
this.consumer.setMessageListener(new ConsumerMessageListener());
this.queueConnection.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
#PreDestroy
public void destroy() {
this.stopConsumer(this.consumer;
if(this.consumer != null) {
try {
this.consumer.close();
} catch (JMSException e) {
}
this.consumer = null;
}
if(this.queueSession != null) {
try {
this.queueSession.close();
} catch (JMSException e) {
}
this.queueSession = null;
}
if(this.queueConnection != null) {
try {
this.queueConnection.close();
} catch (JMSException e) {
}
this.queueConnection = null;
}
}
}
public class ConsumerMessageListener implements MessageListener {
#Override
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("message: " + textMessage.getText() + " (" + msg.getJMSRedelivered() + ")");
message.acknowledge();
} catch (JMSException | InterruptedException e) {
e.printStackTrace();
}
}
}
I would suggest you to
add a #DeliveryActive(false/true) annotation to your MDB in order to boot it in the desired initial state
Use JMX or JBoss CLI to programatically change the value of the active attribute of your MDB
Everything is explained here:
https://docs.wildfly.org/16/Developer_Guide.html#Message_Driven_Beans_Controlled_Delivery
Good luck
Related
We have Spring JMS application ( deployed on K8s) which processes about 100 - 400 messages/sec. The application consumes messages from IBM MQ and processes them. Off late we have started noticing messages getting dropped whenever K8s pod restarts or deployments are done even though we have message ack in place. I am looking for a solution here to resolve this issue.
Software
Version
Spring Boot
2.1.7.RELEASE
IBM MQ Client
9.1.0.5
JMS
2.0.1
Java
11
#Configuration
#EnableJms
public class MqConfiguration {
#Bean
public MQConnectionFactory mqConnectionFactory(Servers configProperties) {
MQConnectionFactory mqConnectionFactory = new MQConnectionFactory();
try {
mqConnectionFactory.setHostName(configProperties.getHost());
mqConnectionFactory.setQueueManager(configProperties.getQueueManager());
mqConnectionFactory.setPort(Integer.valueOf(configProperties.getPort()));
mqConnectionFactory.setChannel(configProperties.getChannel());
mqConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
mqConnectionFactory.setCCSID(1208);
mqConnectionFactory.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
} catch (Exception e) {
logger.logError(mqConnectionFactory, ,
"Failed to create MQ ConnectionFactory", String.valueOf(HttpStatus.SC_BAD_REQUEST), e);
}
return mqConnectionFactory;
}
#Bean(name = "messageListenerContainerFactory")
public DefaultJmsListenerContainerFactory provideJmsListenerContainerFactory(
MQConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
factory.setErrorHandler(new ErrorHandler() {
#Override
public void handleError(Throwable t) {
ServiceMetrics metrics = new ServiceMetrics();
metrics.setCorrelationId(UUID.getUUID());
logger.logError(factory, "Exception occured at JMS Factory Container Listener", String.valueOf(HttpStatus.SC_BAD_REQUEST), t);
}
});
return factory;
}
#Bean(name = "jmsQueueTemplate")
public JmsTemplate provideJmsQueueTemplate(MQConnectionFactory connectionFactory) {
return new JmsTemplate(connectionFactory);
}
}
#Configuration
public class AsyncConfiguration {
#Autowired
private Servers configProperties;
#Bean(name = "asyncTaskExecutor")
public ExecutorService getAsyncTaskExecutor() {
String THREAD_POOL = "th-pool-";
return getExecutor(THREAD_POOL, 70,true);
}
private ExecutorService getExecutor(String threadName, int maxPoolSize, boolean cached) {
final ThreadFactory threadFactory = new CustomizableThreadFactory(threadName);
if (cached) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, maxPoolSize,
60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
threadPoolExecutor.setRejectedExecutionHandler((r, executor) -> {
if (!executor.isShutdown()) {
try {
executor.getQueue().put(r);
} catch (InterruptedException e) {
throw new RejectedExecutionException(e);
}
}
});
return threadPoolExecutor;
} else {
return new ThreadPoolExecutor(maxPoolSize, maxPoolSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
threadFactory);
}
}
#Component
public class InputQueueListener {
#Autowired
private ExecutorService asyncTaskExecutor;
#JmsListener(destination = "${mqserver.queue}", containerFactory = "messageListenerContainerFactory", concurrency = "1-16")
public void processXMLMessage(Message message) {
CompletableFuture.runAsync(() -> processMessage(message), asyncTaskExecutor);
}
private void processMessage(Message message) {
String inputXmlMessage = null;
boolean isSuccess = false;
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
inputXmlMessage = textMessage.getText();
} else if (message instanceof BytesMessage) {
BytesMessage byteMessage = (BytesMessage) message;
inputXmlMessage = CommonHelperUtil.getMessageFromBytes(byteMessage);
} else {
logger.logError(null, "Invalid message type received while converting Message to XML", String.valueOf(HttpStatus.SC_BAD_REQUEST));
errorQueuePublisher.publishErrorMessage(message);
try {
message.acknowledge();
} catch (JMSException jmsException) {
logger.logError(null, null, "Failed to Acknowledge XML message.",
String.valueOf(HttpStatus.SC_BAD_REQUEST), jmsException);
}
}
-
-
if (isSuccessProcessed) {
message.acknowledge();
} else {
message.acknowledge();
// Publishing back to the same queue
publishForRetry.publishMessageForRetry(message);
}
} catch (Exception e) {
if (StringUtils.isBlank(serviceMetrics.getCorrelationId())) {
serviceMetrics.setCorrelationId(UUID.getUUID());
}
logger.logError(null, null, "Exception while Converting Processing Message. Retrying to publish.",
String.valueOf(HttpStatus.SC_BAD_REQUEST), e);
// Publishing back to the same queue
publishForRetry.publishMessageForRetry(message);
try {
message.acknowledge();
} catch (JMSException jmsException) {
logger.logError(null, null,
"Failed to Acknowledge the Message when publishing" + "to Error Queue",
String.valueOf(HttpStatus.SC_BAD_REQUEST), jmsException);
}
}
}
}
}
Below is my application class. The flow is like the DEToken class from here and from DEToken I call RestConnection where I have the #retryable method.
#SpringBootApplication
#EnableRetry
public class SpringBootTrfficApplication implements CommandLineRunner {
Enter code here
#Autowired
DEToken deToken;
#Autowired
SyncService syncService;
public static void main(String[] args) {
SpringApplication.run(SpringBootTrfficApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
deToken.getToken();
}
}
DEToken class: from getToken I am calling RestConnect where I have the #Retrable method:
#Service
public class DEToken {
private Logger logger = LogManager.getLogger(getClass());
#Autowired
RestConnection restConnection;
#Autowired
private Environment env;
public String accessToken;
public void getToken() {
System.out.println("hello from get token");
//String getJsonPayload = "{\"Query\":{\"RegisterExtensionWithDE\":{\"pid\": \"\",\"providerInsName\":" +
//env.getProperty("provider.ins") + "}}}";
//String str = restConnection.restPost(
// env.getProperty("rest.de.url"), getJsonPayload);
try {
String getJsonPayload =
"{\"Query\":{\"RegisterExtensionWithDE\":{\"pid\": \"\",\"providerInsName\":" +
env.getProperty("provider.ins") + "}}}";
StringBuffer tokenResult =
restConnection.restPost(env.getProperty("rest.de.url"),
getJsonPayload);
System.out.println(tokenResult);
JSONObject xmlJSONObj = XML.toJSONObject(tokenResult.toString());
JSONObject registration = new JSONObject();
if (xmlJSONObj.has("Registration")) {
registration = xmlJSONObj.getJSONObject("Registration");
if (registration.has("accessToken")) {
accessToken = registration.get("accessToken").toString();
}
else
logger.info("no accessToken from DE");
}
else
logger.info("no Registration object from DE");
}
catch (Exception e) {
logger.error("Exception while fetching accesstoken from DE ");
logger.error(e.getMessage());
}
}
}
My REST connection class where I have retryable method:
#Service
public class RestConnection {
private Logger logger = LogManager.getLogger(getClass());
#Autowired
private Environment env;
public void setBaseUrl(String value, String ip) {
//baseUrl = value;
HttpsURLConnection.setDefaultHostnameVerifier(
(hostname, session) -> hostname.equals(ip));
}
/*
* REST post call
*/
#Retryable(value = {IOException.class, ConnectException.class},
maxAttempts = 4,
backoff = #Backoff(5000))
public StringBuffer restPost(String restUrl, String payload) {
StringBuffer sb = new StringBuffer();
HttpURLConnection conn = null;
try {
URL url = new URL(restUrl);
String protocol = url.getProtocol();
if (protocol.toLowerCase().equals("http")) {
conn = (HttpURLConnection)url.openConnection();
}
else if (protocol.toLowerCase().equals("https")) {
//setTrustedCert();
conn = (HttpsURLConnection)url.openConnection();
}
else {
logger.info("Protocol is neither HTTP nor HTTPS");
}
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("version", env.getProperty("de.version"));
conn.setRequestProperty("accessToken", env.getProperty("access.token"));
conn.setRequestProperty("requestHost", env.getProperty("server.de.host"));
conn.setRequestProperty("requestPort", env.getProperty("server.port"));
conn.setRequestProperty("requestProtocol",
env.getProperty("server.de.protocol"));
PrintWriter pout =
new PrintWriter(
new OutputStreamWriter(
conn.getOutputStream(), "UTF-8"),
true);
pout.print(payload);
pout.flush();
pout.close();
InputStream isi = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(isi);
int numCharsRead1;
char[] charArray1 = new char[1024];
while ((numCharsRead1 = isr.read(charArray1)) > 0) {
sb.append(charArray1, 0, numCharsRead1);
}
isr.close();
isi.close();
}
catch (MalformedURLException e) {
logger.error("MalformedURLException in restAccessTokenPOST..." +
e.getMessage());
//e.printStackTrace();
}
catch (IOException e) {
logger.error("IOException in restAccessTokenPOST..." +
e.getMessage());
e.printStackTrace();
}
catch (Exception e) {
logger.error("Exception in restAccessTokenPOST..." +
e.getMessage());
e.printStackTrace();
}
finally {
if (null != conn)
conn.disconnect();
}
return sb;
}
#Recover
public String helpHere(ConnectException cause) {
System.out.println("Recovery place! ConnectException");
return "Hello";
}
#Recover
public String helpHere(IOException cause) {
System.out.println("Recovery place! ArithmeticException");
return "Hello";
}
#Recover
public String helpHere(Exception cause) {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere() {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere(Throwable cause) {
System.out.println("Recovery place! Throwable");
return "Hello";
}
}
Considering you see your function restPost() implementation,
#Retryable(value = {IOException.class, ConnectException.class},
maxAttempts = 4,
backoff = #Backoff(5000))
public StringBuffer restPost(String restUrl, String payload) {
try {
// Your code
}
catch(IOException ex){ // These catch block handles the exception
// and nothing to throw to retryable.
}
catch(MalformedURLException ex){ // More catch blocks that you
// define to handle exception.
}
}
Here you handle all of the exceptions that can be a cause to revoke the retry and recover methods.
Note: Recoverable methods only execute when a exception is thrown, not handled by any try-catch block.
Whatever exception is raised by method restPost() is handled by the method try-catch block itself and there are no exceptions that had been rethrow by a catch block.
Now, Spring-Retry is unable to get any exception (because it is handled by the method try-catch block). So, no recovery method will be executed.
Solution: you should remove those catch blocks from the method definition on which you want to perform retry or recover.
Please do the needful and it will work like a charm... :)
Is it possible to have an implementation of a message splitter that can return an Iterator AND add custom header information?
For instance if I have the following class
public class CsvFileToIteratorSplitter extends AbstractMessageSplitter {
#Override
protected Object splitMessage(Message<?> message) {
Object payload = message.getPayload();
Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
try {
InputStream source = new FileInputStream((File) payload);
BufferedReader reader = new BufferedReader(new InputStreamReader(source));
String header = reader.lines().findFirst().orElse(null);
return MessageBuilder.withPayload(reader.lines().iterator())
.setHeaderIfAbsent("HEADER", header)
.build();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
Then I can add to the header but the payload is actually an instance of Iterator and the split fails
If I modify so that the class is now
public class CsvFileToIteratorSplitter extends AbstractMessageSplitter {
#Override
protected Object splitMessage(Message<?> message) {
log.debug("{}", message.toString());
Object payload = message.getPayload();
Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
try {
InputStream source = new FileInputStream((File) payload);
BufferedReader reader = new BufferedReader(new InputStreamReader(source));
return reader.lines().iterator();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
The split works but I lose the header info.
Is there any way to have a functioning split with the ability to add to the header?
You should return an Iterator<MessageBuilder<String>> ...
#SpringBootApplication
public class So44604817Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So44604817Application.class, args);
context.getBean("in", MessageChannel.class).send(new GenericMessage<>(new File("/tmp/foo.txt")));
context.close();
}
#Bean
#Splitter(inputChannel = "in")
public MySplitter splitter() {
MySplitter splitter = new MySplitter();
splitter.setOutputChannelName("out");
return splitter;
}
#Bean
public MessageChannel out() {
return new MessageChannel() {
#Override
public boolean send(Message<?> message) {
return send(message, -1);
}
#Override
public boolean send(Message<?> message, long timeout) {
System.out.println(message);
return true;
}
};
}
public static class MySplitter extends AbstractMessageSplitter {
#SuppressWarnings("resource")
#Override
protected Object splitMessage(Message<?> message) {
Object payload = message.getPayload();
Assert.isInstanceOf(File.class, payload, "Expected java.io.File in the message payload");
try {
InputStream source = new FileInputStream((File) payload);
final BufferedReader reader = new BufferedReader(new InputStreamReader(source));
final String header = reader.lines().findFirst().orElse(null);
final Iterator<String> iterator = reader.lines().iterator();
Iterator<MessageBuilder<String>> builderIterator = new Iterator<MessageBuilder<String>>() {
private String next;
#Override
public boolean hasNext() {
if (this.next != null) { // handle multiple hasNext() calls.
return true;
}
if (!iterator.hasNext()) {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
return false;
}
else {
this.next = iterator.next();
// Handle empty last line
if (next.length() == 0 && !iterator.hasNext()) {
try {
reader.close();
}
catch (IOException e) {
e.printStackTrace();
}
return false;
}
return true;
}
}
#Override
public MessageBuilder<String> next() {
String line = this.next;
this.next = null;
return MessageBuilder
.withPayload(line).setHeaderIfAbsent("HEADER", header);
}
};
return builderIterator;
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
Note that your skip(1) is incorrect, since the first line has already been consumed from the reader.
With file:
FOO,BAR
foo,bar
baz.qux
result:
GenericMessage [payload=foo,bar, headers={sequenceNumber=1, HEADER=FOO,BAR, correlationId=42ce2e1f-5337-1f75-d4fe-0d7f366f76f1, id=94e98261-fd49-b4d0-f6a0-3181b27f145b, sequenceSize=0, timestamp=1497713691192}]
GenericMessage [payload=baz.qux, headers={sequenceNumber=2, HEADER=FOO,BAR, correlationId=42ce2e1f-5337-1f75-d4fe-0d7f366f76f1, id=c0b1edd6-adb9-3857-cb7c-70f603f376bc, sequenceSize=0, timestamp=1497713691192}]
JIRA Issue INT-4297 to add this functionality to FileSplitter.
Hello i am facing a strange(for me ) problemm with MapMessage in ActiveMQ. My code produces no error but it shows nothing.
Producer code:
public void sendMapMessageTopic(String topicName) throws Exception {
try {
initConnectionTopic(topicName);
mapMessage = session.createMapMessage();
mapMessage.setIntProperty("Age", 24);
mapMessage.setStringProperty("Full Name", "Konstantinos Drakonakis");
mapMessage.setStringProperty("Height", "178cm");
List<String> data = new ArrayList<String>();
data.add("Company");
data.add("Project");
mapMessage.setObject("data", data);
Map<String, Object> specs = new HashMap<String, Object>();
specs.put("data", data);
mapMessage.setObject("specs", specs);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
producer.send(mapMessage);
} catch (Exception e) {
System.out.println("Exception while sending map message to the queue" + e.getMessage());
throw e;
} finally {
if (connection != null) {
connection.close();
if (session != null) {
session.close();
}
}
}
}
Consumer code:
public void startReceivingMapMessageTopic(String topicName) throws Exception {
try {
//get connection factory
connectionFactory = new ActiveMQConnectionFactory(username, password, brokerUrl);
//create a connection
connection = connectionFactory.createConnection();
connection.start();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//create destination(unless it already exists)
queue = session.createTopic(topicName);
consumer = session.createConsumer(queue);
messageMap = (MapMessage) consumer.receive(1000);
if (messageMap instanceof MapMessage) {
MapMessage m = messageMap;
System.out.println("The contents of MapMessage is: " + m.getStringProperty("Age"));
}
System.in.read();
consumer.close();
connection.close();
session.close();
} catch (Exception e) {
System.out.println("Exception while sending message to the queue" + e.getMessage());
throw e;
}
}
Main method for Producer:
public static void main(String[] args) {
//connect to the default broker url
ActiveMQQueueSender sender = new ActiveMQQueueSender("tcp://localhost:61616", "admin", "admin");
try {
sender.sendMapMessageTopic("Map Message Topic");
} catch (Exception ex) {
System.out.println("Exception during" + ex.getMessage());
}
}
Main method for consumer:
public static void main(String[] args) {
ActiveMQQueueReceiver receiver = new ActiveMQQueueReceiver("tcp://localhost:61616", "admin", "admin");
try {
receiver.startReceivingMapMessageTopic("Map Message Topic");
} catch (Exception ex) {
System.out.println("Exception during receival in main class" + ex.getMessage());
}
}
But still i get nothing. Any ideas?
I have activemq5.3.2 running and I wanted to subscribe existing advisory topics using my java program. while, `jndi` lookup I am getting following error:
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file:
java.naming.factory.initial
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:657)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:259)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:296)
at javax.naming.InitialContext.lookup(InitialContext.java:363)
at jmsclient.Consumer.<init>(Consumer.java:38)
at jmsclient.Consumer.main(Consumer.java:74)
Exception occurred: javax.jms.InvalidDestinationException: Don't understand null destinations
Please suggest where the problem is, or how could I use my topic name to look for?
package jmsclient;
import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.activemq.ActiveMQConnectionFactory;
public class Consumer implements MessageListener {
private static int ackMode;
private static String clientTopicName;
private boolean transacted = false;
//private MessageConsumer messageConsumer;
static {
clientTopicName = "ActiveMQ.Advisory.Consumer.Queue.example.A";
ackMode = Session.AUTO_ACKNOWLEDGE;
}
#SuppressWarnings("null")
public Consumer()
{// TODO Auto-generated method stub
TextMessage message = null;
Context jndiContext;
//TopicConnectionFactory topicConnectionFactory = null;
TopicConnection topicConnection = null;
TopicSession topicSession = null;
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://usaxwas012ccxra.ccmp.ibm.lab:61616");
try{
Topic myTopic = null;
try { jndiContext = new InitialContext();
myTopic = (Topic) jndiContext.lookup(clientTopicName);
} catch (NamingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
topicConnection = connectionFactory.createTopicConnection();
topicConnection.start();
topicSession = topicConnection.createTopicSession(transacted, ackMode);
TopicSubscriber topicSubscriber = topicSession.createSubscriber(myTopic);
Message m = topicSubscriber.receive(1000);
if (m != null) {
if (m instanceof TextMessage) {
message = (TextMessage) m;
System.out.println("Reading message: " + message.getText());
}
}
} //try ends
catch (JMSException e) {
System.out.println("Exception occurred: " + e.toString());
} finally {
if (topicConnection != null) {
try {
topicConnection.close();
} catch (JMSException e) {}
}}}
public void onMessage(Message arg0) {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
new Consumer();
}
}