/******************************************************************************* * Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * File: $Source: /cvsroot/slrp/boca/com.ibm.adtech.boca.common/src/com/ibm/adtech/boca/services/impl/JMSNotificationService.java,v $ * Created by: Matthew Roy ( <a href="mailto:mroy@us.ibm.com">mroy@us.ibm.com </a>) * Created on: 5/20/2006 * Revision: $Id: JMSNotificationService.java 178 2007-07-31 14:22:33Z mroy $ * * Contributors: * IBM Corporation - initial API and implementation * Cambridge Semantics Incorporated - Fork to Anzo *******************************************************************************/ package org.openanzo.combus; import java.security.AccessControlException; import java.util.ArrayList; import java.util.Collection; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.ExceptionListener; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TemporaryQueue; import javax.jms.TextMessage; import org.apache.activemq.broker.BrokerStoppedException; import org.apache.activemq.transport.TransportDisposedIOException; import org.apache.commons.collections15.BidiMap; import org.apache.commons.collections15.MultiMap; import org.apache.commons.collections15.bidimap.DualHashBidiMap; import org.apache.commons.collections15.multimap.MultiHashMap; import org.apache.commons.lang.StringUtils; import org.openanzo.datasource.IAuthorizationService; import org.openanzo.datasource.IIndexService; import org.openanzo.datasource.IModelService; import org.openanzo.datasource.IQueryService; import org.openanzo.datasource.IReplicationService; import org.openanzo.datasource.IResetService; import org.openanzo.datasource.IUpdateService; import org.openanzo.exceptions.AnzoException; import org.openanzo.exceptions.ExceptionConstants; import org.openanzo.exceptions.LogUtils; import org.openanzo.exceptions.Messages; import org.openanzo.rdf.Constants; import org.openanzo.rdf.URI; import org.openanzo.rdf.Constants.COMBUS; import org.openanzo.rdf.Constants.OPTIONS; import org.openanzo.rdf.utils.SerializationConstants; import org.openanzo.rdf.utils.UriGenerator; import org.openanzo.services.IAuthenticationService; import org.openanzo.services.IExecutionService; import org.openanzo.services.INotificationConnectionListener; import org.openanzo.services.INotificationRegistrationService; import org.openanzo.services.IOperationContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of INotificationService that uses JMS to talk to a notification server. General design is client creates a temporary topic and connects to a * JMS ControlQueue and sends a login message that includes a TemporaryTopic for that user. The server authenticates the user, and registers the Temporary Topic * as a destination. When messages go through the JMS server, if the user is authorized to see the message, it is sent to the user's TemporaryTopic. Any message * selectors are applied to the TemporaryTopic, which filters the messages the user actually sees. * * Class also handles the jms message received over the TemporaryTopic, and processes them. * * @author Matthew Roy ( <a href="mailto:mroy@cambridgesemantics.com">mroy@cambridgesemantics.com </a>) */ public class CombusConnection { private final static Logger log = LoggerFactory.getLogger(CombusConnection.class); /** JMS connection to notification/jms server */ private Connection connection = null; /** JMS session for control messages */ private Session session = null; /** JMS Message consumer for messages from server */ private MessageConsumer messageConsumer = null; /** JMS Message producer to send messages to server */ private MessageProducer messageProducer = null; /** JMS TempQueue used to receive message from server */ private TemporaryQueue tempQueue = null; /** The JMS MessageListener for this connection */ private JMSMessageListener listener = null; private final BidiMap<String, Destination> destinations = new DualHashBidiMap<String, Destination>(); /** Is connected to JMS server */ protected boolean connected = false; /** Is the service closed */ protected boolean closed = false; /** Is the service in the process of closing */ private boolean closing = false; /** messageExecutorClosed close] */ private boolean messageExecutorClosed = true; /** Notification/jms username */ private final String userName; private final String password; private final String host; private final int port; private final boolean useSsl; /** IJmsProvider that service uses to talk to notification/jms server */ private final IJmsProvider jmsProvider; /** * Thread that handles queue of messages to process on local client */ protected Thread messageExecutor = null; private final Lock lock = new ReentrantLock(); private final Condition newMessage = lock.newCondition(); private final Lock eventLock = new ReentrantLock(); private final Condition newEventMessage = eventLock.newCondition(); /** Registered listeners for JMS connection events */ private final CopyOnWriteArraySet<INotificationConnectionListener> connectionListeners = new CopyOnWriteArraySet<INotificationConnectionListener>(); /** Registered listeners for MessageListener events */ private final CopyOnWriteArraySet<MessageListener> messageListeners = new CopyOnWriteArraySet<MessageListener>(); /** Map between a request correlationId and the message response */ private final MultiMap<String, TextMessage> correlationIdToMessage = new MultiHashMap<String, TextMessage>(); private final LinkedList<Message> messageBuffer = new LinkedList<Message>(); private final Map<String, MessageConsumer> topicConsumer = new Hashtable<String, MessageConsumer>(); /** * Create a combus connection to the server * * @param jmsProvider * JMS provider for connections to jms server * @param userName * user to connect * @param password * password to connect * @param host * hostname of server * @param port * port of server * @param useSsl * use ssl connection */ public CombusConnection(IJmsProvider jmsProvider, String userName, String password, String host, int port, boolean useSsl) { this.jmsProvider = jmsProvider; this.userName = userName; this.password = password; this.host = host; this.port = port; this.useSsl = useSsl; } /** * Stop connection * * @param clean * clean jsm disconnect * @throws AnzoException */ public void stop(boolean clean) throws AnzoException { if (connected && !closed) { disconnect(clean); } if (messageExecutor != null) messageExecutor.interrupt(); } /** * Connect to a JMS broker * * @throws AnzoException */ public void connect() throws AnzoException { if (connected) { throw new AnzoException(ExceptionConstants.COMBUS.JMS_ALREADY_CONNECTED); } closed = false; // connect to notification try { performConnect(); } catch (JMSException ne) { if (connection != null) { try { connection.close(); } catch (JMSException ne2) { if (log.isTraceEnabled()) log.trace(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_CLOSING_COMPONENT, "connection"), ne2); } connection = null; } throw new AnzoException(ExceptionConstants.COMBUS.JMS_CONNECT_FAILED, ne, this.host + ":" + this.port); } if (!connected) { throw new AnzoException(ExceptionConstants.COMBUS.JMS_CONNECT_TIMEOUT); } } private void performConnect() throws JMSException, AnzoException { if (connected || closing) { return; } if (jmsProvider == null) { throw new AnzoException(ExceptionConstants.COMBUS.NOTIFICATION_SERVICE_ERROR); } Properties propertiesNew = new Properties(); CombusProperties.setHost(propertiesNew, host); CombusProperties.setPort(propertiesNew, port); CombusProperties.setUseSsl(propertiesNew, useSsl); ConnectionFactory connectionFactory = jmsProvider.createConnectionFactory(propertiesNew); if (connectionFactory != null) { if (userName != null && password != null) { connection = connectionFactory.createConnection(userName, password); } else { connection = connectionFactory.createConnection(); } connection.setExceptionListener(new ExceptionListener() { public void onException(JMSException exception) { if (!closed) { // if user has not requested disconnect if (exception.getCause() instanceof BrokerStoppedException || exception.getCause() instanceof TransportDisposedIOException) { closed = true; } else { try { fireConnectionStateChange(INotificationConnectionListener.CONNECTIONFAILED); performDisconnect(false); } catch (AnzoException e) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.JMS_DISCONNECT_FAILED), e); } finally { connected = false; } } } } }); connection.start(); session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); destinations.clear(); destinations.put(INotificationRegistrationService.SERVICE_NAME, session.createQueue(COMBUS.NOTIFICATION_SERVICE_QUEUE)); destinations.put(IModelService.SERVICE_NAME, session.createQueue(COMBUS.MODEL_SERVICE_QUEUE)); destinations.put(IAuthorizationService.SERVICE_NAME, session.createQueue(COMBUS.AUTHORIZATION_SERVICE_QUEUE)); destinations.put(IAuthenticationService.SERVICE_NAME, session.createQueue(COMBUS.AUTHENTICATION_SERVICE_QUEUE)); destinations.put(IReplicationService.SERVICE_NAME, session.createQueue(COMBUS.REPLICATION_SERVICE_QUEUE)); destinations.put(IResetService.SERVICE_NAME, session.createQueue(COMBUS.RESET_SERVICE_QUEUE)); destinations.put(IUpdateService.SERVICE_NAME, session.createQueue(COMBUS.UPDATE_SERVICE_QUEUE)); destinations.put(IQueryService.SERVICE_NAME, session.createQueue(COMBUS.QUERY_SERVICE_QUEUE)); destinations.put(IIndexService.SERVICE_NAME, session.createQueue(COMBUS.INDEX_SERVICE_QUEUE)); destinations.put(IExecutionService.SERVICE_NAME, session.createQueue(COMBUS.EXECUTION_SERVICE_QUEUE)); tempQueue = session.createTemporaryQueue(); messageProducer = session.createProducer(null); messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); messageConsumer = session.createConsumer(tempQueue); listener = new JMSMessageListener(); messageConsumer.setMessageListener(listener); connected = true; fireConnectionStateChange(INotificationConnectionListener.CONNECTED); } return; } /** * Disconnect the JMS connection manager * * @param clean * true if jms disconnect should fully close * @throws AnzoException * if the connection was already closed, or there was an exception performing disconnect. * */ public void disconnect(boolean clean) throws AnzoException { if (closed && clean) { throw new AnzoException(ExceptionConstants.COMBUS.JMS_NOT_CONNECTED); } if (!closed) { closed = true; performDisconnect(clean); } else if (connection != null) { try { connection.close(); } catch (AccessControlException ace) { //Catch invalid exception thrown in jdk1.5 if (log.isDebugEnabled()) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_CLOSING_COMPONENT, "connection"), ace); } } catch (JMSException jmsex) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_CLOSING_COMPONENT, "connection"), jmsex); } finally { connection = null; } } } protected void performDisconnect(boolean clean) throws AnzoException { AnzoException exception = null; if (closing || !isConnected()) { return; } closing = true; if (connected && clean) { try { messageListeners.clear(); topicConsumer.clear(); stopMessageExecutor(); if (messageConsumer != null) { try { messageConsumer.close(); } catch (NullPointerException npe) { //Catch exception due to defect within activemq's ActiveMQMessageConsumer.dispose() method if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, "NPE due to ActiveMQ dispose issue", npe); } } } if (tempQueue != null) { tempQueue.delete(); } if (messageProducer != null) { messageProducer.close(); } if (session != null) { session.close(); } connection.stop(); } catch (JMSException jmsex) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_CLOSING_COMPONENT, "connection"), exception); // exception = new NotificationException(ExceptionConstants.NOTIFICATION.CODES.JMS_ERROR, ExceptionConstants.NOTIFICATION.JMS_DISCONNECT_FAILED, jmsex); } finally { messageProducer = null; tempQueue = null; messageConsumer = null; } } try { if (connection != null) { try { connection.close(); } catch (AccessControlException ace) { //Catch invalid exception thrown in jdk1.5 if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_CLOSING_COMPONENT, "connection"), ace); } } } } catch (JMSException jmsex) { exception = new AnzoException(ExceptionConstants.COMBUS.JMS_DISCONNECT_FAILED, jmsex); } finally { connection = null; } session = null; connected = false; if (clean) { fireConnectionStateChange(INotificationConnectionListener.DISCONNECTED); } closing = false; if (exception != null) { //throw exception; // we log this exception instead of throwing it. We don't want to raise // new exceptions trying to disconnect when we are already in a JMS error // situation log.warn(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.JMS_DISCONNECT_FAILED), exception); } } /** * Start the message executor * * @throws JMSException */ public void startMessageExecutor() throws JMSException { if (!messageExecutorClosed) { return; } messageExecutorClosed = false; messageExecutor = new Thread("MessageExecutor") { @Override public void run() { Message message = null; while (!interrupted() && !messageExecutorClosed && !closed) { eventLock.lock(); try { message = (!messageBuffer.isEmpty()) ? messageBuffer.removeFirst() : null; if (message == null) { try { newEventMessage.await(); } catch (InterruptedException ie) { } } } finally { eventLock.unlock(); } if (message != null) { if (log.isTraceEnabled()) { if (messageListeners.size() == 0) { try { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "No message listeners. Message getting dropped: ")); } catch (JMSException e) { log.trace(LogUtils.COMBUS_MARKER, "error printing jms message", e); } } } for (MessageListener listener : messageListeners) { listener.onMessage(message); } } message = null; } } }; messageExecutor.setDaemon(true); messageExecutor.start(); } /** * Stop the event message listener */ public void stopMessageExecutor() { if (messageExecutor != null) { messageExecutor.interrupt(); } messageExecutorClosed = true; eventLock.lock(); try { messageBuffer.clear(); newEventMessage.signalAll(); } finally { eventLock.unlock(); } messageExecutor = null; } /** * Determine if the service is connected * * @return true if the service is connected. */ public boolean isConnected() { return connected; } protected class JMSMessageListener implements MessageListener { public void onMessage(final Message message) { try { if (message.getJMSCorrelationID() == null) { onEventMessage(message); return; } } catch (JMSException jmsex) { notifyNotificationException(new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, jmsex)); } if (message != null && message instanceof TextMessage) { try { if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "Message Recieved: ")); } } catch (JMSException e) { } try { String correlationId = message.getJMSCorrelationID(); lock.lock(); try { correlationIdToMessage.put(correlationId, (TextMessage) message); newMessage.signalAll(); } finally { lock.unlock(); } } catch (JMSException jmsex) { notifyNotificationException(new AnzoException(ExceptionConstants.COMBUS.JMS_MESSAGE_PARSING, jmsex)); } } } private void onEventMessage(Message message) { if (log.isTraceEnabled()) { try { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "Recieved Message")); } catch (JMSException e) { } } eventLock.lock(); try { messageBuffer.add(message); newEventMessage.signalAll(); } finally { eventLock.unlock(); } } } /** * Publish message to a topic * * @param topic * topic on which to publish * @param message * message to publish * @throws AnzoException */ public void publishMessage(String topic, Message message) throws AnzoException { try { Destination destination = destinations.get(topic); if (destination == null) { destination = session.createTopic(topic); destinations.put(topic, destination); } if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "Sending Message: (destination=" + destination + ")")); } messageProducer.send(destination, message); } catch (JMSException jmsex) { throw new AnzoException(ExceptionConstants.COMBUS.COULD_NOT_PUBLISH, jmsex); } } /** * Send a request to a destination and wait for a response * * @param context * context for this operational call * @param destinationName * destination queue for this request * @param request * request message * @param timeout * timeout for waiting for a response * @return response message * @throws AnzoException * if there was an exception sending request, or a timeout waiting for a response */ public TextMessage requestResponse(IOperationContext context, String destinationName, Message request, long timeout) throws AnzoException { Destination destination = null; if (context.getAttribute(OPTIONS.DATASOURCE) != null) { URI datasourceUri = (URI) context.getAttribute(OPTIONS.DATASOURCE); Queue defaultDestination = (Queue) destinations.get(destinationName); if (datasourceUri.toString().equals("http://openanzo.org/datasource/systemDatasource")) { destination = defaultDestination; } else { String queueNamePrefix = UriGenerator.generateEncapsulatedString("", datasourceUri.toString()) + "/"; try { String[] parts = StringUtils.split(defaultDestination.getQueueName(), '/'); String queue = "services/" + queueNamePrefix + parts[1]; destination = destinations.get(queue); if (destination == null) { destination = session.createQueue(queue); destinations.put(queue, destination); } } catch (JMSException e) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SUCH_DESTINATION, destinationName); } } } else { destination = destinations.get(destinationName); if (destination == null) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SUCH_DESTINATION, destinationName); } } if (context.getAttribute(COMBUS.TIMEOUT) != null) { timeout = (Long) context.getAttribute(COMBUS.TIMEOUT); } String correlationId = context.getOperationId(); if (correlationId == null) { correlationId = UUID.randomUUID().toString(); } try { request.setJMSCorrelationID(correlationId); request.setJMSReplyTo(tempQueue); request.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION); if (context.getOperationPrincipal() != null && !context.getOperationPrincipal().getName().equals(this.userName)) { request.setStringProperty(SerializationConstants.runAsUser, context.getOperationPrincipal().getName()); } if (context.getAttribute(OPTIONS.PRIORITY) != null) { Integer priority = (Integer) context.getAttribute(OPTIONS.PRIORITY); request.setJMSPriority(priority); messageProducer.setPriority(priority); } else { messageProducer.setPriority(4); } if (context.getAttribute(OPTIONS.SKIPCACHE) != null) { request.setBooleanProperty(OPTIONS.SKIPCACHE, context.getAttribute(OPTIONS.SKIPCACHE, Boolean.class)); } if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(request, "Sending Request: (destination=" + destination + ")")); } messageProducer.send(destination, request); } catch (JMSException jmsex) { performDisconnect(true); throw new AnzoException(ExceptionConstants.COMBUS.COULD_NOT_PUBLISH, jmsex); } lock.lock(); try { Collection<TextMessage> messageSet = correlationIdToMessage.remove(correlationId); long start = System.currentTimeMillis(); while (messageSet == null) { if (timeout <= 0) { try { newMessage.await(2, TimeUnit.SECONDS); } catch (InterruptedException ie) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } if (closed || closing) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } messageSet = correlationIdToMessage.remove(correlationId); } else { try { newMessage.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException ie) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } if (closed || closing) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } messageSet = correlationIdToMessage.remove(correlationId); if (!connected) { log.error(LogUtils.COMBUS_MARKER, "Request Response failed because connection was closed"); throw new AnzoException(ExceptionConstants.COMBUS.JMS_NOT_CONNECTED, correlationId); } if (messageSet == null && ((timeout > 0) && ((System.currentTimeMillis() - start) > timeout))) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SERVER_RESPONSE, correlationId); } } } try { TextMessage message = messageSet.iterator().next(); if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "Received Response:")); } if (message.getBooleanProperty(SerializationConstants.operationFailed)) { long errorCodes = message.propertyExists(SerializationConstants.errorTags) ? message.getLongProperty(SerializationConstants.errorCode) : ExceptionConstants.COMBUS.JMS_SERVICE_EXCEPTION; // if available, use enumerated args, since these can be reconstruct an AnzoException correctly. List<String> args = new ArrayList<String>(); for (int i = 0; true; i++) { String errorArg = message.getStringProperty(SerializationConstants.errorMessageArg + i); if (errorArg == null) { break; } args.add(errorArg); } // NOTE: This doesn't really make any sense, but it was here before and it's better to be too verbose than not verbose enough // when it comes to error messages, so it stays. For now at least. -jpbetz if (args.isEmpty()) { args.add(message.getText()); } throw new AnzoException(errorCodes, args.toArray(new String[0])); } /*Object prp = context.getAttribute("populateResponseProperties"); if (prp != null && ((Boolean) prp)) { HashMap<String, Object> props = new HashMap<String, Object>(); Enumeration<String> keys = message.getPropertyNames(); while (keys.hasMoreElements()) { String key = keys.nextElement(); props.put(key, message.getObjectProperty(key)); } context.setAttribute("responseProperties", props); }*/ return message; } catch (JMSException jmsex) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "request response"), jmsex); } return null; } finally { lock.unlock(); } } /** * Send a request to a destination and wait for a response * * @param context * context for this operational call * @param destinationName * destination queue for this request * @param request * request message * @param timeout * timeout for waiting for a response * @param messageHandler * the handler of multiple messages * @throws AnzoException * if there was an exception sending request, or a timeout waiting for a response */ public void requestMultipleResponse(IOperationContext context, String destinationName, Message request, long timeout, IMessageHandler messageHandler) throws AnzoException { Destination destination = null; if (context.getAttribute(OPTIONS.DATASOURCE) != null) { URI datasourceUri = (URI) context.getAttribute(OPTIONS.DATASOURCE); Queue defaultDestination = (Queue) destinations.get(destinationName); if (datasourceUri.toString().equals("http://openanzo.org/datasource/systemDatasource")) { destination = defaultDestination; } else { String queueNamePrefix = UriGenerator.generateEncapsulatedString("", datasourceUri.toString()) + "/"; try { String[] parts = StringUtils.split(defaultDestination.getQueueName(), '/'); String queue = "services/" + queueNamePrefix + parts[1]; destination = destinations.get(queue); if (destination == null) { destination = session.createQueue(queue); destinations.put(queue, destination); } } catch (JMSException e) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SUCH_DESTINATION, destinationName); } } } else { destination = destinations.get(destinationName); if (destination == null) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SUCH_DESTINATION, destinationName); } } String correlationId = context.getOperationId(); if (correlationId == null) { correlationId = UUID.randomUUID().toString(); } try { request.setJMSCorrelationID(correlationId); request.setJMSReplyTo(tempQueue); request.setIntProperty(SerializationConstants.protocolVersion, Constants.VERSION); if (context.getOperationPrincipal() != null && !context.getOperationPrincipal().getName().equals(this.userName)) { request.setStringProperty(SerializationConstants.runAsUser, context.getOperationPrincipal().getName()); } if (context.getAttribute(OPTIONS.PRIORITY) != null) { Integer priority = (Integer) context.getAttribute(OPTIONS.PRIORITY); request.setJMSPriority(priority); messageProducer.setPriority(priority); } else { messageProducer.setPriority(4); } if (context.getAttribute(OPTIONS.SKIPCACHE) != null) { request.setBooleanProperty(OPTIONS.SKIPCACHE, context.getAttribute(OPTIONS.SKIPCACHE, Boolean.class)); } if (context.getAttribute(OPTIONS.INCLUDEMETADATAGRAPHS) != null) { request.setBooleanProperty(OPTIONS.INCLUDEMETADATAGRAPHS, context.getAttribute(OPTIONS.INCLUDEMETADATAGRAPHS, Boolean.class)); } if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(request, "Sending Request: (destination=" + destination + ")")); } messageProducer.send(destination, request); } catch (JMSException jmsex) { performDisconnect(true); throw new AnzoException(ExceptionConstants.COMBUS.COULD_NOT_PUBLISH, jmsex); } lock.lock(); try { long start = System.currentTimeMillis(); boolean done = false; int seq = 0; while (!done) { Collection<TextMessage> messageSet = correlationIdToMessage.remove(correlationId); while (messageSet == null) { if (timeout <= 0) { try { newMessage.await(2, TimeUnit.SECONDS); } catch (InterruptedException ie) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } if (closed || closing) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } messageSet = correlationIdToMessage.remove(correlationId); } else { try { newMessage.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException ie) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } if (closed || closing) { throw new AnzoException(ExceptionConstants.COMBUS.INTERRUPTED, correlationId); } messageSet = correlationIdToMessage.remove(correlationId); if (!connected) { log.error(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "connection closed")); throw new AnzoException(ExceptionConstants.COMBUS.JMS_NOT_CONNECTED, correlationId); } if (messageSet == null && ((timeout > -1) && ((System.currentTimeMillis() - start) > timeout))) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SERVER_RESPONSE, correlationId); } } } try { for (TextMessage message : messageSet) { if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, MessageUtils.prettyPrint(message, "Recieved Response:")); } if (message.propertyExists("done")) { done = message.getBooleanProperty("done"); } else { done = true; } if (message.propertyExists("sequence")) { int sequence = message.getIntProperty("sequence"); if (sequence != seq) { throw new AnzoException(ExceptionConstants.COMBUS.NO_SERVER_RESPONSE, correlationId); } else { seq++; } } int totalSize = 0; if (message.propertyExists(SerializationConstants.totalSolutions)) { totalSize = message.getIntProperty(SerializationConstants.totalSolutions); } if (message.getBooleanProperty(SerializationConstants.operationFailed)) { long errorCodes = message.propertyExists(SerializationConstants.errorTags) ? message.getLongProperty(SerializationConstants.errorCode) : ExceptionConstants.COMBUS.JMS_SERVICE_EXCEPTION; // if available, use enumerated args, since these can be reconstruct an AnzoException correctly. List<String> args = new ArrayList<String>(); for (int i = 0; true; i++) { String errorArg = message.getStringProperty(SerializationConstants.errorMessageArg + i); if (errorArg == null) { break; } args.add(errorArg); } // NOTE: This doesn't really make any sense, but it was here before and it's better to be too verbose than not verbose enough // when it comes to error messages, so it stays. For now at least. -jpbetz if (args.isEmpty()) { args.add(message.getText()); } throw new AnzoException(errorCodes, args.toArray(new String[0])); } else { messageHandler.handleMessage(message, seq, done, totalSize); } } } catch (JMSException jmsex) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "request multiple response"), jmsex); } } } finally { lock.unlock(); } } /** * Create a new JMS text message used to sending requests * * @return a new JMS text message used to sending requests * @throws AnzoException * if no connection to the JMS exists or there was an exception creating the message */ public TextMessage createMessage() throws AnzoException { if (session == null) { throw new AnzoException(ExceptionConstants.COMBUS.JMS_NOT_CONNECTED); } try { return session.createTextMessage(); } catch (JMSException jmsex) { throw new AnzoException(ExceptionConstants.COMBUS.JMS_DISCONNECT_FAILED, jmsex); } } protected void notifyNotificationException(AnzoException exception) { for (INotificationConnectionListener listener : connectionListeners) { listener.notificationException(exception); } } /** * Register an IJmsConnectionListener with this manager * * @param listener * listener to register with manager */ public void registerConnectionListener(INotificationConnectionListener listener) { connectionListeners.add(listener); } /** * Unregister an IJmsConnectionListener with this manager * * @param listener * listener to unregister from manager */ public void unregisterConnectionListener(INotificationConnectionListener listener) { connectionListeners.remove(listener); } /** * Register a MessageListener with this manager for messages that don't have correlation ids * * @param listener * listener to register with manager */ public void registerMessageListener(MessageListener listener) { messageListeners.add(listener); } /** * Unregister a MessageListener with this manager for messages that don't have correlation ids * * @param listener * listener to unregister from manager */ public void unregisterMessageListener(MessageListener listener) { messageListeners.remove(listener); } /** * Register a topic listener * * @param topic * topic to listen * @param topicListener * listener for topic * @throws AnzoException */ public void registerTopicListener(String topic, MessageListener topicListener) throws AnzoException { if (!connected) { connect(); } if (connected) { synchronized (topicConsumer) { if (!topicConsumer.containsKey(topic)) { try { Destination destination = session.createTopic(topic); MessageConsumer consumer = session.createConsumer(destination); consumer.setMessageListener(topicListener); topicConsumer.put(topic, consumer); } catch (JMSException jmsex) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "register topic listener"), jmsex); throw new AnzoException(ExceptionConstants.COMBUS.NOT_AUTHORIZED_FOR_TOPIC, jmsex, userName, topic); } } } } } /** * Unregister topic * * @param topic * topic to unregister * @throws AnzoException */ public void unregisterTopicListener(String topic) throws AnzoException { if (connected) { synchronized (topicConsumer) { MessageConsumer consumer = topicConsumer.remove(topic); if (consumer != null) { try { try { consumer.close(); } catch (NullPointerException npe) { //Catch exception due to defect within activemq's ActiveMQMessageConsumer.dispose() method if (log.isTraceEnabled()) { log.trace(LogUtils.COMBUS_MARKER, "NPE due to activemq dispose issue", npe); } } } catch (JMSException jmsex) { log.debug(LogUtils.COMBUS_MARKER, Messages.formatString(ExceptionConstants.COMBUS.ERROR_PROCESSING_MESSGE, "unregister topic"), jmsex); throw new AnzoException(ExceptionConstants.COMBUS.NOT_AUTHORIZED_FOR_TOPIC, jmsex, userName, topic); } } } } } private void fireConnectionStateChange(int state) { for (INotificationConnectionListener listener : connectionListeners) { listener.connectionStateChanged(state); } } /** * Message handler for topics * */ public interface IMessageHandler { /** * Handle a message from a topic * * @param message * Received message * @param seq * sequence number * @param done * all all messages for this sequence received * @param totalSize * total number of messages * @throws AnzoException */ public void handleMessage(TextMessage message, int seq, boolean done, int totalSize) throws AnzoException; } }