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);
}
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 have an SQSListener setup, Although its receiving the payload, It is missing the messageId for the consumed message, is there anything I am doing wrong?,
Below is my code,
#SqsListener(value = "${test-queue-url}", deletionPolicy = SqsMessageDeletionPolicy.NEVER)
public void listen(Acknowledgment acknowledgment, String message, String messageId) {
// messageId == null here
LOG.info(messageId);
}
Changed my code to and extracted the value from headers
#Async
#SqsListener(value = "${queue-url}", deletionPolicy = SqsMessageDeletionPolicy.NEVER)
public void listen(Acknowledgment acknowledgment, String message, #Headers MessageHeaders headers) throws ExecutionException, InterruptedException {
String messageId = (String) headers.get("MessageId");
acknowledgment.acknowledge().get();
}
I build a rocketmq serveice on my server,it is 2m-noslave cluster,it's can be send messages to rocketmq,but my consumer cannot receive message,somebody tell me where is wrong,thinks...this is my
Consumer CLass code:
public class Consumer{
public static final String CONSUMER_GROUP_NAME = "broker-b";
public static final String CLUSTER_ADDR = "120.27.128.207:9876;120.27.146.42:9876";
public static final String SUBSCRIBE = "dzg_topic_001";
private void consumerMessage() throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(CONSUMER_GROUP_NAME);
consumer.setNamesrvAddr(CLUSTER_ADDR);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.setMessageModel(MessageModel.CLUSTERING);
//设置批量消费个数
consumer.subscribe(SUBSCRIBE, "*");
consumer.registerMessageListener((List<MessageExt> msgList, ConsumeConcurrentlyContext context)->{
MessageExt msg = msgList.get(0);
System.out.println( "received new message: topic===="+msg.getTopic()+" tag==="+msg.getTags()+" body=="+new String(msg.getBody()));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("ConsumerStarted.");
}
public static void main(String[] args) {
try {
new Consumer().consumerMessage();
} catch (MQClientException e) {
e.printStackTrace();
}
}
}
and my rocketmq server is enter image description here
when I set property autoCreateTopicEnable and autoCreateSubscriptionGroup'svalue is true,the consumer is right,why set false the consumer is can not work?
I'm trying to create a leftJoin in Kafka Streams which works fine for about 10 Records and then it crashes with an exception caused by a NullPointerException with such code:
private static KafkaStreams getKafkaStreams() {
StreamsConfig streamsConfig = new StreamsConfig(getProperties());
KStreamBuilder builder = new KStreamBuilder();
KTable<String, Verkaeufer> umsatzTable = builder.table(Serdes.String(), EventstreamSerde.Verkaeufer(), CommonUtilsConstants.TOPIC_VERKAEUFER_STAMMDATEN);
KStream<String, String> verkaeuferStream = builder.stream(CommonUtilsConstants.TOPIC_ANZAHL_UMSATZ_PER_VERKAEUFER);
KStream<String, String> tuttiStream = verkaeuferStream.leftJoin(umsatzTable,
(tutti, verkaeufer) -> ("Vorname=" + verkaeufer.getVorname().toString() +",Nachname=" +verkaeufer.getNachname().toString() +"," +tutti.toString()), Serdes.String(), Serdes.String());
tuttiStream.to(Serdes.String(), Serdes.String(), CommonUtilsConstants.TOPIC_TUTTI);
return new KafkaStreams(builder, streamsConfig);
}
StreamsConfig looks like this:
private static Properties getProperties() {
Properties props = new Properties();
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, CommonUtilsConstants.BOOTSTRAP_SERVER_CONFIGURATION);
props.put(StreamsConfig.APPLICATION_ID_CONFIG, CommonUtilsConstants.GID_TUTTI);
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,Serdes.String().getClass());
props.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG, "1000");
return props;
}
Full Stack Trace:
22:19:36.550 [gid-tutti-8fe6be58-d5c5-41ce-982d-88081b98004e-StreamThread-1] ERROR o.a.k.s.p.internals.StreamThread - stream-thread [gid-tutti-8fe6be58-d5c5-41ce-982d-88081b98004e-StreamThread-1] Failed to commit StreamTask 0_0 state: org.apache.kafka.streams.errors.ProcessorStateException: task [0_0] Failed to flush state store KTABLE-SOURCE-STATE-STORE-0000000000
at org.apache.kafka.streams.processor.internals.ProcessorStateManager.flush(ProcessorStateManager.java:262)
at org.apache.kafka.streams.processor.internals.AbstractTask.flushState(AbstractTask.java:190)
at org.apache.kafka.streams.processor.internals.StreamTask.flushState(StreamTask.java:282)
at org.apache.kafka.streams.processor.internals.StreamTask$1.run(StreamTask.java:264)
at org.apache.kafka.streams.processor.internals.StreamsMetricsImpl.measureLatencyNs(StreamsMetricsImpl.java:187)
at org.apache.kafka.streams.processor.internals.StreamTask.commitImpl(StreamTask.java:259)
at org.apache.kafka.streams.processor.internals.StreamTask.commit(StreamTask.java:253)
at org.apache.kafka.streams.processor.internals.StreamThread.commitOne(StreamThread.java:815)
at org.apache.kafka.streams.processor.internals.StreamThread.access$2800(StreamThread.java:73)
at org.apache.kafka.streams.processor.internals.StreamThread$2.apply(StreamThread.java:797)
at org.apache.kafka.streams.processor.internals.StreamThread.performOnStreamTasks(StreamThread.java:1448)
at org.apache.kafka.streams.processor.internals.StreamThread.commitAll(StreamThread.java:789)
at org.apache.kafka.streams.processor.internals.StreamThread.maybeCommit(StreamThread.java:778)
at org.apache.kafka.streams.processor.internals.StreamThread.runLoop(StreamThread.java:567)
at org.apache.kafka.streams.processor.internals.StreamThread.run(StreamThread.java:527) Caused by: java.lang.NullPointerException: null
at java.lang.String.<init>(String.java:143)
at ch.wesr.eventstream.commonutils.serde.GsonDeserializer.deserialize(GsonDeserializer.java:38)
at org.apache.kafka.streams.state.StateSerdes.valueFrom(StateSerdes.java:163)
at org.apache.kafka.streams.state.internals.CachingKeyValueStore.putAndMaybeForward(CachingKeyValueStore.java:90)
at org.apache.kafka.streams.state.internals.CachingKeyValueStore.access$000(CachingKeyValueStore.java:34)
at org.apache.kafka.streams.state.internals.CachingKeyValueStore$1.apply(CachingKeyValueStore.java:78)
at org.apache.kafka.streams.state.internals.NamedCache.flush(NamedCache.java:145)
at org.apache.kafka.streams.state.internals.NamedCache.flush(NamedCache.java:103)
at org.apache.kafka.streams.state.internals.ThreadCache.flush(ThreadCache.java:97)
at org.apache.kafka.streams.state.internals.CachingKeyValueStore.flush(CachingKeyValueStore.java:107)
at org.apache.kafka.streams.processor.internals.ProcessorStateManager.flush(ProcessorStateManager.java:260)
... 14 common frames omitted
Update:
This is what GsonDeserialize looks like
public class GsonDeserializer<T> implements Deserializer<T>{
public static final String CONFIG_VALUE_CLASS = "default.value.deserializer.class";
public static final String CONFIG_KEY_CLASS = "default.key.deserializer.class";
private Class<T> deserializedClass;
private Gson gson = new GsonBuilder().create();
public GsonDeserializer() {}
#Override
public void configure(Map<String, ?> config, boolean isKey) {
String configKey = isKey ? CONFIG_KEY_CLASS : CONFIG_VALUE_CLASS;
String clsName = String.valueOf(config.get(configKey));
try {
if (deserializedClass == null) {
deserializedClass = (Class<T>) Class.forName(clsName);
}
} catch (ClassNotFoundException e) {
System.err.printf("Failed to configure GsonDeserializer. " +
"Did you forget to specify the '%s' property ?%n",
configKey);
System.out.println(e.getMessage());
}
}
#Override
public T deserialize(String s, byte[] bytes) {
return gson.fromJson(new String(bytes), deserializedClass);
}
#Override
public void close() {}
}
As long as the cache is not flushed, your deserializer is never called. That's why it doesn't fail in the beginning and you can increase the time until it fails via cache size parameter and commit interval (we flush on commit).
Looking at your code for GsonDeserializer, it seems that new String(bytes) fails with NPE -- String constructor cannot take null as parameter -- your deserializer code must guard against bytes==null and should return null for this case directly.
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 ?