summaryrefslogtreecommitdiff
path: root/java/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/client/src')
-rw-r--r--java/client/src/log4j.properties10
-rw-r--r--java/client/src/org/apache/qpid/client/AMQAuthenticationException.java29
-rw-r--r--java/client/src/org/apache/qpid/client/AMQBrokerDetails.java301
-rw-r--r--java/client/src/org/apache/qpid/client/AMQConnection.java927
-rw-r--r--java/client/src/org/apache/qpid/client/AMQConnectionFactory.java358
-rw-r--r--java/client/src/org/apache/qpid/client/AMQConnectionURL.java399
-rw-r--r--java/client/src/org/apache/qpid/client/AMQDestination.java282
-rw-r--r--java/client/src/org/apache/qpid/client/AMQHeadersExchange.java49
-rw-r--r--java/client/src/org/apache/qpid/client/AMQNoConsumersException.java34
-rw-r--r--java/client/src/org/apache/qpid/client/AMQNoRouteException.java34
-rw-r--r--java/client/src/org/apache/qpid/client/AMQQueue.java91
-rw-r--r--java/client/src/org/apache/qpid/client/AMQSession.java1149
-rw-r--r--java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java44
-rw-r--r--java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java46
-rw-r--r--java/client/src/org/apache/qpid/client/AMQTopic.java89
-rw-r--r--java/client/src/org/apache/qpid/client/BasicMessageConsumer.java499
-rw-r--r--java/client/src/org/apache/qpid/client/BasicMessageProducer.java480
-rw-r--r--java/client/src/org/apache/qpid/client/Closeable.java48
-rw-r--r--java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java69
-rw-r--r--java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java91
-rw-r--r--java/client/src/org/apache/qpid/client/failover/FailoverException.java30
-rw-r--r--java/client/src/org/apache/qpid/client/failover/FailoverHandler.java180
-rw-r--r--java/client/src/org/apache/qpid/client/failover/FailoverState.java46
-rw-r--r--java/client/src/org/apache/qpid/client/failover/FailoverSupport.java63
-rw-r--r--java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java47
-rw-r--r--java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java49
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java79
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java43
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java46
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java89
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java52
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java65
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java64
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java184
-rw-r--r--java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java79
-rw-r--r--java/client/src/org/apache/qpid/client/message/AMQMessage.java68
-rw-r--r--java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java677
-rw-r--r--java/client/src/org/apache/qpid/client/message/AbstractJMSMessageFactory.java77
-rw-r--r--java/client/src/org/apache/qpid/client/message/JMSBytesMessage.java351
-rw-r--r--java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java37
-rw-r--r--java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java175
-rw-r--r--java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java37
-rw-r--r--java/client/src/org/apache/qpid/client/message/JMSTextMessage.java159
-rw-r--r--java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java39
-rw-r--r--java/client/src/org/apache/qpid/client/message/MessageFactory.java35
-rw-r--r--java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java105
-rw-r--r--java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java40
-rw-r--r--java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java61
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java59
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java41
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java530
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java379
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java133
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java57
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java118
-rw-r--r--java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java110
-rw-r--r--java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java27
-rw-r--r--java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java154
-rw-r--r--java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties2
-rw-r--r--java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java125
-rw-r--r--java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties1
-rw-r--r--java/client/src/org/apache/qpid/client/security/JCAProvider.java43
-rw-r--r--java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java53
-rw-r--r--java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java101
-rw-r--r--java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java59
-rw-r--r--java/client/src/org/apache/qpid/client/state/AMQState.java53
-rw-r--r--java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java45
-rw-r--r--java/client/src/org/apache/qpid/client/state/AMQStateListener.java23
-rw-r--r--java/client/src/org/apache/qpid/client/state/AMQStateManager.java224
-rw-r--r--java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java45
-rw-r--r--java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java31
-rw-r--r--java/client/src/org/apache/qpid/client/state/StateListener.java27
-rw-r--r--java/client/src/org/apache/qpid/client/state/StateWaiter.java114
-rw-r--r--java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java38
-rw-r--r--java/client/src/org/apache/qpid/client/transport/ITransportConnection.java30
-rw-r--r--java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java96
-rw-r--r--java/client/src/org/apache/qpid/client/transport/TransportConnection.java71
-rw-r--r--java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java87
-rw-r--r--java/client/src/org/apache/qpid/jms/BrokerDetails.java59
-rw-r--r--java/client/src/org/apache/qpid/jms/ChannelLimitReachedException.java43
-rw-r--r--java/client/src/org/apache/qpid/jms/Connection.java49
-rw-r--r--java/client/src/org/apache/qpid/jms/ConnectionListener.java55
-rw-r--r--java/client/src/org/apache/qpid/jms/ConnectionURL.java69
-rw-r--r--java/client/src/org/apache/qpid/jms/FailoverPolicy.java306
-rw-r--r--java/client/src/org/apache/qpid/jms/MessageConsumer.java24
-rw-r--r--java/client/src/org/apache/qpid/jms/MessageProducer.java49
-rw-r--r--java/client/src/org/apache/qpid/jms/Session.java70
-rw-r--r--java/client/src/org/apache/qpid/jms/failover/FailoverMethod.java72
-rw-r--r--java/client/src/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java256
-rw-r--r--java/client/src/org/apache/qpid/jms/failover/FailoverSingleServer.java144
90 files changed, 12078 insertions, 0 deletions
diff --git a/java/client/src/log4j.properties b/java/client/src/log4j.properties
new file mode 100644
index 0000000000..d3135ff574
--- /dev/null
+++ b/java/client/src/log4j.properties
@@ -0,0 +1,10 @@
+log4j.rootLogger=${root.logging.level}
+
+
+log4j.logger.org.apache.qpid=${amqj.logging.level}, console
+log4j.additivity.org.apache.qpid=false
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.Threshold=info
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n
diff --git a/java/client/src/org/apache/qpid/client/AMQAuthenticationException.java b/java/client/src/org/apache/qpid/client/AMQAuthenticationException.java
new file mode 100644
index 0000000000..93b5cb5b8e
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQAuthenticationException.java
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+
+public class AMQAuthenticationException extends AMQException
+{
+ public AMQAuthenticationException(int error, String msg)
+ {
+ super(error,msg);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQBrokerDetails.java b/java/client/src/org/apache/qpid/client/AMQBrokerDetails.java
new file mode 100644
index 0000000000..52de858b13
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQBrokerDetails.java
@@ -0,0 +1,301 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.url.URLHelper;
+import org.apache.qpid.url.URLSyntaxException;
+
+import java.util.HashMap;
+import java.net.URISyntaxException;
+import java.net.URI;
+
+public class AMQBrokerDetails implements BrokerDetails
+{
+ private String _host;
+ private int _port;
+ private String _transport;
+
+ private HashMap<String, String> _options;
+
+ public AMQBrokerDetails()
+ {
+ _options = new HashMap<String, String>();
+ }
+
+ public AMQBrokerDetails(String url) throws URLSyntaxException
+ {
+ this();
+ // URL should be of format tcp://host:port?option='value',option='value'
+ try
+ {
+ URI connection = new URI(url);
+
+ String transport = connection.getScheme();
+
+ // Handles some defaults to minimise changes to existing broker URLS e.g. localhost
+ if (transport != null)
+ {
+ //todo this list of valid transports should be enumerated somewhere
+ if ((!(transport.equalsIgnoreCase("vm") ||
+ transport.equalsIgnoreCase("tcp"))))
+ {
+ if (transport.equalsIgnoreCase("localhost"))
+ {
+ connection = new URI(DEFAULT_TRANSPORT + "://" + url);
+ transport = connection.getScheme();
+ }
+ else
+ {
+ if (url.charAt(transport.length()) == ':' && url.charAt(transport.length()+1) != '/' )
+ {
+ //Then most likely we have a host:port value
+ connection = new URI(DEFAULT_TRANSPORT + "://" + url);
+ transport = connection.getScheme();
+ }
+ else
+ {
+ URLHelper.parseError(0, transport.length(), "Unknown transport", url);
+ }
+ }
+ }
+ }
+ else
+ {
+ //Default the transport
+ connection = new URI(DEFAULT_TRANSPORT + "://" + url);
+ transport = connection.getScheme();
+ }
+
+ if (transport == null)
+ {
+ URLHelper.parseError(-1, "Unknown transport:'" + transport + "'" +
+ " In broker URL:'" + url + "' Format: " + URL_FORMAT_EXAMPLE, "");
+ }
+
+ setTransport(transport);
+
+ String host = connection.getHost();
+
+ // Fix for Java 1.5
+ if (host == null)
+ {
+ host = "";
+ }
+
+ setHost(host);
+
+ int port = connection.getPort();
+
+ if (port == -1)
+ {
+ // Another fix for Java 1.5 URI handling
+ String auth = connection.getAuthority();
+
+ if (auth != null && auth.startsWith(":"))
+ {
+ setPort(Integer.parseInt(auth.substring(1)));
+ }
+ else
+ {
+ setPort(DEFAULT_PORT);
+ }
+ }
+ else
+ {
+ setPort(port);
+ }
+
+ String queryString = connection.getQuery();
+
+ URLHelper.parseOptions(_options, queryString);
+
+ //Fragment is #string (not used)
+ }
+ catch (URISyntaxException uris)
+ {
+ if (uris instanceof URLSyntaxException)
+ {
+ throw (URLSyntaxException) uris;
+ }
+
+ URLHelper.parseError(uris.getIndex(), uris.getReason(), uris.getInput());
+ }
+ }
+
+ public AMQBrokerDetails(String host, int port, boolean useSSL)
+ {
+ _host = host;
+ _port = port;
+
+ if (useSSL)
+ {
+ setOption(OPTIONS_SSL, "true");
+ }
+ }
+
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setHost(String _host)
+ {
+ this._host = _host;
+ }
+
+ public int getPort()
+ {
+ return _port;
+ }
+
+ public void setPort(int _port)
+ {
+ this._port = _port;
+ }
+
+ public String getTransport()
+ {
+ return _transport;
+ }
+
+ public void setTransport(String _transport)
+ {
+ this._transport = _transport;
+ }
+
+
+ public String getOption(String key)
+ {
+ return _options.get(key);
+ }
+
+ public void setOption(String key, String value)
+ {
+ _options.put(key, value);
+ }
+
+ public long getTimeout()
+ {
+ if (_options.containsKey(OPTIONS_CONNECT_TIMEOUT))
+ {
+ try
+ {
+ return Long.parseLong(_options.get(OPTIONS_CONNECT_TIMEOUT));
+ }
+ catch (NumberFormatException nfe)
+ {
+ //Do nothing as we will use the default below.
+ }
+ }
+
+ return BrokerDetails.DEFAULT_CONNECT_TIMEOUT;
+ }
+
+ public void setTimeout(long timeout)
+ {
+ setOption(OPTIONS_CONNECT_TIMEOUT, Long.toString(timeout));
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(_transport);
+ sb.append("://");
+
+ if (!(_transport.equalsIgnoreCase("vm")))
+ {
+ sb.append(_host);
+ }
+
+ sb.append(':');
+ sb.append(_port);
+
+ sb.append(printOptionsURL());
+
+ return sb.toString();
+ }
+
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof BrokerDetails))
+ {
+ return false;
+ }
+
+ BrokerDetails bd = (BrokerDetails) o;
+
+ return _host.equalsIgnoreCase(bd.getHost()) &&
+ (_port == bd.getPort()) &&
+ _transport.equalsIgnoreCase(bd.getTransport()) &&
+ (useSSL() == bd.useSSL());
+
+ //todo do we need to compare all the options as well?
+ }
+
+ private String printOptionsURL()
+ {
+ StringBuffer optionsURL = new StringBuffer();
+
+ optionsURL.append('?');
+
+ if (!(_options.isEmpty()))
+ {
+
+ for (String key : _options.keySet())
+ {
+ optionsURL.append(key);
+
+ optionsURL.append("='");
+
+ optionsURL.append(_options.get(key));
+
+ optionsURL.append("'");
+
+ optionsURL.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+ }
+
+ //remove the extra DEFAULT_OPTION_SEPERATOR or the '?' if there are no options
+ optionsURL.deleteCharAt(optionsURL.length() - 1);
+
+ return optionsURL.toString();
+ }
+
+ public boolean useSSL()
+ {
+ // To be friendly to users we should be case insensitive.
+ // or simply force users to conform to OPTIONS_SSL
+ // todo make case insensitive by trying ssl Ssl sSl ssL SSl SsL sSL SSL
+
+ if (_options.containsKey(OPTIONS_SSL))
+ {
+ return _options.get(OPTIONS_SSL).equalsIgnoreCase("true");
+ }
+
+ return false;
+ }
+
+ public void useSSL(boolean ssl)
+ {
+ setOption(OPTIONS_SSL, Boolean.toString(ssl));
+ }
+
+
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQConnection.java b/java/client/src/org/apache/qpid/client/AMQConnection.java
new file mode 100644
index 0000000000..f11d4d9307
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQConnection.java
@@ -0,0 +1,927 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUndeliveredException;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.AMQUnresolvedAddressException;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.failover.FailoverSupport;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.jms.*;
+import org.apache.qpid.jms.Connection;
+
+import org.apache.log4j.Logger;
+
+import javax.jms.*;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.naming.Reference;
+import javax.naming.NamingException;
+import javax.naming.StringRefAddr;
+import javax.naming.Referenceable;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.net.ConnectException;
+import java.nio.channels.UnresolvedAddressException;
+import java.text.MessageFormat;
+
+public class AMQConnection extends Closeable implements Connection, QueueConnection, TopicConnection, Referenceable
+{
+ private static final Logger _logger = Logger.getLogger(AMQConnection.class);
+
+ private AtomicInteger _idFactory = new AtomicInteger(0);
+
+ /**
+ * This is the "root" mutex that must be held when doing anything that could be impacted by failover.
+ * This must be held by any child objects of this connection such as the session, producers and consumers.
+ */
+ private final Object _failoverMutex = new Object();
+
+ /**
+ * A channel is roughly analogous to a session. The server can negotiate the maximum number of channels
+ * per session and we must prevent the client from opening too many. Zero means unlimited.
+ */
+ private long _maximumChannelCount;
+
+ /**
+ * The maximum size of frame supported by the server
+ */
+ private long _maximumFrameSize;
+
+ /**
+ * The protocol handler dispatches protocol events for this connection. For example, when the connection is dropped
+ * the handler deals with this. It also deals with the initial dispatch of any protocol frames to their appropriate
+ * handler.
+ */
+ private AMQProtocolHandler _protocolHandler;
+
+ /**
+ * Maps from session id (Integer) to AMQSession instance
+ */
+ private final Map _sessions = new LinkedHashMap(); //fixme this is map is replicated in amqprotocolsession as _channelId2SessionMap
+
+ private String _clientName;
+
+ /**
+ * The user name to use for authentication
+ */
+ private String _username;
+
+ /**
+ * The password to use for authentication
+ */
+ private String _password;
+
+ /**
+ * The virtual path to connect to on the AMQ server
+ */
+ private String _virtualHost;
+
+ private ExceptionListener _exceptionListener;
+
+ private ConnectionListener _connectionListener;
+
+ private ConnectionURL _connectionURL;
+
+ /**
+ * Whether this connection is started, i.e. whether messages are flowing to consumers. It has no meaning for
+ * message publication.
+ */
+ private boolean _started;
+
+ /**
+ * Policy dictating how to failover
+ */
+ private FailoverPolicy _failoverPolicy;
+
+ /*
+ * _Connected should be refactored with a suitable wait object.
+ */
+ private boolean _connected;
+
+ /*
+ * The last error code that occured on the connection. Used to return the correct exception to the client
+ */
+ private AMQException _lastAMQException = null;
+
+ public AMQConnection(String broker, String username, String password,
+ String clientName, String virtualHost) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(ConnectionURL.AMQ_PROTOCOL + "://" +
+ username + ":" + password + "@" + clientName +
+ virtualHost + "?brokerlist='" + broker + "'"));
+ }
+
+ public AMQConnection(String host, int port, String username, String password,
+ String clientName, String virtualHost) throws AMQException, URLSyntaxException
+ {
+ this(host, port, false, username, password, clientName, virtualHost);
+ }
+
+ public AMQConnection(String host, int port, boolean useSSL, String username, String password,
+ String clientName, String virtualHost) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(useSSL ?
+ ConnectionURL.AMQ_PROTOCOL + "://" +
+ username + ":" + password + "@" + clientName +
+ virtualHost + "?brokerlist='tcp://" + host + ":" + port + "'"
+ + "," + ConnectionURL.OPTIONS_SSL + "='true'" :
+ ConnectionURL.AMQ_PROTOCOL + "://" +
+ username + ":" + password + "@" + clientName +
+ virtualHost + "?brokerlist='tcp://" + host + ":" + port + "'"
+ + "," + ConnectionURL.OPTIONS_SSL + "='false'"
+ ));
+ }
+
+ public AMQConnection(String connection) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(connection));
+ }
+
+ public AMQConnection(ConnectionURL connectionURL) throws AMQException
+ {
+ _logger.info("Connection:" + connectionURL);
+
+ if (connectionURL == null)
+ {
+ throw new IllegalArgumentException("Connection must be specified");
+ }
+
+ _connectionURL = connectionURL;
+
+ _clientName = connectionURL.getClientName();
+ _username = connectionURL.getUsername();
+ _password = connectionURL.getPassword();
+ _virtualHost = connectionURL.getVirtualHost();
+
+ _failoverPolicy = new FailoverPolicy(connectionURL);
+
+ _protocolHandler = new AMQProtocolHandler(this);
+
+ // We are not currently connected
+ _connected = false;
+
+
+ Exception lastException = new Exception();
+ lastException.initCause(new ConnectException());
+
+ while (lastException != null && lastException.getCause() instanceof ConnectException && _failoverPolicy.failoverAllowed())
+ {
+ try
+ {
+ makeBrokerConnection(_failoverPolicy.getNextBrokerDetails());
+ lastException = null;
+ }
+ catch (Exception e)
+ {
+ lastException = e;
+
+ _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), e.getCause());
+ _logger.info(e);
+ _logger.info(e.getCause());
+ }
+ }
+
+ _logger.debug("Are we connected:" + _connected);
+
+ // Then the Failover Thread will handle conneciton
+ if (_failoverPolicy.failoverAllowed())
+ {
+ //TODO this needs to be redone so that we are not spinning.
+ // A suitable object should be set that is then waited on
+ // and only notified when a connection is made or when
+ // the AMQConnection gets closed.
+ while (!_connected && !_closed.get())
+ {
+ try
+ {
+ _logger.debug("Sleeping.");
+ Thread.sleep(100);
+ }
+ catch (InterruptedException ie)
+ {
+ _logger.debug("Woken up.");
+ }
+ }
+ if (!_failoverPolicy.failoverAllowed() || _failoverPolicy.getCurrentBrokerDetails() == null)
+ {
+ if (_lastAMQException != null)
+ {
+ throw _lastAMQException;
+ }
+ }
+ }
+ else
+ {
+ String message = null;
+
+ if (lastException != null)
+ {
+ if (lastException.getCause() != null)
+ {
+ message = lastException.getCause().getMessage();
+ }
+ else
+ {
+ message = lastException.getMessage();
+ }
+ }
+
+ if (message == null || message.equals(""))
+ {
+ message = "Unable to Connect";
+ }
+
+ AMQException e = new AMQConnectionException(message);
+
+ if (lastException != null)
+ {
+ if (lastException instanceof UnresolvedAddressException)
+ {
+ e = new AMQUnresolvedAddressException(message);
+ }
+ e.initCause(lastException);
+ }
+
+ throw e;
+ }
+ }
+
+ protected AMQConnection(String username, String password, String clientName, String virtualHost)
+ {
+ _clientName = clientName;
+ _username = username;
+ _password = password;
+ _virtualHost = virtualHost;
+ }
+
+ private void makeBrokerConnection(BrokerDetails brokerDetail) throws IOException, AMQException
+ {
+ try
+ {
+ TransportConnection.getInstance().connect(_protocolHandler, brokerDetail);
+ // this blocks until the connection has been set up or when an error
+ // has prevented the connection being set up
+ _protocolHandler.attainState(AMQState.CONNECTION_OPEN);
+ _failoverPolicy.attainedConnection();
+
+ //Again this should be changed to a suitable notify
+ _connected = true;
+ }
+ catch (AMQException e)
+ {
+ _lastAMQException = e;
+ throw e;
+ }
+ }
+
+ public boolean attemptReconnection(String host, int port, boolean useSSL)
+ {
+ BrokerDetails bd = new AMQBrokerDetails(host, port, useSSL);
+
+ _failoverPolicy.setBroker(bd);
+
+ try
+ {
+ makeBrokerConnection(bd);
+ return true;
+ }
+ catch (Exception e)
+ {
+ _logger.info("Unable to connect to broker at " + bd);
+ attemptReconnection();
+ }
+ return false;
+ }
+
+ public boolean attemptReconnection()
+ {
+ while (_failoverPolicy.failoverAllowed())
+ {
+ try
+ {
+ makeBrokerConnection(_failoverPolicy.getNextBrokerDetails());
+ return true;
+ }
+ catch (Exception e)
+ {
+ if (!(e instanceof AMQException))
+ {
+ _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), e);
+ }
+ else
+ {
+ _logger.info(e.getMessage() + ":Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails());
+ }
+ }
+ }
+
+ //connection unsuccessful
+ return false;
+ }
+
+ /**
+ * Get the details of the currently active broker
+ *
+ * @return null if no broker is active (i.e. no successful connection has been made, or
+ * the BrokerDetail instance otherwise
+ */
+ public BrokerDetails getActiveBrokerDetails()
+ {
+ return _failoverPolicy.getCurrentBrokerDetails();
+ }
+
+ public boolean failoverAllowed()
+ {
+ return _failoverPolicy.failoverAllowed();
+ }
+
+ public Session createSession(final boolean transacted, final int acknowledgeMode) throws JMSException
+ {
+ return createSession(transacted, acknowledgeMode, AMQSession.DEFAULT_PREFETCH);
+ }
+
+ public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode,
+ final int prefetch) throws JMSException
+ {
+ checkNotClosed();
+ if (channelLimitReached())
+ {
+ throw new ChannelLimitReachedException(_maximumChannelCount);
+ }
+ else
+ {
+ return (org.apache.qpid.jms.Session) new FailoverSupport()
+ {
+ public Object operation() throws JMSException
+ {
+ int channelId = _idFactory.incrementAndGet();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Write channel open frame for channel id " + channelId);
+ }
+
+ // We must create the session and register it before actually sending the frame to the server to
+ // open it, so that there is no window where we could receive data on the channel and not be set
+ // up to handle it appropriately.
+ AMQSession session = new AMQSession(AMQConnection.this, channelId, transacted, acknowledgeMode,
+ prefetch);
+ _protocolHandler.addSessionByChannel(channelId, session);
+ registerSession(channelId, session);
+
+ boolean success = false;
+ try
+ {
+ createChannelOverWire(channelId, prefetch, transacted);
+ success = true;
+ }
+ catch (AMQException e)
+ {
+ JMSException jmse = new JMSException("Error creating session: " + e);
+ jmse.setLinkedException(e);
+ throw jmse;
+ }
+ finally
+ {
+ if (!success) {
+ _protocolHandler.removeSessionByChannel(channelId);
+ deregisterSession(channelId);
+ }
+ }
+
+ if (_started)
+ {
+ session.start();
+ }
+ return session;
+ }
+ }.execute(this);
+ }
+ }
+
+ private void createChannelOverWire(int channelId, int prefetch, boolean transacted)
+ throws AMQException
+ {
+ _protocolHandler.syncWrite(
+ ChannelOpenBody.createAMQFrame(channelId, null), ChannelOpenOkBody.class);
+ _protocolHandler.syncWrite(
+ BasicQosBody.createAMQFrame(channelId, 0, prefetch, false),
+ BasicQosOkBody.class);
+
+ if (transacted)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Issuing TxSelect for " + channelId);
+ }
+ _protocolHandler.syncWrite(TxSelectBody.createAMQFrame(channelId), TxSelectOkBody.class);
+ }
+ }
+
+ private void reopenChannel(int channelId, int prefetch, boolean transacted) throws AMQException
+ {
+ try
+ {
+ createChannelOverWire(channelId, prefetch, transacted);
+ }
+ catch (AMQException e)
+ {
+ _protocolHandler.removeSessionByChannel(channelId);
+ deregisterSession(channelId);
+ throw new AMQException("Error reopening channel " + channelId + " after failover: " + e);
+ }
+ }
+
+
+ public void setFailoverPolicy(FailoverPolicy policy)
+ {
+ _failoverPolicy = policy;
+ }
+
+ public FailoverPolicy getFailoverPolicy()
+ {
+ return _failoverPolicy;
+ }
+
+ public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) throws JMSException
+ {
+ return (QueueSession) createSession(transacted, acknowledgeMode);
+ }
+
+ public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException
+ {
+ return (TopicSession) createSession(transacted, acknowledgeMode);
+ }
+
+ private boolean channelLimitReached()
+ {
+ return _maximumChannelCount != 0 && _sessions.size() == _maximumChannelCount;
+ }
+
+ public String getClientID() throws JMSException
+ {
+ checkNotClosed();
+ return _clientName;
+ }
+
+ public void setClientID(String clientID) throws JMSException
+ {
+ checkNotClosed();
+ _clientName = clientID;
+ }
+
+ public ConnectionMetaData getMetaData() throws JMSException
+ {
+ checkNotClosed();
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public ExceptionListener getExceptionListener() throws JMSException
+ {
+ checkNotClosed();
+ return _exceptionListener;
+ }
+
+ public void setExceptionListener(ExceptionListener listener) throws JMSException
+ {
+ checkNotClosed();
+ _exceptionListener = listener;
+ }
+
+ /**
+ * Start the connection, i.e. start flowing messages. Note that this method must be called only from a single thread
+ * and is not thread safe (which is legal according to the JMS specification).
+ *
+ * @throws JMSException
+ */
+ public void start() throws JMSException
+ {
+ checkNotClosed();
+ if (!_started)
+ {
+ final Iterator it = _sessions.entrySet().iterator();
+ while (it.hasNext())
+ {
+ final AMQSession s = (AMQSession) ((Map.Entry) it.next()).getValue();
+ s.start();
+ }
+ _started = true;
+ }
+ }
+
+ public void stop() throws JMSException
+ {
+ checkNotClosed();
+
+ if (_started)
+ {
+ for (Iterator i = _sessions.values().iterator(); i.hasNext();)
+ {
+ ((AMQSession) i.next()).stop();
+ }
+ _started = false;
+ }
+ }
+
+ public void close() throws JMSException
+ {
+ synchronized (getFailoverMutex())
+ {
+ if (!_closed.getAndSet(true))
+ {
+ try
+ {
+ closeAllSessions(null);
+ _protocolHandler.closeConnection();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Error closing connection: " + e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Marks all sessions and their children as closed without sending any protocol messages. Useful when
+ * you need to mark objects "visible" in userland as closed after failover or other significant event that
+ * impacts the connection.
+ * <p/>
+ * The caller must hold the failover mutex before calling this method.
+ */
+ private void markAllSessionsClosed()
+ {
+ final LinkedList sessionCopy = new LinkedList(_sessions.values());
+ final Iterator it = sessionCopy.iterator();
+ while (it.hasNext())
+ {
+ final AMQSession session = (AMQSession) it.next();
+
+ session.markClosed();
+ }
+ _sessions.clear();
+ }
+
+ /**
+ * Close all the sessions, either due to normal connection closure or due to an error occurring.
+ *
+ * @param cause if not null, the error that is causing this shutdown
+ * <p/>
+ * The caller must hold the failover mutex before calling this method.
+ */
+ private void closeAllSessions(Throwable cause) throws JMSException
+ {
+ final LinkedList sessionCopy = new LinkedList(_sessions.values());
+ final Iterator it = sessionCopy.iterator();
+ JMSException sessionException = null;
+ while (it.hasNext())
+ {
+ final AMQSession session = (AMQSession) it.next();
+ if (cause != null)
+ {
+ session.closed(cause);
+ }
+ else
+ {
+ try
+ {
+ session.close();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e);
+ sessionException = e;
+ }
+ }
+ }
+ _sessions.clear();
+ if (sessionException != null)
+ {
+ throw sessionException;
+ }
+ }
+
+ public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector,
+ ServerSessionPool sessionPool,
+ int maxMessages) throws JMSException
+ {
+ checkNotClosed();
+ return null;
+ }
+
+ public ConnectionConsumer createConnectionConsumer(Queue queue, String messageSelector,
+ ServerSessionPool sessionPool,
+ int maxMessages) throws JMSException
+ {
+ checkNotClosed();
+ return null;
+ }
+
+ public ConnectionConsumer createConnectionConsumer(Topic topic, String messageSelector,
+ ServerSessionPool sessionPool,
+ int maxMessages) throws JMSException
+ {
+ checkNotClosed();
+ return null;
+ }
+
+ public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName,
+ String messageSelector, ServerSessionPool sessionPool,
+ int maxMessages)
+ throws JMSException
+ {
+ // TODO Auto-generated method stub
+ checkNotClosed();
+ return null;
+ }
+
+ public long getMaximumChannelCount()
+ {
+ checkNotClosed();
+ return _maximumChannelCount;
+ }
+
+ public void setConnectionListener(ConnectionListener listener)
+ {
+ _connectionListener = listener;
+ }
+
+ public ConnectionListener getConnectionListener()
+ {
+ return _connectionListener;
+ }
+
+ public void setMaximumChannelCount(long maximumChannelCount)
+ {
+ checkNotClosed();
+ _maximumChannelCount = maximumChannelCount;
+ }
+
+ public void setMaximumFrameSize(long frameMax)
+ {
+ _maximumFrameSize = frameMax;
+ }
+
+ public long getMaximumFrameSize()
+ {
+ return _maximumFrameSize;
+ }
+
+ public Map getSessions()
+ {
+ return _sessions;
+ }
+
+ public String getUsername()
+ {
+ return _username;
+ }
+
+ public String getPassword()
+ {
+ return _password;
+ }
+
+ public String getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public AMQProtocolHandler getProtocolHandler()
+ {
+ return _protocolHandler;
+ }
+
+ public void bytesSent(long writtenBytes)
+ {
+ if (_connectionListener != null)
+ {
+ _connectionListener.bytesSent(writtenBytes);
+ }
+ }
+
+ public void bytesReceived(long receivedBytes)
+ {
+ if (_connectionListener != null)
+ {
+ _connectionListener.bytesReceived(receivedBytes);
+ }
+ }
+
+ /**
+ * Fire the preFailover event to the registered connection listener (if any)
+ *
+ * @param redirect true if this is the result of a redirect request rather than a connection error
+ * @return true if no listener or listener does not veto change
+ */
+ public boolean firePreFailover(boolean redirect)
+ {
+ boolean proceed = true;
+ if (_connectionListener != null)
+ {
+ proceed = _connectionListener.preFailover(redirect);
+ }
+ return proceed;
+ }
+
+ /**
+ * Fire the preResubscribe event to the registered connection listener (if any). If the listener
+ * vetoes resubscription then all the sessions are closed.
+ *
+ * @return true if no listener or listener does not veto resubscription.
+ * @throws JMSException
+ */
+ public boolean firePreResubscribe() throws JMSException
+ {
+ if (_connectionListener != null)
+ {
+ boolean resubscribe = _connectionListener.preResubscribe();
+ if (!resubscribe)
+ {
+ markAllSessionsClosed();
+ }
+ return resubscribe;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * Fires a failover complete event to the registered connection listener (if any).
+ */
+ public void fireFailoverComplete()
+ {
+ if (_connectionListener != null)
+ {
+ _connectionListener.failoverComplete();
+ }
+ }
+
+ /**
+ * In order to protect the consistency of the connection and its child sessions, consumers and producers,
+ * the "failover mutex" must be held when doing any operations that could be corrupted during failover.
+ *
+ * @return a mutex. Guaranteed never to change for the lifetime of this connection even if failover occurs.
+ */
+ public final Object getFailoverMutex()
+ {
+ return _failoverMutex;
+ }
+
+ /**
+ * If failover is taking place this will block until it has completed. If failover
+ * is not taking place it will return immediately.
+ *
+ * @throws InterruptedException
+ */
+ public void blockUntilNotFailingOver() throws InterruptedException
+ {
+ _protocolHandler.blockUntilNotFailingOver();
+ }
+
+ /**
+ * Invoked by the AMQProtocolSession when a protocol session exception has occurred.
+ * This method sends the exception to a JMS exception listener, if configured, and
+ * propagates the exception to sessions, which in turn will propagate to consumers.
+ * This allows synchronous consumers to have exceptions thrown to them.
+ *
+ * @param cause the exception
+ */
+ public void exceptionReceived(Throwable cause)
+ {
+
+ _logger.debug("Connection Close done by:" + Thread.currentThread().getName());
+ _logger.debug("exceptionReceived is ", cause);
+
+ final JMSException je;
+ if (cause instanceof JMSException)
+ {
+ je = (JMSException) cause;
+ }
+ else
+ {
+ je = new JMSException("Exception thrown against " + toString() + ": " + cause);
+ if (cause instanceof Exception)
+ {
+ je.setLinkedException((Exception) cause);
+ }
+ }
+
+ // in the case of an IOException, MINA has closed the protocol session so we set _closed to true
+ // so that any generic client code that tries to close the connection will not mess up this error
+ // handling sequence
+ if (cause instanceof IOException)
+ {
+ _closed.set(true);
+ }
+
+ if (_exceptionListener != null)
+ {
+ _exceptionListener.onException(je);
+ }
+
+ if (!(cause instanceof AMQUndeliveredException) && !(cause instanceof AMQAuthenticationException))
+ {
+ try
+ {
+ _logger.info("Closing AMQConnection due to :" + cause.getMessage());
+ _closed.set(true);
+ closeAllSessions(cause); // FIXME: when doing this end up with RejectedExecutionException from executor.
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing all sessions: " + e, e);
+ }
+
+ }
+ else
+ {
+ _logger.info("Not a hard-error connection not closing.");
+ }
+ }
+
+ void registerSession(int channelId, AMQSession session)
+ {
+ _sessions.put(channelId, session);
+ }
+
+ void deregisterSession(int channelId)
+ {
+ _sessions.remove(channelId);
+ }
+
+ /**
+ * For all sessions, and for all consumers in those sessions, resubscribe. This is called during failover handling.
+ * The caller must hold the failover mutex before calling this method.
+ */
+ public void resubscribeSessions() throws AMQException
+ {
+ ArrayList sessions = new ArrayList(_sessions.values());
+ _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: remove?
+ for (Iterator it = sessions.iterator(); it.hasNext();)
+ {
+ AMQSession s = (AMQSession) it.next();
+ _protocolHandler.addSessionByChannel(s.getChannelId(), s);
+ reopenChannel(s.getChannelId(), s.getDefaultPrefetch(), s.getTransacted());
+ s.resubscribe();
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer("AMQConnection:\n");
+ if (_failoverPolicy.getCurrentBrokerDetails() == null)
+ {
+ buf.append("No active broker connection");
+ }
+ else
+ {
+ BrokerDetails bd = _failoverPolicy.getCurrentBrokerDetails();
+ buf.append("Host: ").append(String.valueOf(bd.getHost()));
+ buf.append("\nPort: ").append(String.valueOf(bd.getPort()));
+ }
+ buf.append("\nVirtual Host: ").append(String.valueOf(_virtualHost));
+ buf.append("\nClient ID: ").append(String.valueOf(_clientName));
+ buf.append("\nActive session count: ").append(_sessions == null ? 0 : _sessions.size());
+ return buf.toString();
+ }
+
+ public String toURL()
+ {
+ return _connectionURL.toString();
+ }
+
+ public Reference getReference() throws NamingException
+ {
+ return new Reference(
+ AMQConnection.class.getName(),
+ new StringRefAddr(AMQConnection.class.getName(), toURL()),
+ AMQConnectionFactory.class.getName(),
+ null); // factory location
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQConnectionFactory.java b/java/client/src/org/apache/qpid/client/AMQConnectionFactory.java
new file mode 100644
index 0000000000..05a484e366
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQConnectionFactory.java
@@ -0,0 +1,358 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.jms.ConnectionURL;
+
+import javax.jms.*;
+import javax.jms.Connection;
+import javax.naming.*;
+import javax.naming.spi.ObjectFactory;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Hashtable;
+
+
+public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, ObjectFactory, Referenceable
+{
+ private String _host;
+ private int _port;
+ private String _defaultUsername;
+ private String _defaultPassword;
+ private String _virtualPath;
+
+ private ConnectionURL _connectionDetails;
+
+
+ public AMQConnectionFactory()
+ {
+ }
+
+ public AMQConnectionFactory(String url) throws URLSyntaxException
+ {
+ _connectionDetails = new AMQConnectionURL(url);
+ }
+
+ public AMQConnectionFactory(ConnectionURL url)
+ {
+ _connectionDetails = url;
+ }
+
+ public AMQConnectionFactory(String broker, String username, String password,
+ String clientName, String virtualHost) throws URLSyntaxException
+ {
+ this(new AMQConnectionURL(ConnectionURL.AMQ_PROTOCOL + "://" +
+ username + ":" + password + "@" + clientName +
+ virtualHost + "?brokerlist='" + broker + "'"));
+ }
+
+ public AMQConnectionFactory(String host, int port, String virtualPath)
+ {
+ this(host, port, "guest", "guest", virtualPath);
+ }
+
+ public AMQConnectionFactory(String host, int port, String defaultUsername, String defaultPassword,
+ String virtualPath)
+ {
+ _host = host;
+ _port = port;
+ _defaultUsername = defaultUsername;
+ _defaultPassword = defaultPassword;
+ _virtualPath = virtualPath;
+
+//todo when setting Host/Port has been resolved then we can use this otherwise those methods won't work with the following line.
+// _connectionDetails = new AMQConnectionURL(
+// ConnectionURL.AMQ_PROTOCOL + "://" +
+// _defaultUsername + ":" + _defaultPassword + "@" +
+// virtualPath + "?brokerlist='tcp://" + host + ":" + port + "'");
+ }
+
+ /**
+ * @return The _defaultPassword.
+ */
+ public final String getDefaultPassword(String password)
+ {
+ if (_connectionDetails != null)
+ {
+ return _connectionDetails.getPassword();
+ }
+ else
+ {
+ return _defaultPassword;
+ }
+ }
+
+ /**
+ * @param password The _defaultPassword to set.
+ */
+ public final void setDefaultPassword(String password)
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setPassword(password);
+ }
+ _defaultPassword = password;
+ }
+
+ /**
+ * @return The _defaultPassword.
+ */
+ public final String getDefaultUsername(String password)
+ {
+ if (_connectionDetails != null)
+ {
+ return _connectionDetails.getUsername();
+ }
+ else
+ {
+ return _defaultUsername;
+ }
+ }
+
+ /**
+ * @param username The _defaultUsername to set.
+ */
+ public final void setDefaultUsername(String username)
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setUsername(username);
+ }
+ _defaultUsername = username;
+ }
+
+ /**
+ * @return The _host .
+ */
+ public final String getHost()
+ {
+ //todo this doesn't make sense in a multi broker URL as we have no current as that is done by AMQConnection
+ return _host;
+ }
+
+ /**
+ * @param host The _host to set.
+ */
+ public final void setHost(String host)
+ {
+ //todo if _connectionDetails is set then run _connectionDetails.addBrokerDetails()
+ // Should perhaps have this method changed to setBroker(host,port)
+ _host = host;
+ }
+
+ /**
+ * @return _port The _port to set.
+ */
+ public final int getPort()
+ {
+ //todo see getHost
+ return _port;
+ }
+
+ /**
+ * @param port The port to set.
+ */
+ public final void setPort(int port)
+ {
+ //todo see setHost
+ _port = port;
+ }
+
+ /**
+ * @return he _virtualPath.
+ */
+ public final String getVirtualPath()
+ {
+ if (_connectionDetails != null)
+ {
+ return _connectionDetails.getVirtualHost();
+ }
+ else
+ {
+ return _virtualPath;
+ }
+ }
+
+ /**
+ * @param path The _virtualPath to set.
+ */
+ public final void setVirtualPath(String path)
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setVirtualHost(path);
+ }
+
+ _virtualPath = path;
+ }
+
+ static String getUniqueClientID()
+ {
+ try
+ {
+ InetAddress addr = InetAddress.getLocalHost();
+ return addr.getHostName() + System.currentTimeMillis();
+ }
+ catch (UnknownHostException e)
+ {
+ return null;
+ }
+ }
+
+ public Connection createConnection() throws JMSException
+ {
+ try
+ {
+ if (_connectionDetails != null)
+ {
+ if (_connectionDetails.getClientName() == null || _connectionDetails.getClientName().equals(""))
+ {
+ _connectionDetails.setClientName(getUniqueClientID());
+ }
+ return new AMQConnection(_connectionDetails);
+ }
+ else
+ {
+ return new AMQConnection(_host, _port, _defaultUsername, _defaultPassword, getUniqueClientID(),
+ _virtualPath);
+ }
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Error creating connection: " + e.getMessage());
+ jmse.setLinkedException(e);
+ throw jmse;
+ }
+
+
+ }
+
+ public Connection createConnection(String userName, String password) throws JMSException
+ {
+ try
+ {
+ return new AMQConnection(_host, _port, userName, password, getUniqueClientID(), _virtualPath);
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Error creating connection: " + e.getMessage());
+ jmse.setLinkedException(e);
+ throw jmse;
+ }
+ }
+
+ public QueueConnection createQueueConnection() throws JMSException
+ {
+ return (QueueConnection) createConnection();
+ }
+
+ public QueueConnection createQueueConnection(String username, String password) throws JMSException
+ {
+ return (QueueConnection) createConnection(username, password);
+ }
+
+ public TopicConnection createTopicConnection() throws JMSException
+ {
+ return (TopicConnection) createConnection();
+ }
+
+ public TopicConnection createTopicConnection(String username, String password) throws JMSException
+ {
+ return (TopicConnection) createConnection(username, password);
+ }
+
+
+ public ConnectionURL getConnectionURL()
+ {
+ return _connectionDetails;
+ }
+
+ /**
+ * JNDI interface to create objects from References.
+ *
+ * @param obj The Reference from JNDI
+ * @param name
+ * @param ctx
+ * @param env
+ * @return AMQConnection,AMQTopic,AMQQueue, or AMQConnectionFactory.
+ * @throws Exception
+ */
+ public Object getObjectInstance(Object obj, Name name, Context ctx,
+ Hashtable env) throws Exception
+ {
+ if (obj instanceof Reference)
+ {
+ Reference ref = (Reference) obj;
+
+ if (ref.getClassName().equals(AMQConnection.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQConnection.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQConnection((String) addr.getContent());
+ }
+ }
+
+ if (ref.getClassName().equals(AMQQueue.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQQueue.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQQueue(new AMQBindingURL((String) addr.getContent()).getQueueName());
+ }
+ }
+
+ if (ref.getClassName().equals(AMQTopic.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQTopic.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQTopic(new AMQBindingURL((String) addr.getContent()).getDestinationName());
+ }
+ }
+
+ if (ref.getClassName().equals(AMQConnectionFactory.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQConnectionFactory.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQConnectionFactory(new AMQConnectionURL((String) addr.getContent()));
+ }
+ }
+
+ }
+ return null;
+ }
+
+
+ public Reference getReference() throws NamingException
+ {
+ return new Reference(
+ AMQConnectionFactory.class.getName(),
+ new StringRefAddr(AMQConnectionFactory.class.getName(), _connectionDetails.getURL()),
+ AMQConnectionFactory.class.getName(),
+ null); // factory location
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQConnectionURL.java b/java/client/src/org/apache/qpid/client/AMQConnectionURL.java
new file mode 100644
index 0000000000..b87388b9c8
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQConnectionURL.java
@@ -0,0 +1,399 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.url.URLHelper;
+import org.apache.qpid.url.URLSyntaxException;
+
+import java.util.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class AMQConnectionURL implements ConnectionURL
+{
+ private String _url;
+ private String _failoverMethod;
+ private HashMap<String, String> _failoverOptions;
+ private HashMap<String, String> _options;
+ private List<BrokerDetails> _brokers;
+ private String _clientName;
+ private String _username;
+ private String _password;
+ private String _virtualHost;
+
+ public AMQConnectionURL(String fullURL) throws URLSyntaxException
+ {
+ _url = fullURL;
+ _options = new HashMap<String, String>();
+ _brokers = new LinkedList<BrokerDetails>();
+ _failoverOptions = new HashMap<String, String>();
+
+ // Connection URL format
+ //amqp://[user:pass@][clientid]/virtualhost?brokerlist='tcp://host:port?option=\'value\',option=\'value\';vm://:3/virtualpath?option=\'value\'',failover='method?option=\'value\',option='value''"
+ // Options are of course optional except for requiring a single broker in the broker list.
+ try
+ {
+ URI connection = new URI(fullURL);
+
+ if (connection.getScheme() == null || !(connection.getScheme().equalsIgnoreCase(AMQ_PROTOCOL)))
+ {
+ throw new URISyntaxException(fullURL, "Not an AMQP URL");
+ }
+
+ if (connection.getHost() == null || connection.getHost().equals(""))
+ {
+ String uid = AMQConnectionFactory.getUniqueClientID();
+ if (uid == null)
+ {
+ URLHelper.parseError(-1, "Client Name not specified", fullURL);
+ }
+ else
+ {
+ setClientName(uid);
+ }
+
+ }
+ else
+ {
+ setClientName(connection.getHost());
+ }
+
+ String userInfo = connection.getUserInfo();
+
+ if (userInfo == null)
+ {
+ //Fix for Java 1.5 which doesn't parse UserInfo for non http URIs
+ userInfo = connection.getAuthority();
+
+ if (userInfo != null)
+ {
+ int atIndex = userInfo.indexOf('@');
+
+ if (atIndex != -1)
+ {
+ userInfo = userInfo.substring(0, atIndex);
+ }
+ else
+ {
+ userInfo = null;
+ }
+ }
+
+ }
+
+ if (userInfo == null)
+ {
+ URLHelper.parseError(AMQ_PROTOCOL.length() + 3,
+ "User information not found on url", fullURL);
+ }
+ else
+ {
+ parseUserInfo(userInfo);
+ }
+ String virtualHost = connection.getPath();
+
+ if (virtualHost != null && (!virtualHost.equals("")))
+ {
+ setVirtualHost(virtualHost);
+ }
+ else
+ {
+ int authLength = connection.getAuthority().length();
+ int start = AMQ_PROTOCOL.length() + 3;
+ int testIndex = start + authLength;
+ if (testIndex < fullURL.length() && fullURL.charAt(testIndex) == '?')
+ {
+ URLHelper.parseError(start, testIndex - start, "Virtual host found", fullURL);
+ }
+ else
+ {
+ URLHelper.parseError(-1, "Virtual host not specified", fullURL);
+ }
+
+ }
+
+
+ URLHelper.parseOptions(_options, connection.getQuery());
+
+ processOptions();
+
+ //Fragment is #string (not used)
+ //System.out.println(connection.getFragment());
+
+ }
+ catch (URISyntaxException uris)
+ {
+ if (uris instanceof URLSyntaxException)
+ {
+ throw (URLSyntaxException) uris;
+ }
+
+ int slash = fullURL.indexOf("\\");
+
+ if (slash == -1)
+ {
+ URLHelper.parseError(uris.getIndex(), uris.getReason(), uris.getInput());
+ }
+ else
+ {
+ if (slash != 0 && fullURL.charAt(slash - 1) == ':')
+ {
+ URLHelper.parseError(slash - 2, fullURL.indexOf('?') - slash + 2, "Virtual host looks like a windows path, forward slash not allowed in URL", fullURL);
+ }
+ else
+ {
+ URLHelper.parseError(slash, "Forward slash not allowed in URL", fullURL);
+ }
+ }
+
+ }
+ }
+
+ private void parseUserInfo(String userinfo) throws URLSyntaxException
+ {
+ //user info = user:pass
+
+ int colonIndex = userinfo.indexOf(':');
+
+ if (colonIndex == -1)
+ {
+ URLHelper.parseError(AMQ_PROTOCOL.length() + 3, userinfo.length(),
+ "Null password in user information not allowed.", _url);
+ }
+ else
+ {
+ setUsername(userinfo.substring(0, colonIndex));
+ setPassword(userinfo.substring(colonIndex + 1));
+ }
+
+ }
+
+ private void processOptions() throws URLSyntaxException
+ {
+ if (_options.containsKey(OPTIONS_BROKERLIST))
+ {
+ String brokerlist = _options.get(OPTIONS_BROKERLIST);
+
+ //brokerlist tcp://host:port?option='value',option='value';vm://:3/virtualpath?option='value'
+ StringTokenizer st = new StringTokenizer(brokerlist, "" + URLHelper.BROKER_SEPARATOR);
+
+ while (st.hasMoreTokens())
+ {
+ String broker = st.nextToken();
+
+ _brokers.add(new AMQBrokerDetails(broker));
+ }
+
+ _options.remove(OPTIONS_BROKERLIST);
+ }
+
+ if (_options.containsKey(OPTIONS_FAILOVER))
+ {
+ String failover = _options.get(OPTIONS_FAILOVER);
+
+ // failover='method?option='value',option='value''
+
+ int methodIndex = failover.indexOf('?');
+
+ if (methodIndex > -1)
+ {
+ _failoverMethod = failover.substring(0, methodIndex);
+ URLHelper.parseOptions(_failoverOptions, failover.substring(methodIndex + 1));
+ }
+ else
+ {
+ _failoverMethod = failover;
+ }
+
+ _options.remove(OPTIONS_FAILOVER);
+ }
+ }
+
+ public String getURL()
+ {
+ return _url;
+ }
+
+ public String getFailoverMethod()
+ {
+ return _failoverMethod;
+ }
+
+ public String getFailoverOption(String key)
+ {
+ return _failoverOptions.get(key);
+ }
+
+ public void setFailoverOption(String key, String value)
+ {
+ _failoverOptions.put(key, value);
+ }
+
+ public int getBrokerCount()
+ {
+ return _brokers.size();
+ }
+
+ public BrokerDetails getBrokerDetails(int index)
+ {
+ if (index < _brokers.size())
+ {
+ return _brokers.get(index);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public void addBrokerDetails(BrokerDetails broker)
+ {
+ if (!(_brokers.contains(broker)))
+ {
+ _brokers.add(broker);
+ }
+ }
+
+ public List<BrokerDetails> getAllBrokerDetails()
+ {
+ return _brokers;
+ }
+
+ public String getClientName()
+ {
+ return _clientName;
+ }
+
+ public void setClientName(String clientName)
+ {
+ _clientName = clientName;
+ }
+
+ public String getUsername()
+ {
+ return _username;
+ }
+
+ public void setUsername(String username)
+ {
+ _username = username;
+ }
+
+ public String getPassword()
+ {
+ return _password;
+ }
+
+ public void setPassword(String password)
+ {
+ _password = password;
+ }
+
+ public String getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public void setVirtualHost(String virtuaHost)
+ {
+ _virtualHost = virtuaHost;
+ }
+
+ public String getOption(String key)
+ {
+ return _options.get(key);
+ }
+
+ public void setOption(String key, String value)
+ {
+ _options.put(key, value);
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(AMQ_PROTOCOL);
+ sb.append("://");
+
+ if (_username != null)
+ {
+ sb.append(_username);
+
+ if (_password != null)
+ {
+ sb.append(':');
+ sb.append(_password);
+ }
+
+ sb.append('@');
+ }
+
+ sb.append(_clientName);
+
+ sb.append(_virtualHost);
+
+ sb.append(optionsToString());
+
+ return sb.toString();
+ }
+
+ private String optionsToString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("?" + OPTIONS_BROKERLIST + "='");
+
+ for (BrokerDetails service : _brokers)
+ {
+ sb.append(service.toString());
+ sb.append(';');
+ }
+
+ sb.deleteCharAt(sb.length() - 1);
+ sb.append("'");
+
+ if (_failoverMethod != null)
+ {
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ sb.append(OPTIONS_FAILOVER + "='");
+ sb.append(_failoverMethod);
+ sb.append(URLHelper.printOptions(_failoverOptions));
+ sb.append("'");
+ }
+
+ return sb.toString();
+ }
+
+
+ public static void main(String[] args) throws URLSyntaxException
+ {
+
+ String url2 = "amqp://ritchiem:bob@temp?brokerlist='tcp://localhost:5672;jcp://fancyserver:3000/',failover='roundrobin'";
+ //"amqp://user:pass@clientid/virtualhost?brokerlist='tcp://host:1?option1=\'value\',option2=\'value\';vm://:3?option1=\'value\'',failover='method?option1=\'value\',option2='value''";
+
+ ConnectionURL connectionurl2 = new AMQConnectionURL(url2);
+
+ System.out.println(url2);
+ System.out.println(connectionurl2);
+
+ }
+
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQDestination.java b/java/client/src/org/apache/qpid/client/AMQDestination.java
new file mode 100644
index 0000000000..5deb974c5b
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQDestination.java
@@ -0,0 +1,282 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.url.URLHelper;
+import org.apache.qpid.exchange.ExchangeDefaults;
+
+import javax.naming.Reference;
+import javax.naming.NamingException;
+import javax.naming.StringRefAddr;
+import javax.naming.Referenceable;
+import javax.jms.Destination;
+
+
+public abstract class AMQDestination implements Destination, Referenceable
+{
+ protected final String _exchangeName;
+
+ protected final String _exchangeClass;
+
+ protected final String _destinationName;
+
+ protected boolean _isDurable;
+
+ protected final boolean _isExclusive;
+
+ protected final boolean _isAutoDelete;
+
+ protected String _queueName;
+
+ protected AMQDestination(String url) throws URLSyntaxException
+ {
+ this(new AMQBindingURL(url));
+ }
+
+ protected AMQDestination(BindingURL binding)
+ {
+ _exchangeName = binding.getExchangeName();
+ _exchangeClass = binding.getExchangeClass();
+ _destinationName = binding.getDestinationName();
+
+ _isExclusive = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_EXCLUSIVE));
+ _isAutoDelete = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_AUTODELETE));
+ _isDurable = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_DURABLE));
+ _queueName = binding.getQueueName();
+ }
+
+ protected AMQDestination(String exchangeName, String exchangeClass, String destinationName, String queueName)
+ {
+ this(exchangeName, exchangeClass, destinationName, false, false, queueName);
+ }
+
+ protected AMQDestination(String exchangeName, String exchangeClass, String destinationName)
+ {
+ this(exchangeName, exchangeClass, destinationName, false, false, null);
+ }
+
+ protected AMQDestination(String exchangeName, String exchangeClass, String destinationName, boolean isExclusive,
+ boolean isAutoDelete, String queueName)
+ {
+ if (destinationName == null)
+ {
+ throw new IllegalArgumentException("Destination name must not be null");
+ }
+ if (exchangeName == null)
+ {
+ throw new IllegalArgumentException("Exchange name must not be null");
+ }
+ if (exchangeClass == null)
+ {
+ throw new IllegalArgumentException("Exchange class must not be null");
+ }
+ _exchangeName = exchangeName;
+ _exchangeClass = exchangeClass;
+ _destinationName = destinationName;
+ _isExclusive = isExclusive;
+ _isAutoDelete = isAutoDelete;
+ _queueName = queueName;
+ }
+
+ public abstract String getEncodedName();
+
+ public boolean isDurable()
+ {
+ return _isDurable;
+ }
+
+ public String getExchangeName()
+ {
+ return _exchangeName;
+ }
+
+ public String getExchangeClass()
+ {
+ return _exchangeClass;
+ }
+
+ public boolean isTopic()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_NAME.equals(_exchangeName);
+ }
+
+ public boolean isQueue()
+ {
+ return ExchangeDefaults.DIRECT_EXCHANGE_NAME.equals(_exchangeName);
+ }
+
+ public String getDestinationName()
+ {
+ return _destinationName;
+ }
+
+ public String getQueueName()
+ {
+ return _queueName;
+ }
+
+ public void setQueueName(String queueName)
+ {
+ _queueName = queueName;
+ }
+
+ public abstract String getRoutingKey();
+
+ public boolean isExclusive()
+ {
+ return _isExclusive;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _isAutoDelete;
+ }
+
+ public abstract boolean isNameRequired();
+
+ public String toString()
+ {
+ return toURL();
+
+ /*
+ return "Destination: " + _destinationName + ", " +
+ "Queue Name: " + _queueName + ", Exchange: " + _exchangeName +
+ ", Exchange class: " + _exchangeClass + ", Exclusive: " + _isExclusive +
+ ", AutoDelete: " + _isAutoDelete + ", Routing Key: " + getRoutingKey();
+ */
+ }
+
+ public String toURL()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(_exchangeClass);
+ sb.append("://");
+ sb.append(_exchangeName);
+
+ sb.append("/");
+
+ if (_destinationName != null)
+ {
+ sb.append(_destinationName);
+ }
+
+ sb.append("/");
+
+ if (_queueName != null)
+ {
+ sb.append(_queueName);
+ }
+
+ sb.append("?");
+
+ if (_isDurable)
+ {
+ sb.append(BindingURL.OPTION_DURABLE);
+ sb.append("='true'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ if (_isExclusive)
+ {
+ sb.append(BindingURL.OPTION_EXCLUSIVE);
+ sb.append("='true'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ if (_isAutoDelete)
+ {
+ sb.append(BindingURL.OPTION_AUTODELETE);
+ sb.append("='true'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ //remove the last char '?' if there is no options , ',' if there are.
+ sb.deleteCharAt(sb.length() - 1);
+
+ return sb.toString();
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+
+ final AMQDestination that = (AMQDestination) o;
+
+ if (!_destinationName.equals(that._destinationName))
+ {
+ return false;
+ }
+ if (!_exchangeClass.equals(that._exchangeClass))
+ {
+ return false;
+ }
+ if (!_exchangeName.equals(that._exchangeName))
+ {
+ return false;
+ }
+ if ((_queueName == null && that._queueName != null) ||
+ (_queueName != null && !_queueName.equals(that._queueName)))
+ {
+ return false;
+ }
+ if (_isExclusive != that._isExclusive)
+ {
+ return false;
+ }
+ if (_isAutoDelete != that._isAutoDelete)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = _exchangeName.hashCode();
+ result = 29 * result + _exchangeClass.hashCode();
+ result = 29 * result + _destinationName.hashCode();
+ if (_queueName != null)
+ {
+ result = 29 * result + _queueName.hashCode();
+ }
+ result = result * (_isExclusive ? 13 : 7);
+ result = result * (_isAutoDelete ? 13 : 7);
+ return result;
+ }
+
+ public Reference getReference() throws NamingException
+ {
+ return new Reference(
+ this.getClass().getName(),
+ new StringRefAddr(this.getClass().getName(), toURL()),
+ AMQConnectionFactory.class.getName(),
+ null); // factory location
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQHeadersExchange.java b/java/client/src/org/apache/qpid/client/AMQHeadersExchange.java
new file mode 100644
index 0000000000..5b8de7a13f
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQHeadersExchange.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.exchange.ExchangeDefaults;
+
+/**
+ * A destination backed by a headers exchange
+ */
+public class AMQHeadersExchange extends AMQDestination
+{
+ public AMQHeadersExchange(String queueName)
+ {
+ super(queueName, ExchangeDefaults.HEADERS_EXCHANGE_CLASS, queueName, true, true, null);
+ }
+
+ public String getEncodedName()
+ {
+ return getDestinationName();
+ }
+
+ public String getRoutingKey()
+ {
+ return getDestinationName();
+ }
+
+ public boolean isNameRequired()
+ {
+ //Not sure what the best approach is here, probably to treat this like a topic
+ //and allow server to generate names. As it is AMQ specific it doesn't need to
+ //fit the JMS API expectations so this is not as yet critical.
+ return false;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQNoConsumersException.java b/java/client/src/org/apache/qpid/client/AMQNoConsumersException.java
new file mode 100644
index 0000000000..19d4da0b8c
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQNoConsumersException.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.AMQUndeliveredException;
+import org.apache.qpid.protocol.AMQConstant;
+
+
+public class AMQNoConsumersException extends AMQUndeliveredException
+{
+ public AMQNoConsumersException(String msg, Object bounced)
+ {
+ super(AMQConstant.NO_CONSUMERS.getCode(), msg, bounced);
+ }
+
+
+}
+
+
diff --git a/java/client/src/org/apache/qpid/client/AMQNoRouteException.java b/java/client/src/org/apache/qpid/client/AMQNoRouteException.java
new file mode 100644
index 0000000000..ad885bcbb3
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQNoRouteException.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.AMQUndeliveredException;
+import org.apache.qpid.protocol.AMQConstant;
+
+
+public class AMQNoRouteException extends AMQUndeliveredException
+{
+ public AMQNoRouteException(String msg, Object bounced)
+ {
+ super(AMQConstant.NO_ROUTE.getCode(), msg, bounced);
+ }
+
+
+}
+
+
diff --git a/java/client/src/org/apache/qpid/client/AMQQueue.java b/java/client/src/org/apache/qpid/client/AMQQueue.java
new file mode 100644
index 0000000000..0a8e1bdd7c
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQQueue.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.exchange.ExchangeDefaults;
+
+import javax.jms.Queue;
+
+public class AMQQueue extends AMQDestination implements Queue
+{
+
+ /**
+ * Create a reference to a non temporary queue using a BindingURL object.
+ * Note this does not actually imply the queue exists.
+ * @param binding a BindingURL object
+ */
+ public AMQQueue(BindingURL binding)
+ {
+ super(binding);
+ }
+
+ /**
+ * Create a reference to a non temporary queue. Note this does not actually imply the queue exists.
+ * @param name the name of the queue
+ */
+ public AMQQueue(String name)
+ {
+ this(name, false);
+ }
+
+ /**
+ * Create a queue with a specified name.
+ *
+ * @param name the destination name (used in the routing key)
+ * @param temporary if true the broker will generate a queue name, also if true then the queue is autodeleted
+ * and exclusive
+ */
+ public AMQQueue(String name, boolean temporary)
+ {
+ // queue name is set to null indicating that the broker assigns a name in the case of temporary queues
+ // temporary queues are typically used as response queues
+ this(name, temporary?null:name, temporary, temporary);
+ _isDurable = !temporary;
+ }
+
+ /**
+ * Create a reference to a queue. Note this does not actually imply the queue exists.
+ * @param destinationName the queue name
+ * @param queueName the queue name
+ * @param exclusive true if the queue should only permit a single consumer
+ * @param autoDelete true if the queue should be deleted automatically when the last consumers detaches
+ */
+ public AMQQueue(String destinationName, String queueName, boolean exclusive, boolean autoDelete)
+ {
+ super(ExchangeDefaults.DIRECT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS, destinationName, exclusive,
+ autoDelete, queueName);
+ }
+
+ public String getEncodedName()
+ {
+ return 'Q' + getQueueName();
+ }
+
+ public String getRoutingKey()
+ {
+ return getQueueName();
+ }
+
+ public boolean isNameRequired()
+ {
+ //If the name is null, we require one to be generated by the client so that it will#
+ //remain valid if we failover (see BLZ-24)
+ return getQueueName() == null;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQSession.java b/java/client/src/org/apache/qpid/client/AMQSession.java
new file mode 100644
index 0000000000..6fd4e9cbef
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQSession.java
@@ -0,0 +1,1149 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUndeliveredException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.MessageFactoryRegistry;
+import org.apache.qpid.client.message.UnprocessedMessage;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.failover.FailoverSupport;
+import org.apache.qpid.client.util.FlowControllingBlockingQueue;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.URLSyntaxException;
+
+import javax.jms.*;
+import javax.jms.IllegalStateException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.text.MessageFormat;
+
+public class AMQSession extends Closeable implements Session, QueueSession, TopicSession
+{
+ private static final Logger _logger = Logger.getLogger(AMQSession.class);
+
+ public static final int DEFAULT_PREFETCH = 5000;
+
+ private AMQConnection _connection;
+
+ private boolean _transacted;
+
+ private int _acknowledgeMode;
+
+ private int _channelId;
+
+ private int _defaultPrefetch = DEFAULT_PREFETCH;
+
+ /**
+ * Used in the consume method. We generate the consume tag on the client so that we can use the nowait
+ * feature.
+ */
+ private int _nextTag = 1;
+
+ /**
+ * This queue is bounded and is used to store messages before being dispatched to the consumer
+ */
+ private final FlowControllingBlockingQueue _queue;
+
+ private Dispatcher _dispatcher;
+
+ private MessageFactoryRegistry _messageFactoryRegistry;
+
+ /**
+ * Set of all producers created by this session
+ */
+ private Map _producers = new ConcurrentHashMap();
+
+ /**
+ * Maps from consumer tag (String) to JMSMessageConsumer instance
+ */
+ private Map _consumers = new ConcurrentHashMap();
+
+ /**
+ * Default value for immediate flag used by producers created by this session is false, i.e. a consumer does not
+ * need to be attached to a queue
+ */
+ protected static final boolean DEFAULT_IMMEDIATE = false;
+
+ /**
+ * Default value for mandatory flag used by producers created by this sessio is true, i.e. server will not silently
+ * drop messages where no queue is connected to the exchange for the message
+ */
+ protected static final boolean DEFAULT_MANDATORY = true;
+
+ /**
+ * The counter of the next producer id. This id is generated by the session and used only to allow the
+ * producer to identify itself to the session when deregistering itself.
+ *
+ * Access to this id does not require to be synchronized since according to the JMS specification only one
+ * thread of control is allowed to create producers for any given session instance.
+ */
+ private long _nextProducerId;
+
+ /**
+ * Track the 'stopped' state of the dispatcher, a session starts in the stopped state.
+ */
+ private volatile AtomicBoolean _stopped = new AtomicBoolean(true);
+
+ /**
+ * Responsible for decoding a message fragment and passing it to the appropriate message consumer.
+ */
+ private class Dispatcher extends Thread
+ {
+ public Dispatcher()
+ {
+ super("Dispatcher-Channel-" + _channelId);
+ }
+
+ public void run()
+ {
+ UnprocessedMessage message;
+ _stopped.set(false);
+ try
+ {
+ while (!_stopped.get() && (message = (UnprocessedMessage)_queue.take()) != null)
+ {
+ dispatchMessage(message);
+ }
+ }
+ catch(InterruptedException e)
+ {
+ ;
+ }
+
+ _logger.info("Dispatcher thread terminating for channel " + _channelId);
+ }
+
+ private void dispatchMessage(UnprocessedMessage message)
+ {
+ if (message.deliverBody != null)
+ {
+ final BasicMessageConsumer consumer = (BasicMessageConsumer) _consumers.get(message.deliverBody.consumerTag);
+
+ if (consumer == null)
+ {
+ _logger.warn("Received a message from queue " + message.deliverBody.consumerTag + " without a handler - ignoring...");
+ }
+ else
+ {
+ consumer.notifyMessage(message, _channelId);
+ }
+ }
+ else
+ {
+ try
+ {
+ // Bounced message is processed here, away from the mina thread
+ AbstractJMSMessage bouncedMessage = _messageFactoryRegistry.createMessage(0,
+ false,
+ message.contentHeader,
+ message.bodies);
+
+ int errorCode = message.bounceBody.replyCode;
+ String reason = message.bounceBody.replyText;
+ _logger.debug("Message returned with error code " + errorCode + " (" + reason + ")");
+
+ //Todo should this be moved to an exception handler of sorts. Somewhere errors are converted to correct execeptions.
+ if (errorCode == AMQConstant.NO_CONSUMERS.getCode())
+ {
+ _connection.exceptionReceived(new AMQNoConsumersException("Error: " + reason, bouncedMessage));
+ }
+ else
+ {
+ if (errorCode == AMQConstant.NO_ROUTE.getCode())
+ {
+ _connection.exceptionReceived(new AMQNoRouteException("Error: " + reason, bouncedMessage));
+ }
+ else
+ {
+ _connection.exceptionReceived(new AMQUndeliveredException(errorCode, "Error: " + reason, bouncedMessage));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Caught exception trying to raise undelivered message exception (dump follows) - ignoring...", e);
+ }
+ }
+ }
+
+ public void stopDispatcher()
+ {
+ _stopped.set(true);
+ interrupt();
+ }
+ }
+
+ AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
+ MessageFactoryRegistry messageFactoryRegistry)
+ {
+ this(con, channelId, transacted, acknowledgeMode, messageFactoryRegistry, DEFAULT_PREFETCH);
+ }
+
+ AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
+ MessageFactoryRegistry messageFactoryRegistry, int defaultPrefetch)
+ {
+ _connection = con;
+ _transacted = transacted;
+ if (transacted)
+ {
+ _acknowledgeMode = javax.jms.Session.SESSION_TRANSACTED;
+ }
+ else
+ {
+ _acknowledgeMode = acknowledgeMode;
+ }
+ _channelId = channelId;
+ _messageFactoryRegistry = messageFactoryRegistry;
+ _defaultPrefetch = defaultPrefetch;
+ _queue = new FlowControllingBlockingQueue(_defaultPrefetch,
+ new FlowControllingBlockingQueue.ThresholdListener()
+ {
+ public void aboveThreshold(int currentValue)
+ {
+ if(_acknowledgeMode == NO_ACKNOWLEDGE)
+ {
+ _logger.warn("Above threshold so suspending channel. Current value is " + currentValue);
+ suspendChannel();
+ }
+ }
+
+ public void underThreshold(int currentValue)
+ {
+ if(_acknowledgeMode == NO_ACKNOWLEDGE)
+ {
+ _logger.warn("Below threshold so unsuspending channel. Current value is " + currentValue);
+ unsuspendChannel();
+ }
+ }
+ });
+ }
+
+ AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode)
+ {
+ this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry());
+ }
+
+ AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetch)
+ {
+ this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetch);
+ }
+
+ AMQConnection getAMQConnection()
+ {
+ return _connection;
+ }
+
+ public BytesMessage createBytesMessage() throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+ try
+ {
+ return (BytesMessage) _messageFactoryRegistry.createMessage("application/octet-stream");
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public MapMessage createMapMessage() throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+ try
+ {
+ return (MapMessage) _messageFactoryRegistry.createMessage("jms/map-message");
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public javax.jms.Message createMessage() throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+ try
+ {
+ return (BytesMessage) _messageFactoryRegistry.createMessage("application/octet-stream");
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public ObjectMessage createObjectMessage() throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+ try
+ {
+ return (ObjectMessage) _messageFactoryRegistry.createMessage("application/java-object-stream");
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public ObjectMessage createObjectMessage(Serializable object) throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+ try
+ {
+ ObjectMessage msg = (ObjectMessage) _messageFactoryRegistry.createMessage("application/java-object-stream");
+ msg.setObject(object);
+ return msg;
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public StreamMessage createStreamMessage() throws JMSException
+ {
+ checkNotClosed();
+ throw new UnsupportedOperationException("Stream messages not supported");
+ }
+
+ public TextMessage createTextMessage() throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+
+ try
+ {
+ return (TextMessage) _messageFactoryRegistry.createMessage("text/plain");
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public TextMessage createTextMessage(String text) throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ checkNotClosed();
+ try
+ {
+ TextMessage msg = (TextMessage) _messageFactoryRegistry.createMessage("text/plain");
+ msg.setText(text);
+ return msg;
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Unable to create message: " + e);
+ }
+ }
+ }
+
+ public boolean getTransacted()
+ {
+ checkNotClosed();
+ return _transacted;
+ }
+
+ public int getAcknowledgeMode() throws JMSException
+ {
+ checkNotClosed();
+ return _acknowledgeMode;
+ }
+
+ public void commit() throws JMSException
+ {
+ checkTransacted();
+ try
+ {
+ // Acknowledge up to message last delivered (if any) for each consumer.
+ //need to send ack for messages delivered to consumers so far
+ for(Iterator i = _consumers.values().iterator(); i.hasNext();)
+ {
+ ((BasicMessageConsumer) i.next()).acknowledgeLastDelivered();
+ }
+
+ // Commits outstanding messages sent and outstanding acknowledgements.
+ _connection.getProtocolHandler().syncWrite(TxCommitBody.createAMQFrame(_channelId), TxCommitOkBody.class);
+ }
+ catch (AMQException e)
+ {
+ JMSException exception = new JMSException("Failed to commit: " + e.getMessage());
+ exception.setLinkedException(e);
+ throw exception;
+ }
+ }
+
+ public void rollback() throws JMSException
+ {
+ checkTransacted();
+ try
+ {
+ _connection.getProtocolHandler().syncWrite(
+ TxRollbackBody.createAMQFrame(_channelId), TxRollbackOkBody.class);
+ }
+ catch (AMQException e)
+ {
+ throw (JMSException) (new JMSException("Failed to rollback: " + e).initCause(e));
+ }
+ }
+
+ public void close() throws JMSException
+ {
+ // We must close down all producers and consumers in an orderly fashion. This is the only method
+ // that can be called from a different thread of control from the one controlling the session
+ synchronized (_connection.getFailoverMutex())
+ {
+ _closed.set(true);
+
+ // we pass null since this is not an error case
+ closeProducersAndConsumers(null);
+
+ try
+ {
+ _connection.getProtocolHandler().closeSession(this);
+ final AMQFrame frame = ChannelCloseBody.createAMQFrame(
+ getChannelId(), AMQConstant.REPLY_SUCCESS.getCode(), "JMS client closing channel", 0, 0);
+ _connection.getProtocolHandler().syncWrite(frame, ChannelCloseOkBody.class);
+ // When control resumes at this point, a reply will have been received that
+ // indicates the broker has closed the channel successfully
+
+ }
+ catch (AMQException e)
+ {
+ throw new JMSException("Error closing session: " + e);
+ }
+ finally
+ {
+ _connection.deregisterSession(_channelId);
+ }
+ }
+ }
+
+ /**
+ * Close all producers or consumers. This is called either in the error case or when closing the session normally.
+ * @param amqe the exception, may be null to indicate no error has occurred
+ */
+ private void closeProducersAndConsumers(AMQException amqe)
+ {
+ try
+ {
+ closeProducers();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ }
+ try
+ {
+ closeConsumers(amqe);
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ }
+ }
+
+ /**
+ * Called when the server initiates the closure of the session
+ * unilaterally.
+ * @param e the exception that caused this session to be closed. Null causes the
+ */
+ public void closed(Throwable e)
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ // An AMQException has an error code and message already and will be passed in when closure occurs as a
+ // result of a channel close request
+ _closed.set(true);
+ AMQException amqe;
+ if (e instanceof AMQException)
+ {
+ amqe = (AMQException) e;
+ }
+ else
+ {
+ amqe = new AMQException(_logger, "Closing session forcibly", e);
+ }
+ _connection.deregisterSession(_channelId);
+ closeProducersAndConsumers(amqe);
+ }
+ }
+
+ /**
+ * Called to mark the session as being closed. Useful when the session needs to be made invalid, e.g. after
+ * failover when the client has veoted resubscription.
+ *
+ * The caller of this method must already hold the failover mutex.
+ */
+ void markClosed()
+ {
+ _closed.set(true);
+ _connection.deregisterSession(_channelId);
+ markClosedProducersAndConsumers();
+ }
+
+ private void markClosedProducersAndConsumers()
+ {
+ try
+ {
+ // no need for a markClosed* method in this case since there is no protocol traffic closing a producer
+ closeProducers();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ }
+ try
+ {
+ markClosedConsumers();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ }
+ }
+
+ /**
+ * Called to close message producers cleanly. This may or may <b>not</b> be as a result of an error. There is
+ * currently no way of propagating errors to message producers (this is a JMS limitation).
+ */
+ private void closeProducers() throws JMSException
+ {
+ // we need to clone the list of producers since the close() method updates the _producers collection
+ // which would result in a concurrent modification exception
+ final ArrayList clonedProducers = new ArrayList(_producers.values());
+
+ final Iterator it = clonedProducers.iterator();
+ while (it.hasNext())
+ {
+ final BasicMessageProducer prod = (BasicMessageProducer) it.next();
+ prod.close();
+ }
+ // at this point the _producers map is empty
+ }
+
+ /**
+ * Called to close message consumers cleanly. This may or may <b>not</b> be as a result of an error.
+ * @param error not null if this is a result of an error occurring at the connection level
+ */
+ private void closeConsumers(Throwable error) throws JMSException
+ {
+ if (_dispatcher != null)
+ {
+ _dispatcher.stopDispatcher();
+ }
+ // we need to clone the list of consumers since the close() method updates the _consumers collection
+ // which would result in a concurrent modification exception
+ final ArrayList clonedConsumers = new ArrayList(_consumers.values());
+
+ final Iterator it = clonedConsumers.iterator();
+ while (it.hasNext())
+ {
+ final BasicMessageConsumer con = (BasicMessageConsumer) it.next();
+ if (error != null)
+ {
+ con.notifyError(error);
+ }
+ else
+ {
+ con.close();
+ }
+ }
+ // at this point the _consumers map will be empty
+ }
+
+ private void markClosedConsumers() throws JMSException
+ {
+ if (_dispatcher != null)
+ {
+ _dispatcher.stopDispatcher();
+ }
+ // we need to clone the list of consumers since the close() method updates the _consumers collection
+ // which would result in a concurrent modification exception
+ final ArrayList clonedConsumers = new ArrayList(_consumers.values());
+
+ final Iterator it = clonedConsumers.iterator();
+ while (it.hasNext())
+ {
+ final BasicMessageConsumer con = (BasicMessageConsumer) it.next();
+ con.markClosed();
+ }
+ // at this point the _consumers map will be empty
+ }
+
+ /**
+ * Asks the broker to resend all unacknowledged messages for the session.
+ * @throws JMSException
+ */
+ public void recover() throws JMSException
+ {
+ checkNotClosed();
+ checkNotTransacted(); // throws IllegalStateException if a transacted session
+
+ _connection.getProtocolHandler().writeFrame(BasicRecoverBody.createAMQFrame(_channelId, false));
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ checkNotClosed();
+ throw new java.lang.UnsupportedOperationException("MessageListener interface not supported");
+ }
+
+ public void setMessageListener(MessageListener listener) throws JMSException
+ {
+ checkNotClosed();
+ throw new java.lang.UnsupportedOperationException("MessageListener interface not supported");
+ }
+
+ public void run()
+ {
+ throw new java.lang.UnsupportedOperationException();
+ }
+
+ public MessageProducer createProducer(Destination destination, boolean mandatory,
+ boolean immediate, boolean waitUntilSent)
+ throws JMSException
+ {
+ return createProducerImpl(destination, mandatory, immediate, waitUntilSent);
+ }
+
+ public MessageProducer createProducer(Destination destination, boolean mandatory, boolean immediate)
+ throws JMSException
+ {
+ return createProducerImpl(destination, mandatory, immediate);
+ }
+
+ public MessageProducer createProducer(Destination destination, boolean immediate)
+ throws JMSException
+ {
+ return createProducerImpl(destination, DEFAULT_MANDATORY, immediate);
+ }
+
+ public MessageProducer createProducer(Destination destination) throws JMSException
+ {
+ return createProducerImpl(destination, DEFAULT_MANDATORY, DEFAULT_IMMEDIATE);
+ }
+
+ private org.apache.qpid.jms.MessageProducer createProducerImpl(Destination destination, boolean mandatory,
+ boolean immediate)
+ throws JMSException
+ {
+ return createProducerImpl(destination, mandatory, immediate, false);
+ }
+
+ private org.apache.qpid.jms.MessageProducer createProducerImpl(final Destination destination, final boolean mandatory,
+ final boolean immediate, final boolean waitUntilSent)
+ throws JMSException
+ {
+ return (org.apache.qpid.jms.MessageProducer) new FailoverSupport()
+ {
+ public Object operation()
+ {
+ checkNotClosed();
+
+ return new BasicMessageProducer(_connection, (AMQDestination)destination, _transacted, _channelId,
+ AMQSession.this, _connection.getProtocolHandler(),
+ getNextProducerId(), immediate, mandatory, waitUntilSent);
+ }
+ }.execute(_connection);
+ }
+
+ public MessageConsumer createConsumer(Destination destination) throws JMSException
+ {
+ return createConsumer(destination, _defaultPrefetch, false, false, null);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException
+ {
+ return createConsumer(destination, _defaultPrefetch, false, false, messageSelector);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal)
+ throws JMSException
+ {
+ return createConsumer(destination, _defaultPrefetch, noLocal, false, messageSelector);
+ }
+
+ public MessageConsumer createConsumer(Destination destination,
+ int prefetch,
+ boolean noLocal,
+ boolean exclusive,
+ String selector) throws JMSException
+ {
+ return createConsumer(destination, prefetch, noLocal, exclusive, selector, null);
+ }
+
+ public MessageConsumer createConsumer(Destination destination,
+ int prefetch,
+ boolean noLocal,
+ boolean exclusive,
+ String selector,
+ FieldTable rawSelector) throws JMSException
+ {
+ return createConsumerImpl(destination, prefetch, noLocal, exclusive,
+ selector, rawSelector);
+ }
+
+ protected MessageConsumer createConsumerImpl(final Destination destination,
+ final int prefetch,
+ final boolean noLocal,
+ final boolean exclusive,
+ final String selector,
+ final FieldTable rawSelector) throws JMSException
+ {
+ return (org.apache.qpid.jms.MessageConsumer) new FailoverSupport()
+ {
+ public Object operation() throws JMSException
+ {
+ checkNotClosed();
+
+ AMQDestination amqd = (AMQDestination)destination;
+
+ final AMQProtocolHandler protocolHandler = _connection.getProtocolHandler();
+ // TODO: construct the rawSelector from the selector string if rawSelector == null
+ final FieldTable ft = new FieldTable();
+ //if (rawSelector != null)
+ // ft.put("headers", rawSelector.getDataAsBytes());
+ if (rawSelector != null)
+ {
+ ft.putAll(rawSelector);
+ }
+ BasicMessageConsumer consumer = new BasicMessageConsumer(_channelId, _connection, amqd, selector, noLocal,
+ _messageFactoryRegistry, AMQSession.this,
+ protocolHandler, ft, prefetch, exclusive,
+ _acknowledgeMode);
+
+ try
+ {
+ registerConsumer(consumer);
+ }
+ catch (AMQException e)
+ {
+ JMSException ex = new JMSException("Error registering consumer: " + e);
+ ex.setLinkedException(e);
+ throw ex;
+ }
+
+ return consumer;
+ }
+ }.execute(_connection);
+ }
+
+ public void declareExchange(String name, String type)
+ {
+ declareExchange(name, type, _connection.getProtocolHandler());
+ }
+
+ private void declareExchange(AMQDestination amqd, AMQProtocolHandler protocolHandler)
+ {
+ declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler);
+ }
+
+ private void declareExchange(String name, String type, AMQProtocolHandler protocolHandler)
+ {
+ AMQFrame exchangeDeclare = ExchangeDeclareBody.createAMQFrame(_channelId, 0, name, type, false, false, false, false, true, null);
+ protocolHandler.writeFrame(exchangeDeclare);
+ }
+
+ /**
+ * Declare the queue.
+ * @param amqd
+ * @param protocolHandler
+ * @return the queue name. This is useful where the broker is generating a queue name on behalf of the client.
+ * @throws AMQException
+ */
+ private String declareQueue(AMQDestination amqd, AMQProtocolHandler protocolHandler) throws AMQException
+ {
+ // For queues (but not topics) we generate the name in the client rather than the
+ // server. This allows the name to be reused on failover if required. In general,
+ // the destination indicates whether it wants a name generated or not.
+ if(amqd.isNameRequired())
+ {
+ amqd.setQueueName(protocolHandler.generateQueueName());
+ }
+
+ AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, 0, amqd.getQueueName(),
+ false, amqd.isDurable(), amqd.isExclusive(),
+ amqd.isAutoDelete(), true, null);
+
+ protocolHandler.writeFrame(queueDeclare);
+ return amqd.getQueueName();
+ }
+
+ private void bindQueue(AMQDestination amqd, String queueName, AMQProtocolHandler protocolHandler, FieldTable ft) throws AMQException
+ {
+ AMQFrame queueBind = QueueBindBody.createAMQFrame(_channelId, 0,
+ queueName, amqd.getExchangeName(),
+ amqd.getRoutingKey(), true, ft);
+
+ protocolHandler.writeFrame(queueBind);
+ }
+
+ /**
+ * Register to consume from the queue.
+ * @param queueName
+ * @return the consumer tag generated by the broker
+ */
+ private String consumeFromQueue(String queueName, AMQProtocolHandler protocolHandler, int prefetch,
+ boolean noLocal, boolean exclusive, int acknowledgeMode) throws AMQException
+ {
+ //need to generate a consumer tag on the client so we can exploit the nowait flag
+ String tag = Integer.toString(_nextTag++);
+
+ AMQFrame jmsConsume = BasicConsumeBody.createAMQFrame(_channelId, 0,
+ queueName, tag, noLocal,
+ acknowledgeMode == Session.NO_ACKNOWLEDGE,
+ exclusive, true);
+
+ protocolHandler.writeFrame(jmsConsume);
+ return tag;
+ }
+
+ public Queue createQueue(String queueName) throws JMSException
+ {
+ if (queueName.indexOf('/') == -1)
+ {
+ return new AMQQueue(queueName);
+ }
+ else
+ {
+ try{
+ return new AMQQueue(new AMQBindingURL(queueName));
+ }catch(URLSyntaxException urlse)
+ {
+ JMSException jmse = new JMSException(urlse.getReason());
+ jmse.setLinkedException(urlse);
+
+ throw jmse;
+ }
+ }
+ }
+
+ public QueueReceiver createReceiver(Queue queue) throws JMSException
+ {
+ return (QueueReceiver) createConsumer(queue);
+ }
+
+ public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException
+ {
+ return (QueueReceiver) createConsumer(queue, messageSelector);
+ }
+
+ public QueueSender createSender(Queue queue) throws JMSException
+ {
+ return (QueueSender) createProducer(queue);
+ }
+
+ public Topic createTopic(String topicName) throws JMSException
+ {
+ if (topicName.indexOf('/') == -1)
+ {
+ return new AMQTopic(topicName);
+ }
+ else
+ {
+ try{
+ return new AMQTopic(new AMQBindingURL(topicName));
+ }
+ catch (URLSyntaxException urlse)
+ {
+ JMSException jmse = new JMSException(urlse.getReason());
+ jmse.setLinkedException(urlse);
+
+ throw jmse;
+ }
+ }
+ }
+
+ public TopicSubscriber createSubscriber(Topic topic) throws JMSException
+ {
+ return (TopicSubscriber) createConsumer(topic);
+ }
+
+ public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException
+ {
+ return (TopicSubscriber) createConsumer(topic, messageSelector, noLocal);
+ }
+
+ /**
+ * Note, currently this does not handle reuse of the same name with different topics correctly.
+ * If a name is reused in creating a new subscriber with a different topic/selecto or no-local
+ * flag then the subcriber will receive messages matching the old subscription AND the new one.
+ * The spec states that the new one should replace the old one.
+ * TODO: fix it.
+ */
+ public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException
+ {
+ AMQTopic dest = new AMQTopic((AMQTopic) topic, _connection.getClientID(), name);
+ return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest));
+ }
+
+ /**
+ * Note, currently this does not handle reuse of the same name with different topics correctly.
+ */
+ public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal)
+ throws JMSException
+ {
+ AMQTopic dest = new AMQTopic((AMQTopic) topic, _connection.getClientID(), name);
+ BasicMessageConsumer consumer = (BasicMessageConsumer) createConsumer(dest, messageSelector, noLocal);
+ return new TopicSubscriberAdaptor(dest, consumer);
+ }
+
+ public TopicPublisher createPublisher(Topic topic) throws JMSException
+ {
+ return (TopicPublisher) createProducer(topic);
+ }
+
+ public QueueBrowser createBrowser(Queue queue) throws JMSException
+ {
+ throw new UnsupportedOperationException("Queue browsing not supported");
+ }
+
+ public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException
+ {
+ throw new UnsupportedOperationException("Queue browsing not supported");
+ }
+
+ public TemporaryQueue createTemporaryQueue() throws JMSException
+ {
+ return new AMQTemporaryQueue();
+ }
+
+ public TemporaryTopic createTemporaryTopic() throws JMSException
+ {
+ return new AMQTemporaryTopic();
+ }
+
+ public void unsubscribe(String name) throws JMSException
+ {
+ //send a queue.delete for the subscription
+ String queue = _connection.getClientID() + ":" + name;
+ AMQFrame frame = QueueDeleteBody.createAMQFrame(_channelId, 0, queue, false, false, true);
+ _connection.getProtocolHandler().writeFrame(frame);
+ }
+
+ private void checkTransacted() throws JMSException
+ {
+ if (!getTransacted())
+ {
+ throw new IllegalStateException("Session is not transacted");
+ }
+ }
+
+ private void checkNotTransacted() throws JMSException
+ {
+ if (getTransacted())
+ {
+ throw new IllegalStateException("Session is transacted");
+ }
+ }
+
+ /**
+ * Invoked by the MINA IO thread (indirectly) when a message is received from the transport.
+ * Puts the message onto the queue read by the dispatcher.
+ *
+ * @param message the message that has been received
+ */
+ public void messageReceived(UnprocessedMessage message)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Message received in session with channel id " + _channelId);
+ }
+
+ _queue.add(message);
+ }
+
+ /**
+ * Acknowledge a message or several messages. This method can be called via AbstractJMSMessage or from
+ * a BasicConsumer. The former where the mode is CLIENT_ACK and the latter where the mode is
+ * AUTO_ACK or similar.
+ * @param deliveryTag the tag of the last message to be acknowledged
+ * @param multiple if true will acknowledge all messages up to and including the one specified by the
+ * delivery tag
+ */
+ public void acknowledgeMessage(long deliveryTag, boolean multiple)
+ {
+ final AMQFrame ackFrame = BasicAckBody.createAMQFrame(_channelId, deliveryTag, multiple);
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Sending ack for delivery tag " + deliveryTag + " on channel " + _channelId);
+ }
+ _connection.getProtocolHandler().writeFrame(ackFrame);
+ }
+
+ public int getDefaultPrefetch()
+ {
+ return _defaultPrefetch;
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ void start()
+ {
+ if(_dispatcher != null)
+ {
+ //then we stopped this and are restarting, so signal server to resume delivery
+ unsuspendChannel();
+ }
+ _dispatcher = new Dispatcher();
+ _dispatcher.setDaemon(true);
+ _dispatcher.start();
+ }
+
+ void stop()
+ {
+ //stop the server delivering messages to this session
+ suspendChannel();
+
+ //stop the dispatcher thread
+ _stopped.set(true);
+ }
+
+ boolean isStopped()
+ {
+ return _stopped.get();
+ }
+
+ /**
+ * Callers must hold the failover mutex before calling this method.
+ * @param consumer
+ * @throws AMQException
+ */
+ void registerConsumer(BasicMessageConsumer consumer) throws AMQException
+ {
+ AMQDestination amqd = consumer.getDestination();
+
+ AMQProtocolHandler protocolHandler = _connection.getProtocolHandler();
+
+ declareExchange(amqd, protocolHandler);
+
+ String queueName = declareQueue(amqd, protocolHandler);
+
+ bindQueue(amqd, queueName, protocolHandler, consumer.getRawSelectorFieldTable());
+
+ String consumerTag = consumeFromQueue(queueName, protocolHandler, consumer.getPrefetch(), consumer.isNoLocal(),
+ consumer.isExclusive(), consumer.getAcknowledgeMode());
+
+ consumer.setConsumerTag(consumerTag);
+ _consumers.put(consumerTag, consumer);
+ }
+
+ /**
+ * Called by the MessageConsumer when closing, to deregister the consumer from the
+ * map from consumerTag to consumer instance.
+ * @param consumerTag the consumer tag, that was broker-generated
+ */
+ void deregisterConsumer(String consumerTag)
+ {
+ _consumers.remove(consumerTag);
+ }
+
+ private void registerProducer(long producerId, MessageProducer producer)
+ {
+ _producers.put(new Long(producerId), producer);
+ }
+
+ void deregisterProducer(long producerId)
+ {
+ _producers.remove(new Long(producerId));
+ }
+
+ private long getNextProducerId()
+ {
+ return ++_nextProducerId;
+ }
+
+ /**
+ * Resubscribes all producers and consumers. This is called when performing failover.
+ * @throws AMQException
+ */
+ void resubscribe() throws AMQException
+ {
+ resubscribeProducers();
+ resubscribeConsumers();
+ }
+
+ private void resubscribeProducers() throws AMQException
+ {
+ ArrayList producers = new ArrayList(_producers.values());
+ _logger.info(MessageFormat.format("Resubscribing producers = {0} producers.size={1}", producers, producers.size())); // FIXME: remove
+ for (Iterator it = producers.iterator(); it.hasNext();)
+ {
+ BasicMessageProducer producer = (BasicMessageProducer) it.next();
+ producer.resubscribe();
+ }
+ }
+
+ private void resubscribeConsumers() throws AMQException
+ {
+ ArrayList consumers = new ArrayList(_consumers.values());
+ _consumers.clear();
+
+ for (Iterator it = consumers.iterator(); it.hasNext();)
+ {
+ BasicMessageConsumer consumer = (BasicMessageConsumer) it.next();
+ registerConsumer(consumer);
+ }
+ }
+
+ private void suspendChannel()
+ {
+ _logger.warn("Suspending channel");
+ AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, false);
+ _connection.getProtocolHandler().writeFrame(channelFlowFrame);
+ }
+
+ private void unsuspendChannel()
+ {
+ _logger.warn("Unsuspending channel");
+ AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, true);
+ _connection.getProtocolHandler().writeFrame(channelFlowFrame);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java b/java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java
new file mode 100644
index 0000000000..8c029f4919
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import javax.jms.JMSException;
+import javax.jms.TemporaryQueue;
+
+/**
+ * AMQ implementation of a TemporaryQueue.
+ */
+final class AMQTemporaryQueue extends AMQQueue implements TemporaryQueue {
+
+ /**
+ * Create a new instance of an AMQTemporaryQueue
+ */
+ public AMQTemporaryQueue() {
+ super("TempQueue" + Long.toString(System.currentTimeMillis()),
+ null, true, true);
+ }
+
+ /**
+ * @see javax.jms.TemporaryQueue#delete()
+ */
+ public void delete() throws JMSException {
+ throw new UnsupportedOperationException("Delete not supported, " +
+ "will auto-delete when connection closed");
+ }
+
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java b/java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java
new file mode 100644
index 0000000000..5e96f919e4
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import javax.jms.JMSException;
+import javax.jms.TemporaryTopic;
+
+/**
+ * AMQ implementation of TemporaryTopic.
+ */
+class AMQTemporaryTopic extends AMQTopic implements TemporaryTopic
+{
+
+ /**
+ * Create new temporary topic.
+ */
+ public AMQTemporaryTopic()
+ {
+ super("TempQueue" + Long.toString(System.currentTimeMillis()));
+ }
+
+ /**
+ * @see javax.jms.TemporaryTopic#delete()
+ */
+ public void delete() throws JMSException
+ {
+ throw new UnsupportedOperationException("Delete not supported, " +
+ "will auto-delete when connection closed");
+ }
+
+}
diff --git a/java/client/src/org/apache/qpid/client/AMQTopic.java b/java/client/src/org/apache/qpid/client/AMQTopic.java
new file mode 100644
index 0000000000..aa6d8f95cf
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/AMQTopic.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.exchange.ExchangeDefaults;
+
+import javax.jms.JMSException;
+import javax.jms.Topic;
+
+public class AMQTopic extends AMQDestination implements Topic
+ {
+ /**
+ * Constructor for use in creating a topic using a BindingURL.
+ *
+ * @param binding The binding url object.
+ */
+ public AMQTopic(BindingURL binding)
+ {
+ super(binding);
+ }
+
+ public AMQTopic(String name)
+ {
+ super(ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, name, true, true, null);
+ _isDurable = false;
+ }
+
+ /**
+ * Constructor for use in creating a topic to represent a durable subscription
+ * @param topic
+ * @param clientId
+ * @param subscriptionName
+ */
+ public AMQTopic(AMQTopic topic, String clientId, String subscriptionName)
+ {
+ super(ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, topic.getDestinationName(), true, false, clientId + ":" + subscriptionName);
+ _isDurable = true;
+ }
+
+ public String getTopicName() throws JMSException
+ {
+ return super.getDestinationName();
+ }
+
+ public String getEncodedName()
+ {
+ return 'T' + getDestinationName();
+ }
+
+ public String getRoutingKey()
+ {
+ return getDestinationName();
+ }
+
+ public boolean isNameRequired()
+ {
+ // Topics always rely on a server generated queue name.
+ return false;
+ }
+
+ /**
+ * Override since the queue is always private and we must ensure it remains null. If not,
+ * reuse of the topic when registering consumers will make all consumers listen on the same (private) queue rather
+ * than getting their own (private) queue.
+ *
+ * This is relatively nasty but it is difficult to come up with a more elegant solution, given
+ * the requirement in the case on AMQQueue and possibly other AMQDestination subclasses to
+ * use the underlying queue name even where it is server generated.
+ */
+ public void setQueueName(String queueName)
+ {
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/BasicMessageConsumer.java b/java/client/src/org/apache/qpid/client/BasicMessageConsumer.java
new file mode 100644
index 0000000000..5d13a1cd41
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/BasicMessageConsumer.java
@@ -0,0 +1,499 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.MessageFactoryRegistry;
+import org.apache.qpid.client.message.UnprocessedMessage;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicCancelBody;
+import org.apache.qpid.framing.BasicCancelOkBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.jms.MessageConsumer;
+import org.apache.qpid.jms.Session;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class BasicMessageConsumer extends Closeable implements MessageConsumer
+{
+ private static final Logger _logger = Logger.getLogger(BasicMessageConsumer.class);
+
+ /**
+ * The connection being used by this consumer
+ */
+ private AMQConnection _connection;
+
+ private String _messageSelector;
+
+ private boolean _noLocal;
+
+ private AMQDestination _destination;
+
+ /**
+ * When true indicates that a blocking receive call is in progress
+ */
+ private final AtomicBoolean _receiving = new AtomicBoolean(false);
+ /**
+ * Holds an atomic reference to the listener installed.
+ */
+ private final AtomicReference _messageListener = new AtomicReference();
+
+ /**
+ * The consumer tag allows us to close the consumer by sending a jmsCancel method to the
+ * broker
+ */
+ private String _consumerTag;
+
+ /**
+ * We need to know the channel id when constructing frames
+ */
+ private int _channelId;
+
+ /**
+ * Used in the blocking receive methods to receive a message from
+ * the Session thread. Argument true indicates we want strict FIFO semantics
+ */
+ private final SynchronousQueue _synchronousQueue = new SynchronousQueue(true);
+
+ private MessageFactoryRegistry _messageFactory;
+
+ private AMQSession _session;
+
+ private AMQProtocolHandler _protocolHandler;
+
+ /**
+ * We need to store the "raw" field table so that we can resubscribe in the event of failover being required
+ */
+ private FieldTable _rawSelectorFieldTable;
+
+ /**
+ * We store the prefetch field in order to be able to reuse it when resubscribing in the event of failover
+ */
+ private int _prefetch;
+
+ /**
+ * We store the exclusive field in order to be able to reuse it when resubscribing in the event of failover
+ */
+ private boolean _exclusive;
+
+ /**
+ * The acknowledge mode in force for this consumer. Note that the AMQP protocol allows different ack modes
+ * per consumer whereas JMS defines this at the session level, hence why we associate it with the consumer in our
+ * implementation.
+ */
+ private int _acknowledgeMode;
+
+ /**
+ * Number of messages unacknowledged in DUPS_OK_ACKNOWLEDGE mode
+ */
+ private int _outstanding;
+
+ /**
+ * Tag of last message delievered, whoch should be acknowledged on commit in
+ * transaction mode.
+ */
+ private long _lastDeliveryTag;
+
+ BasicMessageConsumer(int channelId, AMQConnection connection, AMQDestination destination, String messageSelector,
+ boolean noLocal, MessageFactoryRegistry messageFactory, AMQSession session,
+ AMQProtocolHandler protocolHandler, FieldTable rawSelectorFieldTable, int prefetch,
+ boolean exclusive, int acknowledgeMode)
+ {
+ _channelId = channelId;
+ _connection = connection;
+ _messageSelector = messageSelector;
+ _noLocal = noLocal;
+ _destination = destination;
+ _messageFactory = messageFactory;
+ _session = session;
+ _protocolHandler = protocolHandler;
+ _rawSelectorFieldTable = rawSelectorFieldTable;
+ _prefetch = prefetch;
+ _exclusive = exclusive;
+ _acknowledgeMode = acknowledgeMode;
+ }
+
+ public AMQDestination getDestination()
+ {
+ return _destination;
+ }
+
+ public String getMessageSelector() throws JMSException
+ {
+ return _messageSelector;
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ return (MessageListener) _messageListener.get();
+ }
+
+ public int getAcknowledgeMode()
+ {
+ return _acknowledgeMode;
+ }
+
+ private boolean isMessageListenerSet()
+ {
+ return _messageListener.get() != null;
+ }
+
+ public void setMessageListener(MessageListener messageListener) throws JMSException
+ {
+ checkNotClosed();
+
+ //if the current listener is non-null and the session is not stopped, then
+ //it is an error to call this method.
+
+ //i.e. it is only valid to call this method if
+ //
+ // (a) the session is stopped, in which case the dispatcher is not running
+ // OR
+ // (b) the listener is null AND we are not receiving synchronously at present
+ //
+
+ if (_session.isStopped())
+ {
+ _messageListener.set(messageListener);
+ _logger.debug("Message listener set for destination " + _destination);
+ }
+ else
+ {
+ if (_receiving.get())
+ {
+ throw new javax.jms.IllegalStateException("Another thread is already receiving synchronously.");
+ }
+ if (!_messageListener.compareAndSet(null, messageListener))
+ {
+ throw new javax.jms.IllegalStateException("Attempt to alter listener while session is started.");
+ }
+ _logger.debug("Message listener set for destination " + _destination);
+
+ if (messageListener != null)
+ {
+ //handle case where connection has already been started, and the dispatcher is blocked
+ //doing a put on the _synchronousQueue
+ Object msg = _synchronousQueue.poll();
+ if (msg != null)
+ {
+ AbstractJMSMessage jmsMsg = (AbstractJMSMessage) msg;
+ messageListener.onMessage(jmsMsg);
+ postDeliver(jmsMsg);
+ }
+ }
+ }
+ }
+
+ private void acquireReceiving() throws JMSException
+ {
+ if (!_receiving.compareAndSet(false, true))
+ {
+ throw new javax.jms.IllegalStateException("Another thread is already receiving.");
+ }
+ if (isMessageListenerSet())
+ {
+ throw new javax.jms.IllegalStateException("A listener has already been set.");
+ }
+ }
+
+ private void releaseReceiving()
+ {
+ _receiving.set(false);
+ }
+
+ public FieldTable getRawSelectorFieldTable()
+ {
+ return _rawSelectorFieldTable;
+ }
+
+ public int getPrefetch()
+ {
+ return _prefetch;
+ }
+
+ public boolean isNoLocal()
+ {
+ return _noLocal;
+ }
+
+ public boolean isExclusive()
+ {
+ return _exclusive;
+ }
+
+ public Message receive() throws JMSException
+ {
+ return receive(0);
+ }
+
+ public Message receive(long l) throws JMSException
+ {
+ checkNotClosed();
+
+ acquireReceiving();
+
+ try
+ {
+ Object o = null;
+ if (l > 0)
+ {
+ o = _synchronousQueue.poll(l, TimeUnit.MILLISECONDS);
+ }
+ else
+ {
+ o = _synchronousQueue.take();
+ }
+ final AbstractJMSMessage m = returnMessageOrThrow(o);
+ if (m != null)
+ {
+ postDeliver(m);
+ }
+ return m;
+ }
+ catch (InterruptedException e)
+ {
+ return null;
+ }
+ finally
+ {
+ releaseReceiving();
+ }
+ }
+
+ public Message receiveNoWait() throws JMSException
+ {
+ checkNotClosed();
+
+ acquireReceiving();
+
+ try
+ {
+ Object o = _synchronousQueue.poll();
+ final AbstractJMSMessage m = returnMessageOrThrow(o);
+ if (m != null)
+ {
+ postDeliver(m);
+ }
+ return m;
+ }
+ finally
+ {
+ releaseReceiving();
+ }
+ }
+
+ /**
+ * We can get back either a Message or an exception from the queue. This method examines the argument and deals
+ * with it by throwing it (if an exception) or returning it (in any other case).
+ * @param o
+ * @return a message only if o is a Message
+ * @throws JMSException if the argument is a throwable. If it is a JMSException it is rethrown as is, but if not
+ * a JMSException is created with the linked exception set appropriately
+ */
+ private AbstractJMSMessage returnMessageOrThrow(Object o)
+ throws JMSException
+ {
+ // errors are passed via the queue too since there is no way of interrupting the poll() via the API.
+ if (o instanceof Throwable)
+ {
+ JMSException e = new JMSException("Message consumer forcibly closed due to error: " + o);
+ if (o instanceof Exception)
+ {
+ e.setLinkedException((Exception) o);
+ }
+ throw e;
+ }
+ else
+ {
+ return (AbstractJMSMessage) o;
+ }
+ }
+
+ public void close() throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ if (!_closed.getAndSet(true))
+ {
+ final AMQFrame cancelFrame = BasicCancelBody.createAMQFrame(_channelId, _consumerTag, false);
+
+ try
+ {
+ _protocolHandler.syncWrite(cancelFrame, BasicCancelOkBody.class);
+ }
+ catch (AMQException e)
+ {
+ _logger.error("Error closing consumer: " + e, e);
+ throw new JMSException("Error closing consumer: " + e);
+ }
+
+ deregisterConsumer();
+ }
+ }
+ }
+
+ /**
+ * Called when you need to invalidate a consumer. Used for example when failover has occurred and the
+ * client has vetoed automatic resubscription.
+ * The caller must hold the failover mutex.
+ */
+ void markClosed()
+ {
+ _closed.set(true);
+ deregisterConsumer();
+ }
+
+ /**
+ * Called from the AMQSession when a message has arrived for this consumer. This methods handles both the case
+ * of a message listener or a synchronous receive() caller.
+ * @param messageFrame the raw unprocessed mesage
+ * @param channelId channel on which this message was sent
+ */
+ void notifyMessage(UnprocessedMessage messageFrame, int channelId)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("notifyMessage called with message number " + messageFrame.deliverBody.deliveryTag);
+ }
+ try
+ {
+ AbstractJMSMessage jmsMessage = _messageFactory.createMessage(messageFrame.deliverBody.deliveryTag,
+ messageFrame.deliverBody.redelivered,
+ messageFrame.contentHeader,
+ messageFrame.bodies);
+
+ _logger.debug("Message is of type: " + jmsMessage.getClass().getName());
+
+ preDeliver(jmsMessage);
+
+ if (isMessageListenerSet())
+ {
+ //we do not need a lock around the test above, and the dispatch below as it is invalid
+ //for an application to alter an installed listener while the session is started
+ getMessageListener().onMessage(jmsMessage);
+ postDeliver(jmsMessage);
+ }
+ else
+ {
+ _synchronousQueue.put(jmsMessage);
+ }
+ }
+ catch (Exception e)
+ {
+ if (e instanceof InterruptedException)
+ {
+ _logger.info("SynchronousQueue.put interupted. Usually result of connection closing");
+ }
+ else
+ {
+ _logger.error("Caught exception (dump follows) - ignoring...", e);
+ }
+ }
+ }
+
+ private void preDeliver(AbstractJMSMessage msg)
+ {
+ switch (_acknowledgeMode)
+ {
+ case Session.PRE_ACKNOWLEDGE:
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ break;
+ case Session.CLIENT_ACKNOWLEDGE:
+ // we set the session so that when the user calls acknowledge() it can call the method on session
+ // to send out the appropriate frame
+ msg.setAMQSession(_session);
+ break;
+ }
+ }
+
+ private void postDeliver(AbstractJMSMessage msg)
+ {
+ switch (_acknowledgeMode)
+ {
+ case Session.DUPS_OK_ACKNOWLEDGE:
+ if (++_outstanding >= _prefetch)
+ {
+ _session.acknowledgeMessage(msg.getDeliveryTag(), true);
+ }
+ break;
+ case Session.AUTO_ACKNOWLEDGE:
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ break;
+ case Session.SESSION_TRANSACTED:
+ _lastDeliveryTag = msg.getDeliveryTag();
+ break;
+ }
+ }
+
+ /**
+ * Acknowledge up to last message delivered (if any). Used when commiting.
+ */
+ void acknowledgeLastDelivered()
+ {
+ if (_lastDeliveryTag > 0)
+ {
+ _session.acknowledgeMessage(_lastDeliveryTag, true);
+ _lastDeliveryTag = -1;
+ }
+ }
+
+ void notifyError(Throwable cause)
+ {
+ _closed.set(true);
+
+ // we have no way of propagating the exception to a message listener - a JMS limitation - so we
+ // deal with the case where we have a synchronous receive() waiting for a message to arrive
+ if (!isMessageListenerSet())
+ {
+ // offer only succeeds if there is a thread waiting for an item from the queue
+ if (_synchronousQueue.offer(cause))
+ {
+ _logger.debug("Passed exception to synchronous queue for propagation to receive()");
+ }
+ }
+ deregisterConsumer();
+ }
+
+ /**
+ * Perform cleanup to deregister this consumer. This occurs when closing the consumer in both the clean
+ * case and in the case of an error occurring.
+ */
+ private void deregisterConsumer()
+ {
+ _session.deregisterConsumer(_consumerTag);
+ }
+
+ public String getConsumerTag()
+ {
+ return _consumerTag;
+ }
+
+ public void setConsumerTag(String consumerTag)
+ {
+ _consumerTag = consumerTag;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/BasicMessageProducer.java b/java/client/src/org/apache/qpid/client/BasicMessageProducer.java
new file mode 100644
index 0000000000..9ff6d8564b
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/BasicMessageProducer.java
@@ -0,0 +1,480 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.JMSBytesMessage;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.framing.*;
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.jms.DeliveryMode;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import java.io.UnsupportedEncodingException;
+
+public class BasicMessageProducer extends Closeable implements org.apache.qpid.jms.MessageProducer
+{
+ protected final Logger _logger = Logger.getLogger(getClass());
+
+ private AMQConnection _connection;
+
+ /**
+ * If true, messages will not get a timestamp.
+ */
+ private boolean _disableTimestamps;
+
+ /**
+ * Priority of messages created by this producer.
+ */
+ private int _messagePriority;
+
+ /**
+ * Time to live of messages. Specified in milliseconds but AMQ has 1 second resolution.
+ */
+ private long _timeToLive;
+
+ /**
+ * Delivery mode used for this producer.
+ */
+ private int _deliveryMode = DeliveryMode.PERSISTENT;
+
+ /**
+ * The Destination used for this consumer, if specified upon creation.
+ */
+ protected AMQDestination _destination;
+
+ /**
+ * Default encoding used for messages produced by this producer.
+ */
+ private String _encoding;
+
+ /**
+ * Default encoding used for message produced by this producer.
+ */
+ private String _mimeType;
+
+ private AMQProtocolHandler _protocolHandler;
+
+ /**
+ * True if this producer was created from a transacted session
+ */
+ private boolean _transacted;
+
+ private int _channelId;
+
+ /**
+ * This is an id generated by the session and is used to tie individual producers to the session. This means we
+ * can deregister a producer with the session when the producer is clsoed. We need to be able to tie producers
+ * to the session so that when an error is propagated to the session it can close the producer (meaning that
+ * a client that happens to hold onto a producer reference will get an error if he tries to use it subsequently).
+ */
+ private long _producerId;
+
+ /**
+ * The session used to create this producer
+ */
+ private AMQSession _session;
+
+ private final boolean _immediate;
+
+ private final boolean _mandatory;
+
+ private final boolean _waitUntilSent;
+
+ protected BasicMessageProducer(AMQConnection connection, AMQDestination destination, boolean transacted,
+ int channelId, AMQSession session, AMQProtocolHandler protocolHandler,
+ long producerId, boolean immediate, boolean mandatory, boolean waitUntilSent)
+ {
+ _connection = connection;
+ _destination = destination;
+ _transacted = transacted;
+ _protocolHandler = protocolHandler;
+ _channelId = channelId;
+ _session = session;
+ _producerId = producerId;
+ if (destination != null)
+ {
+ declareDestination(destination);
+ }
+ _immediate = immediate;
+ _mandatory = mandatory;
+ _waitUntilSent = waitUntilSent;
+ }
+
+ void resubscribe() throws AMQException
+ {
+ if (_destination != null)
+ {
+ declareDestination(_destination);
+ }
+ }
+
+ private void declareDestination(AMQDestination destination)
+ {
+ // Declare the exchange
+ // Note that the durable and internal arguments are ignored since passive is set to false
+ AMQFrame declare = ExchangeDeclareBody.createAMQFrame(_channelId, 0, destination.getExchangeName(),
+ destination.getExchangeClass(), false,
+ false, false, false, true, null);
+ _protocolHandler.writeFrame(declare);
+ }
+
+ public void setDisableMessageID(boolean b) throws JMSException
+ {
+ checkNotClosed();
+ // IGNORED
+ }
+
+ public boolean getDisableMessageID() throws JMSException
+ {
+ checkNotClosed();
+ // Always false for AMQP
+ return false;
+ }
+
+ public void setDisableMessageTimestamp(boolean b) throws JMSException
+ {
+ checkNotClosed();
+ _disableTimestamps = b;
+ }
+
+ public boolean getDisableMessageTimestamp() throws JMSException
+ {
+ checkNotClosed();
+ return _disableTimestamps;
+ }
+
+ public void setDeliveryMode(int i) throws JMSException
+ {
+ checkNotClosed();
+ if (i != DeliveryMode.NON_PERSISTENT && i != DeliveryMode.PERSISTENT)
+ {
+ throw new JMSException("DeliveryMode must be either NON_PERSISTENT or PERSISTENT. Value of " + i +
+ " is illegal");
+ }
+ _deliveryMode = i;
+ }
+
+ public int getDeliveryMode() throws JMSException
+ {
+ checkNotClosed();
+ return _deliveryMode;
+ }
+
+ public void setPriority(int i) throws JMSException
+ {
+ checkNotClosed();
+ if (i < 0 || i > 9)
+ {
+ throw new IllegalArgumentException("Priority of " + i + " is illegal. Value must be in range 0 to 9");
+ }
+ _messagePriority = i;
+ }
+
+ public int getPriority() throws JMSException
+ {
+ checkNotClosed();
+ return _messagePriority;
+ }
+
+ public void setTimeToLive(long l) throws JMSException
+ {
+ checkNotClosed();
+ if (l < 0)
+ {
+ throw new IllegalArgumentException("Time to live must be non-negative - supplied value was " + l);
+ }
+ _timeToLive = l;
+ }
+
+ public long getTimeToLive() throws JMSException
+ {
+ checkNotClosed();
+ return _timeToLive;
+ }
+
+ public Destination getDestination() throws JMSException
+ {
+ checkNotClosed();
+ return _destination;
+ }
+
+ public void close() throws JMSException
+ {
+ _closed.set(true);
+ _session.deregisterProducer(_producerId);
+ }
+
+ public void send(Message message) throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, (AbstractJMSMessage) message, _deliveryMode, _messagePriority, _timeToLive,
+ _mandatory, _immediate);
+ }
+ }
+
+ public void send(Message message, int deliveryMode) throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, (AbstractJMSMessage) message, deliveryMode, _messagePriority, _timeToLive,
+ _mandatory, _immediate);
+ }
+ }
+
+ public void send(Message message, int deliveryMode, boolean immediate) throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, (AbstractJMSMessage) message, deliveryMode, _messagePriority, _timeToLive,
+ _mandatory, immediate);
+ }
+ }
+
+ public void send(Message message, int deliveryMode, int priority,
+ long timeToLive) throws JMSException
+ {
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, (AbstractJMSMessage)message, deliveryMode, priority, timeToLive, _mandatory,
+ _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message) throws JMSException
+ {
+ checkNotClosed();
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, _deliveryMode, _messagePriority, _timeToLive,
+ _mandatory, _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive)
+ throws JMSException
+ {
+ checkNotClosed();
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive,
+ _mandatory, _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean mandatory)
+ throws JMSException
+ {
+ checkNotClosed();
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive,
+ mandatory, _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean mandatory, boolean immediate)
+ throws JMSException
+ {
+ checkNotClosed();
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive,
+ mandatory, immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean mandatory,
+ boolean immediate, boolean waitUntilSent)
+ throws JMSException
+ {
+ checkNotClosed();
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive,
+ mandatory, immediate, waitUntilSent);
+ }
+ }
+
+ private void validateDestination(Destination destination) throws JMSException
+ {
+ if (!(destination instanceof AMQDestination))
+ {
+ throw new JMSException("Unsupported destination class: " +
+ (destination != null?destination.getClass():null));
+ }
+ declareDestination((AMQDestination)destination);
+ }
+
+ protected void sendImpl(AMQDestination destination, AbstractJMSMessage message, int deliveryMode, int priority,
+ long timeToLive, boolean mandatory, boolean immediate) throws JMSException
+ {
+ sendImpl(destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, _waitUntilSent);
+ }
+ /**
+ * The caller of this method must hold the failover mutex.
+ * @param destination
+ * @param message
+ * @param deliveryMode
+ * @param priority
+ * @param timeToLive
+ * @param mandatory
+ * @param immediate
+ * @throws JMSException
+ */
+ protected void sendImpl(AMQDestination destination, AbstractJMSMessage message, int deliveryMode, int priority,
+ long timeToLive, boolean mandatory, boolean immediate, boolean wait) throws JMSException
+ {
+ AMQFrame publishFrame = BasicPublishBody.createAMQFrame(_channelId, 0, destination.getExchangeName(),
+ destination.getRoutingKey(), mandatory, immediate);
+
+ long currentTime = 0;
+ if (!_disableTimestamps)
+ {
+ currentTime = System.currentTimeMillis();
+ message.setJMSTimestamp(currentTime);
+ }
+ //
+ // Very nasty temporary hack for GRM-206. Will be altered ASAP.
+ //
+ if(message instanceof JMSBytesMessage)
+ {
+ JMSBytesMessage msg = (JMSBytesMessage) message;
+ if(!msg.isReadable())
+ {
+ msg.reset();
+ }
+ }
+ ByteBuffer payload = message.getData();
+ BasicContentHeaderProperties contentHeaderProperties = message.getJmsContentHeaderProperties();
+
+ if (timeToLive > 0)
+ {
+ if (!_disableTimestamps)
+ {
+ contentHeaderProperties.setExpiration(currentTime + timeToLive);
+ }
+ }
+ else
+ {
+ if (!_disableTimestamps)
+ {
+ contentHeaderProperties.setExpiration(0);
+ }
+ }
+ contentHeaderProperties.setDeliveryMode((byte) deliveryMode);
+ contentHeaderProperties.setPriority((byte) priority);
+
+ int size = payload.limit();
+ ContentBody[] contentBodies = createContentBodies(payload);
+ AMQFrame[] frames = new AMQFrame[2 + contentBodies.length];
+ for (int i = 0; i < contentBodies.length; i++)
+ {
+ frames[2 + i] = ContentBody.createAMQFrame(_channelId, contentBodies[i]);
+ }
+ if (contentBodies.length > 0 && _logger.isDebugEnabled())
+ {
+ _logger.debug("Sending content body frames to " + destination);
+ }
+
+ // weight argument of zero indicates no child content headers, just bodies
+ AMQFrame contentHeaderFrame = ContentHeaderBody.createAMQFrame(_channelId, BasicConsumeBody.CLASS_ID, 0,
+ contentHeaderProperties,
+ size);
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Sending content header frame to " + destination);
+ }
+
+ frames[0] = publishFrame;
+ frames[1] = contentHeaderFrame;
+ CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames);
+ _protocolHandler.writeFrame(compositeFrame, wait);
+ }
+
+ /**
+ * Create content bodies. This will split a large message into numerous bodies depending on the negotiated
+ * maximum frame size.
+ * @param payload
+ * @return the array of content bodies
+ */
+ private ContentBody[] createContentBodies(ByteBuffer payload)
+ {
+ if (payload == null)
+ {
+ return null;
+ }
+ else if (payload.remaining() == 0)
+ {
+ return new ContentBody[0];
+ }
+ // we substract one from the total frame maximum size to account for the end of frame marker in a body frame
+ // (0xCE byte).
+ int dataLength = payload.remaining();
+ final long framePayloadMax = _session.getAMQConnection().getMaximumFrameSize() - 1;
+ int lastFrame = (dataLength % framePayloadMax) > 0 ? 1 : 0;
+ int frameCount = (int) (dataLength/framePayloadMax) + lastFrame;
+ final ContentBody[] bodies = new ContentBody[frameCount];
+
+ if (frameCount == 1)
+ {
+ bodies[0] = new ContentBody();
+ bodies[0].payload = payload;
+ }
+ else
+ {
+ long remaining = dataLength;
+ for (int i = 0; i < bodies.length; i++)
+ {
+ bodies[i] = new ContentBody();
+ payload.position((int)framePayloadMax * i);
+ int length = (remaining >= framePayloadMax) ? (int)framePayloadMax : (int)remaining;
+ payload.limit(payload.position() + length);
+ bodies[i].payload = payload.slice();
+ remaining -= length;
+ }
+ }
+ return bodies;
+ }
+
+ public void setMimeType(String mimeType)
+ {
+ checkNotClosed();
+ _mimeType = mimeType;
+ }
+
+ public void setEncoding(String encoding) throws UnsupportedEncodingException
+ {
+ checkNotClosed();
+ _encoding = encoding;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/Closeable.java b/java/client/src/org/apache/qpid/client/Closeable.java
new file mode 100644
index 0000000000..0381823b69
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/Closeable.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import javax.jms.JMSException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provides support for orderly shutdown of an object.
+ */
+public abstract class Closeable
+{
+ /**
+ * We use an atomic boolean so that we do not have to synchronized access to this flag. Synchronizing
+ * access to this flag would mean have a synchronized block in every method.
+ */
+ protected final AtomicBoolean _closed = new AtomicBoolean(false);
+
+ protected void checkNotClosed()
+ {
+ if (_closed.get())
+ {
+ throw new IllegalStateException("Object " + toString() + " has been closed");
+ }
+ }
+
+ public boolean isClosed()
+ {
+ return _closed.get();
+ }
+
+ public abstract void close() throws JMSException;
+}
diff --git a/java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java b/java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java
new file mode 100644
index 0000000000..a3471e9140
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+public class ConnectionTuneParameters
+{
+ private long _frameMax;
+
+ private int _channelMax;
+
+ private int _heartbeat;
+
+ private long _txnLimit;
+
+ public long getFrameMax()
+ {
+ return _frameMax;
+ }
+
+ public void setFrameMax(long frameMax)
+ {
+ _frameMax = frameMax;
+ }
+
+ public int getChannelMax()
+ {
+ return _channelMax;
+ }
+
+ public void setChannelMax(int channelMax)
+ {
+ _channelMax = channelMax;
+ }
+
+ public int getHeartbeat()
+ {
+ return _heartbeat;
+ }
+
+ public void setHeartbeat(int hearbeat)
+ {
+ _heartbeat = hearbeat;
+ }
+
+ public long getTxnLimit()
+ {
+ return _txnLimit;
+ }
+
+ public void setTxnLimit(long txnLimit)
+ {
+ _txnLimit = txnLimit;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java b/java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java
new file mode 100644
index 0000000000..caa7e5139b
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+
+/**
+ * Wraps a MessageConsumer to fulfill the extended TopicSubscriber contract
+ *
+ */
+class TopicSubscriberAdaptor implements TopicSubscriber
+{
+ private final Topic _topic;
+ private final MessageConsumer _consumer;
+ private final boolean _noLocal;
+
+ TopicSubscriberAdaptor(Topic topic, MessageConsumer consumer, boolean noLocal)
+ {
+ _topic = topic;
+ _consumer = consumer;
+ _noLocal = noLocal;
+ }
+ TopicSubscriberAdaptor(Topic topic, BasicMessageConsumer consumer)
+ {
+ this(topic, consumer, consumer.isNoLocal());
+ }
+ public Topic getTopic() throws JMSException
+ {
+ return _topic;
+ }
+
+ public boolean getNoLocal() throws JMSException
+ {
+ return _noLocal;
+ }
+
+ public String getMessageSelector() throws JMSException
+ {
+ return _consumer.getMessageSelector();
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ return _consumer.getMessageListener();
+ }
+
+ public void setMessageListener(MessageListener messageListener) throws JMSException
+ {
+ _consumer.setMessageListener(messageListener);
+ }
+
+ public Message receive() throws JMSException
+ {
+ return _consumer.receive();
+ }
+
+ public Message receive(long l) throws JMSException
+ {
+ return _consumer.receive(l);
+ }
+
+ public Message receiveNoWait() throws JMSException
+ {
+ return _consumer.receiveNoWait();
+ }
+
+ public void close() throws JMSException
+ {
+ _consumer.close();
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverException.java b/java/client/src/org/apache/qpid/client/failover/FailoverException.java
new file mode 100644
index 0000000000..c17c56bdf7
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/failover/FailoverException.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.failover;
+
+/**
+ * This exception is thrown when failover is taking place and we need to let other
+ * parts of the client know about this.
+ */
+public class FailoverException extends RuntimeException
+{
+ public FailoverException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverHandler.java b/java/client/src/org/apache/qpid/client/failover/FailoverHandler.java
new file mode 100644
index 0000000000..0c68c75122
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/failover/FailoverHandler.java
@@ -0,0 +1,180 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.failover;
+
+import org.apache.mina.common.IoSession;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.failover.FailoverState;
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.log4j.Logger;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * When failover is required, we need a separate thread to handle the establishment of the new connection and
+ * the transfer of subscriptions.
+ * </p>
+ * The reason this needs to be a separate thread is because you cannot do this work inside the MINA IO processor
+ * thread. One significant task is the connection setup which involves a protocol exchange until a particular state
+ * is achieved. However if you do this in the MINA thread, you have to block until the state is achieved which means
+ * the IO processor is not able to do anything at all.
+ */
+public class FailoverHandler implements Runnable
+{
+ private static final Logger _logger = Logger.getLogger(FailoverHandler.class);
+
+ private final IoSession _session;
+ private AMQProtocolHandler _amqProtocolHandler;
+
+ /**
+ * Used where forcing the failover host
+ */
+ private String _host;
+
+ /**
+ * Used where forcing the failover port
+ */
+ private int _port;
+
+ public FailoverHandler(AMQProtocolHandler amqProtocolHandler, IoSession session)
+ {
+ _amqProtocolHandler = amqProtocolHandler;
+ _session = session;
+ }
+
+ public void run()
+ {
+ if (Thread.currentThread().isDaemon())
+ {
+ throw new IllegalStateException("FailoverHandler must run on a non-daemon thread.");
+ }
+ //Thread.currentThread().setName("Failover Thread");
+
+ _amqProtocolHandler.setFailoverLatch(new CountDownLatch(1));
+
+ // We wake up listeners. If they can handle failover, they will extend the
+ // FailoverSupport class and will in turn block on the latch until failover
+ // has completed before retrying the operation
+ _amqProtocolHandler.propagateExceptionToWaiters(new FailoverException("Failing over about to start"));
+
+ // Since failover impacts several structures we protect them all with a single mutex. These structures
+ // are also in child objects of the connection. This allows us to manipulate them without affecting
+ // client code which runs in a separate thread.
+ synchronized (_amqProtocolHandler.getConnection().getFailoverMutex())
+ {
+ _logger.info("Starting failover process");
+
+ // We switch in a new state manager temporarily so that the interaction to get to the "connection open"
+ // state works, without us having to terminate any existing "state waiters". We could theoretically
+ // have a state waiter waiting until the connection is closed for some reason. Or in future we may have
+ // a slightly more complex state model therefore I felt it was worthwhile doing this.
+ AMQStateManager existingStateManager = _amqProtocolHandler.getStateManager();
+ _amqProtocolHandler.setStateManager(new AMQStateManager());
+ if (!_amqProtocolHandler.getConnection().firePreFailover(_host != null))
+ {
+ _amqProtocolHandler.setStateManager(existingStateManager);
+ if (_host != null)
+ {
+ _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Redirect was vetoed by client"));
+ }
+ else
+ {
+ _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Failover was vetoed by client"));
+ }
+ _amqProtocolHandler.getFailoverLatch().countDown();
+ _amqProtocolHandler.setFailoverLatch(null);
+ return;
+ }
+ boolean failoverSucceeded;
+ // when host is non null we have a specified failover host otherwise we all the client to cycle through
+ // all specified hosts
+
+ // if _host has value then we are performing a redirect.
+ if (_host != null)
+ {
+ failoverSucceeded = _amqProtocolHandler.getConnection().attemptReconnection(_host, _port, _amqProtocolHandler.isUseSSL());
+ }
+ else
+ {
+ failoverSucceeded = _amqProtocolHandler.getConnection().attemptReconnection();
+ }
+ if (!failoverSucceeded)
+ {
+ _amqProtocolHandler.setStateManager(existingStateManager);
+ _amqProtocolHandler.getConnection().exceptionReceived(
+ new AMQDisconnectedException("Server closed connection and no failover " +
+ "was successful"));
+ }
+ else
+ {
+ _amqProtocolHandler.setStateManager(existingStateManager);
+ try
+ {
+ if (_amqProtocolHandler.getConnection().firePreResubscribe())
+ {
+ _logger.info("Resubscribing on new connection");
+ _amqProtocolHandler.getConnection().resubscribeSessions();
+ }
+ else
+ {
+ _logger.info("Client vetoed automatic resubscription");
+ }
+ _amqProtocolHandler.getConnection().fireFailoverComplete();
+ _amqProtocolHandler.setFailoverState(FailoverState.NOT_STARTED);
+ _logger.info("Connection failover completed successfully");
+ }
+ catch (Exception e)
+ {
+ _logger.info("Failover process failed - exception being propagated by protocol handler");
+ _amqProtocolHandler.setFailoverState(FailoverState.FAILED);
+ try
+ {
+ _amqProtocolHandler.exceptionCaught(_session, e);
+ }
+ catch (Exception ex)
+ {
+ _logger.error("Error notifying protocol session of error: " + ex, ex);
+ }
+ }
+ }
+ }
+ _amqProtocolHandler.getFailoverLatch().countDown();
+ }
+
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setHost(String host)
+ {
+ _host = host;
+ }
+
+ public int getPort()
+ {
+ return _port;
+ }
+
+ public void setPort(int port)
+ {
+ _port = port;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverState.java b/java/client/src/org/apache/qpid/client/failover/FailoverState.java
new file mode 100644
index 0000000000..20886f391c
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/failover/FailoverState.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.failover;
+
+/**
+ * Enumeration of failover states. Used to handle failover from within AMQProtocolHandler where MINA events need to be
+ * dealt with and can happen during failover.
+ */
+public final class FailoverState
+{
+ private final String _state;
+
+ /** Failover has not yet been attempted on this connection */
+ public static final FailoverState NOT_STARTED = new FailoverState("NOT STARTED");
+
+ /** Failover has been requested on this connection but has not completed */
+ public static final FailoverState IN_PROGRESS = new FailoverState("IN PROGRESS");
+
+ /** Failover has been attempted and failed */
+ public static final FailoverState FAILED = new FailoverState("FAILED");
+
+ private FailoverState(String state)
+ {
+ _state = state;
+ }
+
+ public String toString()
+ {
+ return "FailoverState: " + _state;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverSupport.java b/java/client/src/org/apache/qpid/client/failover/FailoverSupport.java
new file mode 100644
index 0000000000..e2d797dfac
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/failover/FailoverSupport.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.failover;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.failover.FailoverException;
+
+import javax.jms.JMSException;
+
+public abstract class FailoverSupport
+{
+ private static final Logger _log = Logger.getLogger(FailoverSupport.class);
+
+ public Object execute(AMQConnection con) throws JMSException
+ {
+ // We wait until we are not in the middle of failover before acquiring the mutex and then proceeding.
+ // Any method that can potentially block for any reason should use this class so that deadlock will not
+ // occur. The FailoverException is propagated by the AMQProtocolHandler to any listeners (e.g. frame listeners)
+ // that might be causing a block. When that happens, the exception is caught here and the mutex is released
+ // before waiting for the failover to complete (either successfully or unsuccessfully).
+ while (true)
+ {
+ try
+ {
+ con.blockUntilNotFailingOver();
+ }
+ catch (InterruptedException e)
+ {
+ _log.info("Interrupted: " + e, e);
+ return null;
+ }
+ synchronized (con.getFailoverMutex())
+ {
+ try
+ {
+ return operation();
+ }
+ catch (FailoverException e)
+ {
+ _log.info("Failover exception caught during operation");
+ }
+ }
+ }
+ }
+
+ protected abstract Object operation() throws JMSException;
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java
new file mode 100644
index 0000000000..ab1e84ee17
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java
@@ -0,0 +1,47 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicDeliverBody;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.message.UnprocessedMessage;
+
+public class BasicDeliverMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(BasicDeliverMethodHandler.class);
+
+ private static final BasicDeliverMethodHandler _instance = new BasicDeliverMethodHandler();
+
+ public static BasicDeliverMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ final UnprocessedMessage msg = new UnprocessedMessage();
+ msg.deliverBody = (BasicDeliverBody) evt.getMethod();
+ msg.channelId = evt.getChannelId();
+ _logger.debug("New JmsDeliver method received");
+ evt.getProtocolSession().unprocessedMessageReceived(msg);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java
new file mode 100644
index 0000000000..ae9d7bcb4a
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.message.UnprocessedMessage;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.BasicReturnBody;
+
+public class BasicReturnMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(BasicReturnMethodHandler.class);
+
+ private static final BasicReturnMethodHandler _instance = new BasicReturnMethodHandler();
+
+ public static BasicReturnMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ _logger.debug("New JmsBounce method received");
+ final UnprocessedMessage msg = new UnprocessedMessage();
+ msg.deliverBody = null;
+ msg.bounceBody = (BasicReturnBody) evt.getMethod();
+ msg.channelId = evt.getChannelId();
+
+ evt.getProtocolSession().unprocessedMessageReceived(msg);
+ }
+} \ No newline at end of file
diff --git a/java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java
new file mode 100644
index 0000000000..959f1eb4df
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQNoConsumersException;
+import org.apache.qpid.client.AMQNoRouteException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ChannelCloseBody;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+
+public class ChannelCloseMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(ChannelCloseMethodHandler.class);
+
+ private static ChannelCloseMethodHandler _handler = new ChannelCloseMethodHandler();
+
+ public static ChannelCloseMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ _logger.debug("ChannelClose method received");
+ ChannelCloseBody method = (ChannelCloseBody) evt.getMethod();
+
+ int errorCode = method.replyCode;
+ String reason = method.replyText;
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason);
+ }
+
+ AMQFrame frame = ChannelCloseOkBody.createAMQFrame(evt.getChannelId());
+ evt.getProtocolSession().writeFrame(frame);
+ if (errorCode != AMQConstant.REPLY_SUCCESS.getCode())
+ {
+ _logger.error("Channel close received with errorCode " + errorCode + ", and reason " + reason);
+ if (errorCode == AMQConstant.NO_CONSUMERS.getCode())
+ {
+ throw new AMQNoConsumersException("Error: " + reason, null);
+ }
+ else
+ {
+ if (errorCode == AMQConstant.NO_ROUTE.getCode())
+ {
+ throw new AMQNoRouteException("Error: " + reason, null);
+ }
+ else
+ {
+ throw new AMQChannelClosedException(errorCode, "Error: " + reason);
+ }
+ }
+ }
+ evt.getProtocolSession().channelClosed(evt.getChannelId(), errorCode, reason);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java
new file mode 100644
index 0000000000..b857b5e91b
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.log4j.Logger;
+
+public class ChannelCloseOkMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(ChannelCloseOkMethodHandler.class);
+
+ private static final ChannelCloseOkMethodHandler _instance = new ChannelCloseOkMethodHandler();
+
+ public static ChannelCloseOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId());
+
+ //todo this should do the closure
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java
new file mode 100644
index 0000000000..f26917b2e3
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ChannelFlowOkBody;
+
+public class ChannelFlowOkMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(ChannelFlowOkMethodHandler.class);
+ private static final ChannelFlowOkMethodHandler _instance = new ChannelFlowOkMethodHandler();
+
+ public static ChannelFlowOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelFlowOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ ChannelFlowOkBody method = (ChannelFlowOkBody) evt.getMethod();
+ _logger.debug("Received Channel.Flow-Ok message, active = " + method.active);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java
new file mode 100644
index 0000000000..6ec5b1cb49
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQConnectionClosedException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.client.AMQAuthenticationException;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+
+public class ConnectionCloseMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class);
+
+ private static ConnectionCloseMethodHandler _handler = new ConnectionCloseMethodHandler();
+
+ public static ConnectionCloseMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ private ConnectionCloseMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ _logger.info("ConnectionClose frame received");
+ ConnectionCloseBody method = (ConnectionCloseBody) evt.getMethod();
+
+ // does it matter
+ //stateManager.changeState(AMQState.CONNECTION_CLOSING);
+
+ int errorCode = method.replyCode;
+ String reason = method.replyText;
+
+ // TODO: check whether channel id of zero is appropriate
+ evt.getProtocolSession().writeFrame(ConnectionCloseOkBody.createAMQFrame((short)0));
+
+ if (errorCode != 200)
+ {
+ if(errorCode == AMQConstant.NOT_ALLOWED.getCode())
+ {
+ _logger.info("Authentication Error:"+Thread.currentThread().getName());
+
+ evt.getProtocolSession().closeProtocolSession();
+
+ //todo this is a bit of a fudge (could be conssidered such as each new connection needs a new state manager or at least a fresh state.
+ stateManager.changeState(AMQState.CONNECTION_NOT_STARTED);
+
+ throw new AMQAuthenticationException(errorCode, reason);
+ }
+ else
+ {
+ _logger.info("Connection close received with error code " + errorCode);
+
+
+ throw new AMQConnectionClosedException(errorCode, "Error: " + reason);
+ }
+ }
+
+ // this actually closes the connection in the case where it is not an error.
+
+ evt.getProtocolSession().closeProtocolSession();
+
+ stateManager.changeState(AMQState.CONNECTION_CLOSED);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java
new file mode 100644
index 0000000000..73986ed81a
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ConnectionOpenOkBody;
+
+public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener
+{
+
+ private static final Logger _logger = Logger.getLogger(ConnectionOpenOkMethodHandler.class);
+
+ private static final ConnectionOpenOkMethodHandler _instance = new ConnectionOpenOkMethodHandler();
+
+ public static ConnectionOpenOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionOpenOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ AMQProtocolSession session = evt.getProtocolSession();
+ ConnectionOpenOkBody method = (ConnectionOpenOkBody) evt.getMethod();
+ stateManager.changeState(AMQState.CONNECTION_OPEN);
+ }
+
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java
new file mode 100644
index 0000000000..d326191d84
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ConnectionRedirectBody;
+
+public class ConnectionRedirectMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionRedirectMethodHandler.class);
+
+ private static final int DEFAULT_REDIRECT_PORT = 5672;
+
+ private static ConnectionRedirectMethodHandler _handler = new ConnectionRedirectMethodHandler();
+
+ public static ConnectionRedirectMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ private ConnectionRedirectMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ _logger.info("ConnectionRedirect frame received");
+ ConnectionRedirectBody method = (ConnectionRedirectBody) evt.getMethod();
+
+ // the host is in the form hostname:port with the port being optional
+ int portIndex = method.host.indexOf(':');
+ String host;
+ int port;
+ if (portIndex == -1)
+ {
+ host = method.host;
+ port = DEFAULT_REDIRECT_PORT;
+ }
+ else
+ {
+ host = method.host.substring(0, portIndex);
+ port = Integer.parseInt(method.host.substring(portIndex + 1));
+ }
+ evt.getProtocolSession().failover(host, port);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java
new file mode 100644
index 0000000000..106ffc908e
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.ConnectionSecureOkBody;
+import org.apache.qpid.framing.ConnectionSecureBody;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+public class ConnectionSecureMethodHandler implements StateAwareMethodListener
+{
+ private static final ConnectionSecureMethodHandler _instance = new ConnectionSecureMethodHandler();
+
+ public static ConnectionSecureMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ SaslClient client = evt.getProtocolSession().getSaslClient();
+ if (client == null)
+ {
+ throw new AMQException("No SASL client set up - cannot proceed with authentication");
+ }
+
+ ConnectionSecureBody body = (ConnectionSecureBody) evt.getMethod();
+
+ try
+ {
+ // Evaluate server challenge
+ byte[] response = client.evaluateChallenge(body.challenge);
+ AMQFrame responseFrame = ConnectionSecureOkBody.createAMQFrame(evt.getChannelId(), response);
+ evt.getProtocolSession().writeFrame(responseFrame);
+ }
+ catch (SaslException e)
+ {
+ throw new AMQException("Error processing SASL challenge: " + e, e);
+ }
+
+
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java
new file mode 100644
index 0000000000..f0d17e9b55
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java
@@ -0,0 +1,184 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.security.AMQCallbackHandler;
+import org.apache.qpid.client.security.CallbackHandlerRegistry;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ConnectionStartBody;
+import org.apache.qpid.framing.ConnectionStartOkBody;
+import org.apache.qpid.framing.FieldTable;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import java.io.UnsupportedEncodingException;
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+public class ConnectionStartMethodHandler implements StateAwareMethodListener
+{
+
+ private static final Logger _log = Logger.getLogger(ConnectionStartMethodHandler.class);
+
+ private static final ConnectionStartMethodHandler _instance = new ConnectionStartMethodHandler();
+
+ public static ConnectionStartMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionStartMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ ConnectionStartBody body = (ConnectionStartBody) evt.getMethod();
+
+ try
+ {
+ // the mechanism we are going to use
+ String mechanism;
+ if (body.mechanisms == null)
+ {
+ throw new AMQException("mechanism not specified in ConnectionStart method frame");
+ }
+ else
+ {
+ mechanism = chooseMechanism(body.mechanisms);
+ }
+
+ if (mechanism == null)
+ {
+ throw new AMQException("No supported security mechanism found, passed: " + new String(body.mechanisms));
+ }
+
+ final AMQProtocolSession ps = evt.getProtocolSession();
+ byte[] saslResponse;
+ try
+ {
+ SaslClient sc = Sasl.createSaslClient(new String[]{mechanism},
+ null, "AMQP", "localhost",
+ null, createCallbackHandler(mechanism, ps));
+ if (sc == null)
+ {
+ throw new AMQException("Client SASL configuration error: no SaslClient could be created for mechanism " +
+ mechanism + ". Please ensure all factories are registered. See DynamicSaslRegistrar for " +
+ " details of how to register non-standard SASL client providers.");
+ }
+ ps.setSaslClient(sc);
+ saslResponse = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null);
+ }
+ catch (SaslException e)
+ {
+ ps.setSaslClient(null);
+ throw new AMQException("Unable to create SASL client: " + e, e);
+ }
+
+ if (body.locales == null)
+ {
+ throw new AMQException("Locales is not defined in Connection Start method");
+ }
+ final String locales = new String(body.locales, "utf8");
+ final StringTokenizer tokenizer = new StringTokenizer(locales, " ");
+ String selectedLocale = null;
+ if (tokenizer.hasMoreTokens())
+ {
+ selectedLocale = tokenizer.nextToken();
+ }
+ else
+ {
+ throw new AMQException("No locales sent from server, passed: " + locales);
+ }
+
+ stateManager.changeState(AMQState.CONNECTION_NOT_TUNED);
+ FieldTable clientProperties = new FieldTable();
+ clientProperties.put("instance", ps.getClientID());
+ clientProperties.put("product", "Qpid");
+ clientProperties.put("version", "1.0");
+ clientProperties.put("platform", getFullSystemInfo());
+ ps.writeFrame(ConnectionStartOkBody.createAMQFrame(evt.getChannelId(), clientProperties, mechanism,
+ saslResponse, selectedLocale));
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new AMQException(_log, "Unable to decode data: " + e, e);
+ }
+ }
+
+ private String getFullSystemInfo()
+ {
+ StringBuffer fullSystemInfo = new StringBuffer();
+ fullSystemInfo.append(System.getProperty("java.runtime.name"));
+ fullSystemInfo.append(", " + System.getProperty("java.runtime.version"));
+ fullSystemInfo.append(", " + System.getProperty("java.vendor"));
+ fullSystemInfo.append(", " + System.getProperty("os.arch"));
+ fullSystemInfo.append(", " + System.getProperty("os.name"));
+ fullSystemInfo.append(", " + System.getProperty("os.version"));
+ fullSystemInfo.append(", " + System.getProperty("sun.os.patch.level"));
+
+ return fullSystemInfo.toString();
+ }
+
+ private String chooseMechanism(byte[] availableMechanisms) throws UnsupportedEncodingException
+ {
+ final String mechanisms = new String(availableMechanisms, "utf8");
+ StringTokenizer tokenizer = new StringTokenizer(mechanisms, " ");
+ HashSet mechanismSet = new HashSet();
+ while (tokenizer.hasMoreTokens())
+ {
+ mechanismSet.add(tokenizer.nextToken());
+ }
+
+ String preferredMechanisms = CallbackHandlerRegistry.getInstance().getMechanisms();
+ StringTokenizer prefTokenizer = new StringTokenizer(preferredMechanisms, " ");
+ while (prefTokenizer.hasMoreTokens())
+ {
+ String mech = prefTokenizer.nextToken();
+ if (mechanismSet.contains(mech))
+ {
+ return mech;
+ }
+ }
+ return null;
+ }
+
+ private AMQCallbackHandler createCallbackHandler(String mechanism, AMQProtocolSession protocolSession)
+ throws AMQException
+ {
+ Class mechanismClass = CallbackHandlerRegistry.getInstance().getCallbackHandlerClass(mechanism);
+ try
+ {
+ Object instance = mechanismClass.newInstance();
+ AMQCallbackHandler cbh = (AMQCallbackHandler) instance;
+ cbh.initialise(protocolSession);
+ return cbh;
+ }
+ catch (Exception e)
+ {
+ throw new AMQException("Unable to create callback handler: " + e, e);
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java
new file mode 100644
index 0000000000..6e32bb692e
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.handler;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.ConnectionTuneParameters;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ConnectionOpenBody;
+import org.apache.qpid.framing.ConnectionTuneBody;
+import org.apache.qpid.framing.ConnectionTuneOkBody;
+import org.apache.qpid.framing.AMQFrame;
+
+public class ConnectionTuneMethodHandler implements StateAwareMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(ConnectionTuneMethodHandler.class);
+
+ private static final ConnectionTuneMethodHandler _instance = new ConnectionTuneMethodHandler();
+
+ public static ConnectionTuneMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ protected ConnectionTuneMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException
+ {
+ _logger.debug("ConnectionTune frame received");
+ ConnectionTuneBody frame = (ConnectionTuneBody) evt.getMethod();
+ AMQProtocolSession session = evt.getProtocolSession();
+
+ ConnectionTuneParameters params = session.getConnectionTuneParameters();
+ if (params == null)
+ {
+ params = new ConnectionTuneParameters();
+ }
+
+ params.setFrameMax(frame.frameMax);
+ params.setChannelMax(frame.channelMax);
+ params.setHeartbeat(Integer.getInteger("amqj.heartbeat.delay", frame.heartbeat));
+ session.setConnectionTuneParameters(params);
+
+ stateManager.changeState(AMQState.CONNECTION_NOT_OPENED);
+ session.writeFrame(createTuneOkFrame(evt.getChannelId(), params));
+ session.writeFrame(createConnectionOpenFrame(evt.getChannelId(), session.getAMQConnection().getVirtualHost(), null, true));
+ }
+
+ protected AMQFrame createConnectionOpenFrame(int channel, String path, String capabilities, boolean insist)
+ {
+ return ConnectionOpenBody.createAMQFrame(channel, path, capabilities, insist);
+ }
+
+ protected AMQFrame createTuneOkFrame(int channel, ConnectionTuneParameters params)
+ {
+ return ConnectionTuneOkBody.createAMQFrame(channel, params.getChannelMax(), params.getFrameMax(), params.getHeartbeat());
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/AMQMessage.java b/java/client/src/org/apache/qpid/client/message/AMQMessage.java
new file mode 100644
index 0000000000..d281a15607
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/AMQMessage.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.framing.ContentHeaderProperties;
+import org.apache.qpid.client.AMQSession;
+
+public class AMQMessage
+{
+ protected ContentHeaderProperties _contentHeaderProperties;
+
+ /**
+ * If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required
+ */
+ protected AMQSession _session;
+
+ protected final long _deliveryTag;
+
+ public AMQMessage(ContentHeaderProperties properties, long deliveryTag)
+ {
+ _contentHeaderProperties = properties;
+ _deliveryTag = deliveryTag;
+ }
+
+ public AMQMessage(ContentHeaderProperties properties)
+ {
+ this(properties, -1);
+ }
+
+ /**
+ * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user
+ * calls acknowledge()
+ * @param s the AMQ session that delivered this message
+ */
+ public void setAMQSession(AMQSession s)
+ {
+ _session = s;
+ }
+
+ public AMQSession getAMQSession()
+ {
+ return _session;
+ }
+
+ /**
+ * Get the AMQ message number assigned to this message
+ * @return the message number
+ */
+ public long getDeliveryTag()
+ {
+ return _deliveryTag;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java b/java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java
new file mode 100644
index 0000000000..9cffe2c484
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java
@@ -0,0 +1,677 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableKeyEnumeration;
+import org.apache.commons.collections.map.ReferenceMap;
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+
+public abstract class AbstractJMSMessage extends AMQMessage implements javax.jms.Message
+{
+ private static final Map _destinationCache = Collections.synchronizedMap(new ReferenceMap());
+
+// protected Map _messageProperties;
+
+ public static final char BOOLEAN_PROPERTY_PREFIX = 'B';
+ public static final char BYTE_PROPERTY_PREFIX = 'b';
+ public static final char SHORT_PROPERTY_PREFIX = 's';
+ public static final char INT_PROPERTY_PREFIX = 'i';
+ public static final char LONG_PROPERTY_PREFIX = 'l';
+ public static final char FLOAT_PROPERTY_PREFIX = 'f';
+ public static final char DOUBLE_PROPERTY_PREFIX = 'd';
+ public static final char STRING_PROPERTY_PREFIX = 'S';
+
+ protected boolean _redelivered;
+
+ protected ByteBuffer _data;
+
+ protected AbstractJMSMessage(ByteBuffer data)
+ {
+ super(new BasicContentHeaderProperties());
+ _data = data;
+ if (_data != null)
+ {
+ _data.acquire();
+ }
+ }
+
+ protected AbstractJMSMessage(long deliveryTag, BasicContentHeaderProperties contentHeader, ByteBuffer data) throws AMQException
+ {
+ this(contentHeader, deliveryTag);
+ _data = data;
+ if (_data != null)
+ {
+ _data.acquire();
+ }
+ }
+
+ protected AbstractJMSMessage(BasicContentHeaderProperties contentHeader, long deliveryTag)
+ {
+ super(contentHeader, deliveryTag);
+ }
+
+ public String getJMSMessageID() throws JMSException
+ {
+ if (getJmsContentHeaderProperties().getMessageId() == null)
+ {
+ getJmsContentHeaderProperties().setMessageId("ID:" + _deliveryTag);
+ }
+ return getJmsContentHeaderProperties().getMessageId();
+ }
+
+ public void setJMSMessageID(String messageId) throws JMSException
+ {
+ getJmsContentHeaderProperties().setMessageId(messageId);
+ }
+
+ public long getJMSTimestamp() throws JMSException
+ {
+ return new Long(getJmsContentHeaderProperties().getTimestamp()).longValue();
+ }
+
+ public void setJMSTimestamp(long timestamp) throws JMSException
+ {
+ getJmsContentHeaderProperties().setTimestamp(timestamp);
+ }
+
+ public byte[] getJMSCorrelationIDAsBytes() throws JMSException
+ {
+ return getJmsContentHeaderProperties().getCorrelationId().getBytes();
+ }
+
+ public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException
+ {
+ getJmsContentHeaderProperties().setCorrelationId(new String(bytes));
+ }
+
+ public void setJMSCorrelationID(String correlationId) throws JMSException
+ {
+ getJmsContentHeaderProperties().setCorrelationId(correlationId);
+ }
+
+ public String getJMSCorrelationID() throws JMSException
+ {
+ return getJmsContentHeaderProperties().getCorrelationId();
+ }
+
+ public Destination getJMSReplyTo() throws JMSException
+ {
+ String replyToEncoding = getJmsContentHeaderProperties().getReplyTo();
+ if (replyToEncoding == null)
+ {
+ return null;
+ }
+ else
+ {
+ Destination dest = (Destination) _destinationCache.get(replyToEncoding);
+ if (dest == null)
+ {
+ char destType = replyToEncoding.charAt(0);
+ if (destType == 'Q')
+ {
+ dest = new AMQQueue(replyToEncoding.substring(1));
+ }
+ else if (destType == 'T')
+ {
+ dest = new AMQTopic(replyToEncoding.substring(1));
+ }
+ else
+ {
+ throw new JMSException("Illegal value in JMS_ReplyTo property: " + replyToEncoding);
+ }
+ _destinationCache.put(replyToEncoding, dest);
+ }
+ return dest;
+ }
+ }
+
+ public void setJMSReplyTo(Destination destination) throws JMSException
+ {
+ if (destination == null)
+ {
+ throw new IllegalArgumentException("Null destination not allowed");
+ }
+ if (!(destination instanceof AMQDestination))
+ {
+ throw new IllegalArgumentException("ReplyTo destination my be an AMQ destination - passed argument was type " +
+ destination.getClass());
+ }
+ final AMQDestination amqd = (AMQDestination) destination;
+
+ final String encodedDestination = amqd.getEncodedName();
+ _destinationCache.put(encodedDestination, destination);
+ getJmsContentHeaderProperties().setReplyTo(encodedDestination);
+ }
+
+ public Destination getJMSDestination() throws JMSException
+ {
+ // TODO: implement this once we have sorted out how to figure out the exchange class
+ throw new NotImplementedException();
+ }
+
+ public void setJMSDestination(Destination destination) throws JMSException
+ {
+ throw new NotImplementedException();
+ }
+
+ public int getJMSDeliveryMode() throws JMSException
+ {
+ return getJmsContentHeaderProperties().getDeliveryMode();
+ }
+
+ public void setJMSDeliveryMode(int i) throws JMSException
+ {
+ getJmsContentHeaderProperties().setDeliveryMode((byte) i);
+ }
+
+ public boolean getJMSRedelivered() throws JMSException
+ {
+ return _redelivered;
+ }
+
+ public void setJMSRedelivered(boolean b) throws JMSException
+ {
+ _redelivered = b;
+ }
+
+ public String getJMSType() throws JMSException
+ {
+ return getMimeType();
+ }
+
+ public void setJMSType(String string) throws JMSException
+ {
+ throw new JMSException("Cannot set JMS Type - it is implicitly defined based on message type");
+ }
+
+ public long getJMSExpiration() throws JMSException
+ {
+ return new Long(getJmsContentHeaderProperties().getExpiration()).longValue();
+ }
+
+ public void setJMSExpiration(long l) throws JMSException
+ {
+ getJmsContentHeaderProperties().setExpiration(l);
+ }
+
+ public int getJMSPriority() throws JMSException
+ {
+ return getJmsContentHeaderProperties().getPriority();
+ }
+
+ public void setJMSPriority(int i) throws JMSException
+ {
+ getJmsContentHeaderProperties().setPriority((byte) i);
+ }
+
+ public void clearProperties() throws JMSException
+ {
+ if (getJmsContentHeaderProperties().getHeaders() != null)
+ {
+ getJmsContentHeaderProperties().getHeaders().clear();
+ }
+ }
+
+ public boolean propertyExists(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return false;
+ }
+ else
+ {
+ // TODO: fix this
+ return getJmsContentHeaderProperties().getHeaders().containsKey(STRING_PROPERTY_PREFIX + propertyName);
+ }
+ }
+
+ public boolean getBooleanProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Boolean.valueOf(null).booleanValue();
+ }
+ else
+ {
+ // store as integer as temporary workaround
+ //Boolean b = (Boolean) getJmsContentHeaderProperties().headers.get(BOOLEAN_PROPERTY_PREFIX + propertyName);
+ Long b = (Long) getJmsContentHeaderProperties().getHeaders().get(BOOLEAN_PROPERTY_PREFIX + propertyName);
+
+ if (b == null)
+ {
+ return Boolean.valueOf(null).booleanValue();
+ }
+ else
+ {
+ return b.longValue() != 0;
+ }
+ }
+ }
+
+ public byte getByteProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Byte.valueOf(null).byteValue();
+ }
+ else
+ {
+ Byte b = (Byte) getJmsContentHeaderProperties().getHeaders().get(BYTE_PROPERTY_PREFIX + propertyName);
+ if (b == null)
+ {
+ return Byte.valueOf(null).byteValue();
+ }
+ else
+ {
+ return b.byteValue();
+ }
+ }
+ }
+
+ public short getShortProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Short.valueOf(null).shortValue();
+ }
+ else
+ {
+ Short s = (Short) getJmsContentHeaderProperties().getHeaders().get(SHORT_PROPERTY_PREFIX + propertyName);
+ if (s == null)
+ {
+ return Short.valueOf(null).shortValue();
+ }
+ else
+ {
+ return s.shortValue();
+ }
+ }
+ }
+
+ public int getIntProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Integer.valueOf(null).intValue();
+ }
+ else
+ {
+ Integer i = (Integer) getJmsContentHeaderProperties().getHeaders().get(INT_PROPERTY_PREFIX + propertyName);
+ if (i == null)
+ {
+ return Integer.valueOf(null).intValue();
+ }
+ else
+ {
+ return i.intValue();
+ }
+ }
+ }
+
+ public long getLongProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Long.valueOf(null).longValue();
+ }
+ else
+ {
+ Long l = (Long) getJmsContentHeaderProperties().getHeaders().get(LONG_PROPERTY_PREFIX + propertyName);
+ if (l == null)
+ {
+ // temp - the spec says do this but this throws a NumberFormatException
+ //return Long.valueOf(null).longValue();
+ return 0;
+ }
+ else
+ {
+ return l.longValue();
+ }
+ }
+ }
+
+ public float getFloatProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Float.valueOf(null).floatValue();
+ }
+ else
+ {
+ final Float f = (Float) getJmsContentHeaderProperties().getHeaders().get(FLOAT_PROPERTY_PREFIX + propertyName);
+ if (f == null)
+ {
+ return Float.valueOf(null).floatValue();
+ }
+ else
+ {
+ return f.floatValue();
+ }
+ }
+ }
+
+ public double getDoubleProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return Double.valueOf(null).doubleValue();
+ }
+ else
+ {
+ final Double d = (Double) getJmsContentHeaderProperties().getHeaders().get(DOUBLE_PROPERTY_PREFIX + propertyName);
+ if (d == null)
+ {
+ return Double.valueOf(null).doubleValue();
+ }
+ else
+ {
+ return d.shortValue();
+ }
+ }
+ }
+
+ public String getStringProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return null;
+ }
+ else
+ {
+ return (String) getJmsContentHeaderProperties().getHeaders().get(STRING_PROPERTY_PREFIX + propertyName);
+ }
+ }
+
+ public Object getObjectProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ throw new JMSException("Not implemented yet");
+ }
+
+ public Enumeration getPropertyNames() throws JMSException
+ {
+ return new FieldTableKeyEnumeration(getJmsContentHeaderProperties().getHeaders())
+ {
+ public Object nextElement()
+ {
+ String propName = (String) _iterator.next();
+
+ //The propertyName has a single Char prefix. Skip this.
+ return propName.substring(1);
+ }
+ };
+ }
+
+ public void setBooleanProperty(String propertyName, boolean b) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ //getJmsContentHeaderProperties().headers.put(BOOLEAN_PROPERTY_PREFIX + propertyName, Boolean.valueOf(b));
+ getJmsContentHeaderProperties().getHeaders().put(BOOLEAN_PROPERTY_PREFIX + propertyName, b ? new Long(1) : new Long(0));
+ }
+
+ public void setByteProperty(String propertyName, byte b) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ getJmsContentHeaderProperties().getHeaders().put(BYTE_PROPERTY_PREFIX + propertyName, new Byte(b));
+ }
+
+ public void setShortProperty(String propertyName, short i) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ getJmsContentHeaderProperties().getHeaders().put(SHORT_PROPERTY_PREFIX + propertyName, new Short(i));
+ }
+
+ public void setIntProperty(String propertyName, int i) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ getJmsContentHeaderProperties().getHeaders().put(INT_PROPERTY_PREFIX + propertyName, new Integer(i));
+ }
+
+ public void setLongProperty(String propertyName, long l) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ getJmsContentHeaderProperties().getHeaders().put(LONG_PROPERTY_PREFIX + propertyName, new Long(l));
+ }
+
+ public void setFloatProperty(String propertyName, float f) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ getJmsContentHeaderProperties().getHeaders().put(FLOAT_PROPERTY_PREFIX + propertyName, new Float(f));
+ }
+
+ public void setDoubleProperty(String propertyName, double v) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ getJmsContentHeaderProperties().getHeaders().put(DOUBLE_PROPERTY_PREFIX + propertyName, new Double(v));
+ }
+
+ public void setStringProperty(String propertyName, String value) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ createPropertyMapIfRequired();
+ propertyName = STRING_PROPERTY_PREFIX + propertyName;
+ getJmsContentHeaderProperties().getHeaders().put(propertyName, value);
+ }
+
+ private void createPropertyMapIfRequired()
+ {
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ getJmsContentHeaderProperties().setHeaders(new FieldTable());
+ }
+ }
+
+ public void setObjectProperty(String string, Object object) throws JMSException
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void acknowledge() throws JMSException
+ {
+ // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge
+ // is not specified. In our case, we only set the session field where client acknowledge mode is specified.
+ if (_session != null)
+ {
+ // we set multiple to true here since acknowledgement implies acknowledge of all previous messages
+ // received on the session
+ _session.acknowledgeMessage(_deliveryTag, true);
+ }
+ }
+
+ public abstract void clearBody() throws JMSException;
+
+ /**
+ * Get a String representation of the body of the message. Used in the
+ * toString() method which outputs this before message properties.
+ */
+ public abstract String toBodyString() throws JMSException;
+
+ public abstract String getMimeType();
+
+ public String toString()
+ {
+ try
+ {
+ StringBuffer buf = new StringBuffer("Body:\n");
+ buf.append(toBodyString());
+ buf.append("\nJMS timestamp: ").append(getJMSTimestamp());
+ buf.append("\nJMS expiration: ").append(getJMSExpiration());
+ buf.append("\nJMS priority: ").append(getJMSPriority());
+ buf.append("\nJMS delivery mode: ").append(getJMSDeliveryMode());
+ buf.append("\nJMS reply to: ").append(String.valueOf(getJMSReplyTo()));
+ buf.append("\nAMQ message number: ").append(_deliveryTag);
+ buf.append("\nProperties:");
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ buf.append("<NONE>");
+ }
+ else
+ {
+ final Iterator it = getJmsContentHeaderProperties().getHeaders().entrySet().iterator();
+ while (it.hasNext())
+ {
+ final Map.Entry entry = (Map.Entry) it.next();
+ final String propertyName = (String) entry.getKey();
+ if (propertyName == null)
+ {
+ buf.append("\nInternal error: Property with NULL key defined");
+ }
+ else
+ {
+ buf.append('\n').append(propertyName.substring(1));
+
+ char typeIdentifier = propertyName.charAt(0);
+ switch (typeIdentifier)
+ {
+ case org.apache.qpid.client.message.AbstractJMSMessage.BOOLEAN_PROPERTY_PREFIX:
+ buf.append("<boolean> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.BYTE_PROPERTY_PREFIX:
+ buf.append("<byte> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.SHORT_PROPERTY_PREFIX:
+ buf.append("<short> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.INT_PROPERTY_PREFIX:
+ buf.append("<int> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.LONG_PROPERTY_PREFIX:
+ buf.append("<long> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.FLOAT_PROPERTY_PREFIX:
+ buf.append("<float> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.DOUBLE_PROPERTY_PREFIX:
+ buf.append("<double> ");
+ break;
+ case org.apache.qpid.client.message.AbstractJMSMessage.STRING_PROPERTY_PREFIX:
+ buf.append("<string> ");
+ break;
+ default:
+ buf.append("<unknown type (identifier " +
+ typeIdentifier + ") ");
+ }
+ buf.append(String.valueOf(entry.getValue()));
+ }
+ }
+ }
+ return buf.toString();
+ }
+ catch (JMSException e)
+ {
+ return e.toString();
+ }
+ }
+
+ public Map getUnderlyingMessagePropertiesMap()
+ {
+ return getJmsContentHeaderProperties().getHeaders();
+ }
+
+ public void setUnderlyingMessagePropertiesMap(FieldTable messageProperties)
+ {
+ getJmsContentHeaderProperties().setHeaders(messageProperties);
+ }
+
+ private void checkPropertyName(String propertyName)
+ {
+ if (propertyName == null)
+ {
+ throw new IllegalArgumentException("Property name must not be null");
+ }
+ else if ("".equals(propertyName))
+ {
+ throw new IllegalArgumentException("Property name must not be the empty string");
+ }
+
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ getJmsContentHeaderProperties().setHeaders(new FieldTable());
+ }
+ }
+
+ public FieldTable populateHeadersFromMessageProperties()
+ {
+ if (getJmsContentHeaderProperties().getHeaders() == null)
+ {
+ return null;
+ }
+ else
+ {
+ //
+ // We need to convert every property into a String representation
+ // Note that type information is preserved in the property name
+ //
+ final FieldTable table = new FieldTable();
+ final Iterator entries = getJmsContentHeaderProperties().getHeaders().entrySet().iterator();
+ while (entries.hasNext())
+ {
+ final Map.Entry entry = (Map.Entry) entries.next();
+ final String propertyName = (String) entry.getKey();
+ if (propertyName == null)
+ {
+ continue;
+ }
+ else
+ {
+ table.put(propertyName, entry.getValue().toString());
+ }
+ }
+ return table;
+ }
+ }
+
+ public BasicContentHeaderProperties getJmsContentHeaderProperties()
+ {
+ return (BasicContentHeaderProperties) _contentHeaderProperties;
+ }
+
+ public ByteBuffer getData()
+ {
+ // make sure we rewind the data just in case any method has moved the
+ // position beyond the start
+ if (_data != null)
+ {
+ _data.rewind();
+ }
+ return _data;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/AbstractJMSMessageFactory.java b/java/client/src/org/apache/qpid/client/message/AbstractJMSMessageFactory.java
new file mode 100644
index 0000000000..57603f8a7e
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/AbstractJMSMessageFactory.java
@@ -0,0 +1,77 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.jms.JMSException;
+import java.util.Iterator;
+import java.util.List;
+
+public abstract class AbstractJMSMessageFactory implements MessageFactory
+{
+ private static final Logger _logger = Logger.getLogger(AbstractJMSMessageFactory.class);
+
+
+ protected abstract AbstractJMSMessage createMessage(long messageNbr, ByteBuffer data,
+ ContentHeaderBody contentHeader) throws AMQException;
+
+ protected AbstractJMSMessage createMessageWithBody(long messageNbr,
+ ContentHeaderBody contentHeader,
+ List bodies) throws AMQException
+ {
+ ByteBuffer data;
+
+ // we optimise the non-fragmented case to avoid copying
+ if (bodies != null && bodies.size() == 1)
+ {
+ _logger.debug("Non-fragmented message body (bodySize=" + contentHeader.bodySize +")");
+ data = ((ContentBody)bodies.get(0)).payload;
+ }
+ else
+ {
+ _logger.debug("Fragmented message body (" + bodies.size() + " frames, bodySize=" + contentHeader.bodySize + ")");
+ data = ByteBuffer.allocate((int)contentHeader.bodySize); // XXX: Is cast a problem?
+ final Iterator it = bodies.iterator();
+ while (it.hasNext())
+ {
+ ContentBody cb = (ContentBody) it.next();
+ data.put(cb.payload);
+ cb.payload.release();
+ }
+ data.flip();
+ }
+ _logger.debug("Creating message from buffer with position=" + data.position() + " and remaining=" + data.remaining());
+
+ return createMessage(messageNbr, data, contentHeader);
+ }
+
+ public AbstractJMSMessage createMessage(long messageNbr, boolean redelivered,
+ ContentHeaderBody contentHeader,
+ List bodies) throws JMSException, AMQException
+ {
+ final AbstractJMSMessage msg = createMessageWithBody(messageNbr, contentHeader, bodies);
+ msg.setJMSRedelivered(redelivered);
+ return msg;
+ }
+
+}
diff --git a/java/client/src/org/apache/qpid/client/message/JMSBytesMessage.java b/java/client/src/org/apache/qpid/client/message/JMSBytesMessage.java
new file mode 100644
index 0000000000..2cd635f6eb
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/JMSBytesMessage.java
@@ -0,0 +1,351 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.AMQException;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.jms.JMSException;
+import javax.jms.MessageNotReadableException;
+import javax.jms.MessageNotWriteableException;
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.CharacterCodingException;
+
+public class JMSBytesMessage extends AbstractJMSMessage implements javax.jms.BytesMessage
+{
+ private static final String MIME_TYPE = "application/octet-stream";
+
+ private boolean _readable = false;
+
+ /**
+ * The default initial size of the buffer. The buffer expands automatically.
+ */
+ private static final int DEFAULT_BUFFER_INITIAL_SIZE = 1024;
+
+ JMSBytesMessage()
+ {
+ this(null);
+ }
+
+ /**
+ * Construct a bytes message with existing data.
+ * @param data the data that comprises this message. If data is null, you get a 1024 byte buffer that is
+ * set to auto expand
+ */
+ JMSBytesMessage(ByteBuffer data)
+ {
+ super(data); // this instanties a content header
+ getJmsContentHeaderProperties().setContentType(MIME_TYPE);
+
+ if (_data == null)
+ {
+ _data = ByteBuffer.allocate(DEFAULT_BUFFER_INITIAL_SIZE);
+ _data.setAutoExpand(true);
+ }
+ _readable = (data != null);
+ }
+
+ JMSBytesMessage(long messageNbr, ByteBuffer data, ContentHeaderBody contentHeader)
+ throws AMQException
+ {
+ // TODO: this casting is ugly. Need to review whole ContentHeaderBody idea
+ super(messageNbr, (BasicContentHeaderProperties) contentHeader.properties, data);
+ getJmsContentHeaderProperties().setContentType(MIME_TYPE);
+ _readable = true;
+ }
+
+ public void clearBody() throws JMSException
+ {
+ _data.clear();
+ _readable = false;
+ }
+
+ public String toBodyString() throws JMSException
+ {
+ checkReadable();
+ try
+ {
+ return getText();
+ }
+ catch (IOException e)
+ {
+ throw new JMSException(e.toString());
+ }
+ }
+
+ /**
+ * We reset the stream before and after reading the data. This means that toString() will always output
+ * the entire message and also that the caller can then immediately start reading as if toString() had
+ * never been called.
+ * @return
+ * @throws IOException
+ */
+ private String getText() throws IOException
+ {
+ // this will use the default platform encoding
+ if (_data == null)
+ {
+ return null;
+ }
+ int pos = _data.position();
+ _data.rewind();
+ // one byte left is for the end of frame marker
+ if (_data.remaining() == 0)
+ {
+ // this is really redundant since pos must be zero
+ _data.position(pos);
+ return null;
+ }
+ else
+ {
+ String data = _data.getString(Charset.forName("UTF8").newDecoder());
+ _data.position(pos);
+ return data;
+ }
+ }
+
+ public String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public long getBodyLength() throws JMSException
+ {
+ checkReadable();
+ return _data.limit();
+ }
+
+ private void checkReadable() throws MessageNotReadableException
+ {
+ if (!_readable)
+ {
+ throw new MessageNotReadableException("You need to call reset() to make the message readable");
+ }
+ }
+
+ private void checkWritable() throws MessageNotWriteableException
+ {
+ if (_readable)
+ {
+ throw new MessageNotWriteableException("You need to call clearBody() to make the message writable");
+ }
+ }
+
+ public boolean readBoolean() throws JMSException
+ {
+ checkReadable();
+ return _data.get() != 0;
+ }
+
+ public byte readByte() throws JMSException
+ {
+ checkReadable();
+ return _data.get();
+ }
+
+ public int readUnsignedByte() throws JMSException
+ {
+ checkReadable();
+ return _data.getUnsigned();
+ }
+
+ public short readShort() throws JMSException
+ {
+ checkReadable();
+ return _data.getShort();
+ }
+
+ public int readUnsignedShort() throws JMSException
+ {
+ checkReadable();
+ return _data.getUnsignedShort();
+ }
+
+ public char readChar() throws JMSException
+ {
+ checkReadable();
+ return _data.getChar();
+ }
+
+ public int readInt() throws JMSException
+ {
+ checkReadable();
+ return _data.getInt();
+ }
+
+ public long readLong() throws JMSException
+ {
+ checkReadable();
+ return _data.getLong();
+ }
+
+ public float readFloat() throws JMSException
+ {
+ checkReadable();
+ return _data.getFloat();
+ }
+
+ public double readDouble() throws JMSException
+ {
+ checkReadable();
+ return _data.getDouble();
+ }
+
+ public String readUTF() throws JMSException
+ {
+ checkReadable();
+ try
+ {
+ return _data.getString(Charset.forName("UTF-8").newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException je = new JMSException("Error decoding byte stream as a UTF8 string: " + e);
+ je.setLinkedException(e);
+ throw je;
+ }
+ }
+
+ public int readBytes(byte[] bytes) throws JMSException
+ {
+ if (bytes == null)
+ {
+ throw new IllegalArgumentException("byte array must not be null");
+ }
+ checkReadable();
+ int count = (_data.remaining() >= bytes.length ? bytes.length : _data.remaining());
+ _data.get(bytes, 0, count);
+ return count;
+ }
+
+ public int readBytes(byte[] bytes, int maxLength) throws JMSException
+ {
+ if (bytes == null)
+ {
+ throw new IllegalArgumentException("byte array must not be null");
+ }
+ if (maxLength > bytes.length)
+ {
+ throw new IllegalArgumentException("maxLength must be <= bytes.length");
+ }
+ checkReadable();
+ int count = (_data.remaining() >= maxLength ? maxLength : _data.remaining());
+ _data.get(bytes, 0, count);
+ return count;
+ }
+
+ public void writeBoolean(boolean b) throws JMSException
+ {
+ checkWritable();
+ _data.put(b?(byte)1:(byte)0);
+ }
+
+ public void writeByte(byte b) throws JMSException
+ {
+ checkWritable();
+ _data.put(b);
+ }
+
+ public void writeShort(short i) throws JMSException
+ {
+ checkWritable();
+ _data.putShort(i);
+ }
+
+ public void writeChar(char c) throws JMSException
+ {
+ checkWritable();
+ _data.putChar(c);
+ }
+
+ public void writeInt(int i) throws JMSException
+ {
+ checkWritable();
+ _data.putInt(i);
+ }
+
+ public void writeLong(long l) throws JMSException
+ {
+ checkWritable();
+ _data.putLong(l);
+ }
+
+ public void writeFloat(float v) throws JMSException
+ {
+ checkWritable();
+ _data.putFloat(v);
+ }
+
+ public void writeDouble(double v) throws JMSException
+ {
+ checkWritable();
+ _data.putDouble(v);
+ }
+
+ public void writeUTF(String string) throws JMSException
+ {
+ checkWritable();
+ try
+ {
+ _data.putString(string, Charset.forName("UTF-8").newEncoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException ex = new JMSException("Unable to encode string: " + e);
+ ex.setLinkedException(e);
+ throw ex;
+ }
+ }
+
+ public void writeBytes(byte[] bytes) throws JMSException
+ {
+ checkWritable();
+ _data.put(bytes);
+ }
+
+ public void writeBytes(byte[] bytes, int offset, int length) throws JMSException
+ {
+ checkWritable();
+ _data.put(bytes, offset, length);
+ }
+
+ public void writeObject(Object object) throws JMSException
+ {
+ checkWritable();
+ if (object == null)
+ {
+ throw new NullPointerException("Argument must not be null");
+ }
+ _data.putObject(object);
+ }
+
+ public void reset() throws JMSException
+ {
+ //checkWritable();
+ _data.flip();
+ _readable = true;
+ }
+
+ public boolean isReadable()
+ {
+ return _readable;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java b/java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java
new file mode 100644
index 0000000000..0e3aa45047
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+import javax.jms.JMSException;
+
+public class JMSBytesMessageFactory extends AbstractJMSMessageFactory
+{
+ protected AbstractJMSMessage createMessage(long deliveryTag, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException
+ {
+ return new JMSBytesMessage(deliveryTag, data, contentHeader);
+ }
+
+ public AbstractJMSMessage createMessage() throws JMSException
+ {
+ return new JMSBytesMessage();
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java b/java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java
new file mode 100644
index 0000000000..f0ac87c2d7
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java
@@ -0,0 +1,175 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.AMQException;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.jms.ObjectMessage;
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageNotWriteableException;
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.CharacterCodingException;
+
+public class JMSObjectMessage extends AbstractJMSMessage implements ObjectMessage
+{
+ static final String MIME_TYPE = "application/java-object-stream";
+ private final boolean _readonly;
+
+ private static final int DEFAULT_BUFFER_SIZE = 1024;
+ /**
+ * Creates empty, writable message for use by producers
+ */
+ JMSObjectMessage()
+ {
+ this(null);
+ }
+
+ private JMSObjectMessage(ByteBuffer data)
+ {
+ super(data);
+ if (data == null)
+ {
+ _data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
+ _data.setAutoExpand(true);
+ }
+ _readonly = (data != null);
+ getJmsContentHeaderProperties().setContentType(MIME_TYPE);
+ }
+
+ /**
+ * Creates read only message for delivery to consumers
+ */
+ JMSObjectMessage(long messageNbr, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException
+ {
+ super(messageNbr, (BasicContentHeaderProperties) contentHeader.properties, data);
+ _readonly = data != null;
+ }
+
+ public void clearBody() throws JMSException
+ {
+ if (_data != null)
+ {
+ _data.release();
+ }
+ _data = null;
+ }
+
+ public String toBodyString() throws JMSException
+ {
+ return toString(_data);
+ }
+
+ public String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public void setObject(Serializable serializable) throws JMSException
+ {
+ if (_readonly)
+ {
+ throw new MessageNotWriteableException("Message is not writable.");
+ }
+
+ if (_data == null)
+ {
+ _data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
+ _data.setAutoExpand(true);
+ }
+ try
+ {
+ ObjectOutputStream out = new ObjectOutputStream(_data.asOutputStream());
+ out.writeObject(serializable);
+ out.flush();
+ out.close();
+ _data.rewind();
+ }
+ catch (IOException e)
+ {
+ throw new MessageFormatException("Message not serializable: " + e);
+ }
+ }
+
+ public Serializable getObject() throws JMSException
+ {
+ ObjectInputStream in = null;
+ if (_data == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ in = new ObjectInputStream(_data.asInputStream());
+ return (Serializable) in.readObject();
+ }
+ catch (IOException e)
+ {
+ throw new MessageFormatException("Could not deserialize message: " + e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new MessageFormatException("Could not deserialize message: " + e);
+ }
+ finally
+ {
+ _data.rewind();
+ close(in);
+ }
+ }
+
+ private static void close(InputStream in)
+ {
+ try
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+ catch (IOException ignore)
+ {
+ }
+ }
+
+ private static String toString(ByteBuffer data)
+ {
+ if (data == null)
+ {
+ return null;
+ }
+ int pos = data.position();
+ try
+ {
+ return data.getString(Charset.forName("UTF8").newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ return null;
+ }
+ finally
+ {
+ data.position(pos);
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java b/java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java
new file mode 100644
index 0000000000..cb5f29ec74
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java
@@ -0,0 +1,37 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+import javax.jms.JMSException;
+
+public class JMSObjectMessageFactory extends AbstractJMSMessageFactory
+{
+ protected AbstractJMSMessage createMessage(long deliveryTag, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException
+ {
+ return new JMSObjectMessage(deliveryTag, data, contentHeader);
+ }
+
+ public AbstractJMSMessage createMessage() throws JMSException
+ {
+ return new JMSObjectMessage();
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/JMSTextMessage.java b/java/client/src/org/apache/qpid/client/message/JMSTextMessage.java
new file mode 100644
index 0000000000..b860d55e94
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/JMSTextMessage.java
@@ -0,0 +1,159 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.AMQException;
+import org.apache.mina.common.ByteBuffer;
+
+import javax.jms.JMSException;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharacterCodingException;
+
+public class JMSTextMessage extends AbstractJMSMessage implements javax.jms.TextMessage
+{
+ private static final String MIME_TYPE = "text/plain";
+
+ private String _decodedValue;
+
+ JMSTextMessage() throws JMSException
+ {
+ this(null, null);
+ }
+
+ JMSTextMessage(ByteBuffer data, String encoding) throws JMSException
+ {
+ super(data); // this instantiates a content header
+ getJmsContentHeaderProperties().setContentType(MIME_TYPE);
+ getJmsContentHeaderProperties().setEncoding(encoding);
+ }
+
+ JMSTextMessage(long deliveryTag, ByteBuffer data, BasicContentHeaderProperties contentHeader)
+ throws AMQException
+ {
+ super(deliveryTag, contentHeader, data);
+ contentHeader.setContentType(MIME_TYPE);
+ _data = data;
+ }
+
+ JMSTextMessage(ByteBuffer data) throws JMSException
+ {
+ this(data, null);
+ }
+
+ JMSTextMessage(String text) throws JMSException
+ {
+ super((ByteBuffer)null);
+ setText(text);
+ }
+
+ public void clearBody() throws JMSException
+ {
+ if (_data != null)
+ {
+ _data.release();
+ }
+ _data = null;
+ _decodedValue = null;
+ }
+
+ public String toBodyString() throws JMSException
+ {
+ return getText();
+ }
+
+ public void setData(ByteBuffer data)
+ {
+ _data = data;
+ }
+
+ public String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public void setText(String string) throws JMSException
+ {
+ clearBody();
+ try
+ {
+ _data = ByteBuffer.allocate(string.length());
+ _data.limit(string.length());
+ //_data.sweep();
+ _data.setAutoExpand(true);
+ if (getJmsContentHeaderProperties().getEncoding() == null)
+ {
+ _data.put(string.getBytes());
+ }
+ else
+ {
+ _data.put(string.getBytes(getJmsContentHeaderProperties().getEncoding()));
+ }
+ _decodedValue = string;
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ // should never occur
+ throw new JMSException("Unable to decode string data");
+ }
+ }
+
+ public String getText() throws JMSException
+ {
+ if (_data == null && _decodedValue == null)
+ {
+ return null;
+ }
+ else if (_decodedValue != null)
+ {
+ return _decodedValue;
+ }
+ else
+ {
+ _data.rewind();
+ if (getJmsContentHeaderProperties().getEncoding() != null)
+ {
+ try
+ {
+ _decodedValue = _data.getString(Charset.forName(getJmsContentHeaderProperties().getEncoding()).newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException je = new JMSException("Could not decode string data: " + e);
+ je.setLinkedException(e);
+ throw je;
+ }
+ }
+ else
+ {
+ try
+ {
+ _decodedValue = _data.getString(Charset.defaultCharset().newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException je = new JMSException("Could not decode string data: " + e);
+ je.setLinkedException(e);
+ throw je;
+ }
+ }
+ return _decodedValue;
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java b/java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java
new file mode 100644
index 0000000000..747965d26e
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+import javax.jms.JMSException;
+
+public class JMSTextMessageFactory extends AbstractJMSMessageFactory
+{
+
+ public AbstractJMSMessage createMessage() throws JMSException
+ {
+ return new JMSTextMessage();
+ }
+
+ protected AbstractJMSMessage createMessage(long deliveryTag, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException
+ {
+ return new JMSTextMessage(deliveryTag, data, (BasicContentHeaderProperties)contentHeader.properties);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/MessageFactory.java b/java/client/src/org/apache/qpid/client/message/MessageFactory.java
new file mode 100644
index 0000000000..a9c7dd1b48
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/MessageFactory.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+import javax.jms.JMSException;
+import java.util.List;
+
+
+public interface MessageFactory
+{
+ AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered,
+ ContentHeaderBody contentHeader,
+ List bodies)
+ throws JMSException, AMQException;
+
+ AbstractJMSMessage createMessage() throws JMSException;
+}
diff --git a/java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java b/java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java
new file mode 100644
index 0000000000..5808f9aa35
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+import javax.jms.JMSException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+
+public class MessageFactoryRegistry
+{
+ private final Map _mimeToFactoryMap = new HashMap();
+
+ public void registerFactory(String mimeType, MessageFactory mf)
+ {
+ if (mf == null)
+ {
+ throw new IllegalArgumentException("Message factory must not be null");
+ }
+ _mimeToFactoryMap.put(mimeType, mf);
+ }
+
+ public MessageFactory deregisterFactory(String mimeType)
+ {
+ return (MessageFactory) _mimeToFactoryMap.remove(mimeType);
+ }
+
+ /**
+ * Create a message. This looks up the MIME type from the content header and instantiates the appropriate
+ * concrete message type.
+ * @param deliveryTag the AMQ message id
+ * @param redelivered true if redelivered
+ * @param contentHeader the content header that was received
+ * @param bodies a list of ContentBody instances
+ * @return the message.
+ * @throws AMQException
+ * @throws JMSException
+ */
+ public AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered,
+ ContentHeaderBody contentHeader,
+ List bodies) throws AMQException, JMSException
+ {
+ BasicContentHeaderProperties properties = (BasicContentHeaderProperties) contentHeader.properties;
+ MessageFactory mf = (MessageFactory) _mimeToFactoryMap.get(properties.getContentType());
+ if (mf == null)
+ {
+ throw new AMQException("Unsupport MIME type of " + properties.getContentType());
+ }
+ else
+ {
+ return mf.createMessage(deliveryTag, redelivered, contentHeader, bodies);
+ }
+ }
+
+ public AbstractJMSMessage createMessage(String mimeType) throws AMQException, JMSException
+ {
+ if (mimeType == null)
+ {
+ throw new IllegalArgumentException("Mime type must not be null");
+ }
+ MessageFactory mf = (MessageFactory) _mimeToFactoryMap.get(mimeType);
+ if (mf == null)
+ {
+ throw new AMQException("Unsupport MIME type of " + mimeType);
+ }
+ else
+ {
+ return mf.createMessage();
+ }
+ }
+
+ /**
+ * Construct a new registry with the default message factories registered
+ * @return a message factory registry
+ */
+ public static MessageFactoryRegistry newDefaultRegistry()
+ {
+ MessageFactoryRegistry mf = new MessageFactoryRegistry();
+ mf.registerFactory("text/plain", new JMSTextMessageFactory());
+ mf.registerFactory("text/xml", new JMSTextMessageFactory());
+ mf.registerFactory("application/octet-stream", new JMSBytesMessageFactory());
+ mf.registerFactory(JMSObjectMessage.MIME_TYPE, new JMSObjectMessageFactory());
+ mf.registerFactory(null, new JMSBytesMessageFactory());
+ return mf;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java b/java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java
new file mode 100644
index 0000000000..f2cb7defd2
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.AMQException;
+import org.apache.log4j.Logger;
+
+public class UnexpectedBodyReceivedException extends AMQException
+{
+
+ public UnexpectedBodyReceivedException(Logger logger, String msg, Throwable t)
+ {
+ super(logger, msg, t);
+ }
+
+ public UnexpectedBodyReceivedException(Logger logger, String msg)
+ {
+ super(logger, msg);
+ }
+
+ public UnexpectedBodyReceivedException(Logger logger, int errorCode, String msg)
+ {
+ super(logger, errorCode, msg);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java b/java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java
new file mode 100644
index 0000000000..7d4ee097d3
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.message;
+
+import org.apache.qpid.framing.*;
+
+import java.util.List;
+import java.util.LinkedList;
+
+/**
+ * This class contains everything needed to process a JMS message. It assembles the
+ * deliver body, the content header and the content body/ies.
+ *
+ * Note that the actual work of creating a JMS message for the client code's use is done
+ * outside of the MINA dispatcher thread in order to minimise the amount of work done in
+ * the MINA dispatcher thread.
+ *
+ */
+public class UnprocessedMessage
+{
+ private long _bytesReceived = 0;
+
+ public BasicDeliverBody deliverBody;
+ public BasicReturnBody bounceBody; // TODO: check change (gustavo)
+ public int channelId;
+ public ContentHeaderBody contentHeader;
+
+ /**
+ * List of ContentBody instances. Due to fragmentation you don't know how big this will be in general
+ */
+ public List bodies = new LinkedList();
+
+ public void receiveBody(ContentBody body) throws UnexpectedBodyReceivedException
+ {
+ bodies.add(body);
+ if (body.payload != null)
+ {
+ _bytesReceived += body.payload.remaining();
+ }
+ }
+
+ public boolean isAllBodyDataReceived()
+ {
+ return _bytesReceived == contentHeader.bodySize;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java b/java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java
new file mode 100644
index 0000000000..b14a7cb8b0
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.qpid.framing.AMQMethodBody;
+
+public class AMQMethodEvent
+{
+ private AMQMethodBody _method;
+
+ private int _channelId;
+
+ private AMQProtocolSession _protocolSession;
+
+ public AMQMethodEvent(int channelId, AMQMethodBody method, AMQProtocolSession protocolSession)
+ {
+ _channelId = channelId;
+ _method = method;
+ _protocolSession = protocolSession;
+ }
+
+ public AMQMethodBody getMethod()
+ {
+ return _method;
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer("Method event: ");
+ buf.append("\nChannel id: ").append(_channelId);
+ buf.append("\nMethod: ").append(_method);
+ return buf.toString();
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java b/java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java
new file mode 100644
index 0000000000..fd20151e95
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.qpid.AMQException;
+
+public interface AMQMethodListener
+{
+ /**
+ * Invoked when a method frame has been received
+ * @param evt the event
+ * @return true if the handler has processed the method frame, false otherwise. Note
+ * that this does not prohibit the method event being delivered to subsequent listeners
+ * but can be used to determine if nobody has dealt with an incoming method frame.
+ * @throws AMQException if an error has occurred. This exception will be delivered
+ * to all registered listeners using the error() method (see below) allowing them to
+ * perform cleanup if necessary.
+ */
+ boolean methodReceived(AMQMethodEvent evt) throws AMQException;
+
+ /**
+ * Callback when an error has occurred. Allows listeners to clean up.
+ * @param e
+ */
+ void error(Exception e);
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java
new file mode 100644
index 0000000000..0c4dbcbb26
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java
@@ -0,0 +1,530 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.log4j.Logger;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoHandlerAdapter;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.filter.SSLFilter;
+import org.apache.mina.filter.codec.ProtocolCodecFilter;
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.AMQException;
+
+import org.apache.qpid.AMQConnectionClosedException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.failover.FailoverHandler;
+import org.apache.qpid.client.failover.FailoverState;
+
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.listener.SpecificMethodFrameListener;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.ssl.BogusSSLContextFactory;
+
+import java.util.Iterator;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.CountDownLatch;
+
+public class AMQProtocolHandler extends IoHandlerAdapter
+{
+ private static final Logger _logger = Logger.getLogger(AMQProtocolHandler.class);
+
+ /**
+ * The connection that this protocol handler is associated with. There is a 1-1
+ * mapping between connection instances and protocol handler instances.
+ */
+ private AMQConnection _connection;
+
+ /**
+ * Used only when determining whether to add the SSL filter or not. This should be made more
+ * generic in future since we will potentially have many transport layer options
+ */
+ private boolean _useSSL;
+
+ /**
+ * Our wrapper for a protocol session that provides access to session values
+ * in a typesafe manner.
+ */
+ private volatile AMQProtocolSession _protocolSession;
+
+ private AMQStateManager _stateManager = new AMQStateManager();
+
+ private final CopyOnWriteArraySet _frameListeners = new CopyOnWriteArraySet();
+
+ /**
+ * We create the failover handler when the session is created since it needs a reference to the IoSession in order
+ * to be able to send errors during failover back to the client application. The session won't be available in the
+ * case where we failing over due to a Connection.Redirect message from the broker.
+ */
+ private FailoverHandler _failoverHandler;
+
+ /**
+ * This flag is used to track whether failover is being attempted. It is used to prevent the application constantly
+ * attempting failover where it is failing.
+ */
+ private FailoverState _failoverState = FailoverState.NOT_STARTED;
+
+ private CountDownLatch _failoverLatch;
+
+ public AMQProtocolHandler(AMQConnection con)
+ {
+ _connection = con;
+
+ // We add a proxy for the state manager so that we can substitute the state manager easily in this class.
+ // We substitute the state manager when performing failover
+ _frameListeners.add(new AMQMethodListener()
+ {
+ public boolean methodReceived(AMQMethodEvent evt) throws AMQException
+ {
+ return _stateManager.methodReceived(evt);
+ }
+
+ public void error(Exception e)
+ {
+ _stateManager.error(e);
+ }
+ });
+ }
+
+ public boolean isUseSSL()
+ {
+ return _useSSL;
+ }
+
+ public void setUseSSL(boolean useSSL)
+ {
+ _useSSL = useSSL;
+ }
+
+ public void sessionCreated(IoSession session) throws Exception
+ {
+ _logger.debug("Protocol session created for session " + System.identityHashCode(session));
+ _failoverHandler = new FailoverHandler(this, session);
+
+ final ProtocolCodecFilter pcf = new ProtocolCodecFilter(new AMQCodecFactory(false));
+
+ if (Boolean.getBoolean("amqj.shared_read_write_pool"))
+ {
+ session.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf);
+ }
+ else
+ {
+ session.getFilterChain().addLast("protocolFilter", pcf);
+ }
+ // we only add the SSL filter where we have an SSL connection
+ if (_useSSL)
+ {
+ //todo FIXME: Bogus context cannot be used in production.
+ SSLFilter sslFilter = new SSLFilter(BogusSSLContextFactory.getInstance(false));
+ sslFilter.setUseClientMode(true);
+ session.getFilterChain().addBefore("protocolFilter", "ssl", sslFilter);
+ }
+
+ _protocolSession = new AMQProtocolSession(this, session, _connection);
+ _protocolSession.init();
+ }
+
+ public void sessionOpened(IoSession session) throws Exception
+ {
+ System.setProperty("foo", "bar");
+ }
+
+ /**
+ * When the broker connection dies we can either get sessionClosed() called or exceptionCaught() followed by
+ * sessionClosed() depending on whether we were trying to send data at the time of failure.
+ *
+ * @param session
+ * @throws Exception
+ */
+ public void sessionClosed(IoSession session) throws Exception
+ {
+ //todo server just closes session with no warning if auth fails.
+ if (_connection.isClosed())
+ {
+ _logger.info("Session closed called by client");
+ }
+ else
+ {
+ _logger.info("Session closed called with failover state currently " + _failoverState);
+
+ //reconnetablility was introduced here so as not to disturb the client as they have made their intentions
+ // known through the policy settings.
+
+ if ((_failoverState != FailoverState.IN_PROGRESS) && _connection.failoverAllowed())
+ {
+ _logger.info("FAILOVER STARTING");
+ if (_failoverState == FailoverState.NOT_STARTED)
+ {
+ _failoverState = FailoverState.IN_PROGRESS;
+ startFailoverThread();
+ }
+ else
+ {
+ _logger.info("Not starting failover as state currently " + _failoverState);
+ }
+ }
+ else
+ {
+ _logger.info("Failover not allowed by policy.");
+
+ if (_failoverState != FailoverState.IN_PROGRESS)
+ {
+ _logger.info("sessionClose() not allowed to failover");
+ _connection.exceptionReceived(
+ new AMQDisconnectedException("Server closed connection and reconnection " +
+ "not permitted."));
+ }
+ else
+ {
+ _logger.info("sessionClose() failover in progress");
+ }
+ }
+ }
+
+ _logger.info("Protocol Session [" + this + "] closed");
+ }
+
+ /**
+ * See {@link FailoverHandler} to see rationale for separate thread.
+ */
+ private void startFailoverThread()
+ {
+ Thread failoverThread = new Thread(_failoverHandler);
+ failoverThread.setName("Failover");
+ // Do not inherit daemon-ness from current thread as this can be a daemon
+ // thread such as a AnonymousIoService thread.
+ failoverThread.setDaemon(false);
+ failoverThread.start();
+ }
+
+ public void sessionIdle(IoSession session, IdleStatus status) throws Exception
+ {
+ _logger.debug("Protocol Session [" + this + ":" + session + "] idle: " + status);
+ if (IdleStatus.WRITER_IDLE.equals(status))
+ {
+ //write heartbeat frame:
+ _logger.debug("Sent heartbeat");
+ session.write(HeartbeatBody.FRAME);
+ HeartbeatDiagnostics.sent();
+ }
+ else if (IdleStatus.READER_IDLE.equals(status))
+ {
+ //failover:
+ HeartbeatDiagnostics.timeout();
+ _logger.warn("Timed out while waiting for heartbeat from peer.");
+ session.close();
+ }
+ }
+
+ public void exceptionCaught(IoSession session, Throwable cause) throws Exception
+ {
+ if (_failoverState == FailoverState.NOT_STARTED)
+ {
+ //if (!(cause instanceof AMQUndeliveredException) && (!(cause instanceof AMQAuthenticationException)))
+ if (cause instanceof AMQConnectionClosedException)
+ {
+ _logger.info("Exception caught therefore going to attempt failover: " + cause, cause);
+ // this will attemp failover
+
+ sessionClosed(session);
+ }
+ }
+ // we reach this point if failover was attempted and failed therefore we need to let the calling app
+ // know since we cannot recover the situation
+ else if (_failoverState == FailoverState.FAILED)
+ {
+ _logger.error("Exception caught by protocol handler: " + cause, cause);
+ // we notify the state manager of the error in case we have any clients waiting on a state
+ // change. Those "waiters" will be interrupted and can handle the exception
+ AMQException amqe = new AMQException("Protocol handler error: " + cause, cause);
+ propagateExceptionToWaiters(amqe);
+ _connection.exceptionReceived(cause);
+ }
+ }
+
+ /**
+ * There are two cases where we have other threads potentially blocking for events to be handled by this
+ * class. These are for the state manager (waiting for a state change) or a frame listener (waiting for a
+ * particular type of frame to arrive). When an error occurs we need to notify these waiters so that they can
+ * react appropriately.
+ *
+ * @param e the exception to propagate
+ */
+ public void propagateExceptionToWaiters(Exception e)
+ {
+ _stateManager.error(e);
+ final Iterator it = _frameListeners.iterator();
+ while (it.hasNext())
+ {
+ final AMQMethodListener ml = (AMQMethodListener) it.next();
+ ml.error(e);
+ }
+ }
+
+ private static int _messageReceivedCount;
+
+ public void messageReceived(IoSession session, Object message) throws Exception
+ {
+
+ if (_messageReceivedCount++ % 1000 == 0)
+ {
+ _logger.debug("Received " + _messageReceivedCount + " protocol messages");
+ }
+ Iterator it = _frameListeners.iterator();
+ AMQFrame frame = (AMQFrame) message;
+
+ HeartbeatDiagnostics.received(frame.bodyFrame instanceof HeartbeatBody);
+
+ if (frame.bodyFrame instanceof AMQMethodBody)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Method frame received: " + frame);
+ }
+
+ final AMQMethodEvent evt = new AMQMethodEvent(frame.channel, (AMQMethodBody)frame.bodyFrame, _protocolSession);
+ try
+ {
+ boolean wasAnyoneInterested = false;
+ while (it.hasNext())
+ {
+ final AMQMethodListener listener = (AMQMethodListener) it.next();
+ wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested;
+ }
+ if (!wasAnyoneInterested)
+ {
+ throw new AMQException("AMQMethodEvent " + evt + " was not processed by any listener.");
+ }
+ }
+ catch (AMQException e)
+ {
+ it = _frameListeners.iterator();
+ while (it.hasNext())
+ {
+ final AMQMethodListener listener = (AMQMethodListener) it.next();
+ listener.error(e);
+ }
+ exceptionCaught(session, e);
+ }
+ }
+ else if (frame.bodyFrame instanceof ContentHeaderBody)
+ {
+ _protocolSession.messageContentHeaderReceived(frame.channel,
+ (ContentHeaderBody) frame.bodyFrame);
+ }
+ else if (frame.bodyFrame instanceof ContentBody)
+ {
+ _protocolSession.messageContentBodyReceived(frame.channel,
+ (ContentBody) frame.bodyFrame);
+ }
+ else if (frame.bodyFrame instanceof HeartbeatBody)
+ {
+ _logger.debug("Received heartbeat");
+ }
+ _connection.bytesReceived(_protocolSession.getIoSession().getReadBytes());
+ }
+
+ private static int _messagesOut;
+
+ public void messageSent(IoSession session, Object message) throws Exception
+ {
+ if (_messagesOut++ % 1000 == 0)
+ {
+ _logger.debug("Sent " + _messagesOut + " protocol messages");
+ }
+ _connection.bytesSent(session.getWrittenBytes());
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Sent frame " + message);
+ }
+ }
+
+ public void addFrameListener(AMQMethodListener listener)
+ {
+ _frameListeners.add(listener);
+ }
+
+ public void removeFrameListener(AMQMethodListener listener)
+ {
+ _frameListeners.remove(listener);
+ }
+
+ public void attainState(AMQState s) throws AMQException
+ {
+ _stateManager.attainState(s);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session. Equivalent
+ * to calling getProtocolSession().write().
+ *
+ * @param frame the frame to write
+ */
+ public void writeFrame(AMQDataBlock frame)
+ {
+ _protocolSession.writeFrame(frame);
+ }
+
+ public void writeFrame(AMQDataBlock frame, boolean wait)
+ {
+ _protocolSession.writeFrame(frame, wait);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session and waits for
+ * a particular response. Equivalent to calling getProtocolSession().write() then
+ * waiting for the response.
+ *
+ * @param frame
+ * @param listener the blocking listener. Note the calling thread will block.
+ */
+ private AMQMethodEvent writeCommandFrameAndWaitForReply(AMQFrame frame,
+ BlockingMethodFrameListener listener)
+ throws AMQException
+ {
+ _frameListeners.add(listener);
+ _protocolSession.writeFrame(frame);
+ return listener.blockForFrame();
+ // When control resumes before this line, a reply will have been received
+ // that matches the criteria defined in the blocking listener
+ }
+
+ /**
+ * More convenient method to write a frame and wait for it's response.
+ */
+ public AMQMethodEvent syncWrite(AMQFrame frame, Class responseClass) throws AMQException
+ {
+ return writeCommandFrameAndWaitForReply(frame,
+ new SpecificMethodFrameListener(frame.channel, responseClass));
+ }
+
+ /**
+ * Convenience method to register an AMQSession with the protocol handler. Registering
+ * a session with the protocol handler will ensure that messages are delivered to the
+ * consumer(s) on that session.
+ *
+ * @param channelId the channel id of the session
+ * @param session the session instance.
+ */
+ public void addSessionByChannel(int channelId, AMQSession session)
+ {
+ _protocolSession.addSessionByChannel(channelId, session);
+ }
+
+ /**
+ * Convenience method to deregister an AMQSession with the protocol handler.
+ *
+ * @param channelId then channel id of the session
+ */
+ public void removeSessionByChannel(int channelId)
+ {
+ _protocolSession.removeSessionByChannel(channelId);
+ }
+
+ public void closeSession(AMQSession session) throws AMQException
+ {
+ _protocolSession.closeSession(session);
+ }
+
+ public void closeConnection() throws AMQException
+ {
+ _stateManager.changeState(AMQState.CONNECTION_CLOSING);
+
+ final AMQFrame frame = ConnectionCloseBody.createAMQFrame(
+ 0, AMQConstant.REPLY_SUCCESS.getCode(), "JMS client is closing the connection.", 0, 0);
+ syncWrite(frame, ConnectionCloseOkBody.class);
+
+ _protocolSession.closeProtocolSession();
+ }
+
+ /**
+ * @return the number of bytes read from this protocol session
+ */
+ public long getReadBytes()
+ {
+ return _protocolSession.getIoSession().getReadBytes();
+ }
+
+ /**
+ * @return the number of bytes written to this protocol session
+ */
+ public long getWrittenBytes()
+ {
+ return _protocolSession.getIoSession().getWrittenBytes();
+ }
+
+ public void failover(String host, int port)
+ {
+ _failoverHandler.setHost(host);
+ _failoverHandler.setPort(port);
+ // see javadoc for FailoverHandler to see rationale for separate thread
+ startFailoverThread();
+ }
+
+ public void blockUntilNotFailingOver() throws InterruptedException
+ {
+ if (_failoverLatch != null)
+ {
+ _failoverLatch.await();
+ }
+ }
+
+ public String generateQueueName()
+ {
+ return _protocolSession.generateQueueName();
+ }
+
+ public CountDownLatch getFailoverLatch()
+ {
+ return _failoverLatch;
+ }
+
+ public void setFailoverLatch(CountDownLatch failoverLatch)
+ {
+ _failoverLatch = failoverLatch;
+ }
+
+ public AMQConnection getConnection()
+ {
+ return _connection;
+ }
+
+ public AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+ public void setStateManager(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ }
+
+ FailoverState getFailoverState()
+ {
+ return _failoverState;
+ }
+
+ public void setFailoverState(FailoverState failoverState)
+ {
+ _failoverState = failoverState;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java
new file mode 100644
index 0000000000..77685a0222
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java
@@ -0,0 +1,379 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.log4j.Logger;
+import org.apache.mina.common.CloseFuture;
+import org.apache.mina.common.IdleStatus;
+import org.apache.mina.common.IoSession;
+import org.apache.mina.common.WriteFuture;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.ConnectionTuneParameters;
+import org.apache.qpid.client.message.UnexpectedBodyReceivedException;
+import org.apache.qpid.client.message.UnprocessedMessage;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.ProtocolInitiation;
+import org.apache.qpid.framing.ProtocolVersionList;
+
+import javax.jms.JMSException;
+import javax.security.sasl.SaslClient;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Wrapper for protocol session that provides type-safe access to session attributes.
+ *
+ * The underlying protocol session is still available but clients should not
+ * use it to obtain session attributes.
+ */
+public class AMQProtocolSession implements ProtocolVersionList
+{
+ private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class);
+
+ public static final String PROTOCOL_INITIATION_RECEIVED = "ProtocolInitiatiionReceived";
+
+ protected static final String CONNECTION_TUNE_PARAMETERS = "ConnectionTuneParameters";
+
+ private static final String AMQ_CONNECTION = "AMQConnection";
+
+ private static final String SASL_CLIENT = "SASLClient";
+
+ private final IoSession _minaProtocolSession;
+
+ /**
+ * The handler from which this session was created and which is used to handle protocol events.
+ * We send failover events to the handler.
+ */
+ private final AMQProtocolHandler _protocolHandler;
+
+ /**
+ * Maps from the channel id to the AMQSession that it represents.
+ */
+ private ConcurrentMap _channelId2SessionMap = new ConcurrentHashMap();
+
+ private ConcurrentMap _closingChannels = new ConcurrentHashMap();
+
+ /**
+ * Maps from a channel id to an unprocessed message. This is used to tie together the
+ * JmsDeliverBody (which arrives first) with the subsequent content header and content bodies.
+ */
+ private ConcurrentMap _channelId2UnprocessedMsgMap = new ConcurrentHashMap();
+
+ /**
+ * Counter to ensure unique queue names
+ */
+ private int _queueId = 1;
+ private final Object _queueIdLock = new Object();
+
+ public AMQProtocolSession(AMQProtocolHandler protocolHandler, IoSession protocolSession, AMQConnection connection)
+ {
+ _protocolHandler = protocolHandler;
+ _minaProtocolSession = protocolSession;
+ // properties of the connection are made available to the event handlers
+ _minaProtocolSession.setAttribute(AMQ_CONNECTION, connection);
+ }
+
+ public void init()
+ {
+ // start the process of setting up the connection. This is the first place that
+ // data is written to the server.
+ /* Find last protocol version in protocol version list. Make sure last protocol version
+ listed in the build file (build-module.xml) is the latest version which will be used
+ here. */
+ int i = pv.length - 1;
+ _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]));
+ }
+
+ public String getClientID()
+ {
+ try
+ {
+ return getAMQConnection().getClientID();
+ }
+ catch (JMSException e)
+ {
+ // we never throw a JMSException here
+ return null;
+ }
+ }
+
+ public void setClientID(String clientID) throws JMSException
+ {
+ getAMQConnection().setClientID(clientID);
+ }
+
+ public String getVirtualHost()
+ {
+ return getAMQConnection().getVirtualHost();
+ }
+
+ public String getUsername()
+ {
+ return getAMQConnection().getUsername();
+ }
+
+ public String getPassword()
+ {
+ return getAMQConnection().getPassword();
+ }
+
+ public IoSession getIoSession()
+ {
+ return _minaProtocolSession;
+ }
+
+ public SaslClient getSaslClient()
+ {
+ return (SaslClient) _minaProtocolSession.getAttribute(SASL_CLIENT);
+ }
+
+ /**
+ * Store the SASL client currently being used for the authentication handshake
+ * @param client if non-null, stores this in the session. if null clears any existing client
+ * being stored
+ */
+ public void setSaslClient(SaslClient client)
+ {
+ if (client == null)
+ {
+ _minaProtocolSession.removeAttribute(SASL_CLIENT);
+ }
+ else
+ {
+ _minaProtocolSession.setAttribute(SASL_CLIENT, client);
+ }
+ }
+
+ public ConnectionTuneParameters getConnectionTuneParameters()
+ {
+ return (ConnectionTuneParameters) _minaProtocolSession.getAttribute(CONNECTION_TUNE_PARAMETERS);
+ }
+
+ public void setConnectionTuneParameters(ConnectionTuneParameters params)
+ {
+ _minaProtocolSession.setAttribute(CONNECTION_TUNE_PARAMETERS, params);
+ AMQConnection con = getAMQConnection();
+ con.setMaximumChannelCount(params.getChannelMax());
+ con.setMaximumFrameSize(params.getFrameMax());
+ initHeartbeats((int) params.getHeartbeat());
+ }
+
+ /**
+ * Callback invoked from the BasicDeliverMethodHandler when a message has been received.
+ * This is invoked on the MINA dispatcher thread.
+ * @param message
+ * @throws AMQException if this was not expected
+ */
+ public void unprocessedMessageReceived(UnprocessedMessage message) throws AMQException
+ {
+ _channelId2UnprocessedMsgMap.put(message.channelId, message);
+ }
+
+ public void messageContentHeaderReceived(int channelId, ContentHeaderBody contentHeader)
+ throws AMQException
+ {
+ UnprocessedMessage msg = (UnprocessedMessage) _channelId2UnprocessedMsgMap.get(channelId);
+ if (msg == null)
+ {
+ throw new AMQException("Error: received content header without having received a BasicDeliver frame first");
+ }
+ if (msg.contentHeader != null)
+ {
+ throw new AMQException("Error: received duplicate content header or did not receive correct number of content body frames");
+ }
+ msg.contentHeader = contentHeader;
+ if (contentHeader.bodySize == 0)
+ {
+ deliverMessageToAMQSession(channelId, msg);
+ }
+ }
+
+ public void messageContentBodyReceived(int channelId, ContentBody contentBody) throws AMQException
+ {
+ UnprocessedMessage msg = (UnprocessedMessage) _channelId2UnprocessedMsgMap.get(channelId);
+ if (msg == null)
+ {
+ throw new AMQException("Error: received content body without having received a JMSDeliver frame first");
+ }
+ if (msg.contentHeader == null)
+ {
+ _channelId2UnprocessedMsgMap.remove(channelId);
+ throw new AMQException("Error: received content body without having received a ContentHeader frame first");
+ }
+ try
+ {
+ msg.receiveBody(contentBody);
+ }
+ catch (UnexpectedBodyReceivedException e)
+ {
+ _channelId2UnprocessedMsgMap.remove(channelId);
+ throw e;
+ }
+ if (msg.isAllBodyDataReceived())
+ {
+ deliverMessageToAMQSession(channelId, msg);
+ }
+ }
+
+ /**
+ * Deliver a message to the appropriate session, removing the unprocessed message
+ * from our map
+ * @param channelId the channel id the message should be delivered to
+ * @param msg the message
+ */
+ private void deliverMessageToAMQSession(int channelId, UnprocessedMessage msg)
+ {
+ AMQSession session = (AMQSession) _channelId2SessionMap.get(channelId);
+ session.messageReceived(msg);
+ _channelId2UnprocessedMsgMap.remove(channelId);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session. Equivalent
+ * to calling getProtocolSession().write().
+ *
+ * @param frame the frame to write
+ */
+ public void writeFrame(AMQDataBlock frame)
+ {
+ _minaProtocolSession.write(frame);
+ }
+
+ public void writeFrame(AMQDataBlock frame, boolean wait)
+ {
+ WriteFuture f =_minaProtocolSession.write(frame);
+ if(wait)
+ {
+ f.join();
+ }
+ }
+
+ public void addSessionByChannel(int channelId, AMQSession session)
+ {
+ if (channelId <=0)
+ {
+ throw new IllegalArgumentException("Attempt to register a session with a channel id <= zero");
+ }
+ if (session == null)
+ {
+ throw new IllegalArgumentException("Attempt to register a null session");
+ }
+ _logger.debug("Add session with channel id " + channelId);
+ _channelId2SessionMap.put(channelId, session);
+ }
+
+ public void removeSessionByChannel(int channelId)
+ {
+ if (channelId <=0)
+ {
+ throw new IllegalArgumentException("Attempt to deregister a session with a channel id <= zero");
+ }
+ _logger.debug("Removing session with channelId " + channelId);
+ _channelId2SessionMap.remove(channelId);
+ }
+
+ /**
+ * Starts the process of closing a session
+ * @param session the AMQSession being closed
+ */
+ public void closeSession(AMQSession session)
+ {
+ _logger.debug("closeSession called on protocol session for session " + session.getChannelId());
+ final int channelId = session.getChannelId();
+ if (channelId <=0)
+ {
+ throw new IllegalArgumentException("Attempt to close a channel with id < 0");
+ }
+ // we need to know when a channel is closing so that we can respond
+ // with a channel.close frame when we receive any other type of frame
+ // on that channel
+ _closingChannels.putIfAbsent(channelId, session);
+ }
+
+ /**
+ * Called from the ChannelClose handler when a channel close frame is received.
+ * This method decides whether this is a response or an initiation. The latter
+ * case causes the AMQSession to be closed and an exception to be thrown if
+ * appropriate.
+ * @param channelId the id of the channel (session)
+ * @return true if the client must respond to the server, i.e. if the server
+ * initiated the channel close, false if the channel close is just the server
+ * responding to the client's earlier request to close the channel.
+ */
+ public boolean channelClosed(int channelId, int code, String text)
+ {
+ final Integer chId = channelId;
+ // if this is not a response to an earlier request to close the channel
+ if (_closingChannels.remove(chId) == null)
+ {
+ final AMQSession session = (AMQSession) _channelId2SessionMap.get(chId);
+ session.closed(new AMQException(_logger, code, text));
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public AMQConnection getAMQConnection()
+ {
+ return (AMQConnection) _minaProtocolSession.getAttribute(AMQ_CONNECTION);
+ }
+
+ public void closeProtocolSession()
+ {
+ _logger.debug("Closing protocol session");
+ final CloseFuture future = _minaProtocolSession.close();
+ future.join();
+ }
+
+ public void failover(String host, int port)
+ {
+ _protocolHandler.failover(host, port);
+ }
+
+ String generateQueueName()
+ {
+ int id;
+ synchronized(_queueIdLock)
+ {
+ id = _queueId++;
+ }
+ //todo remove '/' and ':' from local Address as this doesn't conform to spec.
+ return "tmp_" + _minaProtocolSession.getLocalAddress() + "_" + id;
+ }
+
+ /**
+ *
+ * @param delay delay in seconds (not ms)
+ */
+ void initHeartbeats(int delay)
+ {
+ if (delay > 0)
+ {
+ _minaProtocolSession.setIdleTime(IdleStatus.WRITER_IDLE, delay);
+ _minaProtocolSession.setIdleTime(IdleStatus.READER_IDLE, HeartbeatConfig.CONFIG.getTimeout(delay));
+ HeartbeatDiagnostics.init(delay, HeartbeatConfig.CONFIG.getTimeout(delay));
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java b/java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java
new file mode 100644
index 0000000000..a7e3ebdd14
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java
@@ -0,0 +1,133 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+
+public abstract class BlockingMethodFrameListener implements AMQMethodListener
+{
+ private volatile boolean _ready = false;
+
+ public abstract boolean processMethod(int channelId, AMQMethodBody frame) throws AMQException;
+
+ private final Object _lock = new Object();
+
+ /**
+ * This is set if there is an exception thrown from processCommandFrame and the
+ * exception is rethrown to the caller of blockForFrame()
+ */
+ private volatile Exception _error;
+
+ protected int _channelId;
+
+ protected AMQMethodEvent _doneEvt = null;
+
+ public BlockingMethodFrameListener(int channelId)
+ {
+ _channelId = channelId;
+ }
+
+ /**
+ * This method is called by the MINA dispatching thread. Note that it could
+ * be called before blockForFrame() has been called.
+ * @param evt the frame event
+ * @return true if the listener has dealt with this frame
+ * @throws AMQException
+ */
+ public boolean methodReceived(AMQMethodEvent evt) throws AMQException
+ {
+ AMQMethodBody method = evt.getMethod();
+
+ try
+ {
+ boolean ready = (evt.getChannelId() == _channelId) && processMethod(evt.getChannelId(), method);
+ if (ready)
+ {
+ // we only update the flag from inside the synchronized block
+ // so that the blockForFrame method cannot "miss" an update - it
+ // will only ever read the flag from within the synchronized block
+ synchronized (_lock)
+ {
+ _doneEvt = evt;
+ _ready = ready;
+ _lock.notify();
+ }
+ }
+ return ready;
+ }
+ catch (AMQException e)
+ {
+ error(e);
+ // we rethrow the error here, and the code in the frame dispatcher will go round
+ // each listener informing them that an exception has been thrown
+ throw e;
+ }
+ }
+
+ /**
+ * This method is called by the thread that wants to wait for a frame.
+ */
+ public AMQMethodEvent blockForFrame() throws AMQException
+ {
+ synchronized (_lock)
+ {
+ while (!_ready)
+ {
+ try
+ {
+ _lock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ // IGNORE
+ }
+ }
+ }
+ if (_error != null)
+ {
+ if (_error instanceof AMQException)
+ {
+ throw (AMQException)_error;
+ }
+ else
+ {
+ throw new AMQException("Woken up due to exception", _error); // FIXME: This will wrap FailoverException and prevent it being caught.
+ }
+ }
+
+ return _doneEvt;
+ }
+
+ /**
+ * This is a callback, called by the MINA dispatcher thread only. It is also called from within this
+ * class to avoid code repetition but again is only called by the MINA dispatcher thread.
+ * @param e
+ */
+ public void error(Exception e)
+ {
+ // set the error so that the thread that is blocking (against blockForFrame())
+ // can pick up the exception and rethrow to the caller
+ _error = e;
+ synchronized (_lock)
+ {
+ _ready = true;
+ _lock.notify();
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java b/java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java
new file mode 100644
index 0000000000..d6240b919a
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.log4j.Logger;
+
+class HeartbeatConfig
+{
+ private static final Logger _logger = Logger.getLogger(HeartbeatConfig.class);
+ static final HeartbeatConfig CONFIG = new HeartbeatConfig();
+
+ /**
+ * The factor used to get the timeout from the delay between heartbeats.
+ */
+ private float timeoutFactor = 2;
+
+ HeartbeatConfig()
+ {
+ String property = System.getProperty("amqj.heartbeat.timeoutFactor");
+ if(property != null)
+ {
+ try
+ {
+ timeoutFactor = Float.parseFloat(property);
+ }
+ catch(NumberFormatException e)
+ {
+ _logger.warn("Invalid timeout factor (amqj.heartbeat.timeoutFactor): " + property);
+ }
+ }
+ }
+
+ float getTimeoutFactor()
+ {
+ return timeoutFactor;
+ }
+
+ int getTimeout(int writeDelay)
+ {
+ return (int) (timeoutFactor * writeDelay);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java b/java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java
new file mode 100644
index 0000000000..07a9213402
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java
@@ -0,0 +1,118 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+class HeartbeatDiagnostics
+{
+ private static final Diagnostics _impl = init();
+
+ private static Diagnostics init()
+ {
+ return Boolean.getBoolean("amqj.heartbeat.diagnostics") ? new On() : new Off();
+ }
+
+ static void sent()
+ {
+ _impl.sent();
+ }
+
+ static void timeout()
+ {
+ _impl.timeout();
+ }
+
+ static void received(boolean heartbeat)
+ {
+ _impl.received(heartbeat);
+ }
+
+ static void init(int delay, int timeout)
+ {
+ _impl.init(delay, timeout);
+ }
+
+ private static interface Diagnostics
+ {
+ void sent();
+ void timeout();
+ void received(boolean heartbeat);
+ void init(int delay, int timeout);
+ }
+
+ private static class On implements Diagnostics
+ {
+ private final String[] messages = new String[50];
+ private int i;
+
+ private void save(String msg)
+ {
+ messages[i++] = msg;
+ if(i >= messages.length){
+ i = 0;//i.e. a circular buffer
+ }
+ }
+
+ public void sent()
+ {
+ save(System.currentTimeMillis() + ": sent heartbeat");
+ }
+
+ public void timeout()
+ {
+ for(int i = 0; i < messages.length; i++)
+ {
+ if(messages[i] != null)
+ {
+ System.out.println(messages[i]);
+ }
+ }
+ System.out.println(System.currentTimeMillis() + ": timed out");
+ }
+
+ public void received(boolean heartbeat)
+ {
+ save(System.currentTimeMillis() + ": received " + (heartbeat ? "heartbeat" : "data"));
+ }
+
+ public void init(int delay, int timeout)
+ {
+ System.out.println(System.currentTimeMillis() + ": initialised delay=" + delay + ", timeout=" + timeout);
+ }
+ }
+
+ private static class Off implements Diagnostics
+ {
+ public void sent()
+ {
+
+ }
+ public void timeout()
+ {
+
+ }
+ public void received(boolean heartbeat)
+ {
+
+ }
+
+ public void init(int delay, int timeout)
+ {
+
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java b/java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java
new file mode 100644
index 0000000000..88cbd32764
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.protocol;
+
+import org.apache.mina.common.IoFilterAdapter;
+import org.apache.mina.common.IoSession;
+import org.apache.log4j.Logger;
+
+/**
+ * A MINA filter that monitors the numbers of messages pending to be sent by MINA. It outputs a message
+ * when a threshold has been exceeded, and has a frequency configuration so that messages are not output
+ * too often.
+ *
+ */
+public class ProtocolBufferMonitorFilter extends IoFilterAdapter
+{
+ private static final Logger _logger = Logger.getLogger(ProtocolBufferMonitorFilter.class);
+
+ public static long DEFAULT_FREQUENCY = 5000;
+
+ public static int DEFAULT_THRESHOLD = 3000;
+
+ private int _bufferedMessages = 0;
+
+ private int _threshold;
+
+ private long _lastMessageOutputTime;
+
+ private long _outputFrequencyInMillis;
+
+ public ProtocolBufferMonitorFilter()
+ {
+ _threshold = DEFAULT_THRESHOLD;
+ _outputFrequencyInMillis = DEFAULT_FREQUENCY;
+ }
+
+ public ProtocolBufferMonitorFilter(int threshold, long frequency)
+ {
+ _threshold = threshold;
+ _outputFrequencyInMillis = frequency;
+ }
+
+ public void messageReceived( NextFilter nextFilter, IoSession session, Object message ) throws Exception
+ {
+ _bufferedMessages++;
+ if (_bufferedMessages > _threshold)
+ {
+ long now = System.currentTimeMillis();
+ if ((now - _lastMessageOutputTime) > _outputFrequencyInMillis)
+ {
+ _logger.warn("Protocol message buffer exceeded threshold of " + _threshold + ". Current backlog: " +
+ _bufferedMessages);
+ _lastMessageOutputTime = now;
+ }
+ }
+
+ nextFilter.messageReceived(session, message);
+ }
+
+ public void messageSent( NextFilter nextFilter, IoSession session, Object message ) throws Exception
+ {
+ _bufferedMessages--;
+ nextFilter.messageSent(session, message);
+ }
+
+ public int getBufferedMessages()
+ {
+ return _bufferedMessages;
+ }
+
+ public int getThreshold()
+ {
+ return _threshold;
+ }
+
+ public void setThreshold(int threshold)
+ {
+ _threshold = threshold;
+ }
+
+ public long getOutputFrequencyInMillis()
+ {
+ return _outputFrequencyInMillis;
+ }
+
+ public void setOutputFrequencyInMillis(long outputFrequencyInMillis)
+ {
+ _outputFrequencyInMillis = outputFrequencyInMillis;
+ }
+
+ public long getLastMessageOutputTime()
+ {
+ return _lastMessageOutputTime;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java b/java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java
new file mode 100644
index 0000000000..43e1e6ab70
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java
@@ -0,0 +1,27 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security;
+
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+
+import javax.security.auth.callback.CallbackHandler;
+
+public interface AMQCallbackHandler extends CallbackHandler
+{
+ void initialise(AMQProtocolSession protocolSession);
+}
diff --git a/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java
new file mode 100644
index 0000000000..8bfa0c32c4
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java
@@ -0,0 +1,154 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security;
+
+import org.apache.log4j.Logger;
+
+import java.io.*;
+import java.util.*;
+
+public class CallbackHandlerRegistry
+{
+ private static final String FILE_PROPERTY = "amq.callbackhandler.properties";
+
+ private static final Logger _logger = Logger.getLogger(CallbackHandlerRegistry.class);
+
+ private static CallbackHandlerRegistry _instance = new CallbackHandlerRegistry();
+
+ private Map _mechanismToHandlerClassMap = new HashMap();
+
+ private String _mechanisms;
+
+ public static CallbackHandlerRegistry getInstance()
+ {
+ return _instance;
+ }
+
+ public Class getCallbackHandlerClass(String mechanism)
+ {
+ return (Class) _mechanismToHandlerClassMap.get(mechanism);
+ }
+
+ public String getMechanisms()
+ {
+ return _mechanisms;
+ }
+
+ private CallbackHandlerRegistry()
+ {
+ // first we register any Sasl client factories
+ DynamicSaslRegistrar.registerSaslProviders();
+
+ InputStream is = openPropertiesInputStream();
+ try
+ {
+ Properties props = new Properties();
+ props.load(is);
+ parseProperties(props);
+ _logger.info("Available SASL mechanisms: " + _mechanisms);
+ }
+ catch (IOException e)
+ {
+ _logger.error("Error reading properties: " + e, e);
+ }
+ finally
+ {
+ if (is != null)
+ {
+ try
+ {
+ is.close();
+
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to close properties stream: " + e, e);
+ }
+ }
+ }
+ }
+
+ private InputStream openPropertiesInputStream()
+ {
+ String filename = System.getProperty(FILE_PROPERTY);
+ boolean useDefault = true;
+ InputStream is = null;
+ if (filename != null)
+ {
+ try
+ {
+ is = new BufferedInputStream(new FileInputStream(new File(filename)));
+ useDefault = false;
+ }
+ catch (FileNotFoundException e)
+ {
+ _logger.error("Unable to read from file " + filename + ": " + e, e);
+ }
+ }
+
+ if (useDefault)
+ {
+ is = CallbackHandlerRegistry.class.getResourceAsStream("CallbackHandlerRegistry.properties");
+ }
+
+ return is;
+ }
+
+ private void parseProperties(Properties props)
+ {
+ Enumeration e = props.propertyNames();
+ while (e.hasMoreElements())
+ {
+ String propertyName = (String) e.nextElement();
+ int period = propertyName.indexOf(".");
+ if (period < 0)
+ {
+ _logger.warn("Unable to parse property " + propertyName + " when configuring SASL providers");
+ continue;
+ }
+ String mechanism = propertyName.substring(period + 1);
+ String className = props.getProperty(propertyName);
+ Class clazz = null;
+ try
+ {
+ clazz = Class.forName(className);
+ if (!AMQCallbackHandler.class.isAssignableFrom(clazz))
+ {
+ _logger.warn("SASL provider " + clazz + " does not implement " + AMQCallbackHandler.class +
+ ". Skipping");
+ continue;
+ }
+ _mechanismToHandlerClassMap.put(mechanism, clazz);
+ if (_mechanisms == null)
+ {
+ _mechanisms = mechanism;
+ }
+ else
+ {
+ // one time cost
+ _mechanisms = _mechanisms + " " + mechanism;
+ }
+ }
+ catch (ClassNotFoundException ex)
+ {
+ _logger.warn("Unable to load class " + className + ". Skipping that SASL provider");
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
new file mode 100644
index 0000000000..357f9a1199
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
@@ -0,0 +1,2 @@
+CallbackHandler.CRAM-MD5=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
+CallbackHandler.PLAIN=org.apache.qpid.client.security.UsernamePasswordCallbackHandler \ No newline at end of file
diff --git a/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java
new file mode 100644
index 0000000000..3d81ad505a
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java
@@ -0,0 +1,125 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security;
+
+import org.apache.log4j.Logger;
+
+import javax.security.sasl.SaslClientFactory;
+import java.io.*;
+import java.util.Properties;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.TreeMap;
+import java.security.Security;
+
+public class DynamicSaslRegistrar
+{
+ private static final String FILE_PROPERTY = "amq.dynamicsaslregistrar.properties";
+
+ private static final Logger _logger = Logger.getLogger(DynamicSaslRegistrar.class);
+
+ public static void registerSaslProviders()
+ {
+ InputStream is = openPropertiesInputStream();
+ try
+ {
+ Properties props = new Properties();
+ props.load(is);
+ Map<String, Class<? extends SaslClientFactory>> factories = parseProperties(props);
+ if (factories.size() > 0)
+ {
+ Security.addProvider(new JCAProvider(factories));
+ _logger.debug("Dynamic SASL provider added as a security provider");
+ }
+ }
+ catch (IOException e)
+ {
+ _logger.error("Error reading properties: " + e, e);
+ }
+ finally
+ {
+ if (is != null)
+ {
+ try
+ {
+ is.close();
+
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to close properties stream: " + e, e);
+ }
+ }
+ }
+ }
+
+ private static InputStream openPropertiesInputStream()
+ {
+ String filename = System.getProperty(FILE_PROPERTY);
+ boolean useDefault = true;
+ InputStream is = null;
+ if (filename != null)
+ {
+ try
+ {
+ is = new BufferedInputStream(new FileInputStream(new File(filename)));
+ useDefault = false;
+ }
+ catch (FileNotFoundException e)
+ {
+ _logger.error("Unable to read from file " + filename + ": " + e, e);
+ }
+ }
+
+ if (useDefault)
+ {
+ is = CallbackHandlerRegistry.class.getResourceAsStream("DynamicSaslRegistrar.properties");
+ }
+
+ return is;
+ }
+
+ private static Map<String, Class<? extends SaslClientFactory>> parseProperties(Properties props)
+ {
+ Enumeration e = props.propertyNames();
+ TreeMap<String, Class<? extends SaslClientFactory>> factoriesToRegister =
+ new TreeMap<String, Class<? extends SaslClientFactory>>();
+ while (e.hasMoreElements())
+ {
+ String mechanism = (String) e.nextElement();
+ String className = props.getProperty(mechanism);
+ try
+ {
+ Class<?> clazz = Class.forName(className);
+ if (!(SaslClientFactory.class.isAssignableFrom(clazz)))
+ {
+ _logger.error("Class " + clazz + " does not implement " + SaslClientFactory.class + " - skipping");
+ continue;
+ }
+ factoriesToRegister.put(mechanism, (Class<? extends SaslClientFactory>) clazz);
+ }
+ catch (Exception ex)
+ {
+ _logger.error("Error instantiating SaslClientFactory calss " + className + " - skipping");
+ }
+ }
+ return factoriesToRegister;
+ }
+
+
+}
diff --git a/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
new file mode 100644
index 0000000000..b853d86d65
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
@@ -0,0 +1 @@
+AMQPLAIN=org.apache.qpid.client.security.amqplain.AmqPlainSaslClientFactory \ No newline at end of file
diff --git a/java/client/src/org/apache/qpid/client/security/JCAProvider.java b/java/client/src/org/apache/qpid/client/security/JCAProvider.java
new file mode 100644
index 0000000000..4a14063793
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/JCAProvider.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security;
+
+import javax.security.sasl.SaslClientFactory;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Map;
+
+public class JCAProvider extends Provider
+{
+ public JCAProvider(Map<String, Class<? extends SaslClientFactory>> providerMap)
+ {
+ super("AMQSASLProvider", 1.0, "A JCA provider that registers all " +
+ "AMQ SASL providers that want to be registered");
+ register(providerMap);
+ Security.addProvider(this);
+ }
+
+ private void register(Map<String, Class<? extends SaslClientFactory>> providerMap)
+ {
+ for (Map.Entry<String, Class<? extends SaslClientFactory>> me :
+ providerMap.entrySet())
+ {
+ put("SaslClientFactory." + me.getKey(), me.getValue().getName());
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java b/java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java
new file mode 100644
index 0000000000..201cc04726
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security;
+
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+
+import javax.security.auth.callback.*;
+import java.io.IOException;
+
+public class UsernamePasswordCallbackHandler implements AMQCallbackHandler
+{
+ private AMQProtocolSession _protocolSession;
+
+ public void initialise(AMQProtocolSession protocolSession)
+ {
+ _protocolSession = protocolSession;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+ {
+ for (int i = 0; i < callbacks.length; i++)
+ {
+ Callback cb = callbacks[i];
+ if (cb instanceof NameCallback)
+ {
+ ((NameCallback)cb).setName(_protocolSession.getUsername());
+ }
+ else if (cb instanceof PasswordCallback)
+ {
+ ((PasswordCallback)cb).setPassword(_protocolSession.getPassword().toCharArray());
+ }
+ else
+ {
+ throw new UnsupportedCallbackException(cb);
+ }
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java
new file mode 100644
index 0000000000..d564e82ec3
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java
@@ -0,0 +1,101 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security.amqplain;
+
+import org.apache.qpid.framing.FieldTable;
+
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.Callback;
+
+/**
+ * Implements the "AMQPlain" authentication protocol that uses FieldTables to send username and pwd.
+ *
+ */
+public class AmqPlainSaslClient implements SaslClient
+{
+ /**
+ * The name of this mechanism
+ */
+ public static final String MECHANISM = "AMQPLAIN";
+
+ private CallbackHandler _cbh;
+
+ public AmqPlainSaslClient(CallbackHandler cbh)
+ {
+ _cbh = cbh;
+ }
+
+ public String getMechanismName()
+ {
+ return "AMQPLAIN";
+ }
+
+ public boolean hasInitialResponse()
+ {
+ return true;
+ }
+
+ public byte[] evaluateChallenge(byte[] challenge) throws SaslException
+ {
+ // we do not care about the prompt or the default name
+ NameCallback nameCallback = new NameCallback("prompt", "defaultName");
+ PasswordCallback pwdCallback = new PasswordCallback("prompt", false);
+ Callback[] callbacks = new Callback[]{nameCallback, pwdCallback};
+ try
+ {
+ _cbh.handle(callbacks);
+ }
+ catch (Exception e)
+ {
+ throw new SaslException("Error handling SASL callbacks: " + e, e);
+ }
+ FieldTable table = new FieldTable();
+ table.put("LOGIN", nameCallback.getName());
+ table.put("PASSWORD", pwdCallback.getPassword());
+ return table.getDataAsBytes();
+ }
+
+ public boolean isComplete()
+ {
+ return true;
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Not supported");
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Not supported");
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ _cbh = null;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java
new file mode 100644
index 0000000000..b4cc34dd03
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.security.amqplain;
+
+import javax.security.sasl.SaslClientFactory;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.Sasl;
+import javax.security.auth.callback.CallbackHandler;
+import java.util.Map;
+
+public class AmqPlainSaslClientFactory implements SaslClientFactory
+{
+ public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException
+ {
+ for (int i = 0; i < mechanisms.length; i++)
+ {
+ if (mechanisms[i].equals(AmqPlainSaslClient.MECHANISM))
+ {
+ if (cbh == null)
+ {
+ throw new SaslException("CallbackHandler must not be null");
+ }
+ return new AmqPlainSaslClient(cbh);
+ }
+ }
+ return null;
+ }
+
+ public String[] getMechanismNames(Map props)
+ {
+ if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) ||
+ props.containsKey(Sasl.POLICY_NODICTIONARY) ||
+ props.containsKey(Sasl.POLICY_NOACTIVE))
+ {
+ // returned array must be non null according to interface documentation
+ return new String[0];
+ }
+ else
+ {
+ return new String[]{AmqPlainSaslClient.MECHANISM};
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/state/AMQState.java b/java/client/src/org/apache/qpid/client/state/AMQState.java
new file mode 100644
index 0000000000..02a391ee71
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/AMQState.java
@@ -0,0 +1,53 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+/**
+ * States used in the AMQ protocol. Used by the finite state machine to determine
+ * valid responses.
+ */
+public class AMQState
+{
+ private final int _id;
+
+ private final String _name;
+
+ private AMQState(int id, String name)
+ {
+ _id = id;
+ _name = name;
+ }
+
+ public String toString()
+ {
+ return "AMQState: id = " + _id + " name: " + _name;
+ }
+
+ public static final AMQState CONNECTION_NOT_STARTED = new AMQState(1, "CONNECTION_NOT_STARTED");
+
+ public static final AMQState CONNECTION_NOT_TUNED = new AMQState(2, "CONNECTION_NOT_TUNED");
+
+ public static final AMQState CONNECTION_NOT_OPENED = new AMQState(3, "CONNECTION_NOT_OPENED");
+
+ public static final AMQState CONNECTION_OPEN = new AMQState(4, "CONNECTION_OPEN");
+
+ public static final AMQState CONNECTION_CLOSING = new AMQState(5, "CONNECTION_CLOSING");
+
+ public static final AMQState CONNECTION_CLOSED = new AMQState(6, "CONNECTION_CLOSED");
+
+}
diff --git a/java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java b/java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java
new file mode 100644
index 0000000000..91955e37fd
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+/**
+ * An event that is fired when the protocol state has changed.
+ *
+ */
+public class AMQStateChangedEvent
+{
+ private final AMQState _oldState;
+
+ private final AMQState _newState;
+
+ public AMQStateChangedEvent(AMQState oldState, AMQState newState)
+ {
+ _oldState = oldState;
+ _newState = newState;
+ }
+
+ public AMQState getOldState()
+ {
+ return _oldState;
+ }
+
+ public AMQState getNewState()
+ {
+ return _newState;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/state/AMQStateListener.java b/java/client/src/org/apache/qpid/client/state/AMQStateListener.java
new file mode 100644
index 0000000000..7f9698dc92
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/AMQStateListener.java
@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+public interface AMQStateListener
+{
+ void stateChanged(AMQStateChangedEvent evt);
+}
diff --git a/java/client/src/org/apache/qpid/client/state/AMQStateManager.java b/java/client/src/org/apache/qpid/client/state/AMQStateManager.java
new file mode 100644
index 0000000000..cdabcf1df4
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/AMQStateManager.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.handler.*;
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.client.protocol.AMQMethodListener;
+import org.apache.qpid.framing.*;
+import org.apache.log4j.Logger;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * The state manager is responsible for managing the state of the protocol session.
+ * <p/>
+ * For each AMQProtocolHandler there is a separate state manager.
+ */
+public class AMQStateManager implements AMQMethodListener
+{
+ private static final Logger _logger = Logger.getLogger(AMQStateManager.class);
+
+ /**
+ * The current state
+ */
+ private AMQState _currentState;
+
+ /**
+ * Maps from an AMQState instance to a Map from Class to StateTransitionHandler.
+ * The class must be a subclass of AMQFrame.
+ */
+ private final Map _state2HandlersMap = new HashMap();
+
+ private final CopyOnWriteArraySet _stateListeners = new CopyOnWriteArraySet();
+
+ public AMQStateManager()
+ {
+ this(AMQState.CONNECTION_NOT_STARTED, true);
+ }
+
+ protected AMQStateManager(AMQState state, boolean register)
+ {
+ _currentState = state;
+ if(register)
+ {
+ registerListeners();
+ }
+ }
+
+ protected void registerListeners()
+ {
+ Map frame2handlerMap = new HashMap();
+
+ // we need to register a map for the null (i.e. all state) handlers otherwise you get
+ // a stack overflow in the handler searching code when you present it with a frame for which
+ // no handlers are registered
+ //
+ _state2HandlersMap.put(null, frame2handlerMap);
+
+ frame2handlerMap = new HashMap();
+ frame2handlerMap.put(ConnectionStartBody.class, ConnectionStartMethodHandler.getInstance());
+ frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap);
+
+ frame2handlerMap = new HashMap();
+ frame2handlerMap.put(ConnectionTuneBody.class, ConnectionTuneMethodHandler.getInstance());
+ frame2handlerMap.put(ConnectionSecureBody.class, ConnectionSecureMethodHandler.getInstance());
+ frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap);
+
+ frame2handlerMap = new HashMap();
+ frame2handlerMap.put(ConnectionOpenOkBody.class, ConnectionOpenOkMethodHandler.getInstance());
+ frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap);
+
+ //
+ // ConnectionOpen handlers
+ //
+ frame2handlerMap = new HashMap();
+ frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseMethodHandler.getInstance());
+ frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkMethodHandler.getInstance());
+ frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance());
+ frame2handlerMap.put(BasicDeliverBody.class, BasicDeliverMethodHandler.getInstance());
+ frame2handlerMap.put(BasicReturnBody.class, BasicReturnMethodHandler.getInstance());
+ frame2handlerMap.put(ChannelFlowOkBody.class, ChannelFlowOkMethodHandler.getInstance());
+ _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap);
+ }
+
+ public AMQState getCurrentState()
+ {
+ return _currentState;
+ }
+
+ public void changeState(AMQState newState) throws AMQException
+ {
+ _logger.debug("State changing to " + newState + " from old state " + _currentState);
+ final AMQState oldState = _currentState;
+ _currentState = newState;
+
+ synchronized (_stateListeners)
+ {
+ final Iterator it = _stateListeners.iterator();
+ while (it.hasNext())
+ {
+ final StateListener l = (StateListener) it.next();
+ l.stateChanged(oldState, newState);
+ }
+ }
+ }
+
+ public void error(Exception e)
+ {
+ _logger.debug("State manager receive error notification: " + e);
+ synchronized (_stateListeners)
+ {
+ final Iterator it = _stateListeners.iterator();
+ while (it.hasNext())
+ {
+ final StateListener l = (StateListener) it.next();
+ l.error(e);
+ }
+ }
+ }
+
+ public boolean methodReceived(AMQMethodEvent evt) throws AMQException
+ {
+ StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod());
+ if (handler != null)
+ {
+ handler.methodReceived(this, evt);
+ return true;
+ }
+ return false;
+ }
+
+ protected StateAwareMethodListener findStateTransitionHandler(AMQState currentState,
+ AMQMethodBody frame)
+ throws IllegalStateTransitionException
+ {
+ final Class clazz = frame.getClass();
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Looking for state transition handler for frame " + clazz);
+ }
+ final Map classToHandlerMap = (Map) _state2HandlersMap.get(currentState);
+
+ if (classToHandlerMap == null)
+ {
+ // if no specialised per state handler is registered look for a
+ // handler registered for "all" states
+ return findStateTransitionHandler(null, frame);
+ }
+ final StateAwareMethodListener handler = (StateAwareMethodListener) classToHandlerMap.get(clazz);
+ if (handler == null)
+ {
+ if (currentState == null)
+ {
+ _logger.debug("No state transition handler defined for receiving frame " + frame);
+ return null;
+ }
+ else
+ {
+ // if no specialised per state handler is registered look for a
+ // handler registered for "all" states
+ return findStateTransitionHandler(null, frame);
+ }
+ }
+ else
+ {
+ return handler;
+ }
+ }
+
+ public void addStateListener(StateListener listener)
+ {
+ _logger.debug("Adding state listener");
+ _stateListeners.add(listener);
+ }
+
+ public void removeStateListener(StateListener listener)
+ {
+ _stateListeners.remove(listener);
+ }
+
+ public void attainState(AMQState s) throws AMQException
+ {
+ boolean needToWait = false;
+ StateWaiter sw = null;
+ synchronized (_stateListeners)
+ {
+ if (_currentState != s)
+ {
+ _logger.debug("Adding state wait to reach state " + s);
+ sw = new StateWaiter(s);
+ addStateListener(sw);
+ // we use a boolean since we must release the lock before starting to wait
+ needToWait = true;
+ }
+ }
+ if (needToWait)
+ {
+ sw.waituntilStateHasChanged();
+ }
+ // at this point the state will have changed.
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java b/java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java
new file mode 100644
index 0000000000..58bb38352b
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+import org.apache.qpid.AMQException;
+
+public class IllegalStateTransitionException extends AMQException
+{
+ private AMQState _originalState;
+
+ private Class _frame;
+
+ public IllegalStateTransitionException(AMQState originalState, Class frame)
+ {
+ super("No valid state transition defined for receiving frame " + frame +
+ " from state " + originalState);
+ _originalState = originalState;
+ _frame = frame;
+ }
+
+ public AMQState getOriginalState()
+ {
+ return _originalState;
+ }
+
+ public Class getFrameClass()
+ {
+ return _frame;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java b/java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java
new file mode 100644
index 0000000000..6fed4f2df6
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+import org.apache.qpid.client.protocol.AMQMethodEvent;
+import org.apache.qpid.AMQException;
+
+/**
+ * A frame listener that is informed of the protocl state when invoked and has
+ * the opportunity to update state.
+ *
+ */
+public interface StateAwareMethodListener
+{
+ void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException;
+}
diff --git a/java/client/src/org/apache/qpid/client/state/StateListener.java b/java/client/src/org/apache/qpid/client/state/StateListener.java
new file mode 100644
index 0000000000..0a5bee5106
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/StateListener.java
@@ -0,0 +1,27 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+import org.apache.qpid.AMQException;
+
+public interface StateListener
+{
+ void stateChanged(AMQState oldState, AMQState newState) throws AMQException;
+
+ void error(Throwable t);
+}
diff --git a/java/client/src/org/apache/qpid/client/state/StateWaiter.java b/java/client/src/org/apache/qpid/client/state/StateWaiter.java
new file mode 100644
index 0000000000..53ce452b78
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/StateWaiter.java
@@ -0,0 +1,114 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+
+/**
+ * Waits for a particular state to be reached.
+ *
+ */
+public class StateWaiter implements StateListener
+{
+ private static final Logger _logger = Logger.getLogger(StateWaiter.class);
+
+ private final AMQState _state;
+
+ private volatile boolean _newStateAchieved;
+
+ private volatile Throwable _throwable;
+
+ private final Object _monitor = new Object();
+
+ public StateWaiter(AMQState state)
+ {
+ _state = state;
+ }
+
+ public void waituntilStateHasChanged() throws AMQException
+ {
+ synchronized (_monitor)
+ {
+ //
+ // The guard is required in case we are woken up by a spurious
+ // notify().
+ //
+ while (!_newStateAchieved && _throwable == null)
+ {
+ try
+ {
+ _logger.debug("State " + _state + " not achieved so waiting...");
+ _monitor.wait();
+ }
+ catch (InterruptedException e)
+ {
+ _logger.debug("Interrupted exception caught while waiting: " + e, e);
+ }
+ }
+ }
+
+ if (_throwable != null)
+ {
+ _logger.debug("Throwable reached state waiter: " + _throwable);
+ if (_throwable instanceof AMQException)
+ {
+ throw (AMQException) _throwable;
+ }
+ else
+ {
+ throw new AMQException("Error: " + _throwable, _throwable); // FIXME: this will wrap FailoverException in throwable which will prevent it being caught.
+ }
+ }
+ }
+
+ public void stateChanged(AMQState oldState, AMQState newState)
+ {
+ synchronized (_monitor)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("stateChanged called");
+ }
+ if (_state == newState)
+ {
+ _newStateAchieved = true;
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("New state reached so notifying monitor");
+ }
+ _monitor.notifyAll();
+ }
+ }
+ }
+
+ public void error(Throwable t)
+ {
+ synchronized (_monitor)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("exceptionThrown called");
+ }
+
+ _throwable = t;
+ _monitor.notifyAll();
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java b/java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java
new file mode 100644
index 0000000000..b2d7d777d8
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.state.listener;
+
+import org.apache.qpid.client.protocol.BlockingMethodFrameListener;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.AMQException;
+
+public class SpecificMethodFrameListener extends BlockingMethodFrameListener
+{
+ private final Class _expectedClass;
+
+ public SpecificMethodFrameListener(int channelId, Class expectedClass)
+ {
+ super(channelId);
+ _expectedClass = expectedClass;
+ }
+
+ public boolean processMethod(int channelId, AMQMethodBody frame) throws AMQException
+ {
+ return _expectedClass.isInstance(frame);
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/transport/ITransportConnection.java b/java/client/src/org/apache/qpid/client/transport/ITransportConnection.java
new file mode 100644
index 0000000000..49af2d1bdb
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/transport/ITransportConnection.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.transport;
+
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.jms.BrokerDetails;
+
+import java.io.IOException;
+
+public interface ITransportConnection
+{
+ void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail)
+ throws IOException;
+}
diff --git a/java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java b/java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java
new file mode 100644
index 0000000000..0ec5ba2473
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.transport;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.pool.ReadWriteThreadModel;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.log4j.Logger;
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.ConnectFuture;
+import org.apache.mina.common.IoConnector;
+import org.apache.mina.common.SimpleByteBufferAllocator;
+import org.apache.mina.transport.socket.nio.SocketConnectorConfig;
+import org.apache.mina.transport.socket.nio.SocketSessionConfig;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+public class SocketTransportConnection implements ITransportConnection
+{
+ private static final Logger _logger = Logger.getLogger(SocketTransportConnection.class);
+ private static final int DEFAULT_BUFFER_SIZE = 32 * 1024;
+
+ private SocketConnectorFactory _socketConnectorFactory;
+
+ static interface SocketConnectorFactory {
+ IoConnector newSocketConnector();
+ }
+
+ public SocketTransportConnection(SocketConnectorFactory socketConnectorFactory)
+ {
+ _socketConnectorFactory = socketConnectorFactory;
+ }
+
+ public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail)
+ throws IOException
+ {
+ ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers"));
+
+ // the MINA default is currently to use the pooled allocator although this may change in future
+ // once more testing of the performance of the simple allocator has been done
+ if (!Boolean.getBoolean("amqj.enablePooledAllocator"))
+ {
+ ByteBuffer.setAllocator(new SimpleByteBufferAllocator());
+ }
+
+ final IoConnector ioConnector = _socketConnectorFactory.newSocketConnector();
+ SocketConnectorConfig cfg = (SocketConnectorConfig) ioConnector.getDefaultConfig();
+
+ // if we do not use our own thread model we get the MINA default which is to use
+ // its own leader-follower model
+ boolean readWriteThreading = Boolean.getBoolean("amqj.shared_read_write_pool");
+ if (readWriteThreading)
+ {
+ cfg.setThreadModel(new ReadWriteThreadModel());
+ }
+
+ SocketSessionConfig scfg = (SocketSessionConfig) cfg.getSessionConfig();
+ scfg.setTcpNoDelay("true".equalsIgnoreCase(System.getProperty("amqj.tcpNoDelay", "true")));
+ scfg.setSendBufferSize(Integer.getInteger("amqj.sendBufferSize", DEFAULT_BUFFER_SIZE));
+ _logger.info("send-buffer-size = " + scfg.getSendBufferSize());
+ scfg.setReceiveBufferSize(Integer.getInteger("amqj.receiveBufferSize", DEFAULT_BUFFER_SIZE));
+ _logger.info("recv-buffer-size = " + scfg.getReceiveBufferSize());
+ final InetSocketAddress address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort());
+ protocolHandler.setUseSSL(brokerDetail.useSSL());
+ _logger.info("Attempting connection to " + address);
+ ConnectFuture future = ioConnector.connect(address, protocolHandler);
+
+ // wait for connection to complete
+ if (future.join(brokerDetail.getTimeout()))
+ {
+ // we call getSession which throws an IOException if there has been an error connecting
+ future.getSession();
+ }
+ else
+ {
+ throw new IOException("Timeout waiting for connection.");
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/org/apache/qpid/client/transport/TransportConnection.java
new file mode 100644
index 0000000000..a898e182f7
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/transport/TransportConnection.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.transport;
+
+import org.apache.mina.common.IoConnector;
+import org.apache.mina.transport.socket.nio.SocketConnector;
+
+/**
+ * The TransportConnection is a helper class responsible for connecting to an AMQ server. It sets up
+ * the underlying connector, which currently always uses TCP/IP sockets. It creates the
+ * "protocol handler" which deals with MINA protocol events.
+ *
+ * Could be extended in future to support different transport types by turning this into concrete class/interface
+ * combo.
+ */
+public class TransportConnection
+{
+ private static ITransportConnection _instance;
+
+ static
+ {
+ if (Boolean.getBoolean("amqj.useBlockingIo"))
+ {
+ _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() {
+ public IoConnector newSocketConnector() {
+ return new org.apache.qpid.bio.SocketConnector(); // blocking connector
+ }
+ });
+ }
+ else
+ {
+ _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() {
+ public IoConnector newSocketConnector() {
+ SocketConnector result = new SocketConnector(); // non-blocking connector
+
+ // Don't have the connector's worker thread wait around for other connections (we only use
+ // one SocketConnector per connection at the moment anyway). This allows short-running
+ // clients (like unit tests) to complete quickly.
+ result.setWorkerTimeout(0L);
+
+ return result;
+ }
+ });
+ }
+ }
+
+ public static void setInstance(ITransportConnection transport)
+ {
+ _instance = transport;
+ }
+
+ public static ITransportConnection getInstance()
+ {
+ return _instance;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java b/java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java
new file mode 100644
index 0000000000..ad2ca7b731
--- /dev/null
+++ b/java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java
@@ -0,0 +1,87 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.client.util;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A blocking queue that emits events above a user specified threshold allowing
+ * the caller to take action (e.g. flow control) to try to prevent the queue
+ * growing (much) further. The underlying queue itself is not bounded therefore
+ * the caller is not obliged to react to the events.
+ * <p/>
+ * This implementation is <b>only</b> safe where we have a single thread adding
+ * items and a single (different) thread removing items.
+ *
+ */
+public class FlowControllingBlockingQueue
+{
+ /**
+ * This queue is bounded and is used to store messages before being dispatched to the consumer
+ */
+ private final BlockingQueue _queue = new LinkedBlockingQueue();
+
+ private final int _flowControlThreshold;
+
+ private final ThresholdListener _listener;
+
+ /**
+ * We require a separate count so we can track whether we have reached the
+ * threshold
+ */
+ private int _count;
+
+ public interface ThresholdListener
+ {
+ void aboveThreshold(int currentValue);
+
+ void underThreshold(int currentValue);
+ }
+
+ public FlowControllingBlockingQueue(int threshold, ThresholdListener listener)
+ {
+ _flowControlThreshold = threshold;
+ _listener = listener;
+ }
+
+ public Object take() throws InterruptedException
+ {
+ Object o = _queue.take();
+ synchronized (_listener)
+ {
+ if (--_count == (_flowControlThreshold - 1))
+ {
+ _listener.underThreshold(_count);
+ }
+ }
+ return o;
+ }
+
+ public void add(Object o)
+ {
+ _queue.add(o);
+ synchronized (_listener)
+ {
+ if (++_count == _flowControlThreshold)
+ {
+ _listener.aboveThreshold(_count);
+ }
+ }
+ }
+}
diff --git a/java/client/src/org/apache/qpid/jms/BrokerDetails.java b/java/client/src/org/apache/qpid/jms/BrokerDetails.java
new file mode 100644
index 0000000000..fc8af2091e
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/BrokerDetails.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+public interface BrokerDetails
+{
+
+ /*
+ * Known URL Options
+ * @see ConnectionURL
+ */
+ public static final String OPTIONS_RETRY = "retries";
+ public static final String OPTIONS_SSL = ConnectionURL.OPTIONS_SSL;
+ public static final String OPTIONS_CONNECT_TIMEOUT = "connecttimeout";
+ public static final int DEFAULT_PORT = 5672;
+ public static final String DEFAULT_TRANSPORT = "tcp";
+
+ public static final String URL_FORMAT_EXAMPLE =
+ "<transport>://<hostname>[:<port Default=\""+DEFAULT_PORT+"\">][?<option>='<value>'[,<option>='<value>']]";
+
+ public static final long DEFAULT_CONNECT_TIMEOUT = 30000L;
+
+ String getHost();
+ void setHost(String host);
+
+ int getPort();
+ void setPort(int port);
+
+ String getTransport();
+ void setTransport(String transport);
+
+ boolean useSSL();
+ void useSSL(boolean ssl);
+
+ String getOption(String key);
+ void setOption(String key,String value);
+
+ long getTimeout();
+ void setTimeout(long timeout);
+
+ String toString();
+
+ boolean equals(Object o);
+}
diff --git a/java/client/src/org/apache/qpid/jms/ChannelLimitReachedException.java b/java/client/src/org/apache/qpid/jms/ChannelLimitReachedException.java
new file mode 100644
index 0000000000..d18831f5ff
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/ChannelLimitReachedException.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+import javax.jms.ResourceAllocationException;
+
+/**
+ * Indicates that the maximum number of sessions per connection limit has been reached.
+ */
+public class ChannelLimitReachedException extends ResourceAllocationException
+{
+ private static final String ERROR_CODE = "1";
+
+ private long _limit;
+
+ public ChannelLimitReachedException(long limit)
+ {
+ super("Unable to create session since maximum number of sessions per connection is " +
+ limit + ". Either close one or more sessions or increase the " +
+ "maximum number of sessions per connection (or contact your AMQP administrator.", ERROR_CODE);
+ _limit = limit;
+ }
+
+ public long getLimit()
+ {
+ return _limit;
+ }
+}
diff --git a/java/client/src/org/apache/qpid/jms/Connection.java b/java/client/src/org/apache/qpid/jms/Connection.java
new file mode 100644
index 0000000000..88cfdfc65f
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/Connection.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+import javax.jms.*;
+
+
+public interface Connection extends javax.jms.Connection
+{
+ /**
+ * @return the maximum number of sessions supported by this Connection
+ */
+ long getMaximumChannelCount();
+
+ void setConnectionListener(ConnectionListener listener);
+
+ /**
+ * Get the connection listener that has been registered with this connection, if any
+ * @return the listener or null if none has been set
+ */
+ ConnectionListener getConnectionListener();
+
+ /**
+ * Create a session specifying the prefetch limit of messages.
+ * @param transacted
+ * @param acknowledgeMode
+ * @param prefetch the maximum number of messages to buffer in the client. This
+ * applies as a total across all consumers
+ * @return
+ * @throws JMSException
+ */
+ org.apache.qpid.jms.Session createSession(boolean transacted, int acknowledgeMode,
+ int prefetch) throws JMSException;
+}
diff --git a/java/client/src/org/apache/qpid/jms/ConnectionListener.java b/java/client/src/org/apache/qpid/jms/ConnectionListener.java
new file mode 100644
index 0000000000..2ffcf0c8fc
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/ConnectionListener.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+public interface ConnectionListener
+{
+ /**
+ * Called when bytes have been transmitted to the server
+ * @param count the number of bytes sent in total since the connection was opened
+ */
+ void bytesSent(long count);
+
+ /**
+ * Called when some bytes have been received on a connection
+ * @param count the number of bytes received in total since the connection was opened
+ */
+ void bytesReceived(long count);
+
+ /**
+ * Called after the infrastructure has detected that failover is required but before attempting failover.
+ * @param redirect true if the broker requested redirect. false if failover is occurring due to a connection error.
+ * @return true to continue failing over, false to veto failover and raise a connection exception
+ */
+ boolean preFailover(boolean redirect);
+
+ /**
+ * Called after connection has been made to another broker after failover has been started but before
+ * any resubscription has been done.
+ * @return true to continue with resubscription, false to prevent automatic resubscription. This is useful in
+ * cases where the application wants to handle resubscription. Note that in the latter case all sessions, producers
+ * and consumers are invalidated.
+ */
+ boolean preResubscribe();
+
+ /**
+ * Called once failover has completed successfully. This is called irrespective of whether the client has
+ * vetoed automatic resubscription.
+ */
+ void failoverComplete();
+}
diff --git a/java/client/src/org/apache/qpid/jms/ConnectionURL.java b/java/client/src/org/apache/qpid/jms/ConnectionURL.java
new file mode 100644
index 0000000000..555bbd2f46
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/ConnectionURL.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+import java.util.List;
+
+/**
+ Connection URL format
+ amqp://[user:pass@][clientid]/virtualhost?brokerlist='tcp://host:port?option=\'value\'&option=\'value\';vm://:3/virtualpath?option=\'value\''&failover='method?option=\'value\'&option='value''"
+ Options are of course optional except for requiring a single broker in the broker list.
+ The option seperator is defined to be either '&' or ','
+ */
+public interface ConnectionURL
+{
+ public static final String AMQ_PROTOCOL = "amqp";
+ public static final String OPTIONS_BROKERLIST = "brokerlist";
+ public static final String OPTIONS_FAILOVER = "failover";
+ public static final String OPTIONS_FAILOVER_CYCLE = "cyclecount";
+ public static final String OPTIONS_SSL = "ssl";
+
+ String getURL();
+
+ String getFailoverMethod();
+
+ String getFailoverOption(String key);
+
+ int getBrokerCount();
+
+ BrokerDetails getBrokerDetails(int index);
+
+ void addBrokerDetails(BrokerDetails broker);
+
+ List<BrokerDetails> getAllBrokerDetails();
+
+ String getClientName();
+
+ void setClientName(String clientName);
+
+ String getUsername();
+
+ void setUsername(String username);
+
+ String getPassword();
+
+ void setPassword(String password);
+
+ String getVirtualHost();
+
+ void setVirtualHost(String virtualHost);
+
+ String getOption(String key);
+
+ void setOption(String key, String value);
+}
diff --git a/java/client/src/org/apache/qpid/jms/FailoverPolicy.java b/java/client/src/org/apache/qpid/jms/FailoverPolicy.java
new file mode 100644
index 0000000000..a1a89f8a66
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/FailoverPolicy.java
@@ -0,0 +1,306 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+import org.apache.qpid.jms.failover.FailoverMethod;
+import org.apache.qpid.jms.failover.FailoverRoundRobinServers;
+import org.apache.qpid.jms.failover.FailoverSingleServer;
+import org.apache.log4j.Logger;
+
+
+public class FailoverPolicy
+{
+ private static final Logger _logger = Logger.getLogger(FailoverPolicy.class);
+
+ private static final long MINUTE = 60000L;
+
+ private static final long DEFAULT_METHOD_TIMEOUT = 1 * MINUTE;
+ private static final long DEFAULT_FAILOVER_TIMEOUT = 4 * MINUTE;
+
+ private FailoverMethod _methods[] = new FailoverMethod[1];
+
+ private int _currentMethod;
+
+ private int _methodsRetries;
+
+ private int _currentRetry;
+
+ private boolean _timing;
+
+ private long _lastMethodTime;
+ private long _lastFailTime;
+
+ public FailoverPolicy(ConnectionURL connectionDetails)
+ {
+ FailoverMethod method;
+
+ //todo This should be integrated in to the connection url when it supports
+ // multiple strategies.
+
+ _methodsRetries = 0;
+
+ if (connectionDetails.getFailoverMethod() == null)
+ {
+ if (connectionDetails.getBrokerCount() > 1)
+ {
+ method = new FailoverRoundRobinServers(connectionDetails);
+ }
+ else
+ {
+ method = new FailoverSingleServer(connectionDetails);
+ }
+ }
+ else
+ {
+ String failoverMethod = connectionDetails.getFailoverMethod();
+
+/*
+ if (failoverMethod.equals(FailoverMethod.RANDOM))
+ {
+ //todo write a random connection Failover
+ }
+*/
+ if (failoverMethod.equals(FailoverMethod.ROUND_ROBIN))
+ {
+ method = new FailoverRoundRobinServers(connectionDetails);
+ }
+ else
+ {
+ try
+ {
+ Class[] constructorSpec = {ConnectionURL.class};
+ Object [] params = {connectionDetails};
+
+ method = (FailoverMethod) ClassLoader.getSystemClassLoader().
+ loadClass(failoverMethod).
+ getConstructor(constructorSpec).newInstance(params);
+ }
+ catch (Exception cnfe)
+ {
+ throw new IllegalArgumentException("Unknown failover method:" + failoverMethod);
+ }
+ }
+ }
+
+ if (method == null)
+ {
+ throw new IllegalArgumentException("Unknown failover method specified.");
+ }
+
+ reset();
+
+ _methods[_currentMethod] = method;
+ }
+
+ public FailoverPolicy(FailoverMethod method)
+ {
+ this(method, 0);
+ }
+
+ public FailoverPolicy(FailoverMethod method, int retries)
+ {
+ _methodsRetries = retries;
+
+ reset();
+
+ _methods[_currentMethod] = method;
+ }
+
+ private void reset()
+ {
+ _currentMethod = 0;
+ _currentRetry = 0;
+ _timing = false;
+
+ }
+
+ public boolean failoverAllowed()
+ {
+ boolean failoverAllowed;
+
+ if (_timing)
+ {
+ long now = System.currentTimeMillis();
+
+ if ((now - _lastMethodTime) >= DEFAULT_METHOD_TIMEOUT)
+ {
+ _logger.info("Failover method timeout");
+ _lastMethodTime = now;
+
+ if (!nextMethod())
+ {
+ return false;
+ }
+
+
+ }
+ else if ((now - _lastFailTime) >= DEFAULT_FAILOVER_TIMEOUT)
+ {
+ _logger.info("Failover timeout");
+ return false;
+ }
+ else
+ {
+ _lastMethodTime = now;
+ }
+ }
+ else
+ {
+ _timing = true;
+ _lastMethodTime = System.currentTimeMillis();
+ _lastFailTime = _lastMethodTime;
+ }
+
+
+ if (_methods[_currentMethod].failoverAllowed())
+ {
+ failoverAllowed = true;
+ }
+ else
+ {
+ if (_currentMethod < (_methods.length - 1))
+ {
+ nextMethod();
+ _logger.info("Changing method to " + _methods[_currentMethod].methodName());
+ return failoverAllowed();
+ }
+ else
+ {
+ return cycleMethods();
+ }
+ }
+
+ return failoverAllowed;
+ }
+
+ private boolean nextMethod()
+ {
+ if (_currentMethod < (_methods.length - 1))
+ {
+ _currentMethod++;
+ _methods[_currentMethod].reset();
+ return true;
+ }
+ else
+ {
+ return cycleMethods();
+ }
+ }
+
+ private boolean cycleMethods()
+ {
+ if (_currentRetry < _methodsRetries)
+ {
+ _currentRetry++;
+
+ _currentMethod = 0;
+
+ _logger.info("Retrying methods starting with " + _methods[_currentMethod].methodName());
+ _methods[_currentMethod].reset();
+ return failoverAllowed();
+ }
+ else
+ {
+ _logger.debug("All failover methods exhausted");
+ return false;
+ }
+ }
+
+ /**
+ * Notification that connection was successful.
+ */
+ public void attainedConnection()
+ {
+ _currentRetry = 0;
+
+ _methods[_currentMethod].attainedConnection();
+
+ _timing = false;
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ return _methods[_currentMethod].getCurrentBrokerDetails();
+ }
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ return _methods[_currentMethod].getNextBrokerDetails();
+ }
+
+ public void setBroker(BrokerDetails broker)
+ {
+ _methods[_currentMethod].setBroker(broker);
+ }
+
+ public void addMethod(FailoverMethod method)
+ {
+ int len = _methods.length + 1;
+ FailoverMethod[] newMethods = new FailoverMethod[len];
+ System.arraycopy(_methods, 0, newMethods, 0, _methods.length);
+ int index = len - 1;
+ newMethods[index] = method;
+ _methods = newMethods;
+ }
+
+ public void setMethodRetries(int retries)
+ {
+ _methodsRetries = retries;
+ }
+
+ public FailoverMethod getCurrentMethod()
+ {
+ if (_currentMethod >= 0 && _currentMethod < (_methods.length - 1))
+ {
+ return _methods[_currentMethod];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Failover Policy:\n");
+
+ if (failoverAllowed())
+ {
+ sb.append("Failover allowed\n");
+ }
+ else
+ {
+ sb.append("Failover not allowed\n");
+ }
+
+ sb.append("Failover policy methods\n");
+ for (int i = 0; i < _methods.length; i++)
+ {
+
+ if (i == _currentMethod)
+ {
+ sb.append(">");
+ }
+ sb.append(_methods[i].toString());
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/java/client/src/org/apache/qpid/jms/MessageConsumer.java b/java/client/src/org/apache/qpid/jms/MessageConsumer.java
new file mode 100644
index 0000000000..2116ebe918
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/MessageConsumer.java
@@ -0,0 +1,24 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+/**
+ */
+public interface MessageConsumer extends javax.jms.MessageConsumer
+{
+}
diff --git a/java/client/src/org/apache/qpid/jms/MessageProducer.java b/java/client/src/org/apache/qpid/jms/MessageProducer.java
new file mode 100644
index 0000000000..e00d271cc3
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/MessageProducer.java
@@ -0,0 +1,49 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import java.io.UnsupportedEncodingException;
+
+/**
+ */
+public interface MessageProducer extends javax.jms.MessageProducer
+{
+ /**
+ * Set the default MIME type for messages produced by this producer. This reduces the overhead of each message.
+ * @param mimeType
+ */
+ void setMimeType(String mimeType);
+
+ /**
+ * Set the default encoding for messages produced by this producer. This reduces the overhead of each message.
+ * @param encoding the encoding as understood by XXXX how do I specify this?? RG
+ * @throws UnsupportedEncodingException if the encoding is not understood
+ */
+ void setEncoding(String encoding) throws UnsupportedEncodingException;
+
+ void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean immediate)
+ throws JMSException;
+
+ void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean mandatory, boolean immediate)
+ throws JMSException;
+}
diff --git a/java/client/src/org/apache/qpid/jms/Session.java b/java/client/src/org/apache/qpid/jms/Session.java
new file mode 100644
index 0000000000..82a2311498
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/Session.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms;
+
+import javax.jms.*;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+
+
+public interface Session extends javax.jms.Session
+{
+ /**
+ * Indicates that no client acknowledgements are required. Broker assumes that once it has delivered
+ * a message packet successfully it is acknowledged.
+ */
+ static final int NO_ACKNOWLEDGE = 257;
+
+ /**
+ * Pre acknowledge means that an ack is sent per message but sent before user code has processed
+ * the message (i.e. before the onMessage() call or the receive() method has returned).
+ */
+ static final int PRE_ACKNOWLEDGE = 258;
+
+ MessageConsumer createConsumer(Destination destination,
+ int prefetch,
+ boolean noLocal,
+ boolean exclusive,
+ String selector) throws JMSException;
+
+ /**
+ * @return the prefetch value used by default for consumers created on this session.
+ */
+ int getDefaultPrefetch();
+
+ /**
+ * Create a producer
+ * @param destination
+ * @param mandatory the value of the mandatory flag used by default on the producer
+ * @param immediate the value of the immediate flag used by default on the producer
+ * @return
+ * @throws JMSException
+ */
+ MessageProducer createProducer(Destination destination, boolean mandatory, boolean immediate)
+ throws JMSException;
+
+ /**
+ * Create a producer
+ * @param destination
+ * @param immediate the value of the immediate flag used by default on the producer
+ * @return
+ * @throws JMSException
+ */
+ MessageProducer createProducer(Destination destination, boolean immediate)
+ throws JMSException;
+}
diff --git a/java/client/src/org/apache/qpid/jms/failover/FailoverMethod.java b/java/client/src/org/apache/qpid/jms/failover/FailoverMethod.java
new file mode 100644
index 0000000000..ff1336eb9d
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/failover/FailoverMethod.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms.failover;
+
+import org.apache.qpid.jms.BrokerDetails;
+
+public interface FailoverMethod
+{
+ public static final String ROUND_ROBIN = "roundrobin";
+ public static final String RANDOM = "random";
+ /**
+ * Reset the Failover to initial conditions
+ */
+ void reset();
+
+ /**
+ * Check if failover is possible for this method
+ *
+ * @return true if failover is allowed
+ */
+ boolean failoverAllowed();
+
+ /**
+ * Notification to the Failover method that a connection has been attained.
+ */
+ void attainedConnection();
+
+ /**
+ * If there is no current BrokerDetails the null will be returned.
+ * @return The current BrokerDetail value to use
+ */
+ BrokerDetails getCurrentBrokerDetails();
+
+ /**
+ * Move to the next BrokerDetails if one is available.
+ * @return the next BrokerDetail or null if there is none.
+ */
+ BrokerDetails getNextBrokerDetails();
+
+ /**
+ * Set the currently active broker to be the new value.
+ * @param broker The new BrokerDetail value
+ */
+ void setBroker(BrokerDetails broker);
+
+ /**
+ * Set the retries for this method
+ * @param maxRetries the maximum number of time to retry this Method
+ */
+ void setRetries(int maxRetries);
+
+ /**
+ * @return The name of this method for display purposes.
+ */
+ String methodName();
+}
diff --git a/java/client/src/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java b/java/client/src/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java
new file mode 100644
index 0000000000..715f605308
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java
@@ -0,0 +1,256 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms.failover;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.log4j.Logger;
+
+public class FailoverRoundRobinServers implements FailoverMethod
+{
+ private static final Logger _logger = Logger.getLogger(FailoverRoundRobinServers.class);
+
+ /** The default number of times to cycle through all servers */
+ public static final int DEFAULT_CYCLE_RETRIES = 0;
+ /** The default number of times to retry each server */
+ public static final int DEFAULT_SERVER_RETRIES = 0;
+
+ /**
+ * The index into the hostDetails array of the broker to which we are connected
+ */
+ private int _currentBrokerIndex = -1;
+
+ /**
+ * The number of times to retry connecting for each server
+ */
+ private int _serverRetries;
+
+ /**
+ * The current number of retry attempts made
+ */
+ private int _currentServerRetry;
+
+ /**
+ * The number of times to cycle through the servers
+ */
+ private int _cycleRetries;
+
+ /**
+ * The current number of cycles performed.
+ */
+ private int _currentCycleRetries;
+
+ /**
+ * Array of BrokerDetail used to make connections.
+ */
+ private ConnectionURL _connectionDetails;
+
+ public FailoverRoundRobinServers(ConnectionURL connectionDetails)
+ {
+ if (!(connectionDetails.getBrokerCount() > 0))
+ {
+ throw new IllegalArgumentException("At least one broker details must be specified.");
+ }
+
+ _connectionDetails = connectionDetails;
+
+ //There is no current broker at startup so set it to -1.
+ _currentBrokerIndex = -1;
+
+ String cycleRetries = _connectionDetails.getFailoverOption(ConnectionURL.OPTIONS_FAILOVER_CYCLE);
+
+ if (cycleRetries != null)
+ {
+ try
+ {
+ _cycleRetries = Integer.parseInt(cycleRetries);
+ }
+ catch (NumberFormatException nfe)
+ {
+ _cycleRetries = DEFAULT_CYCLE_RETRIES;
+ }
+ }
+
+ _currentCycleRetries = 0;
+
+ _serverRetries = 0;
+ _currentServerRetry = -1;
+ }
+
+ public void reset()
+ {
+ _currentBrokerIndex = 0;
+ _currentCycleRetries = 0;
+ _currentServerRetry = -1;
+ }
+
+ public boolean failoverAllowed()
+ {
+ return ((_currentCycleRetries < _cycleRetries)
+ || (_currentServerRetry < _serverRetries)
+ || (_currentBrokerIndex < (_connectionDetails.getBrokerCount() - 1)));
+ }
+
+ public void attainedConnection()
+ {
+ _currentCycleRetries = 0;
+ _currentServerRetry = -1;
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ if (_currentBrokerIndex == -1)
+ {
+ return null;
+ }
+
+ return _connectionDetails.getBrokerDetails(_currentBrokerIndex);
+ }
+
+
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ if (_currentBrokerIndex == (_connectionDetails.getBrokerCount() - 1))
+ {
+ if (_currentServerRetry < _serverRetries)
+ {
+ if (_currentBrokerIndex == -1)
+ {
+ _currentBrokerIndex = 0;
+
+ setBroker(_connectionDetails.getBrokerDetails(_currentBrokerIndex ));
+
+ _logger.info("First run using " + _connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ }
+ else
+ {
+ _logger.info("Retrying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ }
+
+ _currentServerRetry++;
+ }
+ else
+ {
+ _currentCycleRetries++;
+ //failed to connect to first broker
+ _currentBrokerIndex = 0;
+
+ setBroker(_connectionDetails.getBrokerDetails(_currentBrokerIndex ));
+
+ // This is zero rather than -1 as we are already retrieving the details.
+ _currentServerRetry = 0;
+ }
+ //else - should force client to stop as max retries has been reached.
+ }
+ else
+ {
+ if (_currentServerRetry < _serverRetries)
+ {
+ if (_currentBrokerIndex == -1)
+ {
+ _currentBrokerIndex = 0;
+
+ setBroker(_connectionDetails.getBrokerDetails(_currentBrokerIndex ));
+
+ _logger.info("First run using " + _connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ }
+ else
+ {
+ _logger.info("Retrying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ }
+ _currentServerRetry++;
+ }
+ else
+ {
+ _currentBrokerIndex++;
+
+ setBroker(_connectionDetails.getBrokerDetails(_currentBrokerIndex ));
+ // This is zero rather than -1 as we are already retrieving the details.
+ _currentServerRetry = 0;
+ }
+ }
+
+ return _connectionDetails.getBrokerDetails(_currentBrokerIndex);
+ }
+
+
+ public void setBroker(BrokerDetails broker)
+ {
+
+ _connectionDetails.addBrokerDetails(broker);
+
+ int index = _connectionDetails.getAllBrokerDetails().indexOf(broker);
+
+ String serverRetries = broker.getOption(BrokerDetails.OPTIONS_RETRY);
+
+ if (serverRetries != null)
+ {
+ try
+ {
+ _serverRetries = Integer.parseInt(serverRetries);
+ }
+ catch (NumberFormatException nfe)
+ {
+ _serverRetries = DEFAULT_SERVER_RETRIES;
+ }
+ }
+
+ _currentServerRetry = -1;
+ _currentBrokerIndex = index;
+ }
+
+ public void setRetries(int maxRetries)
+ {
+ _cycleRetries = maxRetries;
+ }
+
+ public String methodName()
+ {
+ return "Cycle Servers";
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Cycle Servers:\n");
+
+ sb.append("Cycle Retries:");
+ sb.append(_cycleRetries);
+ sb.append("\nCurrent Cycle:");
+ sb.append(_currentCycleRetries);
+ sb.append("\nServer Retries:");
+ sb.append(_serverRetries);
+ sb.append("\nCurrent Retry:");
+ sb.append(_currentServerRetry);
+ sb.append("\n");
+
+ for(int i=0; i < _connectionDetails.getBrokerCount() ; i++)
+ {
+ if (i == _currentBrokerIndex)
+ {
+ sb.append(">");
+ }
+ sb.append(_connectionDetails.getBrokerDetails(i));
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/java/client/src/org/apache/qpid/jms/failover/FailoverSingleServer.java b/java/client/src/org/apache/qpid/jms/failover/FailoverSingleServer.java
new file mode 100644
index 0000000000..76dc6a2dd5
--- /dev/null
+++ b/java/client/src/org/apache/qpid/jms/failover/FailoverSingleServer.java
@@ -0,0 +1,144 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * 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.apache.qpid.jms.failover;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+
+public class FailoverSingleServer implements FailoverMethod
+{
+ /** The default number of times to rety a conection to this server */
+ public static final int DEFAULT_SERVER_RETRIES = 1;
+
+ /**
+ * The details of the Single Server
+ */
+ private BrokerDetails _brokerDetail;
+
+ /**
+ * The number of times to retry connecting to the sever
+ */
+ private int _retries;
+
+ /**
+ * The current number of attempts made to the server
+ */
+ private int _currentRetries;
+
+
+ public FailoverSingleServer(ConnectionURL connectionDetails)
+ {
+ if (connectionDetails.getBrokerCount() > 0)
+ {
+ setBroker(connectionDetails.getBrokerDetails(0));
+ }
+ else
+ {
+ throw new IllegalArgumentException("BrokerDetails details required for connection.");
+ }
+ }
+
+ public FailoverSingleServer(BrokerDetails brokerDetail)
+ {
+ setBroker(brokerDetail);
+ }
+
+ public void reset()
+ {
+ _currentRetries = -1;
+ }
+
+ public boolean failoverAllowed()
+ {
+ return _currentRetries < _retries;
+ }
+
+ public void attainedConnection()
+ {
+ reset();
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ return _brokerDetail;
+ }
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ if (_currentRetries == _retries)
+ {
+ return null;
+ }
+ else
+ {
+ if (_currentRetries < _retries)
+ {
+ _currentRetries ++;
+ }
+
+ return _brokerDetail;
+ }
+ }
+
+ public void setBroker(BrokerDetails broker)
+ {
+ if (broker == null)
+ {
+ throw new IllegalArgumentException("BrokerDetails details cannot be null");
+ }
+ _brokerDetail = broker;
+
+ String retries = broker.getOption(BrokerDetails.OPTIONS_RETRY);
+ if (retries != null)
+ {
+ try
+ {
+ _retries = Integer.parseInt(retries);
+ }
+ catch (NumberFormatException nfe)
+ {
+ _retries = DEFAULT_SERVER_RETRIES;
+ }
+ }
+ else
+ {
+ _retries = DEFAULT_SERVER_RETRIES;
+ }
+
+ reset();
+ }
+
+ public void setRetries(int retries)
+ {
+ _retries = retries;
+ }
+
+ public String methodName()
+ {
+ return "Single Server";
+ }
+
+ public String toString()
+ {
+ return "SingleServer:\n"+
+ "Max Retries:"+_retries+
+ "\nCurrent Retry:"+_currentRetries+
+ "\n"+_brokerDetail+"\n";
+ }
+
+}