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
Related
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 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 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(){
}
}
}
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 ?
I followed the example to create a websocket server:
Server server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
ServletContextHandler servletContextHandler = new ServletContextHandler(server, "/", true, false);
EventServlet es = injector.getInstance(EventServlet.class);
servletContextHandler.addServlet(new ServletHolder(es), "/events/*");
The EventServlet class looks like:
public class EventServlet extends WebSocketServlet {
#Override
public void configure(WebSocketServletFactory factory) {
factory.getPolicy().setIdleTimeout(10000);
factory.register(EventSocketCache.class);
}
}
The EventSocketCache looks like:
public class EventSocketCache extends WebSocketAdapter {
private static int i = 0;
private static int counter = 0;
private static Map<Integer, Session> sessionMap = new HashMap<>();
private final Cache<String, String> testCache;
#Inject
public EventSocketCache(Cache<String, String> testCache) {
this.testCache = testCache;
}
#Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
System.out.println("Socket Connected: " + session);
System.out.println("Connect: " + session.getRemoteAddress().getAddress());
try {
session.getRemote().sendString("Hello Webbrowser");
session.setIdleTimeout(50000);
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
System.out.println("Received TEXT message: " + message);
}
#Override
public void onWebSocketBinary(byte[] payload, int offset, int len) {
byte[] newData = Arrays.copyOfRange(payload, offset, offset + len);
try {
Common.Success success = Common.Success.parseFrom(newData);
System.err.println("------> " + success.getIsSuccess());
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
#Override
public void onWebSocketClose(int statusCode, String reason) {
System.err.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
// Remove from the list here....
super.onWebSocketClose(statusCode, reason);
System.out.println("Socket Closed: [" + statusCode + "] " + reason);
}
#Override
public void onWebSocketError(Throwable cause) {
System.err.println("######################################");
super.onWebSocketError(cause);
cause.printStackTrace(System.err);
}
}
Now when I use my client and send a request, I end up getting:
org.eclipse.jetty.websocket.api.UpgradeException: Didn't switch protocols
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.validateResponse(UpgradeConnection.java:249)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.read(UpgradeConnection.java:181)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.onFillable(UpgradeConnection.java:126)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:358)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527)
at java.lang.Thread.run(Thread.java:722)
Disconnected from the target VM, address: '127.0.0.1:63256', transport: 'socket'
java.util.concurrent.ExecutionException: org.eclipse.jetty.websocket.api.UpgradeException: Didn't switch protocols
at org.eclipse.jetty.util.FuturePromise.get(FuturePromise.java:123)
at com.gamecenter.websockets.EventClient.main(EventClient.java:25)
Caused by: org.eclipse.jetty.websocket.api.UpgradeException: Didn't switch protocols
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.validateResponse(UpgradeConnection.java:249)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.read(UpgradeConnection.java:181)
at org.eclipse.jetty.websocket.client.io.UpgradeConnection.onFillable(UpgradeConnection.java:126)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:358)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:596)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:527)
at java.lang.Thread.run(Thread.java:722)
It seems as there is a problem creating an instance of EventSocketCache; if I don't have the constructor in there, everything works fine.
I'd like to know how to instantiate EventSocketCache properly and register it with EventServlet so things work?
I guess I've found a solution for your problem. You have to use a WebSocketCreator in your WebSocketServlet:
public class MenuServlet extends WebSocketServlet {
#Inject
private Injector injector;
#Override
public void configure(WebSocketServletFactory webSocketServletFactory) {
// Register your Adapater
webSocketServletFactory.register(MenuSocket.class);
// Get the current creator (for reuse)
final WebSocketCreator creator = webSocketServletFactory.getCreator();
// Set your custom Creator
webSocketServletFactory.setCreator(new WebSocketCreator() {
#Override
public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse) {
Object webSocket = creator.createWebSocket(servletUpgradeRequest, servletUpgradeResponse);
// Use the object created by the default creator and inject your members
injector.injectMembers(webSocket);
return webSocket;
}
});
}
}
there you can inject your members into your WebSocketAdapater. This actually worked for me.