/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kie.server.client.jms; import java.util.Arrays; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; import org.kie.server.api.jms.JMSConstants; import org.kie.server.api.marshalling.Marshaller; import org.kie.server.api.model.ServiceResponse; import org.kie.server.api.model.ServiceResponsesList; import org.kie.server.client.KieServicesClient; import org.kie.server.client.KieServicesConfiguration; import org.kie.server.api.exception.KieServicesException; import org.kie.server.client.impl.KieServicesClientImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Async response handler that receives message from response queue using message listener. * It requires callback to be invoked upon message delivery otherwise will throw IllegalStateException on runtime. * <br/> * Due to nature of message listener (cannot clean up its connection and session) another thread is used to perform the cleanup * in finally block of message listener. * <br/> * Response is only delivered via callback thus return value of handleResponse is always single ServiceResponse of type NO_RESPONSE */ public class AsyncResponseHandler implements ResponseHandler { private static final Logger logger = LoggerFactory.getLogger(AsyncResponseHandler.class); private ResponseCallback callback; private ExecutorService executorService = Executors.newSingleThreadExecutor(); public AsyncResponseHandler(ResponseCallback callback) { this.callback = callback; } @Override public int getInteractionPattern() { return JMSConstants.ASYNC_REPLY_PATTERN; } @Override public ServiceResponsesList handleResponse(String selector, Connection connection, Session session, Queue responseQueue, KieServicesConfiguration config, Marshaller marshaller, KieServicesClient owner) { if (callback == null) { throw new IllegalStateException("There is no callback defined, can't continue..."); } MessageConsumer consumer = null; try { consumer = session.createConsumer(responseQueue, selector); consumer.setMessageListener(new AsyncMessageListener(connection, session, selector, consumer, marshaller, owner)); logger.debug("Message listener for async message retrieval successfully registered on consumer {}", consumer); } catch( JMSException jmse ) { throw new KieServicesException("Unable to retrieve JMS response from queue " + responseQueue + " with selector " + selector, jmse); } ServiceResponse messageSentResponse = new ServiceResponse(ServiceResponse.ResponseType.NO_RESPONSE, "Message sent"); return new ServiceResponsesList(Arrays.asList(messageSentResponse)); } @Override public void dispose(Connection connection, Session session) { // no op as the resources are closed from within message listener (via separate thread) } private class AsyncMessageListener implements MessageListener { private String selector; private MessageConsumer consumer; private Marshaller marshaller; private KieServicesClient owner; private Connection connection; private Session session; public AsyncMessageListener(Connection connection, Session session, String selector, MessageConsumer consumer, Marshaller marshaller, KieServicesClient owner) { this.selector = selector; this.consumer = consumer; this.marshaller = marshaller; this.owner = owner; this.connection = connection; this.session = session; } @Override public void onMessage(Message message) { try { ((KieServicesClientImpl) owner).setConversationId(message.getStringProperty(JMSConstants.CONVERSATION_ID_PROPERTY_NAME)); String responseStr = ((TextMessage) message).getText(); logger.debug("Received response from server '{}'", responseStr); ServiceResponsesList cmdResponse = marshaller.unmarshall(responseStr, ServiceResponsesList.class); logger.debug("Unmarshalled response from async delivery {} calling callback {}", cmdResponse, callback); callback.onResponse(selector, cmdResponse); logger.debug("Callback {} successfully invoked with response {}", callback, cmdResponse); } catch (Exception e) { logger.error("Error while receiving message due to {}, this means response from the server won't be delivered to client", e.getMessage(), e); } finally { if (consumer != null) { try { consumer.close(); } catch (JMSException e) { logger.warn("Error when closing JMS consumer due to {}", e.getMessage()); } } // submit work to executor service to close resources // as they cannot be closed from message listener // due to AMQ129006: It is illegal to call this method (session.close()) from within a Message Listener executorService.submit(new Runnable() { @Override public void run() { try { if (session != null) { session.close(); logger.debug("Session closed via separate thread."); } if (connection != null) { connection.close(); logger.debug("Connection closed via separate thread."); } } catch (JMSException jmse) { logger.warn("Unable to close connection or session!", jmse); } } }); logger.debug("Cleanup of JMS resources requested via separate thread."); } } } }