org.openanzo.combus.CombusConnection.java Source code

Java tutorial

Introduction

Here is the source code for org.openanzo.combus.CombusConnection.java

Source

/*******************************************************************************
 * 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;

    }
}