I have a small spring-boot app set up that connects to one or more Topics on ActiveMQ, which are set in the application's application.properties file on startup - and then sends these messages on to a database.
This is all working fine, but I am having some problems when trying to implement a failover - basically, the app will try to reconnect, but after a certain number of retries, the application process will just automatically exit, preventing the retry (ideally, I would like the app to just retry forever until killed manually or ActiveMQ becomes available again). I have tried explicitly setting the connection options (such as maxReconnectAttempts) in the connection URL (using url.options in application.properties) to -1/0/99999 but none of these seem to be right as the behavior is the same each time. From looking at the advice on Apache's own reference page I would also expect this behavior to be working as default too.
If anyone has any advice to force the app not to quit, I would be very grateful! The bits of my code that I think will matter is below:
#Configuration
public class AmqConfig {
private static final Logger LOG = LogManager.getLogger(AmqConfig.class);
private static final String LOG_PREFIX = "[AmqConfig] ";
private String clientId;
private static ArrayList<String> amqUrls = new ArrayList<>();
private static String amqConnectionUrl;
private static Integer numSubs;
private static ArrayList<String> destinations = new ArrayList<>();
#Autowired
DatabaseService databaseService;
public AmqConfig (#Value("${amq.urls}") String[] amqUrl,
#Value("${amq.options}") String amqOptions,
#Value("${tocCodes}") String[] tocCodes,
#Value("${amq.numSubscribers}") Integer numSubs,
#Value("${clientId}") String clientId) throws UnknownHostException {
Arrays.asList(amqUrl).forEach(url -> {
amqUrls.add("tcp://" + url);
});
String amqServerAddress = "failover:(" + String.join(",", amqUrls) + ")";
String options = Strings.isNullOrEmpty(amqOptions) ? "" : "?" + amqOptions;
this.amqConnectionUrl = amqServerAddress + options;
this.numSubs = Optional.ofNullable(numSubs).orElse(4);
this.clientId = Strings.isNullOrEmpty(clientId) ? InetAddress.getLocalHost().getHostName() : clientId;
String topic = "Consumer." + this.clientId + ".VirtualTopic.Feed";
if (tocCodes.length > 0){
Arrays.asList(tocCodes).forEach(s -> destinations.add(topic + "_" + s));
} else { // no TOC codes = connecting to default feed
destinations.add(topic);
}
}
#Bean
public ActiveMQConnectionFactory connectionFactory() throws JMSException {
LOG.info("{}Connecting to AMQ at {}", LOG_PREFIX, amqConnectionUrl);
LOG.info("{}Using client id {}", LOG_PREFIX, clientId);
ActiveMQConnectionFactory connectionFactory =
new ActiveMQConnectionFactory(amqConnectionUrl);
Connection conn = connectionFactory.createConnection();
conn.setClientID(clientId);
conn.setExceptionListener(new AmqExceptionListener());
conn.start();
destinations.forEach(destinationName -> {
try {
for (int i = 0; i < numSubs; i++) {
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue(destinationName);
MessageConsumer messageConsumer = session.createConsumer(destination);
messageConsumer.setMessageListener(new MessageReceiver(databaseService, destinationName));
}
} catch (JMSException e) {
LOG.error("{}Error setting up queue # {}", LOG_PREFIX, destinationName);
LOG.error(e.getMessage());
}
});
return connectionFactory;
}
}
public class MessageReceiver implements MessageListener, ExceptionListener {
public static final Logger LOG = LogManager.getLogger(MessageReceiver.class);
private static final String LOG_PREFIX = "[Message Receiver] ";
private DatabaseService databaseService;
public MessageReceiver(DatabaseService databaseService, String destinationName){
this.databaseService = databaseService;
LOG.info("{}Creating MessageReceiver for queue with destination: {}", LOG_PREFIX, destinationName);
}
#Override
public void onMessage(Message message) {
String messageText = null;
if (message instanceof TextMessage) {
TextMessage tm = (TextMessage) message;
try {
messageText = tm.getText();
} catch (JMSException e) {
LOG.error("{} Error getting message from AMQ", e);
}
} else if (message instanceof ActiveMQMessage) {
messageText = message.toString();
} else {
LOG.warn("{}Unrecognised message type, cannot process", LOG_PREFIX);
LOG.warn(message.toString());
}
try {
databaseService.sendMessageNoResponse(messageText);
} catch (Exception e) {
LOG.error("{}Unable to acknowledge message from AMQ. Message: {}", LOG_PREFIX, messageText, e);
}
}
}
public class AmqExceptionListener implements ExceptionListener {
public static final Logger LOG = LogManager.getLogger(AmqExceptionListener.class);
private static final String LOG_PREFIX = "[AmqExceptionListener ] ";
#Override
public void onException(JMSException e){
LOG.error("{}Exception thrown by ActiveMQ", LOG_PREFIX, e);
}
}
The console output I get from my application is just the below (apologies, as it is not much to go off)
[2019-12-12 14:43:30.292] [WARN ] Transport (tcp://[address]:61616) failed , attempting to automatically reconnect: java.io.EOFException
[2019-12-12 14:43:51.098] [WARN ] Failed to connect to [tcp://[address]:61616] after: 10 attempt(s) continuing to retry.
Process finished with exit code 0
Very interesting Question!
Configuring the maxReconnectAttempts=-1 will cause the connection attempts to be retried forever, but what I feel the problem here are as follows:
You are trying to connect to ActiveMQ while creating the Bean at App
startup, If ActiveMQ is not running when APP is starting up, the
Bean creation would retry the connection attempts forever causing a
timeout and not letting the APP to start.
Also when the ActiveMQ stops running midway you are not reattempting the connection as it is done inside #Bean and will only happen on APP startup
Hence the Connection shouldn't happen at Bean creation time, but maybe it can be done after the APP is up (maybe inside a #PostConstruct block)
These are just the pointers, You need to take it forward
Hope this helps!
Good luck!
Related
Problem I'm facing is the something like this :
in Main class, I'll try to connect to a server and attach Channel Listener for future actions.
If Connection establishes successfully, SSL Handshake is done without any problem.
But if Connection in Step 1 fails, I'll try to connect to same or different server and again attach same channel listener same as point.
But expectation is it should SSL handshake as before in point 2 if connection is established. But it's not. Even if I forcefully call renegotiate metthod in SslHandler.
Expected behavior
If any connection exception using bootstrap object to connect to the server, expectation is it should SSL handshake.
Actual behavior
It's skipping the SSL handshake while retrying and failing with UnknownMessage type expected(ByteBuf)
Steps to reproduce
While Main Connection
public class Main {
private final static Logger LOGGER = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
ClientConfig clientConfig = null;
LOGGER.info("initializing Agent Stats uploader");
// Set up.
InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE);
Bootstrap clientBootstrap = getBootstrap();
clientConfig = ClientConfig.getInstance();
InetSocketAddress server = clientConfig.getPrimaryScnInetAddrs();
Objects.nonNull(server.getHostName());
Objects.nonNull(server.getPort());
// Make a new connection.
LOGGER.info("Initialization complete, ready to connect to the host and port {}:{}", server.getHostName(),
server.getPort());
ServerChannelFutureListener serverChannelFutureListener = ServerChannelFutureListener.getInstance();
serverChannelFutureListener.setClientBootStrap(clientBootstrap);
ChannelPromise channelPromise =
(ChannelPromise) clientBootstrap.connect(server).addListener(serverChannelFutureListener);
EventLoopGroup eventGroupExecutor = clientBootstrap.config().group();
AgentStatsProcess agentStatsThread = AgentStatsProcess.getInstance();
agentStatsThread.setParentChannelFuture(channelPromise);
eventGroupExecutor.scheduleAtFixedRate(agentStatsThread, clientConfig.getInitialDelay(),
clientConfig.getScheduleInterval(), TimeUnit.SECONDS);
LOGGER.info("Scheduled Agent Stats uploading, should start in 30 secs");
LOGGER.info("Connection complete");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
LOGGER.info("Killing AgentStatUploader Thread");
eventGroupExecutor.shutdownGracefully();
}));
}
public static final Bootstrap getBootstrap() {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group);
b.channel(NioSocketChannel.class);
b.handler(new AgentStatsChannelInitializationHandler());
b.option(ChannelOption.SO_KEEPALIVE, true);
b.option(ChannelOption.TCP_NODELAY, true);
return b;
}
}
Having Channel Future handler for implementing re-try logic in step 1
public final class ServerChannelFutureListener implements GenericFutureListener {
private static final Logger logger = LoggerFactory.getLogger(ServerChannelFutureListener.class.getName());
private static ServerChannelFutureListener instance;
private AtomicInteger count = new AtomicInteger(1);
private ClientConfig clientConfig = ClientConfig.getInstance();
private boolean isPrimary=true;
private ChannelFuture channelFuture;
private Bootstrap clientBootStrap;
private long timeout;
private ServerChannelFutureListener(){
this.timeout = clientConfig.getRetryAfter();
}
#override
public void operationComplete(ChannelFuture future) throws Exception {
channelFuture = future;
int maxretries = clientConfig.getMaxRetries();
if (!future.isSuccess()) {
logger.info("Connection to {} scn is not successful, retrying ({}/{})", getServerType(), count.get(),maxretries);
logger.debug("Connection to server is failed with error: ",future.cause());
if ( count.incrementAndGet() > maxretries) {
// fails to connect even after max-retries, try to connect to next server.
logger.info("Failed to connect to {} server, will try to connect to {} now.",
getServerType(),
isPrimary() ? "SECONDARY":"PRIMARY");
count.getAndSet(1);
isPrimary = !isPrimary();
this.timeout = clientConfig.getRetryAfter();
logger.info("Connecting Server type changed, so resetting timeout: {}", this.timeout);
}else{
// retry
logger.info("Exponential Back-off set to: {} secs, waiting for next server connection", this.timeout);
//TimeUnit.SECONDS.sleep(this.timeout);
this.timeout = ExpontentialBackOff.getNextBackOff(this.timeout);
}
InetSocketAddress server = getServer();
logger.info("Initialization complete, ready to connect to the host and port {}:{}", server.getHostName(),
server.getPort());
channelFuture = clientBootStrap.connect(server).addListener(this);
}else {
logger.info("Using Connection with config: {}, to Server {} ", future.channel().config(),
future.channel().localAddress());
this.timeout = clientConfig.getRetryAfter();
logger.info("Time out Back-off reset to: {} for next server connection", this.timeout);
}
AgentStatsProcess.getInstance().setParentChannelFuture(channelFuture);
}
private String getServerType() {
return isPrimary() ? "PRIMARY" : "SECONDARY";
}
private InetSocketAddress getServer(){
return isPrimary()?clientConfig.getPrimaryScnInetAddrs():clientConfig.getSecondaryScnInetAddrs();
}
public static ServerChannelFutureListener getInstance(){
if(null == instance){
instance = new ServerChannelFutureListener();
}
return instance;
}
public boolean isPrimary() {
return isPrimary;
}
public ChannelFuture getChannelFuture() {
return channelFuture;
}
public void setClientBootStrap(Bootstrap cb) {
this.clientBootStrap = cb;
}
}
Expectation is SSL Handshake should happen after trying to reconnect but its failing.
Netty version: 4.1.12.Final
Fixed this issue, Culprit here is "ProtobufVarint32FrameDecoder " and it's parent Class "ByteToMessageDecoder". "ByteToMessageDecoder" make sure it's child classes are not shareable.
Because above classes are not shareable, every time code try to reconnect using boostrap, initializer class fails to add handlers in pipeline results in "ctx.close()" and no handlers.
I've did work-around of adding those two classes into my project and raised #10371 bug to address this issue.
I'm trying to send push message using the emulator of pubsub, I'm using spring boot too, this is my configuration:
Dependency:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-pubsub</artifactId>
</dependency>
My bean:
#Configuration
#AutoConfigureBefore(value= GcpPubSubAutoConfiguration.class)
#EnableConfigurationProperties(value= GcpPubSubProperties.class)
public class EmulatorPubSubConfiguration {
#Value("${spring.gcp.pubsub.projectid}")
private String projectId;
#Value("${spring.gcp.pubsub.subscriptorid}")
private String subscriptorId;
#Value("${spring.gcp.pubsub.topicid}")
private String topicId;
#Bean
public Publisher pubsubEmulator() throws IOException {
String hostport = System.getenv("PUBSUB_EMULATOR_HOST");
ManagedChannel channel = ManagedChannelBuilder.forTarget(hostport).usePlaintext().build();
try {
TransportChannelProvider channelProvider =
FixedTransportChannelProvider.create(GrpcTransportChannel.create(channel));
CredentialsProvider credentialsProvider = NoCredentialsProvider.create();
// Set the channel and credentials provider when creating a `TopicAdminClient`.
// Similarly for SubscriptionAdminClient
TopicAdminClient topicClient =
TopicAdminClient.create(
TopicAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build());
ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId);
// Set the channel and credentials provider when creating a `Publisher`.
// Similarly for Subscriber
return Publisher.newBuilder(topicName)
.setChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build();
} finally {
channel.shutdown();
}
}
}
Of course, I have set PUBSUB_EMULATOR_HOST system variable to localhost:8085, where is the emulator running
I created a rest controller for testing:
for send push message
#Autowired
private Publisher pubsubPublisher;
#PostMapping("/send1")
public String publishMessage(#RequestParam("message") String message) throws InterruptedException, IOException {
Publisher pubsubPublisher = this.getPublisher();
ByteString data = ByteString.copyFromUtf8(message);
PubsubMessage pubsubMessage = PubsubMessage.newBuilder().setData(data).build();
ApiFuture<String> future = pubsubPublisher.publish(pubsubMessage);
//pubsubPublisher.publishAllOutstanding();
try {
// Add an asynchronous callback to handle success / failure
ApiFutures.addCallback(future,
new ApiFutureCallback<String>() {
#Override
public void onFailure(Throwable throwable) {
if (throwable instanceof ApiException) {
ApiException apiException = ((ApiException) throwable);
// details on the API exception
System.out.println(apiException.getStatusCode().getCode());
System.out.println(apiException.isRetryable());
}
System.out.println("Error publishing message : " + message);
System.out.println("Error publishing error : " + throwable.getMessage());
System.out.println("Error publishing cause : " + throwable.getCause());
}
#Override
public void onSuccess(String messageId) {
// Once published, returns server-assigned message ids (unique within the topic)
System.out.println(messageId);
}
},
MoreExecutors.directExecutor());
}
finally {
if (pubsubPublisher != null) {
// When finished with the publisher, shutdown to free up resources.
pubsubPublisher.shutdown();
pubsubPublisher.awaitTermination(1, TimeUnit.MINUTES);
}
}
return "ok";
for get message:
#PostMapping("/pushtest")
public String pushTest(#RequestBody CloudPubSubPushMessage request) {
System.out.println( "------> message received: " + decode(request.getMessage().getData()) );
return request.toString();
}
I have created my topic and subscription in the emulator, I followed this tutorial:
https://cloud.google.com/pubsub/docs/emulator
I'm set the endpoint "pushtest" for get push message in the emulator, with this command:
python subscriber.py PUBSUB_PROJECT_ID create-push TOPIC_ID SUBSCRIPTION_ID PUSH_ENDPOINT
But when I run the test, doesn't reach "/pushtest" endpoint and I'm getting this error:
Task java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask#265d5d05
[Not completed, task = java.util.concurrent.Executors$RunnableAdapter#a8c8be3
[Wrapped task = com.google.common.util.concurrent.TrustedListenableFutureTask#1a53c57c
[status=PENDING, info=[task=[running=[NOT STARTED YET], com.google.api.gax.rpc.AttemptCallable#3866e1d0]]]]]
rejected from java.util.concurrent.ScheduledThreadPoolExecutor#3f34809a
[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1]
for assurance that the emulator is running ok, I'm run the test in python with the following command:
python publisher.py PUBSUB_PROJECT_ID publish TOPIC_ID
And I'm getting messages correctly in "pushtest" endpoint.
I don't know why sorry for my hazing.
Thanks for your help.
I found the problem.
Only comment this line in the bean
channel.shutdown();
HAHA very simple.
I have a list of objects that I put in Spring AMQP. Objects come from the controller. There is a service that processes these objects. And this service may crash with an OutOfMemoryException. Therefore, I run several instances of the application.
There is a problem: when the service crashes, I lose the received messages. I read about NACK. And could use it in case of Exception or RuntimeException. But my service crashes in Error. Therefore, I cannot send NACK. Is it possible to set a timeout in AMQP, after which I would be sent a message again if I had not confirmed the messages that had arrived earlier?
Here is the code I wrote:
public class Exchanges {
public static final String EXC_RENDER_NAME = "render.exchange.topic";
public static final TopicExchange EXC_RENDER = new TopicExchange(EXC_RENDER_NAME, true, false);
}
public class Queues {
public static final String RENDER_NAME = "render.queue.topic";
public static final Queue RENDER = new Queue(RENDER_NAME);
}
#RequiredArgsConstructor
#Service
public class RenderRabbitEventListener extends RabbitEventListener {
private final ApplicationEventPublisher eventPublisher;
#RabbitListener(bindings = #QueueBinding(value = #Queue(Queues.RENDER_NAME),
exchange = #Exchange(value = Exchanges.EXC_RENDER_NAME, type = "topic"),
key = "render.#")
)
public void onMessage(Message message, Channel channel) {
String routingKey = parseRoutingKey(message);
log.debug(String.format("Event %s", routingKey));
RenderQueueObject queueObject = parseRender(message, RenderQueueObject.class);
handleMessage(queueObject);
}
public void handleMessage(RenderQueueObject render) {
GenericSpringEvent<RenderQueueObject> springEvent = new GenericSpringEvent<>(render);
springEvent.setRender(true);
eventPublisher.publishEvent(springEvent);
}
}
And this is the method that sends messages:
#Async ("threadPoolTaskExecutor")
#EventListener (condition = "# event.queue")
public void start (GenericSpringEvent <RenderQueueObject> event) {
RenderQueueObject renderQueueObject = event.getWhat ();
send (RENDER_NAME, renderQueueObject);
}
private void send(String routingKey, Object queue) {
try {
rabbitTemplate.convertAndSend(routingKey, objectMapper.writeValueAsString(queue));
} catch (JsonProcessingException e) {
log.warn("Can't send event!", e);
}
}
You need to close the connection to get the message re-queued.
It's best to terminate the application after an OOME (which, of course, will close the connection).
I have implemented a Restful web interface using Jersey for sending messages received from an internal JMS publisher to external clients via HTTP. I have managed to get a test message out to a Java client, but the Thread throws a null pointer exception before completing the write() execution, closing the connection and preventing further communication.
Here is my resource class:
#GET
#Path("/stream_data")
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents(#Context ServletContext context){
final EventOutput eventOutput = new EventOutput();
new Thread( new ObserverThread(eventOutput, (MService) context.getAttribute("instance")) ).start();
return eventOutput;
}
And here is my thread's run method:
public class ObserverThread implements Observer, Runnable {
//constructor sets eventOutput & mService objects
//mService notifyObservers() called when JMS message received
//text added to Thread's message queue to await sending to client
public void run() {
try {
String message = "{'symbol':'test','entryType'='0','price'='test'}";
Thread.sleep(1000);
OutboundEvent.Builder builder = new OutboundEvent.Builder();
builder.mediaType(MediaType.APPLICATION_JSON_TYPE);
builder.data(String.class, message);
OutboundEvent event = builder.build();
eventOutput.write(event);
System.out.println(">>>>>>SSE CLIENT HAS BEEN REGISTERED!");
mService.addObserver(this);
while(!eventOutput.isClosed()){
if(!updatesQ.isEmpty()){
pushUpdate(updatesQ.dequeue());
}
}
System.out.println("<<<<<<<SSE CLIENT HAS BEEN DEREGISTERED!");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Here is my client code:
Client client = ClientBuilder.newBuilder().register(SseFeature.class).build();
WebTarget target = client.target(url);
EventInput eventInput = target.request().get(EventInput.class);
try {
while (!eventInput.isClosed()) {
eventInput.setChunkType(MediaType.WILDCARD_TYPE);
final InboundEvent inboundEvent = eventInput.read();
if (inboundEvent != null) {
String theString = inboundEvent.readData();
System.out.println(theString + "\n");
}
}
} catch (Exception e) {
e.printStackTrace();
}
I am getting the "{'symbol':'test','entryType'='0','price'='test'}" test message printed to the client console, but the server then prints a NullPointerException before it can print the ">>>>SSE Client registered" message. This closes the connection so the client exits the while loop and stops listening for updates.
I converted the project to a webapp 3.0 version facet in order to add an async-supported tag to the web.xml but i am receiving the same null pointer error. I am inclined to think that it is caused by the servlet ending the Request/Response objects once the first message is returned, evidence is shown in the stack trace:
Exception in thread "Thread-20" java.lang.NullPointerException
at org.apache.coyote.http11.InternalOutputBuffer.realWriteBytes(InternalOutputBuffer.java:741)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:434)
at org.apache.coyote.http11.InternalOutputBuffer.flush(InternalOutputBuffer.java:299)
at org.apache.coyote.http11.Http11Processor.action(Http11Processor.java:981)
at org.apache.coyote.Response.action(Response.java:183)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:314)
at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:288)
at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:98)
at org.glassfish.jersey.message.internal.CommittingOutputStream.flush(CommittingOutputStream.java:292)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:241)
at org.glassfish.jersey.server.ChunkedOutput$1.call(ChunkedOutput.java:192)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:242)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:345)
at org.glassfish.jersey.server.ChunkedOutput.flushQueue(ChunkedOutput.java:192)
at org.glassfish.jersey.server.ChunkedOutput.write(ChunkedOutput.java:182)
at com.bpc.services.service.ObserverThread.run(MarketObserverThread.java:32)
at java.lang.Thread.run(Thread.java:745)
<<<<<<<SSE CLIENT HAS BEEN DEREGISTERED!
I have attempted to test an sse broadcaster as well. In this case I am not seeing any exceptions thrown, but the connection is closed once the first message has been received, leading me to believe it is something in the servlet forcing the connection to close. Can anyone advise me on how to debug this on the server-side?
I had a similar issue from what seems to be a long standing bug in Jersey's #Context injection for ExecutorService instances. In their current implementation of Sse (version 2.27),
class JerseySse implements Sse {
#Context
private ExecutorService executorService;
#Override
public OutboundSseEvent.Builder newEventBuilder() {
return new OutboundEvent.Builder();
}
#Override
public SseBroadcaster newBroadcaster() {
return new JerseySseBroadcaster(executorService);
}
}
the executorService field is never initialized, so the JerseySseBroadcaster raises a NullPointerException in my case. I worked around the bug by explicitly triggering the injection.
If you're using HK2 for CDI (Jersey's default), a rough sketch of a solution to the question above could look similar to the following:
#Singleton
#Path("...")
public class JmsPublisher {
private Sse sse;
private SseBroadcaster broadcaster;
private final ExecutorService executor;
private final BlockingQueue<String> jmsMessageQueue;
...
#Context
public void setSse(Sse sse, ServiceLocator locator) {
locator.inject(sse); // Inject sse.executorService
this.sse = sse;
this.broadcaster = sse.newBroadcaster();
}
...
#GET
#Path("/stream_data")
#Produces(MediaType.SERVER_SENT_EVENTS)
public void register(SseEventSink eventSink) {
broadcaster.register(eventSink);
}
...
#PostConstruct
private void postConstruct() {
executor.submit(() -> {
try {
while(true) {
String message = jmsMessageQueue.take();
broadcaster.broadcast(sse.newEventBuilder()
.mediaType(MediaType.APPLICATION_JSON_TYPE)
.data(String.class, message)
.build());
}
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
#PreDestroy
private void preDestroy() {
executor.shutdownNow();
}
}
I successfully managed to send the message to queue name ReceiverQueue on my localhost Jboss server, how can I retrieve message I sent to it or how do I check if there is any messages in the queue if any retrieve them. or can I get an explanation of some sort what is the best way to do this. Thank you
A working send/receive tutorial would be accept as well. Anything that will get me to just send to the queue and receive message from that queue will get accepted answer.
I'm using Spring.
I want a solution that does it using application context with bean injection ..
Standard JMS API steps:
1. Create a javax.naming.Context with the access details of the server
context = new InitialContext(environment)
2. Look up javax.jms.QueueConnectionFactory in the context. Factory name is specific to the JMS server
factory = (QueueConnectionFactory)context.lookup(factoryName)
3. Create a javax.jms.QueueConnection
connection = factory.createQueueConnection(...)
4. Create a javax.jms.QueueSession
session = connection.createQueueSession(...)
5. Look up your javax.jms.Queue in the context
queue = (Queue) context.lookup(qJndiName)
Till now it is the same as sending....
6. Create a javax.jms.QueueReceiver with the session
receiver = session.createReceiver(queue)
7. JMS API provides 2 ways to retrieve a message:
7.a Wait for a message with one of the receiver.receive() methods
7.b Implement javax.jms.MessageListener in your class and register it as the listener
receiver.setMessageListener(this)
JMS API will call your onMessage() method whenever a new message arrives
8. Don't forget to start the listener:
connection.start()
9. Close the context (very important, when you access multiple JMS servers from the same program):
context.close()
The above is a typical solution from a stand-alone application. In EJB environment you should use message driven beans. You can find ino on them on http://java.sun.com/javaee/6/docs/tutorial/doc/gipko.html and a tutorial on http://schuchert.wikispaces.com/EJB3+Tutorial+5+-+Message+Driven+Beans
Here is the working example you've asked for:
import java.util.Hashtable;
import javax.naming.*;
import javax.jms.*;
public class JMSJNDISample implements MessageListener {
public static final String JNDI_URL = "jnp://localhost:1099";
public static final String JNDI_CONTEXT_FACTORY = "org.jnp.interfaces.NamingContextFactory";
public static final String JMS_USER = null;
public static final String JMS_PASSWORD = null;
public static final String JMS_CONNECTION_FACTORY = "MyConnectionFactory";
public static final String QUEUE_JNDI_NAME = "ReceiverQueue";
QueueConnection qConn = null;
QueueSession qSession = null;
QueueSender qSender = null;
QueueReceiver qReceiver = null;
public JMSJNDISample () {
}
public void init() throws JMSException, NamingException {
// Set up JNDI Context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, JNDI_URL);
if (JMS_USER != null)
env.put(Context.SECURITY_PRINCIPAL, JMS_USER);
if (JMS_PASSWORD != null)
env.put(Context.SECURITY_CREDENTIALS, JMS_PASSWORD);
Context jndiContext = new InitialContext(env);
// Lookup queue connection factory
QueueConnectionFactory cFactory = (QueueConnectionFactory)jndiContext.lookup(JMS_CONNECTION_FACTORY);
// Create Connection
if (JMS_USER == null || JMS_PASSWORD == null)
qConn = cFactory.createQueueConnection();
else {
qConn = cFactory.createQueueConnection(JMS_USER, JMS_PASSWORD);
}
// Create Session
qSession = qConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
// Lookup Queue
Queue queue = (Queue) jndiContext.lookup(QUEUE_JNDI_NAME);
// Create Queue Sender
qSender = qSession.createSender(queue);
// Create Queue Receiver
qReceiver = qSession.createReceiver(queue);
qReceiver.setMessageListener(this);
// Start receiving messages
qConn.start();
// Close JNDI context
jndiContext.close();
}
public void sendMessage (String str) throws JMSException {
TextMessage msg = qSession.createTextMessage(str);
qSender.send(msg);
}
public void onMessage (Message message) {
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage)message;
System.out.println("Text Message Received: "+textMessage.getText());
} else {
System.out.println(message.getJMSType()+" Message Received");
}
} catch (JMSException je) {
je.printStackTrace();
}
}
public void destroy() throws JMSException {
if (qSender != null) qSender.close();
if (qReceiver != null) qReceiver.close();
if (qSession != null) qSession.close();
if (qConn != null) qConn.close();
}
public static void main(String args[]) {
try {
JMSJNDISample sample = new JMSJNDISample();
// Initialize connetion
sample.init();
// Send Message
sample.sendMessage("Hello World");
// Wait 2 sec for answer
Thread.sleep(2000);
// Disconnect
sample.destroy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Other than having a MessageDrivenBean listening to that queue?
EDIT:
You are using spring just to create the payload, right? JMS is a JavaEE spec. You don't need to use Spring for actually sending/receiving messages. You don't have to manually check whether there are messages in the queue etc., either. All you need to do is have an MDB(MessageDrivenBean) set up like this,
#MessageDriven(activationConfig = {
#ActivationConfigProperty(
propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
#ActivationConfigProperty(
propertyName = "destination", propertyValue = "queue/myqueue")
})
public class MyMessageDrivenBean implements MessageListener {
public void onMessage(Message message) {
ObjectMessage objMsg = (ObjectMessage) message;
Payload payload = (Payload)objMsg.getObject();
//do stuff
}
}
And then send some JMS messages.
#Stateless
public class QueuerBean implements QueuerLocal {
#Resource(mappedName = "java:/JmsXA")
private ConnectionFactory jmsConnectionFactory;
#Resource(mappedName = "queue/myqueue")
private Queue queue;
private void queue(MyPayload payload) {
try {
Connection connect = jmsConnectionFactory.createConnection();
Session session = connect.createSession(false,
Session.DUPS_OK_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
// create a JMS message and send it
ObjectMessage objMsg = session.createObjectMessage(payload);
producer.send(objMsg);
producer.close();
session.close();
connect.close();
} catch (JMSException e) {
log.error("Bad thing happened", e);
}
}
}
The queue is configured by the annotation. When a message is sent, JBoss will automatically trigger the MDB.
Here's an example showing how to set up a message-driven POJO in Spring. I'd recommend following this idiom if you're already using Spring.
As for the part about seeing how many messages are on the queue, I'd say you should be using the admin console for JBOSS, not your code.
I would recommend also using a tool like HermesJMS (http://www.hermesjms.com/confluence/display/HJMS/Home) to inspect the queue manager and queues. It's a great debugging tool.