I am using below config to send JMS message with an Object payload( lets say HelloWorld object) but when I receive message using Message driven adapater the payload is converted into byte[].
Is there a way to prevent this? I see outbound adapter eventually calls SimpleMessageConverter method `createMessageForSerializable --> session.createObjectMessage(object)
<si:chain input-channel="errorChannelIn">
<si:router
expression="headers.jms_redelivered.equals(T(java.lang.Boolean).FALSE) ? 'errorQueueChannel' : 'channel2Name' "
apply-sequence="true">
<si:mapping value="errorQueueChannel" channel="errorChannel"/>
<si:mapping value="channel2Name" channel="channel2"/>
</si:router>
</si:chain>
<si-jms:outbound-channel-adapter id="errorQueueMessageSender"
channel="errorChannel"
connection-factory="connectionFactory" session-transacted="true" destination="errorQueue"
/>
However, when I send message using JmsTemplate then it perfectly sends message as messageObject and message driven adapter picks up the Object as payload. Code snippet below.
Any idea where am I going wrong.
jmsTemplate.send(new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
return session.createObjectMessage(messageObject);
}
});
I guess you mean the extract-payload="false" on the <si-jms:outbound-channel-adapter>.
In that case <si-jms:message-driven-channel-adapter> delegates work to the org.springframework.jms.support.converter.SimpleMessageConverter which ends up with:
else if (message instanceof ObjectMessage) {
return extractSerializableFromMessage((ObjectMessage) message);
}
and you get deserialized Message<?>.
UPDATE
Sorry, it is really not clear what's going on. Just tested with our Samples. There you can find server/client configs: outboundChannelAdapter.xml and inboundChannelAdapter.xml. And I added a test-case to the GatewayDemoTest:
private final static String[] configFilesChannelAdapterDemo = {
"/META-INF/spring/integration/common.xml",
"/META-INF/spring/integration/inboundChannelAdapter.xml",
"/META-INF/spring/integration/outboundChannelAdapter.xml"
};
#Test
public void testAdapterDemo() throws InterruptedException {
System.setProperty("spring.profiles.active", "testCase");
final GenericXmlApplicationContext applicationContext = new GenericXmlApplicationContext(configFilesChannelAdapterDemo);
final MessageChannel stdinToJmsoutChannel = applicationContext.getBean("stdinToJmsoutChannel", MessageChannel.class);
Foo foo = new Foo("bar");
stdinToJmsoutChannel.send(MessageBuilder.withPayload(foo).build());
final QueueChannel queueChannel = applicationContext.getBean("queueChannel", QueueChannel.class);
Message<?> reply = queueChannel.receive(20000);
Assert.assertNotNull(reply);
Object out = reply.getPayload();
Assert.assertThat(out, Matchers.instanceOf(Foo.class));
Assert.assertEquals(foo, out);
applicationContext.close();
}
public static class Foo implements Serializable {
private final String bar;
public Foo(String bar) {
this.bar = bar;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Foo foo = (Foo) o;
return !(bar != null ? !bar.equals(foo.bar) : foo.bar != null);
}
#Override
public int hashCode() {
return bar != null ? bar.hashCode() : 0;
}
}
As you see I sand some Serializable object to the queue and the <jms:message-driven-channel-adapter> receives for me exactly the same deserialized object.
Maybe your issue is somewhere before the <jms:outbound-channel-adapter> where you may use a <payload-serializing-transformer>?
Would you mind rechecking the payload just before <jms:message-driven-channel-adapter> or even putting break point to the SimpleMessageConverter.toMessage ?
Related
I'm using spring-jms with azure's servicebus. I'm trying to use selector to distinguish between message types, but I can't get it working. This is my code.
#Component
#Slf4j
public class MessageTestRunner implements CommandLineRunner {
private static final String QUEUE_NAME = "lva-test-queue";
private static final String PING_SELECTOR = "selector = 'PING'";
private static final String PONG_SELECTOR = "selector = 'PONG'";
private static final String SB_SCHEDULED_ENQUEUE_HEADER = "x-opt-scheduled-enqueue-time";
private final JmsTemplate jmsTemplate;
public MessageTestRunner(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
#Override
public void run(String... args) throws Exception {
jmsTemplate.convertAndSend(QUEUE_NAME, ping(), m -> {
m.setStringProperty("selector", "PING");
return m;
});
jmsTemplate.convertAndSend(QUEUE_NAME, pong(), m -> {
m.setStringProperty("selector", "PONG");
return m;
});
}
private PingMessage ping() {
final PingMessage msg = new PingMessage();
msg.setAt(ZonedDateTime.now());
return msg;
}
private PongMessage pong() {
final PongMessage msg = new PongMessage();
msg.setAt(ZonedDateTime.now());
return msg;
}
#JmsListener(destination = QUEUE_NAME, selector = PING_SELECTOR, containerFactory = JMS_FACTORY_NAME)
public void handle(PingMessage message) {
log.debug("Handling ping message [{}]", message);
}
#JmsListener(destination = QUEUE_NAME, selector = PONG_SELECTOR, containerFactory = JMS_FACTORY_NAME)
public void handle(PongMessage message) {
log.debug("Handling pong message [{}]", message);
}
}
and this is the exception which I get
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method could not be invoked with incoming message
Endpoint handler details:
Method [public void com.example.MessageTestRunner.handle(com.example.data.model.PingMessage)]
Bean [com.example.MessageTestRunner#7aae1170]
; nested exception is org.springframework.messaging.converter.MessageConversionException: Cannot convert from [com.example.data.model.PongMessage] to [com.example.data.model.PingMessage] for org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#72eb85b7, failedMessage=org.springframework.jms.listener.adapter.AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage#72eb85b7
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:118) ~[spring-jms-5.3.10.jar:5.3.10]
at org.springframework.jms.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:77) ~[spring-jms-5.3.10.jar:5.3.10]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:736) ~[spring-jms-5.3.10.jar:5.3.10]
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:696) ~[spring-jms-5.3.10.jar:5.3.10]
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:674) ~[spring-jms-5.3.10.jar:5.3.10]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:318) ~[spring-jms-5.3.10.jar:5.3.10]
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:257) ~[spring-jms-5.3.10.jar:5.3.10]
Is there some configuration/... which should I change to enable selector?
EDIT:
The problem seems to be when I disable sending of PONG message, then PONG handler is receiving also PING messages and I can see the error in logs. However I would expect that PING message is never sent to PONG handler as there is selector that won't allow that.
EDIT2:
Seems like selector is supported only in service bus premium tier which supports JMS 2.0 (link)
So the issue is with service bus itself. We are using service bus in standard tier which doesn't support selector. More details here and here
I'm trying to unit test an advice on the poller which blocks execution of the mongo channel adapter until a certain condition is met (=all messages from this batch are processed).
The flow looks as follow:
IntegrationFlows.from(MongoDb.reactiveInboundChannelAdapter(mongoDbFactory,
new Query().with(Sort.by(Sort.Direction.DESC, "modifiedDate")).limit(1))
.collectionName("metadata")
.entityClass(Metadata.class)
.expectSingleResult(true),
e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(pollingIntervalSeconds))
.advice(this.advices.waitUntilCompletedAdvice())))
.handle((p, h) -> {
this.advices.waitUntilCompletedAdvice().setWait(true);
return p;
})
.handle(doSomething())
.channel(Channels.DOCUMENT_HEADER.name())
.get();
And the following advice bean:
#Bean
public WaitUntilCompletedAdvice waitUntilCompletedAdvice() {
DynamicPeriodicTrigger trigger = new DynamicPeriodicTrigger(Duration.ofSeconds(1));
return new WaitUntilCompletedAdvice(trigger);
}
And the advice itself:
public class WaitUntilCompletedAdvice extends SimpleActiveIdleMessageSourceAdvice {
AtomicBoolean wait = new AtomicBoolean(false);
public WaitUntilCompletedAdvice(DynamicPeriodicTrigger trigger) {
super(trigger);
}
#Override
public boolean beforeReceive(MessageSource<?> source) {
if (getWait())
return false;
return true;
}
public boolean getWait() {
return wait.get();
}
public void setWait(boolean newWait) {
if (getWait() == newWait)
return;
while (true) {
if (wait.compareAndSet(!newWait, newWait)) {
return;
}
}
}
}
I'm using the following test for testing the flow:
#Test
public void testClaimPoollingAdapterFlow() throws Exception {
// given
ArgumentCaptor<Message<?>> captor = messageArgumentCaptor();
CountDownLatch receiveLatch = new CountDownLatch(1);
MessageHandler mockMessageHandler = mockMessageHandler(captor).handleNext(m -> receiveLatch.countDown());
this.mockIntegrationContext.substituteMessageHandlerFor("retrieveDocumentHeader", mockMessageHandler);
LocalDateTime modifiedDate = LocalDateTime.now();
ProcessingMetadata data = Metadata.builder()
.modifiedDate(modifiedDate)
.build();
assert !this.advices.waitUntilCompletedAdvice().getWait();
// when
itf.getInputChannel().send(new GenericMessage<>(Mono.just(data)));
// then
assertThat(receiveLatch.await(10, TimeUnit.SECONDS)).isTrue();
verify(mockMessageHandler).handleMessage(any());
assertThat(captor.getValue().getPayload()).isEqualTo(modifiedDate);
assert this.advices.waitUntilCompletedAdvice().getWait();
}
Which works fine but when I send another message to the input channel, it still processes the message without respecting the advice.
Is it intended behaviour? If so, how can I verify using unit test that the poller is really waiting for this advice?
itf.getInputChannel().send(new GenericMessage<>(Mono.just(data)));
That bypasses the poller and sends the message directly.
You can unit test the advice has been configured by calling beforeReceive() from your test
Or you can create a dummy test flow with the same advice
IntegationFlows.from(() -> "foo", e -> e.poller(...))
...
And verify that just one message is sent.
Example implementation:
#Test
public void testWaitingActivate() {
// given
this.advices.waitUntilCompletedAdvice().setWait(true);
// when
Message<ProcessingMetadata> receive = (Message<ProcessingMetadata>) testChannel.receive(3000);
// then
assertThat(receive).isNull();
}
#Test
public void testWaitingInactive() {
// given
this.advices.waitUntilCompletedAdvice().setWait(false);
// when
Message<ProcessingMetadata> receive = (Message<ProcessingMetadata>) testChannel.receive(3000);
// then
assertThat(receive).isNotNull();
}
#Configuration
#EnableIntegration
public static class Config {
#Autowired
private Advices advices;
#Bean
public PollableChannel testChannel() {
return new QueueChannel();
}
#Bean
public IntegrationFlow fakeFlow() {
this.advices.waitUntilCompletedAdvice().setWait(true);
return IntegrationFlows.from(() -> "foo", e -> e.poller(Pollers.fixedDelay(Duration.ofSeconds(1))
.advice(this.advices.waitUntilCompletedAdvice()))).channel("testChannel").get();
}
}
i have an error in a Spring JMS Listener version 5
i have set a header to a boolean value .
i checked the message header on IBM mq browser and the value is correctly set ;
but when the listener consumes the message the header could not be resolved .
this is the error:
MessageHandlingException: Missing header 'VERSION_MESSAGE' for method parameter type [class java.lang.Boolean],
here is the listener
#JmsListener(destination = QUEUE_INTERNE, containerFactory = "ListenerContainerFactory")
public void onMessageReceived(String message,
#Header (value = JmsHeaders.VERSION_MESSAGE) Boolean version)
here is the value of the JmsHeaders.VERSION_MESSAGE is JmsHeaders
public static final String VERSION_MESSAGE="VERSION_MESSAGE";
i tried another approach where i removed header from listener like this:
#JmsListener(destination = QUEUE_INTERNE, containerFactory = "ListenerContainerFactory")
public void onMessageReceived(javax.jms.Message message)
boolean version=message.getBooleanProperty(JmsHeaders.VERSION_MESSAGE);
i have no error but the boolean value is always to false.
extra infomation ,
the listener container is overriden like this :
#Override
protected Message receiveMessage(MessageConsumer consumer) throws JMSException {
BatchMessage batch = new BatchMessage(batchSize);
while (!batch.releaseAfterMessage(super.receiveMessage(consumer))) ;
return batch.getMessages().isEmpty() ? null : batch;
}
and the overriden method always returns false:
#Override
public boolean getBooleanProperty(String s) throws JMSException {
return false;
}
now i understand why i always get false ,but how could i make it reruen the correct value?
thanks
finally i found a solution to my question,
given that my defaultListenerContainerFactory was overriden and when we receive a message this method is executed
#Override
protected Message receiveMessage(MessageConsumer consumer) throws JMSException {
BatchMessage batch = new BatchMessage(batchSize);
while (!batch.releaseAfterMessage(super.receiveMessage(consumer))) ;
return batch.getMessages().isEmpty() ? null : batch;
}
so we dont have access to headers from the listener ,because we get the messages wrapped in batchMessage :
public class BatchMessage implements Message {
private List<Message> messages = new ArrayList<>();
private int batchSize;
that implemets Message Class:
package javax.jms;
import java.util.Enumeration;
public interface Message {
int DEFAULT_DELIVERY_MODE = 2;
int DEFAULT_PRIORITY = 4;
long DEFAULT_TIME_TO_LIVE = 0L;
long DEFAULT_DELIVERY_DELAY = 0L;
String getJMSMessageID() throws JMSException;
void setJMSMessageID(String var1) throws JMSException;
long getJMSTimestamp() throws JMSException;
void setJMSTimestamp(long var1) throws JMSException;
byte[] getJMSCorrelationIDAsBytes() throws JMSException;
what it means that all the JmsHeaders and Jms Data are inside Message ;
so i had to go through the List inside BatchMessage to get all JmsAttributes and Headers .
here is the Listener:
#JmsListener(destination = TARGETQUEUE, containerFactory = "BatchMessageListenerContainerFactory")
public void onMessageReception(Message message) throws StockageQueueListenerException {
try{
insererBatch((BatchMessage) message);
}
I am developing extending WebSocketBehavior in order to send logging data to a client.. have generated the logging handler and it fires as and when needed.
I am having trouble understanding how exactly to push the log entries to the clients and update the console panel. I already know the onMessage method is what I need to override with the console taking the WeSocketRequestHandler as an argument along with the message I want to send. How exactly do I get the onMessage to fire properly?? Here is the code I am using:
public class LogWebSocketBehavior extends WebSocketBehavior {
private static final long serialVersionUID = 1L;
Console console;
private Handler logHandler;
private Model model;
public LogWebSocketBehavior(Console console) {
super();
configureLogger();
this.console = console;
}
private void configureLogger() {
Logger l = Logger.getLogger(AppUtils.loggerName);
logHandler = getLoggerHandler();
l.addHandler(logHandler);
}
#Override
protected void onMessage(WebSocketRequestHandler handler, TextMessage message) {
console.info(handler, model.getObject());
}
private Handler getLoggerHandler() {
return new Handler() {
#Override
public void publish(LogRecord record) {
model.setObject(record);
}
#Override
public void flush() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void close() throws SecurityException {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
};
}
private Collection<IWebSocketConnection> getConnectedClients() {
IWebSocketConnectionRegistry registry = new SimpleWebSocketConnectionRegistry();
return registry.getConnections(getApplication());
}
private void sendToAllConnectedClients(String message) {
Collection<IWebSocketConnection> wsConnections = getConnectedClients();
for (IWebSocketConnection wsConnection : wsConnections) {
if (wsConnection != null && wsConnection.isOpen()) {
try {
wsConnection.sendMessage("test");
} catch (IOException e) {
}
}
}
}
}
The logger works as I want it to, providing messages as needed, but I cannot find how to actually fire the onMessage method to update my console. Any help is appreciated...
#onMessage() is called by Wicket whenever the browser pushes a message via Wicket.WebSocket.send("some message").
It is not very clear but I guess you need to push messages from the server to the clients (the browsers). If this is the case then you need to get a handle to IWebSocketRequestHandler and use its #push(String) method. You can do this with WebSocketSettings.Holder.get(Application.get()).getConnectionRegistry().getConnection(...).push("message").
Here is the class working as I need. Thank you Martin!!
public class LogWebSocketBehavior extends WebSocketBehavior {
private static final long serialVersionUID = 1L;
Console console;
private Handler logHandler;
private IModel model;
public LogWebSocketBehavior(Console console, IModel model) {
super();
configureLogger();
this.console = console;
this.model = model;
}
private void configureLogger() {
Logger l = Logger.getLogger(AppUtils.loggerName);
logHandler = getLoggerHandler();
l.addHandler(logHandler);
}
#Override
protected void onPush(WebSocketRequestHandler handler, IWebSocketPushMessage message) {
super.onPush(handler, message);
console.info(handler, model);
}
private Handler getLoggerHandler() {
return new Handler() {
#Override
public void publish(LogRecord record) {
model.setObject(record);
sendToAllConnectedClients(record.toString());
}
#Override
public void flush() {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void close() throws SecurityException {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
};
}
private Collection<IWebSocketConnection> getConnectedClients() {
IWebSocketConnectionRegistry registry = new SimpleWebSocketConnectionRegistry();
return registry.getConnections(getApplication());
}
private void sendToAllConnectedClients(String message) {
IWebSocketConnectionRegistry registry = new SimpleWebSocketConnectionRegistry();
WebSocketPushBroadcaster b = new WebSocketPushBroadcaster(registry);
IWebSocketPushMessage msg = new Message();
b.broadcastAll(getApplication(), msg);
}
class Message implements IWebSocketPushMessage {
public Message(){
}
}
}
Hi My thread class is showing null pointer exception please help me to resolve
#Component
public class AlertsToProfile extends Thread {
public final Map<Integer, List<String>> userMessages = Collections.synchronizedMap(new HashMap<Integer, List<String>>());
#Autowired
ProfileDAO profileDAO;
private String categoryType;
private String dataMessage;
public String getCategoryType() {
return categoryType;
}
public void setCategoryType(String categoryType) {
this.categoryType = categoryType;
}
public String getDataMessage() {
return dataMessage;
}
public void setDataMessage(String dataMessage) {
this.dataMessage = dataMessage;
}
public void run() {
String category=getCategoryType();
String data= getDataMessage();
List<Profile> all = profileDAO.findAll();
if (all != null) {
if (category == "All" || category.equalsIgnoreCase("All")) {
for (Profile profile : all) {
List<String> list = userMessages.get(profile.getId());
if (list == null ) {
ArrayList<String> strings = new ArrayList<String>();
strings.add(data);
userMessages.put(profile.getId(), strings);
} else {
list.add(data);
}
}
}
}
}
and my service method is as follows
#Service
public class NoteManager
{
#Autowired AlertsToProfile alertsToProfile;
public void addNote(String type, String message, String category) {
alertsToProfile.setCategoryType(category);
String data = type + "," + message;
alertsToProfile.setDataMessage(data);
alertsToProfile.start();
System.out.println("addNotes is done");
}
But when i call start() method am getting null pointer exception please help me. I am new to spring with thread concept
It pretty obvious: you instantiate your thread directly, as opposed to letting spring create AlertsToProfile and auto wire your instance.
To fix this, create a Runnable around your run() method and embed that into a method, something like this:
public void startThread() {
new Thread(new Runnable() {
#Override
public void run() {
// your code in here
}}).start();
}
you will want to bind the Thread instance to a field in AlertsToProfile in order to avoid leaks and stop the thread when you're done.