diff options
| author | Aidan Skinner <aidan@apache.org> | 2008-04-24 01:54:20 +0000 |
|---|---|---|
| committer | Aidan Skinner <aidan@apache.org> | 2008-04-24 01:54:20 +0000 |
| commit | 559e9702d5a7c26dddee708e267f2f685d4de89e (patch) | |
| tree | b3114d58b39092b4699186533a50553715e42b96 /java | |
| parent | 04fe7be0efbc3687a5a302fea6fbec82adbfedae (diff) | |
| download | qpid-python-559e9702d5a7c26dddee708e267f2f685d4de89e.tar.gz | |
QPID-832 merge M2.x
git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@651133 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'java')
259 files changed, 20360 insertions, 3620 deletions
diff --git a/java/broker/pom.xml b/java/broker/pom.xml index 153e186a26..53913f161f 100644 --- a/java/broker/pom.xml +++ b/java/broker/pom.xml @@ -22,14 +22,14 @@ <groupId>org.apache.qpid</groupId> <artifactId>qpid-broker</artifactId> <packaging>jar</packaging> - <version>1.0-incubating-M2.1-SNAPSHOT</version> + <version>1.0-incubating-M3-SNAPSHOT</version> <name>Qpid Broker</name> <url>http://cwiki.apache.org/confluence/display/qpid</url> <parent> <groupId>org.apache.qpid</groupId> <artifactId>qpid</artifactId> - <version>1.0-incubating-M2.1-SNAPSHOT</version> + <version>1.0-incubating-M3-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> diff --git a/java/broker/src/main/grammar/SelectorParser.jj b/java/broker/src/main/grammar/SelectorParser.jj index f6a843e080..eb4467f1cc 100644 --- a/java/broker/src/main/grammar/SelectorParser.jj +++ b/java/broker/src/main/grammar/SelectorParser.jj @@ -94,7 +94,7 @@ public class SelectorParser { return this.JmsSelector();
}
catch (Throwable e) {
- throw (AMQInvalidArgumentException)new AMQInvalidArgumentException(sql).initCause(e);
+ throw (AMQInvalidArgumentException)new AMQInvalidArgumentException(sql, e);
}
}
diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java index 1a9dc6673a..636aa7eb03 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -67,7 +67,7 @@ public class DefaultExchangeFactory implements ExchangeFactory if (exchType == null) { - throw new AMQUnknownExchangeType("Unknown exchange type: " + type); + throw new AMQUnknownExchangeType("Unknown exchange type: " + type, null); } Exchange e = exchType.newInstance(_host, exchange, durable, ticket, autoDelete); return e; diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java index 9a98bc9659..97d25f650d 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java @@ -101,7 +101,7 @@ public class ExchangeDeclareHandler implements StateAwareMethodListener<Exchange else if (!exchange.getType().equals(body.getType())) { - throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + body.getExchange() + " of type " + exchange.getType() + " to " + body.getType() +".",body.getClazz(), body.getMethod(),body.getMajor(),body.getMinor()); + throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + body.getExchange() + " of type " + exchange.getType() + " to " + body.getType() +".",body.getClazz(), body.getMethod(),body.getMajor(),body.getMinor(), null); } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java b/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java index dee1676632..126ff22d69 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java +++ b/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java @@ -37,9 +37,9 @@ public class AllowAll implements ACLPlugin public AccessResult authorise(AMQProtocolSession session, Permission permission, AMQMethodBody body, Object... parameters) { - if (_logger.isDebugEnabled()) + if (ACLManager.getLogger().isDebugEnabled()) { - _logger.debug("Allowing user:" + session.getAuthorizedID() + " for :" + permission.toString() + ACLManager.getLogger().debug("Allowing user:" + session.getAuthorizedID() + " for :" + permission.toString() + " on " + body.getClass().getSimpleName() + (parameters == null || parameters.length == 0 ? "" : "-" + accessablesToString(parameters))); } diff --git a/java/client/build.xml b/java/client/build.xml index 1fb980b908..aeadfa2f0f 100644 --- a/java/client/build.xml +++ b/java/client/build.xml @@ -21,7 +21,7 @@ <project name="AMQ Client" default="build"> <property name="module.depends" value="common"/> - <property name="module.test.depends" value="broker"/> + <property name="module.test.depends" value="broker junit-toolkit"/> <import file="../module.xml"/> @@ -29,9 +29,9 @@ <target name="precompile"> <mkdir dir="${output.dir}"/> - <exec executable="javacc"> - <arg line="-OUTPUT_DIRECTORY=${output.dir} src/main/grammar/SelectorParser.jj"/> - </exec> + <javacc target="src/main/grammar/SelectorParser.jj" + outputdirectory="${output.dir}" + javacchome="${project.root}/lib"/> </target> </project> diff --git a/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java new file mode 100644 index 0000000000..d7eb138523 --- /dev/null +++ b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java @@ -0,0 +1,171 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.example.transport; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.url.URLSyntaxException; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.SocketChannel; +import java.util.UUID; + +/** + * This is a simple application that demonstrates how you can use the Qpid AMQP interfaces to use existing sockets as + * the transport for the Client API. + * + * The Demo here runs twice: + * 1. Just to show a simple publish and receive. + * 2. To demonstrate how to use existing sockets and utilise the underlying client failover mechnaism. + */ +public class ExistingSocketConnectorDemo implements ConnectionListener +{ + private static boolean DEMO_FAILOVER = false; + + public static void main(String[] args) throws IOException, URLSyntaxException, AMQException, JMSException + { + System.out.println("Testing socket connection to localhost:5672."); + + new ExistingSocketConnectorDemo(); + + System.out.println("Testing socket connection failover between localhost:5672 and localhost:5673."); + + DEMO_FAILOVER = true; + + new ExistingSocketConnectorDemo(); + } + + Connection _connection; + MessageProducer _producer; + Session _session; + + String Socket1_ID = UUID.randomUUID().toString(); + String Socket2_ID = UUID.randomUUID().toString(); + + + + /** Here we can see the broker we are connecting to is set to be 'socket:///' signifying we will provide the socket. */ + public final String CONNECTION = "amqp://guest:guest@id/test?brokerlist='socket://" + Socket1_ID + ";socket://" + Socket2_ID + "'"; + + + public ExistingSocketConnectorDemo() throws IOException, URLSyntaxException, AMQException, JMSException + { + + Socket socket = SocketChannel.open().socket(); + socket.connect(new InetSocketAddress("localhost", 5672)); + + TransportConnection.registerOpenSocket(Socket1_ID, socket); + + + _connection = new AMQConnection(CONNECTION); + + _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + MessageConsumer consumer = _session.createConsumer(_session.createQueue("Queue")); + + _producer = _session.createProducer(_session.createQueue("Queue")); + + _connection.start(); + + if (!DEMO_FAILOVER) + { + _producer.send(_session.createTextMessage("Simple Test")); + } + else + { + // Using the Qpid interfaces we can set a listener that allows us to demonstrate failover + ((AMQConnection) _connection).setConnectionListener(this); + + System.out.println("Testing failover: Please ensure second broker running on localhost:5673 and shutdown broker on 5672."); + } + + //We do a blocking receive here so that we can demonstrate failover. + Message message = consumer.receive(); + + System.out.println("Recevied :" + message); + + _connection.close(); + } + + // ConnectionListener Interface + + public void bytesSent(long count) + { + //not used in this example + } + public void bytesReceived(long count) + { + //not used in this example + } + + public boolean preFailover(boolean redirect) + { + /** + * This method is called before the underlying client library starts to reconnect. This gives us the opportunity + * to set a new socket for the failover to occur on. + */ + try + { + Socket socket = SocketChannel.open().socket(); + + socket.connect(new InetSocketAddress("localhost", 5673)); + + // This is the new method to pass in an open socket for the connection to use. + TransportConnection.registerOpenSocket(Socket2_ID, socket); + } + catch (IOException e) + { + e.printStackTrace(); + return false; + } + return true; + } + + public boolean preResubscribe() + { + //not used in this example - but must return true to allow the resubscription of existing clients. + return true; + } + + public void failoverComplete() + { + // Now that failover has completed we can send a message that the receiving thread will pick up + try + { + _producer.send(_session.createTextMessage("Simple Failover Test")); + } + catch (JMSException e) + { + e.printStackTrace(); + } + } +} diff --git a/java/client/pom.xml b/java/client/pom.xml index 07a3520f58..bcce3e1d3b 100644 --- a/java/client/pom.xml +++ b/java/client/pom.xml @@ -69,18 +69,8 @@ <artifactId>commons-lang</artifactId> </dependency> - <dependency> - <groupId>org.apache.mina</groupId> - <artifactId>mina-filter-ssl</artifactId> - </dependency> <!-- Test Dependencies --> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-log4j12</artifactId> - <version>1.4.0</version> - <scope>test</scope> - </dependency> <dependency> <!-- for inVm Broker --> <groupId>org.apache.qpid</groupId> @@ -89,19 +79,7 @@ </dependency> <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>org.easymock</groupId> - <artifactId>easymockclassextension</artifactId> - <scope>test</scope> - </dependency> - - <dependency> - <groupId>uk.co.thebadgerset</groupId> + <groupId>org.apache.qpid</groupId> <artifactId>junit-toolkit</artifactId> <scope>test</scope> </dependency> diff --git a/java/client/src/main/grammar/SelectorParser.jj b/java/client/src/main/grammar/SelectorParser.jj index 2dca11748e..a72da526ae 100644 --- a/java/client/src/main/grammar/SelectorParser.jj +++ b/java/client/src/main/grammar/SelectorParser.jj @@ -170,6 +170,8 @@ TOKEN [IGNORE_CASE] : TOKEN [IGNORE_CASE] :
{
< ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* >
+ | < QUOTED_ID : "\"" ( ("\"\"") | ~["\""] )* "\"" >
+
}
// ----------------------------------------------------------------------------
@@ -577,6 +579,7 @@ String stringLitteral() : PropertyExpression variable() :
{
Token t;
+ StringBuffer rc = new StringBuffer();
PropertyExpression left=null;
}
{
@@ -585,6 +588,20 @@ PropertyExpression variable() : {
left = new PropertyExpression(t.image);
}
+ |
+ t = <QUOTED_ID>
+ {
+ // Decode the sting value.
+ String image = t.image;
+ for( int i=1; i < image.length()-1; i++ ) {
+ char c = image.charAt(i);
+ if( c == '"' )
+ i++;
+ rc.append(c);
+ }
+ return new PropertyExpression(rc.toString());
+ }
+
)
{
return left;
diff --git a/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java b/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java new file mode 100644 index 0000000000..98716c0c3c --- /dev/null +++ b/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java @@ -0,0 +1,478 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.BaseIoConnector; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.Queue; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; + +/** + * {@link IoConnector} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 627427 $, $Date: 2008-02-13 14:39:10 +0000 (Wed, 13 Feb 2008) $ + */ +public class ExistingSocketConnector extends BaseIoConnector +{ + /** @noinspection StaticNonFinalField */ + private static volatile int nextId = 0; + + private final Object lock = new Object(); + private final int id = nextId++; + private final String threadName = "SocketConnector-" + id; + private SocketConnectorConfig defaultConfig = new SocketConnectorConfig(); + private final Queue connectQueue = new Queue(); + private final SocketIoProcessor[] ioProcessors; + private final int processorCount; + private final Executor executor; + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + private int workerTimeout = 60; // 1 min. + private Socket _openSocket = null; + + /** Create a connector with a single processing thread using a NewThreadExecutor */ + public ExistingSocketConnector() + { + this(1, new NewThreadExecutor()); + } + + /** + * Create a connector with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public ExistingSocketConnector(int processorCount, Executor executor) + { + if (processorCount < 1) + { + throw new IllegalArgumentException("Must have at least one processor"); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new SocketIoProcessor[processorCount]; + + for (int i = 0; i < processorCount; i++) + { + ioProcessors[i] = new SocketIoProcessor("SocketConnectorIoProcessor-" + id + "." + i, executor); + } + } + + /** + * How many seconds to keep the connection thread alive between connection requests + * + * @return Number of seconds to keep connection thread alive + */ + public int getWorkerTimeout() + { + return workerTimeout; + } + + /** + * Set how many seconds the connection worker thread should remain alive once idle before terminating itself. + * + * @param workerTimeout Number of seconds to keep thread alive. Must be >=0 + */ + public void setWorkerTimeout(int workerTimeout) + { + if (workerTimeout < 0) + { + throw new IllegalArgumentException("Must be >= 0"); + } + this.workerTimeout = workerTimeout; + } + + public ConnectFuture connect(SocketAddress address, IoHandler handler, IoServiceConfig config) + { + return connect(address, null, handler, config); + } + + public ConnectFuture connect(SocketAddress address, SocketAddress localAddress, + IoHandler handler, IoServiceConfig config) + { + /** Changes here from the Mina OpenSocketConnector. + * Ignoreing all address as they are not needed */ + + if (handler == null) + { + throw new NullPointerException("handler"); + } + + + if (config == null) + { + config = getDefaultConfig(); + } + + if (_openSocket == null) + { + throw new IllegalArgumentException("Specifed Socket not active"); + } + + boolean success = false; + + try + { + DefaultConnectFuture future = new DefaultConnectFuture(); + newSession(_openSocket, handler, config, future); + success = true; + return future; + } + catch (IOException e) + { + return DefaultConnectFuture.newFailedFuture(e); + } + finally + { + if (!success && _openSocket != null) + { + try + { + _openSocket.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + public IoServiceConfig getDefaultConfig() + { + return defaultConfig; + } + + /** + * Sets the config this connector will use by default. + * + * @param defaultConfig the default config. + * + * @throws NullPointerException if the specified value is <code>null</code>. + */ + public void setDefaultConfig(SocketConnectorConfig defaultConfig) + { + if (defaultConfig == null) + { + throw new NullPointerException("defaultConfig"); + } + this.defaultConfig = defaultConfig; + } + + private synchronized void startupWorker() throws IOException + { + if (worker == null) + { + selector = Selector.open(); + worker = new Worker(); + executor.execute(new NamePreservingRunnable(worker)); + } + } + + private void registerNew() + { + if (connectQueue.isEmpty()) + { + return; + } + + for (; ;) + { + ConnectionRequest req; + synchronized (connectQueue) + { + req = (ConnectionRequest) connectQueue.pop(); + } + + if (req == null) + { + break; + } + + SocketChannel ch = req.channel; + try + { + ch.register(selector, SelectionKey.OP_CONNECT, req); + } + catch (IOException e) + { + req.setException(e); + } + } + } + + private void processSessions(Set keys) + { + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isConnectable()) + { + continue; + } + + SocketChannel ch = (SocketChannel) key.channel(); + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + boolean success = false; + try + { + ch.finishConnect(); + newSession(ch, entry.handler, entry.config, entry); + success = true; + } + catch (Throwable e) + { + entry.setException(e); + } + finally + { + key.cancel(); + if (!success) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + keys.clear(); + } + + private void processTimedOutSessions(Set keys) + { + long currentTime = System.currentTimeMillis(); + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isValid()) + { + continue; + } + + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + if (currentTime >= entry.deadline) + { + entry.setException(new ConnectException()); + try + { + key.channel().close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + key.cancel(); + } + } + } + } + + private void newSession(Socket socket, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + SocketSessionImpl session = new SocketSessionImpl(this, + nextProcessor(), + getListeners(), + config, + socket.getChannel(), + handler, + socket.getRemoteSocketAddress()); + + newSession(session, config, connectFuture); + } + + private void newSession(SocketChannel ch, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + + { + SocketSessionImpl session = new SocketSessionImpl(this, + nextProcessor(), + getListeners(), + config, + ch, + handler, + ch.socket().getRemoteSocketAddress()); + + newSession(session, config, connectFuture); + } + + private void newSession(SocketSessionImpl session, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + try + { + getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getThreadModel().buildFilterChain(session.getFilterChain()); + } + catch (Throwable e) + { + throw (IOException) new IOException("Failed to create a session.").initCause(e); + } + session.getIoProcessor().addNew(session); + connectFuture.setSession(session); + } + + private SocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + public void setOpenSocket(Socket openSocket) + { + _openSocket = openSocket; + } + + private class Worker implements Runnable + { + private long lastActive = System.currentTimeMillis(); + + public void run() + { + Thread.currentThread().setName(ExistingSocketConnector.this.threadName); + + for (; ;) + { + try + { + int nKeys = selector.select(1000); + + registerNew(); + + if (nKeys > 0) + { + processSessions(selector.selectedKeys()); + } + + processTimedOutSessions(selector.keys()); + + if (selector.keys().isEmpty()) + { + if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) + { + synchronized (lock) + { + if (selector.keys().isEmpty() && + connectQueue.isEmpty()) + { + worker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + break; + } + } + } + } + else + { + lastActive = System.currentTimeMillis(); + } + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + } + } + + private class ConnectionRequest extends DefaultConnectFuture + { + private final SocketChannel channel; + private final long deadline; + private final IoHandler handler; + private final IoServiceConfig config; + + private ConnectionRequest(SocketChannel channel, IoHandler handler, IoServiceConfig config) + { + this.channel = channel; + long timeout; + if (config instanceof IoConnectorConfig) + { + timeout = ((IoConnectorConfig) config).getConnectTimeoutMillis(); + } + else + { + timeout = ((IoConnectorConfig) getDefaultConfig()).getConnectTimeoutMillis(); + } + this.deadline = System.currentTimeMillis() + timeout; + this.handler = handler; + this.config = config; + } + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java b/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java index 6bae0166d1..05ac3dca9e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java @@ -39,4 +39,9 @@ public class AMQAuthenticationException extends AMQException { super(error, msg, cause); } + public boolean isHardError() + { + return true; + } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java index b35f73d84a..f051450260 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java @@ -58,8 +58,9 @@ public class AMQBrokerDetails implements BrokerDetails if (transport != null) { //todo this list of valid transports should be enumerated somewhere - if ((!(transport.equalsIgnoreCase("vm") || - transport.equalsIgnoreCase("tcp")))) + if ((!(transport.equalsIgnoreCase(BrokerDetails.VM) || + transport.equalsIgnoreCase(BrokerDetails.TCP) || + transport.equalsIgnoreCase(BrokerDetails.SOCKET)))) { if (transport.equalsIgnoreCase("localhost")) { @@ -164,7 +165,10 @@ public class AMQBrokerDetails implements BrokerDetails } else { - setPort(port); + if (!_transport.equalsIgnoreCase(SOCKET)) + { + setPort(port); + } } String queryString = connection.getQuery(); @@ -271,13 +275,16 @@ public class AMQBrokerDetails implements BrokerDetails sb.append(_transport); sb.append("://"); - if (!(_transport.equalsIgnoreCase("vm"))) + if (!(_transport.equalsIgnoreCase(VM))) { sb.append(_host); } - sb.append(':'); - sb.append(_port); + if (!(_transport.equalsIgnoreCase(SOCKET))) + { + sb.append(':'); + sb.append(_port); + } sb.append(printOptionsURL()); diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java index 8ab16c65c0..3969eef8a9 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java @@ -1,5 +1,5 @@ /* - * +* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -20,17 +20,31 @@ */ package org.apache.qpid.client; -import java.io.IOException; -import java.net.ConnectException; -import java.nio.channels.UnresolvedAddressException; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import org.apache.qpid.AMQConnectionFailureException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQProtocolException; +import org.apache.qpid.AMQUndeliveredException; +import org.apache.qpid.AMQUnresolvedAddressException; +import org.apache.qpid.client.failover.FailoverException; +import org.apache.qpid.client.failover.FailoverProtectedOperation; +import org.apache.qpid.client.failover.FailoverRetrySupport; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.*; +import org.apache.qpid.jms.BrokerDetails; +import org.apache.qpid.jms.ChannelLimitReachedException; +import org.apache.qpid.jms.Connection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.jms.ConnectionURL; +import org.apache.qpid.jms.FailoverPolicy; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpidity.transport.TransportConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.jms.ConnectionConsumer; import javax.jms.ConnectionMetaData; @@ -49,25 +63,117 @@ import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.Referenceable; import javax.naming.StringRefAddr; - -import org.apache.qpid.*; -import org.apache.qpid.client.failover.FailoverException; -import org.apache.qpid.client.protocol.AMQProtocolHandler; -import org.apache.qpid.exchange.ExchangeDefaults; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.jms.BrokerDetails; -import org.apache.qpid.jms.Connection; -import org.apache.qpid.jms.ConnectionListener; -import org.apache.qpid.jms.ConnectionURL; -import org.apache.qpid.jms.FailoverPolicy; -import org.apache.qpid.protocol.AMQConstant; -import org.apache.qpid.url.URLSyntaxException; -import org.apache.qpidity.transport.TransportConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.ConnectException; +import java.nio.channels.UnresolvedAddressException; +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class AMQConnection extends Closeable implements Connection, QueueConnection, TopicConnection, Referenceable { + public static final class ChannelToSessionMap + { + private final AMQSession[] _fastAccessSessions = new AMQSession[16]; + private final LinkedHashMap<Integer, AMQSession> _slowAccessSessions = new LinkedHashMap<Integer, AMQSession>(); + private int _size = 0; + private static final int FAST_CHANNEL_ACCESS_MASK = 0xFFFFFFF0; + + public AMQSession get(int channelId) + { + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + return _fastAccessSessions[channelId]; + } + else + { + return _slowAccessSessions.get(channelId); + } + } + + public AMQSession put(int channelId, AMQSession session) + { + AMQSession oldVal; + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + oldVal = _fastAccessSessions[channelId]; + _fastAccessSessions[channelId] = session; + } + else + { + oldVal = _slowAccessSessions.put(channelId, session); + } + if((oldVal != null) && (session == null)) + { + _size--; + } + else if((oldVal == null) && (session != null)) + { + _size++; + } + + return session; + + } + + + public AMQSession remove(int channelId) + { + AMQSession session; + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + session = _fastAccessSessions[channelId]; + _fastAccessSessions[channelId] = null; + } + else + { + session = _slowAccessSessions.remove(channelId); + } + + if(session != null) + { + _size--; + } + return session; + + } + + public Collection<AMQSession> values() + { + ArrayList<AMQSession> values = new ArrayList<AMQSession>(size()); + + for(int i = 0; i < 16; i++) + { + if(_fastAccessSessions[i] != null) + { + values.add(_fastAccessSessions[i]); + } + } + values.addAll(_slowAccessSessions.values()); + + return values; + } + + public int size() + { + return _size; + } + + public void clear() + { + _size = 0; + _slowAccessSessions.clear(); + for(int i = 0; i<16; i++) + { + _fastAccessSessions[i] = null; + } + } + } + + private static final Logger _logger = LoggerFactory.getLogger(AMQConnection.class); protected AtomicInteger _idFactory = new AtomicInteger(0); @@ -76,7 +182,9 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect * 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. */ - protected final Object _failoverMutex = new Object(); + private final Object _failoverMutex = new Object(); + + private final Object _sessionCreationLock = new Object(); /** * A channel is roughly analogous to a session. The server can negotiate the maximum number of channels per session @@ -85,7 +193,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect protected long _maximumChannelCount; /** The maximum size of frame supported by the server */ - protected long _maximumFrameSize; + private long _maximumFrameSize; /** * The protocol handler dispatches protocol events for this connection. For example, when the connection is dropped @@ -95,30 +203,31 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect protected AMQProtocolHandler _protocolHandler; /** Maps from session id (Integer) to AMQSession instance */ - protected final Map<Integer, AMQSession> _sessions = new LinkedHashMap<Integer, AMQSession>(); + private final ChannelToSessionMap _sessions = new ChannelToSessionMap(); - protected String _clientName; + private String _clientName; /** The user name to use for authentication */ - protected String _username; + private String _username; /** The password to use for authentication */ - protected String _password; + private String _password; /** The virtual path to connect to on the AMQ server */ - protected String _virtualHost; + private String _virtualHost; + protected ExceptionListener _exceptionListener; - protected ConnectionListener _connectionListener; + private ConnectionListener _connectionListener; - protected ConnectionURL _connectionURL; + private ConnectionURL _connectionURL; /** * Whether this connection is started, i.e. whether messages are flowing to consumers. It has no meaning for message * publication. */ - protected boolean _started; + protected volatile boolean _started; /** Policy dictating how to failover */ protected FailoverPolicy _failoverPolicy; @@ -136,22 +245,23 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect /* * The connection meta data */ - protected QpidConnectionMetaData _connectionMetaData; + private QpidConnectionMetaData _connectionMetaData; /** Configuration info for SSL */ - protected SSLConfiguration _sslConfiguration; + private SSLConfiguration _sslConfiguration; - protected AMQShortString _defaultTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; - protected AMQShortString _defaultQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; - protected AMQShortString _temporaryTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; - protected AMQShortString _temporaryQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; + private AMQShortString _defaultTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; + private AMQShortString _defaultQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; + private AMQShortString _temporaryTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; + private AMQShortString _temporaryQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; /** Thread Pool for executing connection level processes. Such as returning bounced messages. */ - protected final ExecutorService _taskPool = Executors.newCachedThreadPool(); - protected static final long DEFAULT_TIMEOUT = 1000 * 30; + private final ExecutorService _taskPool = Executors.newCachedThreadPool(); + private static final long DEFAULT_TIMEOUT = 1000 * 30; + private ProtocolVersion _protocolVersion = ProtocolVersion.v0_9; // FIXME TGM, shouldn't need this protected AMQConnectionDelegate _delegate; - + // this connection maximum number of prefetched messages private long _maxPrefetch; @@ -259,7 +369,8 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } _failoverPolicy = new FailoverPolicy(connectionURL); - if (_failoverPolicy.getCurrentBrokerDetails().getTransport().equals(BrokerDetails.VM)) + BrokerDetails brokerDetails = _failoverPolicy.getNextBrokerDetails(); + if (brokerDetails.getTransport().equals(BrokerDetails.VM)) { _delegate = new AMQConnectionDelegate_0_8(this); } @@ -272,6 +383,26 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _delegate = new AMQConnectionDelegate_0_10(this); } + final ArrayList<JMSException> exceptions = new ArrayList<JMSException>(); + + class Listener implements ExceptionListener + { + public void onException(JMSException e) + { + exceptions.add(e); + } + } + + try + { + setExceptionListener(new Listener()); + } + catch (JMSException e) + { + // Shouldn't happen + throw new AMQException(null, null, e); + } + if (_logger.isInfoEnabled()) { _logger.info("Connection:" + connectionURL); @@ -288,6 +419,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _clientName = connectionURL.getClientName(); _username = connectionURL.getUsername(); _password = connectionURL.getPassword(); + setVirtualHost(connectionURL.getVirtualHost()); if (connectionURL.getDefaultQueueExchangeName() != null) @@ -311,7 +443,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } - _protocolHandler = new AMQProtocolHandler(this); + _protocolHandler = new AMQProtocolHandler(this); // We are not currently connected _connected = false; @@ -319,11 +451,13 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect Exception lastException = new Exception(); lastException.initCause(new ConnectException()); - while (!_connected && _failoverPolicy.failoverAllowed()) + // TMG FIXME this seems... wrong... + boolean retryAllowed = true; + while (!_connected && retryAllowed ) { try { - makeBrokerConnection(_failoverPolicy.getNextBrokerDetails()); + makeBrokerConnection(brokerDetails); lastException = null; _connected = true; } @@ -346,8 +480,10 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (_logger.isInfoEnabled()) { _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), - e.getCause()); + e.getCause()); } + retryAllowed = _failoverPolicy.failoverAllowed(); + brokerDetails = _failoverPolicy.getNextBrokerDetails(); } } @@ -359,8 +495,31 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (!_connected) { String message = null; + try + { + Thread.sleep(150); + } + catch (InterruptedException e) + { + // Eat it, we've hopefully got all the exceptions if this happened + } + if (exceptions.size() > 0) + { + JMSException e = exceptions.get(0); + int code = -1; + try + { + code = new Integer(e.getErrorCode()).intValue(); + } + catch (NumberFormatException nfe) + { + // Ignore this, we have some error codes and messages swapped around + } - if (lastException != null) + throw new AMQConnectionFailureException(AMQConstant.getConstant(code), + e.getMessage(), e); + } + else if (lastException != null) { if (lastException.getCause() != null) { @@ -397,7 +556,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (e.getCause() != null) { e.initCause(lastException); - } + } } throw e; @@ -406,6 +565,18 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _connectionMetaData = new QpidConnectionMetaData(this); } + protected boolean checkException(Throwable thrown) + { + Throwable cause = thrown.getCause(); + + if (cause == null) + { + cause = thrown; + } + + return ((cause instanceof ConnectException) || (cause instanceof UnresolvedAddressException)); + } + private void getDelegate() throws AMQProtocolException { try @@ -444,20 +615,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _virtualHost = virtualHost; } - protected boolean checkException(Throwable thrown) - { - Throwable cause = thrown.getCause(); - - if (cause == null) - { - cause = thrown; - } - - return ((cause instanceof ConnectException) || (cause instanceof UnresolvedAddressException)); - } - - - public boolean attemptReconnection(String host, int port) { BrokerDetails bd = new AMQBrokerDetails(host, port, _sslConfiguration); @@ -559,8 +716,55 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode, final int prefetchHigh, final int prefetchLow) throws JMSException { + synchronized (_sessionCreationLock) + { + checkNotClosed(); + return _delegate.createSession(transacted, acknowledgeMode, prefetchHigh, prefetchLow); + } + } + + private void createChannelOverWire(int channelId, int prefetchHigh, int prefetchLow, boolean transacted) + throws AMQException, FailoverException + { + + ChannelOpenBody channelOpenBody = getProtocolHandler().getMethodRegistry().createChannelOpenBody(null); + + // TODO: Be aware of possible changes to parameter order as versions change. + + _protocolHandler.syncWrite(channelOpenBody.generateFrame(channelId), ChannelOpenOkBody.class); + + BasicQosBody basicQosBody = getProtocolHandler().getMethodRegistry().createBasicQosBody(0, prefetchHigh, false); + + // todo send low water mark when protocol allows. + // todo Be aware of possible changes to parameter order as versions change. + _protocolHandler.syncWrite(basicQosBody.generateFrame(channelId), BasicQosOkBody.class); + + if (transacted) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Issuing TxSelect for " + channelId); + } - return _delegate.createSession(transacted, acknowledgeMode, prefetchHigh, prefetchLow); + TxSelectBody body = getProtocolHandler().getMethodRegistry().createTxSelectBody(); + + // TODO: Be aware of possible changes to parameter order as versions change. + _protocolHandler.syncWrite(body.generateFrame(channelId), TxSelectOkBody.class); + } + } + + private void reopenChannel(int channelId, int prefetchHigh, int prefetchLow, boolean transacted) + throws AMQException, FailoverException + { + try + { + createChannelOverWire(channelId, prefetchHigh, prefetchLow, transacted); + } + catch (AMQException e) + { + deregisterSession(channelId); + throw new AMQException(null, "Error reopening channel " + channelId + " after failover: " + e, e); + } } public void setFailoverPolicy(FailoverPolicy policy) @@ -659,10 +863,10 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (!_started) { _started = true; - final Iterator it = _sessions.entrySet().iterator(); + final Iterator it = _sessions.values().iterator(); while (it.hasNext()) { - final AMQSession s = (AMQSession) ((Map.Entry) it.next()).getValue(); + final AMQSession s = (AMQSession) (it.next()); try { s.start(); @@ -673,6 +877,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } } + } } @@ -704,49 +909,89 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect public void close(long timeout) throws JMSException { - synchronized (getFailoverMutex()) + close(new ArrayList<AMQSession>(_sessions.values()),timeout); + } + + public void close(List<AMQSession> sessions, long timeout) throws JMSException + { + synchronized(_sessionCreationLock) { - if (!_closed.getAndSet(true)) + if(!sessions.isEmpty()) { - try + AMQSession session = sessions.remove(0); + synchronized(session.getMessageDeliveryLock()) { - long startCloseTime = System.currentTimeMillis(); - - _taskPool.shutdown(); - closeAllSessions(null, timeout, startCloseTime); - - if (!_taskPool.isTerminated()) + close(sessions, timeout); + } + } + else + { + if (!_closed.getAndSet(true)) + { + synchronized (getFailoverMutex()) { try { - // adjust timeout - long taskPoolTimeout = adjustTimeout(timeout, startCloseTime); + long startCloseTime = System.currentTimeMillis(); + + closeAllSessions(null, timeout, startCloseTime); + + //This MUST occur after we have successfully closed all Channels/Sessions + _taskPool.shutdown(); - _taskPool.awaitTermination(taskPoolTimeout, TimeUnit.MILLISECONDS); + if (!_taskPool.isTerminated()) + { + try + { + // adjust timeout + long taskPoolTimeout = adjustTimeout(timeout, startCloseTime); + + _taskPool.awaitTermination(taskPoolTimeout, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) + { + _logger.info("Interrupted while shutting down connection thread pool."); + } + } + + // adjust timeout + timeout = adjustTimeout(timeout, startCloseTime); + _delegate.closeConneciton(timeout); + + //If the taskpool hasn't shutdown by now then give it shutdownNow. + // This will interupt any running tasks. + if (!_taskPool.isTerminated()) + { + List<Runnable> tasks = _taskPool.shutdownNow(); + for (Runnable r : tasks) + { + _logger.warn("Connection close forced taskpool to prevent execution:" + r); + } + } } - catch (InterruptedException e) + catch (AMQException e) { - _logger.info("Interrupted while shutting down connection thread pool."); + JMSException jmse = new JMSException("Error closing connection: " + e); + jmse.setLinkedException(e); + throw jmse; } } - - // adjust timeout - timeout = adjustTimeout(timeout, startCloseTime); - _delegate.closeConneciton(timeout); - //_protocolHandler.closeConnection(timeout); - - } - catch (AMQException e) - { - JMSException jmse = new JMSException("Error closing connection: " + e); - jmse.setLinkedException(e); - throw jmse; } } } } + private long adjustTimeout(long timeout, long startTime) + { + long now = System.currentTimeMillis(); + timeout -= now - startTime; + if (timeout < 0) + { + timeout = 0; + } + return timeout; + } /** * Marks all sessions and their children as closed without sending any protocol messages. Useful when you need to @@ -811,19 +1056,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } } - - private long adjustTimeout(long timeout, long startTime) - { - long now = System.currentTimeMillis(); - timeout -= now - startTime; - if (timeout < 0) - { - timeout = 0; - } - - return timeout; - } - public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { @@ -889,11 +1121,11 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect return _maximumFrameSize; } - public Map getSessions() + public ChannelToSessionMap getSessions() { return _sessions; } - + public String getUsername() { return _username; @@ -1017,18 +1249,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect /** * Invoked by the AMQProtocolSession when a protocol session exception has occurred. This method sends the exception - * to a JMS exception liste - { - ArrayList sessions = new ArrayList(_sessions.values()); - _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: removeKey? - for (Iterator it = sessions.iterator(); it.hasNext();) - { - AMQSession s = (AMQSession) it.next(); - // _protocolHandler.addSessionByChannel(s.getChannelId(), s); - reopenChannel(s.getChannelId(), s.getDefaultPrefetchHigh(), s.getDefaultPrefetchLow(), s.getTransacted()); - s.resubscribe(); - } - }ner, if configured, and propagates the exception to sessions, which in turn will + * 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 @@ -1085,8 +1306,12 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect { _exceptionListener.onException(je); } + else + { + _logger.error("Throwable Received but no listener set: " + cause.getMessage()); + } - if (!(cause instanceof AMQUndeliveredException) && !(cause instanceof AMQAuthenticationException)) + if (hardError(cause)) { try { @@ -1110,6 +1335,16 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } } + private boolean hardError(Throwable cause) + { + if (cause instanceof AMQException) + { + return ((AMQException)cause).isHardError(); + } + + return true; + } + void registerSession(int channelId, AMQSession session) { _sessions.put(channelId, session); @@ -1120,6 +1355,24 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _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 resubscribeSesssions() throws JMSException, AMQException, FailoverException + { + ArrayList sessions = new ArrayList(_sessions.values()); + _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: removeKey? + for (Iterator it = sessions.iterator(); it.hasNext();) + { + AMQSession s = (AMQSession) it.next(); + // _protocolHandler.addSessionByChannel(s.getChannelId(), s); + reopenChannel(s.getChannelId(), s.getDefaultPrefetchHigh(), s.getDefaultPrefetchLow(), s.getTransacted()); + s.resubscribe(); + s.setFlowControl(true); + } + } + public String toString() { StringBuffer buf = new StringBuffer("AMQConnection:\n"); @@ -1207,6 +1460,22 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect return _sessions.get(channelId); } + public ProtocolVersion getProtocolVersion() + { + return _protocolVersion; + } + + public void setProtocolVersion(ProtocolVersion protocolVersion) + { + _protocolVersion = protocolVersion; + _protocolHandler.getProtocolSession().setProtocolVersion(protocolVersion); + } + + public boolean isFailingOver() + { + return (_protocolHandler.getFailoverLatch() != null); + } + /** * Get the maximum number of messages that this connection can pre-fetch. * diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java index c22fcc3c7a..637cff5b7e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java @@ -178,31 +178,25 @@ public class AMQConnectionDelegate_0_8 implements AMQConnectionDelegate private void createChannelOverWire(int channelId, int prefetchHigh, int prefetchLow, boolean transacted) throws AMQException, FailoverException { - + ChannelOpenBody channelOpenBody = _conn.getProtocolHandler().getMethodRegistry().createChannelOpenBody(null); // TODO: Be aware of possible changes to parameter order as versions change. - - _conn._protocolHandler.syncWrite(ChannelOpenBody.createAMQFrame(channelId, _conn._protocolHandler.getProtocolMajorVersion(), - _conn._protocolHandler.getProtocolMinorVersion(), null), // outOfBand - ChannelOpenOkBody.class); + _conn._protocolHandler.syncWrite(channelOpenBody.generateFrame(channelId), ChannelOpenOkBody.class); // todo send low water mark when protocol allows. // todo Be aware of possible changes to parameter order as versions change. - _conn._protocolHandler.syncWrite(BasicQosBody.createAMQFrame(channelId, _conn._protocolHandler.getProtocolMajorVersion(), - _conn._protocolHandler.getProtocolMinorVersion(), false, // global - prefetchHigh, // prefetchCount - 0), // prefetchSize - BasicQosOkBody.class); - + BasicQosBody basicQosBody = _conn.getProtocolHandler().getMethodRegistry().createBasicQosBody(0,prefetchHigh,false); + _conn._protocolHandler.syncWrite(basicQosBody.generateFrame(channelId),BasicQosOkBody.class); + if (transacted) { if (_logger.isDebugEnabled()) { _logger.debug("Issuing TxSelect for " + channelId); } - + TxSelectBody body = _conn.getProtocolHandler().getMethodRegistry().createTxSelectBody(); + // TODO: Be aware of possible changes to parameter order as versions change. - _conn._protocolHandler.syncWrite(TxSelectBody.createAMQFrame(channelId, _conn._protocolHandler.getProtocolMajorVersion(), - _conn._protocolHandler.getProtocolMinorVersion()), TxSelectOkBody.class); + _conn._protocolHandler.syncWrite(body.generateFrame(channelId), TxSelectOkBody.class); } } @@ -212,7 +206,7 @@ public class AMQConnectionDelegate_0_8 implements AMQConnectionDelegate */ public void resubscribeSessions() throws JMSException, AMQException, FailoverException { - ArrayList sessions = new ArrayList(_conn._sessions.values()); + ArrayList sessions = new ArrayList(_conn.getSessions().values()); _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: removeKey? for (Iterator it = sessions.iterator(); it.hasNext();) { diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java index 3fcec67fe1..fd8063e99b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java @@ -7,9 +7,9 @@ * to you 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 @@ -23,6 +23,7 @@ package org.apache.qpid.client; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Hashtable; +import java.util.UUID; import javax.jms.*; import javax.naming.Context; @@ -259,7 +260,7 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF } catch (UnknownHostException e) { - return null; + return "UnknownHost" + UUID.randomUUID(); } } @@ -352,7 +353,9 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF * @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 @@ -408,8 +411,9 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF public Reference getReference() throws NamingException { - return new Reference(AMQConnectionFactory.class.getName(), - new StringRefAddr(AMQConnectionFactory.class.getName(), _connectionDetails.getURL()), + 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/main/java/org/apache/qpid/client/AMQConnectionURL.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java index 770fab7a81..02959aff3b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java @@ -27,6 +27,7 @@ import java.util.Map; import org.apache.qpid.client.url.URLParser; import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ProtocolVersion; import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.jms.ConnectionURL; import org.apache.qpid.url.URLHelper; diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java b/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java index e02b3d6643..52080112c9 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java @@ -56,7 +56,9 @@ public abstract class AMQDestination implements Destination, Referenceable private String _url; private AMQShortString _urlAsShortString; - private boolean _validated; + private boolean _checkedForQueueBinding; + + private boolean _exchangeExistsChecked; private byte[] _byteEncoding; private static final int IS_DURABLE_MASK = 0x1; @@ -234,14 +236,25 @@ public abstract class AMQDestination implements Destination, Referenceable } - public boolean isValidated() + public boolean isCheckedForQueueBinding() + { + return _checkedForQueueBinding; + } + + public void setCheckedForQueueBinding(boolean checkedForQueueBinding) + { + _checkedForQueueBinding = checkedForQueueBinding; + } + + + public boolean isExchangeExistsChecked() { - return _validated; + return _exchangeExistsChecked; } - public void setValidated(boolean validated) + public void setExchangeExistsChecked(final boolean exchangeExistsChecked) { - _validated = validated; + _exchangeExistsChecked = exchangeExistsChecked; } public String toURL() diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java b/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java index b27dfc6dcb..b9e9a33cd6 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java @@ -49,6 +49,6 @@ public class AMQHeadersExchange extends AMQDestination //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; + return getAMQQueueName() == null; } } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java b/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java index f56477c0fc..08fd49286b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java @@ -22,6 +22,7 @@ package org.apache.qpid.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.qpid.AMQException; import javax.jms.IllegalStateException; import javax.jms.JMSException; @@ -50,7 +51,9 @@ public class AMQQueueBrowser implements QueueBrowser _messageSelector = ((messageSelector == null) || (messageSelector.trim().length() == 0)) ? null : messageSelector; // Create Consumer to verify message selector. BasicMessageConsumer consumer = - (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false); + (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false); + // Close this consumer as we are not looking to consume only to establish that, at least for now, + // the QB can be created consumer.close(); } @@ -88,36 +91,37 @@ public class AMQQueueBrowser implements QueueBrowser checkState(); final BasicMessageConsumer consumer = (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false); + _consumers.add(consumer); return new Enumeration() + { + + Message _nextMessage = consumer == null ? null : consumer.receive(1000); + + public boolean hasMoreElements() { - Message _nextMessage = consumer.receive(1000); + _logger.info("QB:hasMoreElements:" + (_nextMessage != null)); + return (_nextMessage != null); + } - public boolean hasMoreElements() + public Object nextElement() + { + Message msg = _nextMessage; + try { - _logger.info("QB:hasMoreElements:" + (_nextMessage != null)); - return (_nextMessage != null); + _logger.info("QB:nextElement about to receive"); + _nextMessage = consumer.receive(1000); + _logger.info("QB:nextElement received:" + _nextMessage); } - - public Object nextElement() + catch (JMSException e) { - Message msg = _nextMessage; - try - { - _logger.info("QB:nextElement about to receive"); - _nextMessage = consumer.receive(1000); - _logger.info("QB:nextElement received:" + _nextMessage); - } - catch (JMSException e) - { - _logger.warn("Exception caught while queue browsing", e); - _nextMessage = null; - } - - return msg; + _logger.warn("Exception caught while queue browsing", e); + _nextMessage = null; } - }; + return msg; + } + }; } public void close() throws JMSException diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java index ee55743d0e..404c0cd381 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java @@ -20,14 +20,15 @@ */ package org.apache.qpid.client; - import java.io.Serializable; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -57,7 +58,9 @@ import javax.jms.Topic; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.jms.TopicSubscriber; +import javax.jms.TransactionRolledBackException; +import org.apache.qpid.AMQDisconnectedException; import org.apache.qpid.AMQException; import org.apache.qpid.AMQInvalidArgumentException; import org.apache.qpid.AMQInvalidRoutingKeyException; @@ -77,11 +80,12 @@ import org.apache.qpid.client.message.ReturnMessage; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.util.FlowControllingBlockingQueue; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; import org.apache.qpid.jms.Session; import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQMethodEvent; import org.apache.qpid.url.AMQBindingURL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,6 +110,86 @@ import org.slf4j.LoggerFactory; */ public abstract class AMQSession extends Closeable implements Session, QueueSession, TopicSession { + public static final class IdToConsumerMap + { + private final BasicMessageConsumer[] _fastAccessConsumers = new BasicMessageConsumer[16]; + private final ConcurrentHashMap<Integer, BasicMessageConsumer> _slowAccessConsumers = new ConcurrentHashMap<Integer, BasicMessageConsumer>(); + + + public BasicMessageConsumer get(int id) + { + if((id & 0xFFFFFFF0) == 0) + { + return _fastAccessConsumers[id]; + } + else + { + return _slowAccessConsumers.get(id); + } + } + + public BasicMessageConsumer put(int id, BasicMessageConsumer consumer) + { + BasicMessageConsumer oldVal; + if((id & 0xFFFFFFF0) == 0) + { + oldVal = _fastAccessConsumers[id]; + _fastAccessConsumers[id] = consumer; + } + else + { + oldVal = _slowAccessConsumers.put(id, consumer); + } + + return consumer; + + } + + + public BasicMessageConsumer remove(int id) + { + BasicMessageConsumer consumer; + if((id & 0xFFFFFFF0) == 0) + { + consumer = _fastAccessConsumers[id]; + _fastAccessConsumers[id] = null; + } + else + { + consumer = _slowAccessConsumers.remove(id); + } + + return consumer; + + } + + public Collection<BasicMessageConsumer> values() + { + ArrayList<BasicMessageConsumer> values = new ArrayList<BasicMessageConsumer>(); + + for(int i = 0; i < 16; i++) + { + if(_fastAccessConsumers[i] != null) + { + values.add(_fastAccessConsumers[i]); + } + } + values.addAll(_slowAccessConsumers.values()); + + return values; + } + + + public void clear() + { + _slowAccessConsumers.clear(); + for(int i = 0; i<16; i++) + { + _fastAccessConsumers[i] = null; + } + } + } + /** Used for debugging. */ private static final Logger _logger = LoggerFactory.getLogger(AMQSession.class); @@ -155,7 +239,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess protected boolean _transacted; /** Holds the sessions acknowledgement mode. */ - protected int _acknowledgeMode; + protected final int _acknowledgeMode; /** Holds this session unique identifier, used to distinguish it from other sessions. */ protected int _channelId; @@ -231,8 +315,16 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess * Maps from identifying tags to message consumers, in order to pass dispatch incoming messages to the right * consumer. */ - protected Map<AMQShortString, BasicMessageConsumer> _consumers = - new ConcurrentHashMap<AMQShortString, BasicMessageConsumer>(); + protected final IdToConsumerMap _consumers = new IdToConsumerMap(); + + //Map<AMQShortString, BasicMessageConsumer> _consumers = + //new ConcurrentHashMap<AMQShortString, BasicMessageConsumer>(); + + /** + * Contains a list of consumers which have been removed but which might still have + * messages to acknowledge, eg in client ack or transacted modes + */ + private CopyOnWriteArrayList<BasicMessageConsumer> _removedConsumers = new CopyOnWriteArrayList<BasicMessageConsumer>(); /** Provides a count of consumers on destinations, in order to be able to know if a destination has consumers. */ private ConcurrentHashMap<Destination, AtomicInteger> _destinationConsumerCount = @@ -284,6 +376,32 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess protected final boolean _strictAMQPFATAL; private final Object _messageDeliveryLock = new Object(); + /** Session state : used to detect if commit is a) required b) allowed , i.e. does the tx span failover. */ + private boolean _dirty; + /** Has failover occured on this session */ + private boolean _failedOver; + + + + private static final class FlowControlIndicator + { + private volatile boolean _flowControl = true; + + public synchronized void setFlowControl(boolean flowControl) + { + _flowControl= flowControl; + notify(); + } + + public boolean getFlowControl() + { + return _flowControl; + } + } + + /** Flow control */ + private FlowControlIndicator _flowControl = new FlowControlIndicator(); + /** * Creates a new session on a connection. * @@ -330,24 +448,20 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { public void aboveThreshold(int currentValue) { - if (_acknowledgeMode == NO_ACKNOWLEDGE) - { _logger.debug( "Above threshold(" + _defaultPrefetchHighMark + ") so suspending channel. Current value is " + currentValue); new Thread(new SuspenderRunner(true)).start(); - } + } public void underThreshold(int currentValue) { - if (_acknowledgeMode == NO_ACKNOWLEDGE) - { _logger.debug( "Below threshold(" + _defaultPrefetchLowMark + ") so unsuspending channel. Current value is " + currentValue); new Thread(new SuspenderRunner(false)).start(); - } + } }); } @@ -357,7 +471,22 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } - + /** + * Creates a new session on a connection with the default message factory factory. + * + * @param con The connection on which to create the session. + * @param channelId The unique identifier for the session. + * @param transacted Indicates whether or not the session is transactional. + * @param acknowledgeMode The acknoledgement mode for the session. + * @param defaultPrefetchHigh The maximum number of messages to prefetched before suspending the session. + * @param defaultPrefetchLow The number of prefetched messages at which to resume the session. + */ + AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetchHigh, + int defaultPrefetchLow) + { + this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetchHigh, + defaultPrefetchLow); + } // ===== JMS Session methods. @@ -371,6 +500,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess close(-1); } + public BytesMessage createBytesMessage() throws JMSException + { + checkNotClosed(); + return new JMSBytesMessage(); + } + /** * Acknowledges all unacknowledged messages on the session, for all message consumers on the session. * @@ -381,6 +516,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess if (isClosed()) { throw new IllegalStateException("Session is already closed"); + } + else if (hasFailedOver()) + { + throw new IllegalStateException("has failed over"); } while (true) @@ -405,6 +544,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public abstract void acknowledgeMessage(long deliveryTag, boolean multiple); + public MethodRegistry getMethodRegistry() + { + MethodRegistry methodRegistry = getProtocolHandler().getMethodRegistry(); + return methodRegistry; + } + /** * Binds the named queue, with the specified routing key, to the named exchange. * @@ -471,30 +616,26 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { if (_logger.isInfoEnabled()) { - _logger.info("Closing session: " + this );//+ ":" - // + Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 6)); + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + _logger.info("Closing session: " + this); // + ":" + // + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); } - if( _dispatcher != null ) - { - _dispatcher.setConnectionStopped(true); - } - synchronized (_messageDeliveryLock) + // Ensure we only try and close an open session. + if (!_closed.getAndSet(true)) { - - // 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()) { - // Ensure we only try and close an open session. - if (!_closed.getAndSet(true)) + // 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 (_messageDeliveryLock) { // we pass null since this is not an error case closeProducersAndConsumers(null); try { - sendClose(timeout); + sendClose(timeout); } catch (AMQException e) { @@ -527,25 +668,44 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public void closed(Throwable e) throws JMSException { - synchronized (_messageDeliveryLock) + // This method needs to be improved. Throwables only arrive here from the mina : exceptionRecived + // calls through connection.closeAllSessions which is also called by the public connection.close() + // with a null cause + // When we are closing the Session due to a protocol session error we simply create a new AMQException + // with the correct error code and text this is cleary WRONG as the instanceof check below will fail. + // We need to determin here if the connection should be + + if (e instanceof AMQDisconnectedException) + { + if (_dispatcher != null) + { + // Failover failed and ain't coming back. Knife the dispatcher. + _dispatcher.interrupt(); + } + } + + if (!_closed.getAndSet(true)) { 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 + synchronized (_messageDeliveryLock) { - amqe = new AMQException(null, "Closing session forcibly", e); - } + // 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 + AMQException amqe; + if (e instanceof AMQException) + { + amqe = (AMQException) e; + } + else + { + amqe = new AMQException("Closing session forcibly", e); + } + - _connection.deregisterSession(_channelId); - closeProducersAndConsumers(amqe); + _connection.deregisterSession(_channelId); + closeProducersAndConsumers(amqe); + } } } } @@ -565,10 +725,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public void commit() throws JMSException { - checkTransacted(); + checkTransacted(); try { + + // TGM FIXME: what about failover? // Acknowledge all delivered messages while (true) { @@ -580,7 +742,6 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess acknowledgeMessage(tag, false); } - // Commits outstanding messages and acknowledgments sendCommit(); } @@ -600,16 +761,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { // Remove the consumer from the map - BasicMessageConsumer consumer = (BasicMessageConsumer) _consumers.get(consumerTag); + BasicMessageConsumer consumer = _consumers.get(consumerTag.toIntValue()); if (consumer != null) { - // fixme this isn't right.. needs to check if _queue contains data for this consumer - if (consumer.isAutoClose()) // && _queue.isEmpty()) - { - consumer.closeWhenNoMessages(true); - } - - if (!consumer.isNoConsume()) + if (!consumer.isNoConsume()) // Normal Consumer { // Clean the Maps up first // Flush any pending messages for this consumerTag @@ -620,13 +775,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess else { _logger.info("Dispatcher is null so created stopped dispatcher"); - - startDistpatcherIfNecessary(true); + startDispatcherIfNecessary(true); } _dispatcher.rejectPending(consumer); } - else + else // Queue Browser { // Just close the consumer // fixme the CancelOK is being processed before the arriving messages.. @@ -634,13 +788,24 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // has yet to receive before the close comes in. // consumer.markClosed(); + + + + if (consumer.isAutoClose()) + { + // There is a small window where the message is between the two queues in the dispatcher. + if (consumer.isClosed()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing consumer:" + consumer.debugIdentity()); + } + + deregisterConsumer(consumer); + } + } } } - else - { - _logger.warn("Unable to confirm cancellation of consumer (" + consumerTag + "). Not found in consumer map."); - } - } public QueueBrowser createBrowser(Queue queue) throws JMSException @@ -675,21 +840,11 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess messageSelector, null, true, true); } - public BytesMessage createBytesMessage() throws JMSException - { - synchronized (_connection.getFailoverMutex()) - { - checkNotClosed(); - - return new JMSBytesMessage(); - } - } - public MessageConsumer createConsumer(Destination destination) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, false, null, null, + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, (destination instanceof Topic), null, null, false, false); } @@ -701,11 +856,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess false, false); } + public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, false, + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, (destination instanceof Topic), messageSelector, null, false, false); } @@ -714,16 +870,27 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { checkValidDestination(destination); - return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, false, + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, (destination instanceof Topic), messageSelector, null, false, false); } + + public MessageConsumer createExclusiveConsumer(Destination destination, String messageSelector, boolean noLocal) + throws JMSException + { + checkValidDestination(destination); + + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, true, + messageSelector, null, false, false); + } + + public MessageConsumer createConsumer(Destination destination, int prefetch, boolean noLocal, boolean exclusive, String selector) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, prefetch, prefetch, noLocal, exclusive, selector, null, false, false); + return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, null, false, false); } public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal, @@ -739,7 +906,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { checkValidDestination(destination); - return createConsumerImpl(destination, prefetch, prefetch, noLocal, exclusive, selector, rawSelector, false, false); + return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, rawSelector, false, false); } public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal, @@ -770,12 +937,8 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess public MapMessage createMapMessage() throws JMSException { - synchronized (_connection.getFailoverMutex()) - { - checkNotClosed(); - - return new JMSMapMessage(); - } + checkNotClosed(); + return new JMSMapMessage(); } public javax.jms.Message createMessage() throws JMSException @@ -785,12 +948,8 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess public ObjectMessage createObjectMessage() throws JMSException { - synchronized (_connection.getFailoverMutex()) - { - checkNotClosed(); - - return (ObjectMessage) new JMSObjectMessage(); - } + checkNotClosed(); + return (ObjectMessage) new JMSObjectMessage(); } public ObjectMessage createObjectMessage(Serializable object) throws JMSException @@ -827,7 +986,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { checkNotClosed(); - return new TopicPublisherAdapter((BasicMessageProducer) createProducer(topic), topic); + return new TopicPublisherAdapter((BasicMessageProducer) createProducer(topic,false,false), topic); } public Queue createQueue(String queueName) throws JMSException @@ -874,7 +1033,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { public Object execute() throws AMQException, FailoverException { - sendCreateQueue(name, autoDelete,durable,exclusive); + sendCreateQueue(name, autoDelete, durable, exclusive); return null; } }, _connection).execute(); @@ -989,9 +1148,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess AMQTopic dest = checkValidTopic(topic); // AMQTopic dest = new AMQTopic(topic.getTopicName()); - return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); + return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createExclusiveConsumer(dest)); } + /** * Creates a non-durable subscriber with a message selector * @@ -1009,7 +1169,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess AMQTopic dest = checkValidTopic(topic); // AMQTopic dest = new AMQTopic(topic.getTopicName()); - return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest, messageSelector, noLocal)); + return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createExclusiveConsumer(dest, messageSelector, noLocal)); } public abstract TemporaryQueue createTemporaryQueue() throws JMSException; @@ -1267,14 +1427,14 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } - public abstract void sendRecover() throws AMQException, FailoverException; + abstract void sendRecover() throws AMQException, FailoverException; public void rejectMessage(UnprocessedMessage message, boolean requeue) { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Rejecting Unacked message:" + message.getDeliveryTag()); + _logger.debug("Rejecting Unacked message:" + message.getDeliveryTag()); } rejectMessage(message.getDeliveryTag(), requeue); @@ -1282,9 +1442,9 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess public void rejectMessage(AbstractJMSMessage message, boolean requeue) { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Rejecting Abstract message:" + message.getDeliveryTag()); + _logger.debug("Rejecting Abstract message:" + message.getDeliveryTag()); } rejectMessage(message.getDeliveryTag(), requeue); @@ -1325,6 +1485,8 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess sendRollback(); + markClean(); + if (!isSuspended) { suspendChannel(false); @@ -1460,6 +1622,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess AMQDestination amqd = (AMQDestination) destination; + final AMQProtocolHandler protocolHandler = getProtocolHandler(); // TODO: Define selectors in AMQP // TODO: construct the rawSelector from the selector string if rawSelector == null final FieldTable ft = FieldTableFactory.newFieldTable(); @@ -1499,11 +1662,6 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { JMSException ex = new JMSException("Error registering consumer: " + e); - if (_logger.isDebugEnabled()) - { - e.printStackTrace(); - } - ex.setLinkedException(e); throw ex; } @@ -1531,7 +1689,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ void deregisterConsumer(BasicMessageConsumer consumer) { - if (_consumers.remove(consumer.getConsumerTag()) != null) + if (_consumers.remove(consumer.getConsumerTag().toIntValue()) != null) { String subscriptionName = _reverseSubscriptionMap.remove(consumer); if (subscriptionName != null) @@ -1547,6 +1705,13 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess _destinationConsumerCount.remove(dest); } } + + // Consumers that are closed in a transaction must be stored + // so that messages they have received can be acknowledged on commit + if (_transacted) + { + _removedConsumers.add(consumer); + } } } @@ -1582,8 +1747,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public abstract boolean isQueueBound(final AMQShortString exchangeName, final AMQShortString queueName, final AMQShortString routingKey) throws JMSException; - - + public abstract boolean isQueueBound(final AMQDestination destination) throws JMSException; /** @@ -1605,6 +1769,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ void resubscribe() throws AMQException { + _failedOver = true; resubscribeProducers(); resubscribeConsumers(); } @@ -1639,13 +1804,20 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // If the event dispatcher is not running then start it too. if (hasMessageListeners()) { - startDistpatcherIfNecessary(); + startDispatcherIfNecessary(); } } - synchronized void startDistpatcherIfNecessary() + void startDispatcherIfNecessary() { + //If we are the dispatcher then we don't need to check we are started + if (Thread.currentThread() == _dispatcher) + { + return; + } + // If IMMEDIATE_PREFETCH is not set then we need to start fetching + // This is final per session so will be multi-thread safe. if (!_immediatePrefetch) { // We do this now if this is the first call on a started connection @@ -1662,10 +1834,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } - startDistpatcherIfNecessary(false); + startDispatcherIfNecessary(false); } - synchronized void startDistpatcherIfNecessary(boolean initiallyStopped) + synchronized void startDispatcherIfNecessary(boolean initiallyStopped) { if (_dispatcher == null) { @@ -1816,7 +1988,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } else { - con.close(); + con.close(false); } } // at this point the _consumers map will be empty @@ -1891,20 +2063,22 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess private void consumeFromQueue(BasicMessageConsumer consumer, AMQShortString queueName, AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector) throws AMQException, FailoverException { - //need to generate a consumer tag on the client so we can exploit the nowait flag - AMQShortString tag = new AMQShortString(Integer.toString(_nextTag++)); + int tagId = _nextTag++; + // need to generate a consumer tag on the client so we can exploit the nowait flag + AMQShortString tag = new AMQShortString(Integer.toString(tagId)); + consumer.setConsumerTag(tag); // we must register the consumer in the map before we actually start listening - _consumers.put(tag, consumer); + _consumers.put(tagId, consumer); try { - sendConsume(consumer,queueName,protocolHandler,nowait,messageSelector,tag); + sendConsume(consumer, queueName, protocolHandler, nowait, messageSelector, tag); } catch (AMQException e) { // clean-up the map in the event of an error - _consumers.remove(tag); + _consumers.remove(tagId); throw e; } } @@ -1945,6 +2119,34 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler, nowait); } + + /** + * Returns the number of messages currently queued for the given destination. + * + * <p/>Note that this operation automatically retries in the event of fail-over. + * + * @param amqd The destination to be checked + * + * @return the number of queued messages. + * + * @throws AMQException If the queue cannot be declared for any reason. + */ + public long getQueueDepth(final AMQDestination amqd) + throws AMQException + { + return new FailoverNoopSupport<Long, AMQException>( + new FailoverProtectedOperation<Long, AMQException>() + { + public Long execute() throws AMQException, FailoverException + { + return requestQueueDepth(amqd); + } + }, _connection).execute(); + + } + + abstract Long requestQueueDepth(AMQDestination amqd) throws AMQException, FailoverException; + /** * Declares the named exchange and type of exchange. * @@ -1960,7 +2162,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess * @todo Be aware of possible changes to parameter order as versions change. */ private void declareExchange(final AMQShortString name, final AMQShortString type, - final AMQProtocolHandler protocolHandler, final boolean nowait) throws AMQException + final AMQProtocolHandler protocolHandler, final boolean nowait) throws AMQException { new FailoverNoopSupport<Object, AMQException>(new FailoverProtectedOperation<Object, AMQException>() { @@ -2013,7 +2215,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess amqd.setQueueName(protocolHandler.generateQueueName()); } - sendQueueDeclare(amqd,protocolHandler); + sendQueueDeclare(amqd, protocolHandler); return amqd.getAMQQueueName(); } @@ -2064,12 +2266,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess return _connection.getProtocolHandler(); } - protected byte getProtocolMajorVersion() + public byte getProtocolMajorVersion() { return getProtocolHandler().getProtocolMajorVersion(); } - protected byte getProtocolMinorVersion() + public byte getProtocolMinorVersion() { return getProtocolHandler().getProtocolMinorVersion(); } @@ -2227,7 +2429,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess if (_logger.isDebugEnabled()) { _logger.debug("Removing message(" + System.identityHashCode(message) + ") from _queue DT:" - + message.getDeliveryTag()); + + message.getDeliveryTag()); } messages.remove(); @@ -2250,6 +2452,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess for (Iterator it = consumers.iterator(); it.hasNext();) { BasicMessageConsumer consumer = (BasicMessageConsumer) it.next(); + consumer.failedOver(); registerConsumer(consumer, true); } } @@ -2276,11 +2479,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // Bounced message is processed here, away from the mina thread AbstractJMSMessage bouncedMessage = _messageFactoryRegistry.createMessage(0, false, msg.getExchange(), - msg.getExchange(), msg.getContentHeader(), msg.getBodies()); - - AMQConstant errorCode = AMQConstant.getConstant(msg.getReplyCode()); - AMQShortString reason = msg.getReplyText(); - _logger.debug("Message returned with error code " + errorCode + " (" + reason + ")"); + msg.getRoutingKey(), msg.getContentHeader(), msg.getBodies()); + AMQConstant errorCode = AMQConstant.getConstant(msg.getReplyCode()); + AMQShortString reason = msg.getReplyText(); + _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) @@ -2318,7 +2520,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess * * @todo Be aware of possible changes to parameter order as versions change. */ - protected void suspendChannel(boolean suspend) throws AMQException // , FailoverException + protected void suspendChannel(boolean suspend) throws AMQException // , FailoverException { synchronized (_suspensionLock) { @@ -2339,6 +2541,13 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } + public abstract void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException; + + Object getMessageDeliveryLock() + { + return _messageDeliveryLock; + } + /** * Indicates whether this session consumers pre-fetche messages * @@ -2350,7 +2559,62 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } - public abstract void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException; + /** Signifies that the session has pending sends to commit. */ + public void markDirty() + { + _dirty = true; + } + + /** Signifies that the session has no pending sends to commit. */ + public void markClean() + { + _dirty = false; + _failedOver = false; + } + + /** + * Check to see if failover has occured since the last call to markClean(commit or rollback). + * + * @return boolean true if failover has occured. + */ + public boolean hasFailedOver() + { + return _failedOver; + } + + /** + * Check to see if any message have been sent in this transaction and have not been commited. + * + * @return boolean true if a message has been sent but not commited + */ + public boolean isDirty() + { + return _dirty; + } + + public void setTicket(int ticket) + { + _ticket = ticket; + } + + public void setFlowControl(final boolean active) + { + _flowControl.setFlowControl(active); + } + + + public void checkFlowControl() throws InterruptedException + { + synchronized(_flowControl) + { + while(!_flowControl.getFlowControl()) + { + _flowControl.wait(); + } + } + + } + /** Responsible for decoding a message fragment and passing it to the appropriate message consumer. */ class Dispatcher extends Thread @@ -2361,6 +2625,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess private final Object _lock = new Object(); private final AtomicLong _rollbackMark = new AtomicLong(-1); + private String dispatcherID = "" + System.identityHashCode(this); public Dispatcher() { @@ -2392,10 +2657,11 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } // Reject messages on pre-receive queue - consumer.rollback(); + consumer.rollbackPendingMessages(); // Reject messages on pre-dispatch queue rejectMessagesForConsumerTag(consumer.getConsumerTag(), true); + //Let the dispatcher deal with this when it gets to them. // closeConsumer consumer.markClosed(); @@ -2436,6 +2702,13 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } + for (int i = 0; i < _removedConsumers.size(); i++) + { + // Sends acknowledgement to server + _removedConsumers.get(i).rollback(); + _removedConsumers.remove(i); + } + setConnectionStopped(isStopped); } @@ -2484,9 +2757,16 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } else { - synchronized (_messageDeliveryLock) + if (message.getDeliveryTag() <= _rollbackMark.get()) { - dispatchMessage(message); + rejectMessage(message, true); + } + else + { + synchronized (_messageDeliveryLock) + { + dispatchMessage(message); + } } } @@ -2535,38 +2815,38 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess //This if block is not needed anymore as bounce messages are handled separately //if (message.getDeliverBody() != null) //{ - final BasicMessageConsumer consumer = - (BasicMessageConsumer) _consumers.get(new AMQShortString(message.getConsumerTag())); + final BasicMessageConsumer consumer = + _consumers.get(message.getConsumerTag().toIntValue()); - if ((consumer == null) || consumer.isClosed()) + if ((consumer == null) || consumer.isClosed()) + { + if (_dispatcherLogger.isInfoEnabled()) { - if (_dispatcherLogger.isInfoEnabled()) + if (consumer == null) { - if (consumer == null) - { - _dispatcherLogger.info("Received a message(" + System.identityHashCode(message) + ")" + "[" - + message.getDeliveryTag() + "] from queue " - + message.getConsumerTag() + " )without a handler - rejecting(requeue)..."); - } - else - { - _dispatcherLogger.info("Received a message(" + System.identityHashCode(message) + ")" + "[" - + message.getDeliveryTag() + "] from queue " + " consumer(" - + consumer.debugIdentity() + ") is closed rejecting(requeue)..."); - } + _dispatcherLogger.info("Dispatcher(" + dispatcherID + ")Received a message(" + System.identityHashCode(message) + ")" + "[" + + message.getDeliveryTag() + "] from queue " + + message.getConsumerTag() + " )without a handler - rejecting(requeue)..."); } - // Don't reject if we're already closing - if (!_closed.get()) + else { - rejectMessage(message, true); + _dispatcherLogger.info("Received a message(" + System.identityHashCode(message) + ")" + "[" + + message.getDeliveryTag() + "] from queue " + " consumer(" + + message.getConsumerTag() + ") is closed rejecting(requeue)..."); } } - else + // Don't reject if we're already closing + if (!_closed.get()) { - consumer.notifyMessage(message, _channelId); + rejectMessage(message, true); } } - //} + else + { + consumer.notifyMessage(message); + } + + } } /*public void requestAccess(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write, diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java b/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java new file mode 100644 index 0000000000..a1b240ed54 --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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; + +/** + * AMQSessionDirtyException represents all failures to send data on a transacted session that is + * no longer in a state that the client expects. i.e. failover has occured so previously sent messages + * will not be part of the transaction. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent attempt to perform additional sends on a dirty session. + * </table> + */ +public class AMQSessionDirtyException extends AMQException +{ + public AMQSessionDirtyException(String msg) + { + super(AMQConstant.RESOURCE_ERROR, msg, null); + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java index d72668bb53..6089c54314 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java @@ -395,16 +395,16 @@ public class AMQSession_0_10 extends AMQSession try { preAcquire = ( ! consumer.isNoConsume() && consumer.getMessageSelector() == null) || !(consumer.getDestination() instanceof AMQQueue); + getQpidSession().messageSubscribe(queueName.toString(), tag.toString(), + getAcknowledgeMode() == NO_ACKNOWLEDGE ? Session.TRANSFER_CONFIRM_MODE_NOT_REQUIRED:Session.TRANSFER_CONFIRM_MODE_REQUIRED, + preAcquire ? Session.TRANSFER_ACQUIRE_MODE_PRE_ACQUIRE : Session.TRANSFER_ACQUIRE_MODE_NO_ACQUIRE, + new MessagePartListenerAdapter((BasicMessageConsumer_0_10) consumer), null, + consumer.isExclusive() ? Option.EXCLUSIVE : Option.NO_OPTION); } catch (JMSException e) { throw new AMQException(AMQConstant.INTERNAL_ERROR, "problem when registering consumer", e); } - getQpidSession().messageSubscribe(queueName.toString(), tag.toString(), - (Boolean.getBoolean("noAck") ?Session.TRANSFER_CONFIRM_MODE_NOT_REQUIRED:Session.TRANSFER_CONFIRM_MODE_REQUIRED), - preAcquire ? Session.TRANSFER_ACQUIRE_MODE_PRE_ACQUIRE : Session.TRANSFER_ACQUIRE_MODE_NO_ACQUIRE, - new MessagePartListenerAdapter((BasicMessageConsumer_0_10) consumer), null, - consumer.isExclusive() ? Option.EXCLUSIVE : Option.NO_OPTION); if (! prefetch()) { @@ -746,4 +746,10 @@ public class AMQSession_0_10 extends AMQSession return subscriber; } + + Long requestQueueDepth(AMQDestination amqd) + { + return getQpidSession().queueQuery(amqd.getQueueName()).get().getMessageCount(); + } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java index dd0fd9c457..740bd5dace 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java @@ -30,8 +30,10 @@ import org.apache.qpid.client.failover.FailoverProtectedOperation; import org.apache.qpid.client.failover.FailoverRetrySupport; import org.apache.qpid.client.message.MessageFactoryRegistry; import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.state.listener.SpecificMethodFrameListener; import org.apache.qpid.common.AMQPFilterTypes; import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; import org.apache.qpid.jms.Session; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQMethodEvent; @@ -79,11 +81,16 @@ public class AMQSession_0_8 extends AMQSession defaultPrefetchLow); } + private ProtocolVersion getProtocolVersion() + { + return getProtocolHandler().getProtocolVersion(); + } + public void acknowledgeMessage(long deliveryTag, boolean multiple) { - final AMQFrame ackFrame = - BasicAckBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), deliveryTag, - multiple); + BasicAckBody body = getMethodRegistry().createBasicAckBody(deliveryTag, multiple); + + final AMQFrame ackFrame = body.generateFrame(_channelId); if (_logger.isDebugEnabled()) { @@ -96,28 +103,17 @@ public class AMQSession_0_8 extends AMQSession public void sendQueueBind(final AMQShortString queueName, final AMQShortString routingKey, final FieldTable arguments, final AMQShortString exchangeName, final AMQDestination dest) throws AMQException, FailoverException { - AMQFrame queueBind = QueueBindBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), arguments, // arguments - exchangeName, // exchange - false, // nowait - queueName, // queue - routingKey, // routingKey - getTicket()); // ticket - - getProtocolHandler().syncWrite(queueBind, QueueBindOkBody.class); + getProtocolHandler().syncWrite(getProtocolHandler().getMethodRegistry().createQueueBindBody + (getTicket(),queueName,exchangeName,routingKey,false,arguments). + generateFrame(_channelId), QueueBindOkBody.class); } public void sendClose(long timeout) throws AMQException, FailoverException { getProtocolHandler().closeSession(this); - - final AMQFrame frame = ChannelCloseBody.createAMQFrame - (getChannelId(), getProtocolMajorVersion(), getProtocolMinorVersion(), - 0, // classId - 0, // methodId - AMQConstant.REPLY_SUCCESS.getCode(), // replyCode - new AMQShortString("JMS client closing channel")); // replyText - - getProtocolHandler().syncWrite(frame, ChannelCloseOkBody.class, timeout); + getProtocolHandler().syncWrite(getProtocolHandler().getMethodRegistry().createChannelCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), + new AMQShortString("JMS client closing channel"), 0, 0).generateFrame(_channelId), + ChannelCloseOkBody.class, timeout); // When control resumes at this point, a reply will have been received that // indicates the broker has closed the channel successfully. } @@ -126,21 +122,14 @@ public class AMQSession_0_8 extends AMQSession { final AMQProtocolHandler handler = getProtocolHandler(); - handler.syncWrite(TxCommitBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion()), TxCommitOkBody.class); + handler.syncWrite(getProtocolHandler().getMethodRegistry().createTxCommitBody().generateFrame(_channelId), TxCommitOkBody.class); } public void sendCreateQueue(AMQShortString name, final boolean autoDelete, final boolean durable, final boolean exclusive) throws AMQException, FailoverException { - AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), null, // arguments - autoDelete, // autoDelete - durable, // durable - exclusive, // exclusive - false, // nowait - false, // passive - name, // queue - getTicket()); // ticket - + QueueDeclareBody body = getMethodRegistry().createQueueDeclareBody(getTicket(),name,false,durable,exclusive,autoDelete,false,null); + AMQFrame queueDeclare = body.generateFrame(_channelId); getProtocolHandler().syncWrite(queueDeclare, QueueDeclareOkBody.class); } @@ -151,16 +140,29 @@ public class AMQSession_0_8 extends AMQSession if (isStrictAMQP()) { // We can't use the BasicRecoverBody-OK method as it isn't part of the spec. - _connection.getProtocolHandler().writeFrame( - BasicRecoverBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), false)); // requeue + + BasicRecoverBody body = getMethodRegistry().createBasicRecoverBody(false); + _connection.getProtocolHandler().writeFrame(body.generateFrame(_channelId)); _logger.warn("Session Recover cannot be guaranteed with STRICT_AMQP. Messages may arrive out of order."); } else { - - _connection.getProtocolHandler().syncWrite( - BasicRecoverBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), false) // requeue - , BasicRecoverOkBody.class); + // in Qpid the 0-8 spec was hacked to have a recover-ok method... this is bad + // in 0-9 we used the cleaner addition of a new sync recover method with its own ok + if(getProtocolHandler().getProtocolVersion().equals(ProtocolVersion.v8_0)) + { + BasicRecoverBody body = getMethodRegistry().createBasicRecoverBody(false); + _connection.getProtocolHandler().syncWrite(body.generateFrame(_channelId), BasicRecoverOkBody.class); + } + else if(getProtocolVersion().equals(ProtocolVersion.v0_9)) + { + BasicRecoverSyncBody body = ((MethodRegistry_0_9)getMethodRegistry()).createBasicRecoverSyncBody(false); + _connection.getProtocolHandler().syncWrite(body.generateFrame(_channelId), BasicRecoverSyncOkBody.class); + } + else + { + throw new RuntimeException("Unsupported version of the AMQP Protocol: " + getProtocolVersion()); + } } } @@ -189,13 +191,13 @@ public class AMQSession_0_8 extends AMQSession { if (_logger.isDebugEnabled()) { - _logger.debug("Rejecting delivery tag:" + deliveryTag); + _logger.debug("Rejecting delivery tag:" + deliveryTag + ":SessionHC:" + this.hashCode()); } - AMQFrame basicRejectBody = BasicRejectBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), deliveryTag, - requeue); + BasicRejectBody body = getMethodRegistry().createBasicRejectBody(deliveryTag, requeue); + AMQFrame frame = body.generateFrame(_channelId); - _connection.getProtocolHandler().writeFrame(basicRejectBody); + _connection.getProtocolHandler().writeFrame(frame); } } @@ -214,10 +216,8 @@ public class AMQSession_0_8 extends AMQSession { public AMQMethodEvent execute() throws AMQException, FailoverException { - AMQFrame boundFrame = ExchangeBoundBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), - exchangeName, // exchange - queueName, // queue - routingKey); // routingKey + AMQFrame boundFrame = getProtocolHandler().getMethodRegistry().createExchangeBoundBody + (exchangeName, routingKey, queueName).generateFrame(_channelId); return getProtocolHandler().syncWrite(boundFrame, ExchangeBoundOkBody.class); @@ -227,7 +227,7 @@ public class AMQSession_0_8 extends AMQSession // Extract and return the response code from the query. ExchangeBoundOkBody responseBody = (ExchangeBoundOkBody) response.getMethod(); - return (responseBody.replyCode == 0); + return (responseBody.getReplyCode() == 0); } catch (AMQException e) { @@ -238,7 +238,6 @@ public class AMQSession_0_8 extends AMQSession public void sendConsume(BasicMessageConsumer consumer, AMQShortString queueName, AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector, AMQShortString tag) throws AMQException, FailoverException { - FieldTable arguments = FieldTableFactory.newFieldTable(); if ((messageSelector != null) && !messageSelector.equals("")) { @@ -255,18 +254,17 @@ public class AMQSession_0_8 extends AMQSession arguments.put(AMQPFilterTypes.NO_CONSUME.getValue(), Boolean.TRUE); } - consumer.setConsumerTag(tag); - // we must register the consumer in the map before we actually start listening - _consumers.put(tag, consumer); - // TODO: Be aware of possible changes to parameter order as versions change. - AMQFrame jmsConsume = BasicConsumeBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), arguments, // arguments - tag, // consumerTag - consumer.isExclusive(), // exclusive - consumer.getAcknowledgeMode() == Session.NO_ACKNOWLEDGE, // noAck - consumer.isNoLocal(), // noLocal - nowait, // nowait - queueName, // queue - getTicket()); // ticket + BasicConsumeBody body = getMethodRegistry().createBasicConsumeBody(getTicket(), + queueName, + tag, + consumer.isNoLocal(), + consumer.getAcknowledgeMode() == Session.NO_ACKNOWLEDGE, + consumer.isExclusive(), + nowait, + arguments); + + + AMQFrame jmsConsume = body.generateFrame(_channelId); if (nowait) { @@ -281,48 +279,37 @@ public class AMQSession_0_8 extends AMQSession public void sendExchangeDeclare(final AMQShortString name, final AMQShortString type, final AMQProtocolHandler protocolHandler, final boolean nowait) throws AMQException, FailoverException { - AMQFrame exchangeDeclare = ExchangeDeclareBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), null, // arguments - false, // autoDelete - false, // durable - name, // exchange - false, // internal - nowait, // nowait - false, // passive - getTicket(), // ticket - type); // type + ExchangeDeclareBody body = getMethodRegistry().createExchangeDeclareBody(getTicket(),name,type,false,false,false,false,nowait,null); + AMQFrame exchangeDeclare = body.generateFrame(_channelId); protocolHandler.syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class); } public void sendQueueDeclare(final AMQDestination amqd, final AMQProtocolHandler protocolHandler) throws AMQException, FailoverException { - AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), null, // arguments - amqd.isAutoDelete(), // autoDelete - amqd.isDurable(), // durable - amqd.isExclusive(), // exclusive - false, // nowait - false, // passive - amqd.getAMQQueueName(), // queue - getTicket()); // ticket + QueueDeclareBody body = getMethodRegistry().createQueueDeclareBody(getTicket(),amqd.getAMQQueueName(),false,amqd.isDurable(),amqd.isExclusive(),amqd.isAutoDelete(),false,null); + + AMQFrame queueDeclare = body.generateFrame(_channelId); protocolHandler.syncWrite(queueDeclare, QueueDeclareOkBody.class); } public void sendQueueDelete(final AMQShortString queueName) throws AMQException, FailoverException { - AMQFrame queueDeleteFrame = QueueDeleteBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), false, // ifEmpty - false, // ifUnused - true, // nowait - queueName, // queue - getTicket()); // ticket + QueueDeleteBody body = getMethodRegistry().createQueueDeleteBody(getTicket(), + queueName, + false, + false, + true); + AMQFrame queueDeleteFrame = body.generateFrame(_channelId); getProtocolHandler().syncWrite(queueDeleteFrame, QueueDeleteOkBody.class); } public void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException { - AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), !suspend); - + ChannelFlowBody body = getMethodRegistry().createChannelFlowBody(!suspend); + AMQFrame channelFlowFrame = body.generateFrame(_channelId); _connection.getProtocolHandler().syncWrite(channelFlowFrame, ChannelFlowOkBody.class); } @@ -348,8 +335,9 @@ public class AMQSession_0_8 extends AMQSession public void sendRollback() throws AMQException, FailoverException { - _connection.getProtocolHandler().syncWrite(TxRollbackBody.createAMQFrame(_channelId, - getProtocolMajorVersion(), getProtocolMinorVersion()), TxRollbackOkBody.class); + TxRollbackBody body = getMethodRegistry().createTxRollbackBody(); + AMQFrame frame = body.generateFrame(getChannelId()); + getProtocolHandler().syncWrite(frame, TxRollbackOkBody.class); } public TemporaryQueue createTemporaryQueue() throws JMSException @@ -360,68 +348,109 @@ public class AMQSession_0_8 extends AMQSession } public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException - { - - checkNotClosed(); - AMQTopic origTopic = checkValidTopic(topic); - AMQTopic dest = AMQTopic.createDurableTopic(origTopic, name, _connection); - TopicSubscriberAdaptor subscriber = _subscriptions.get(name); - if (subscriber != null) - { - if (subscriber.getTopic().equals(topic)) - { - throw new IllegalStateException("Already subscribed to topic " + topic + " with subscription exchange " - + name); - } - else - { - unsubscribe(name); - } - } - else - { - AMQShortString topicName; - if (topic instanceof AMQTopic) - { - topicName = ((AMQTopic) topic).getRoutingKey(); - } - else - { - topicName = new AMQShortString(topic.getTopicName()); - } - - if (_strictAMQP) - { - if (_strictAMQPFATAL) - { - throw new UnsupportedOperationException("JMS Durable not currently supported by AMQP."); - } - else - { - _logger.warn("Unable to determine if subscription already exists for '" + topicName + "' " - + "for creation durableSubscriber. Requesting queue deletion regardless."); - } - - deleteQueue(dest.getAMQQueueName()); - } - else - { - // if the queue is bound to the exchange but NOT for this topic, then the JMS spec - // says we must trash the subscription. - if (isQueueBound(dest.getExchangeName(), dest.getAMQQueueName()) - && !isQueueBound(dest.getExchangeName(), dest.getAMQQueueName(), topicName)) - { - deleteQueue(dest.getAMQQueueName()); - } - } - } - - subscriber = new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); - - _subscriptions.put(name, subscriber); - _reverseSubscriptionMap.put(subscriber.getMessageConsumer(), name); - - return subscriber; - } + { + + checkNotClosed(); + AMQTopic origTopic = checkValidTopic(topic); + AMQTopic dest = AMQTopic.createDurableTopic(origTopic, name, _connection); + TopicSubscriberAdaptor subscriber = _subscriptions.get(name); + if (subscriber != null) + { + if (subscriber.getTopic().equals(topic)) + { + throw new IllegalStateException("Already subscribed to topic " + topic + " with subscription exchange " + + name); + } + else + { + unsubscribe(name); + } + } + else + { + AMQShortString topicName; + if (topic instanceof AMQTopic) + { + topicName = ((AMQTopic) topic).getRoutingKey(); + } + else + { + topicName = new AMQShortString(topic.getTopicName()); + } + + if (_strictAMQP) + { + if (_strictAMQPFATAL) + { + throw new UnsupportedOperationException("JMS Durable not currently supported by AMQP."); + } + else + { + _logger.warn("Unable to determine if subscription already exists for '" + topicName + "' " + + "for creation durableSubscriber. Requesting queue deletion regardless."); + } + + deleteQueue(dest.getAMQQueueName()); + } + else + { + // if the queue is bound to the exchange but NOT for this topic, then the JMS spec + // says we must trash the subscription. + if (isQueueBound(dest.getExchangeName(), dest.getAMQQueueName()) + && !isQueueBound(dest.getExchangeName(), dest.getAMQQueueName(), topicName)) + { + deleteQueue(dest.getAMQQueueName()); + } + } + } + + subscriber = new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); + + _subscriptions.put(name, subscriber); + _reverseSubscriptionMap.put(subscriber.getMessageConsumer(), name); + + return subscriber; + } + + class QueueDeclareOkHandler extends SpecificMethodFrameListener + { + + private long _messageCount; + private long _consumerCount; + + public QueueDeclareOkHandler() + { + super(getChannelId(), QueueDeclareOkBody.class); + } + + public boolean processMethod(int channelId, AMQMethodBody frame) //throws AMQException + { + boolean matches = super.processMethod(channelId, frame); + if (matches) + { + QueueDeclareOkBody declareOk = (QueueDeclareOkBody) frame; + _messageCount = declareOk.getMessageCount(); + _consumerCount = declareOk.getConsumerCount(); + } + return matches; + } + + } + + Long requestQueueDepth(AMQDestination amqd) throws AMQException, FailoverException + { + AMQFrame queueDeclare = + getMethodRegistry().createQueueDeclareBody(getTicket(), + amqd.getAMQQueueName(), + true, + amqd.isDurable(), + amqd.isExclusive(), + amqd.isAutoDelete(), + false, + null).generateFrame(_channelId); + QueueDeclareOkHandler okHandler = new QueueDeclareOkHandler(); + getProtocolHandler().writeCommandFrameAndWaitForReply(queueDeclare, okHandler); + return okHandler._messageCount; + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java b/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java index 6c954ec3df..7b5781530b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java @@ -24,6 +24,7 @@ import org.apache.qpid.framing.AMQShortString; import javax.jms.JMSException; import javax.jms.TemporaryTopic; +import java.util.UUID; /** * AMQ implementation of TemporaryTopic. @@ -38,7 +39,7 @@ class AMQTemporaryTopic extends AMQTopic implements TemporaryTopic, TemporaryDes */ public AMQTemporaryTopic(AMQSession session) { - super(session.getTemporaryTopicExchangeName(),new AMQShortString("TempQueue" + Long.toString(System.currentTimeMillis()))); + super(session.getTemporaryTopicExchangeName(),new AMQShortString("tmp_" + UUID.randomUUID())); _session = session; } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java b/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java index fcc36cd3fd..40041afdc6 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java @@ -120,8 +120,7 @@ public class AMQTopic extends AMQDestination implements Topic public boolean isNameRequired() { - // Topics always rely on a server generated queue name. - return false; + return !isDurable(); } /** @@ -136,4 +135,17 @@ public class AMQTopic extends AMQDestination implements Topic public void setQueueName(String queueName) { } + + public boolean equals(Object o) + { + return (o instanceof AMQTopic) + && ((AMQTopic)o).getExchangeName().equals(getExchangeName()) + && ((AMQTopic)o).getRoutingKey().equals(getRoutingKey()); + + } + + public int hashCode() + { + return getExchangeName().hashCode() + getRoutingKey().hashCode(); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java index 015a2ccc57..1741903bb8 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java @@ -20,45 +20,47 @@ */ package org.apache.qpid.client; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageListener; - +import org.apache.qpid.AMQException; +import org.apache.qpid.client.failover.FailoverException; 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.AMQShortString; -import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.*; import org.apache.qpid.jms.MessageConsumer; import org.apache.qpid.jms.Session; -import org.apache.qpid.AMQException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.TreeSet; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + public abstract class BasicMessageConsumer<H, B> extends Closeable implements MessageConsumer { private static final Logger _logger = LoggerFactory.getLogger(BasicMessageConsumer.class); - /** - * The connection being used by this consumer - */ - protected AMQConnection _connection; + /** The connection being used by this consumer */ + protected final AMQConnection _connection; - protected String _messageSelector; + protected final String _messageSelector; - private boolean _noLocal; + private final boolean _noLocal; - private AMQDestination _destination; + private final AMQDestination _destination; /** * When true indicates that a blocking receive call is in progress @@ -69,15 +71,11 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me */ private final AtomicReference<MessageListener> _messageListener = new AtomicReference<MessageListener>(); - /** - * The consumer tag allows us to close the consumer by sending a jmsCancel method to the broker - */ + /** The consumer tag allows us to close the consumer by sending a jmsCancel method to the broker */ protected AMQShortString _consumerTag; - /** - * We need to know the channel id when constructing frames - */ - protected int _channelId; + /** We need to know the channel id when constructing frames */ + protected final int _channelId; /** * Used in the blocking receive methods to receive a message from the Session thread. <p/> Or to notify of errors @@ -85,40 +83,40 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me */ protected final ArrayBlockingQueue _synchronousQueue; - protected MessageFactoryRegistry _messageFactory; + protected final MessageFactoryRegistry _messageFactory; protected final AMQSession _session; - protected AMQProtocolHandler _protocolHandler; + protected final 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; + private final FieldTable _rawSelectorFieldTable; /** * We store the high water prefetch field in order to be able to reuse it when resubscribing in the event of * failover */ - private int _prefetchHigh; + private final int _prefetchHigh; /** * We store the low water prefetch field in order to be able to reuse it when resubscribing in the event of * failover */ - private int _prefetchLow; + private final int _prefetchLow; /** * We store the exclusive field in order to be able to reuse it when resubscribing in the event of failover */ - private boolean _exclusive; + private final 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; + private final int _acknowledgeMode; /** * Number of messages unacknowledged in DUPS_OK_ACKNOWLEDGE mode @@ -132,6 +130,19 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me private boolean _dups_ok_acknowledge_send; /** + * List of tags delievered, The last of which which should be acknowledged on commit in transaction mode. + */ + private ConcurrentLinkedQueue<Long> _receivedDeliveryTags = new ConcurrentLinkedQueue<Long>(); + + /** The last tag that was "multiple" acknowledged on this session (if transacted) */ + private long _lastAcked; + + /** set of tags which have previously been acked; but not part of the multiple ack (transacted mode only) */ + private final SortedSet<Long> _previouslyAcked = new TreeSet<Long>(); + + private final Object _commitLock = new Object(); + + /** * The thread that was used to call receive(). This is important for being able to interrupt that thread if a * receive() is in progress. */ @@ -148,12 +159,13 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me * autoClose denotes that the consumer will automatically cancel itself when there are no more messages to receive * on the queue. This is used for queue browsing. */ - private boolean _autoClose; - private boolean _closeWhenNoMessages; + private final boolean _autoClose; - private boolean _noConsume; + private final boolean _noConsume; private List<StackTraceElement> _closedStack = null; + + protected BasicMessageConsumer(int channelId, AMQConnection connection, AMQDestination destination, String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory, AMQSession session, AMQProtocolHandler protocolHandler, @@ -172,7 +184,7 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me _prefetchHigh = prefetchHigh; _prefetchLow = prefetchLow; _exclusive = exclusive; - _acknowledgeMode = acknowledgeMode; + _synchronousQueue = new ArrayBlockingQueue(prefetchHigh, true); _autoClose = autoClose; _noConsume = noConsume; @@ -182,6 +194,10 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me { _acknowledgeMode = Session.NO_ACKNOWLEDGE; } + else + { + _acknowledgeMode = acknowledgeMode; + } } public AMQDestination getDestination() @@ -254,14 +270,14 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me if (messageListener != null) { - // handle case where connection has already been started, and the dispatcher has alreaded started + //todo: handle case where connection has already been started, and the dispatcher has alreaded started // putting values on the _synchronousQueue synchronized (_session) { _messageListener.set(messageListener); _session.setHasMessageListeners(); - _session.startDistpatcherIfNecessary(); + _session.startDispatcherIfNecessary(); } } } @@ -273,12 +289,32 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me { _session.addUnacknowledgedMessage(jmsMsg.getDeliveryTag()); } - + _session.setInRecovery(false); } - private void acquireReceiving() throws JMSException + /** + * @param immediate if true then return immediately if the connection is failing over + * + * @return boolean if the acquisition was successful + * + * @throws JMSException if a listener has already been set or another thread is receiving + * @throws InterruptedException if interrupted + */ + private boolean acquireReceiving(boolean immediate) throws JMSException, InterruptedException { + if (_connection.isFailingOver()) + { + if (immediate) + { + return false; + } + else + { + _connection.blockUntilNotFailingOver(); + } + } + if (!_receiving.compareAndSet(false, true)) { throw new javax.jms.IllegalStateException("Another thread is already receiving."); @@ -290,6 +326,7 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me } _receivingThread = Thread.currentThread(); + return true; } private void releaseReceiving() @@ -343,26 +380,30 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me checkPreConditions(); - acquireReceiving(); - - _session.startDistpatcherIfNecessary(); - try { - if (closeOnAutoClose()) + acquireReceiving(false); + } + catch (InterruptedException e) + { + _logger.warn("Interrupted acquire: " + e); + if (isClosed()) { return null; } + } - Object o = getMessageFromQueue(l); + _session.startDispatcherIfNecessary(); + try + { + Object o = getMessageFromQueue(l); final AbstractJMSMessage m = returnMessageOrThrow(o); if (m != null) { preApplicationProcessing(m); postDeliver(m); } - return m; } catch (InterruptedException e) @@ -395,35 +436,34 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me return o; } - private boolean closeOnAutoClose() throws JMSException - { - if (isAutoClose() && _closeWhenNoMessages && _synchronousQueue.isEmpty()) - { - close(false); - - return true; - } - else - { - return false; - } - } - public Message receiveNoWait() throws JMSException { checkPreConditions(); - acquireReceiving(); - - _session.startDistpatcherIfNecessary(); - try { - if (closeOnAutoClose()) + if (!acquireReceiving(true)) { + //If we couldn't acquire the receiving thread then return null. + // This will occur if failing over. return null; } + } + catch (InterruptedException e) + { + /* + * This seems slightly shoddy but should never actually be executed + * since we told acquireReceiving to return immediately and it shouldn't + * block on anything. + */ + + return null; + } + + _session.startDispatcherIfNecessary(); + try + { Object o = getMessageFromQueue(-1); final AbstractJMSMessage m = returnMessageOrThrow(o); if (m != null) @@ -450,7 +490,7 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me * 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 + * @param o the object to return or throw * @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 @@ -481,61 +521,69 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me public void close(boolean sendClose) throws JMSException { - // synchronized (_closed) - if (_logger.isInfoEnabled()) { _logger.info("Closing consumer:" + debugIdentity()); } - synchronized (_connection.getFailoverMutex()) + if (!_closed.getAndSet(true)) { - if (!_closed.getAndSet(true)) + if (_logger.isDebugEnabled()) { - if (_logger.isTraceEnabled()) - { - if (_closedStack != null) - { - _logger.trace(_consumerTag + " close():" + Arrays.asList(Thread.currentThread().getStackTrace()) - .subList(3, 6)); - _logger.trace(_consumerTag + " previously:" + _closedStack.toString()); - } - else - { - _closedStack = Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 6); - } - } - - if (sendClose) + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (_closedStack != null) { - // TODO: Be aware of possible changes to parameter order as versions change. - sendCancel(); + _logger.debug(_consumerTag + " previously:" + _closedStack.toString()); } else { - // //fixme this probably is not right - // if (!isNoConsume()) - //{ // done in BasicCancelOK Handler but not sending one so just deregister. - // deregisterConsumer(); - //} + _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1); } + } - deregisterConsumer(); - - if (_messageListener != null && _receiving.get() && _receivingThread != null) + if (sendClose) + { + // The Synchronized block only needs to protect network traffic. + synchronized (_connection.getFailoverMutex()) { - if (_logger.isInfoEnabled()) + try { - _logger.info("Interrupting thread: " + _receivingThread); + sendCancel(); } + catch (AMQException e) + { + throw new JMSAMQException("Error closing consumer: " + e, e); + } + catch (FailoverException e) + { + throw new JMSAMQException("FailoverException interrupted basic cancel.", e); + } + } + } + else + { + // //fixme this probably is not right + // if (!isNoConsume()) + { // done in BasicCancelOK Handler but not sending one so just deregister. + deregisterConsumer(); + } + } - _receivingThread.interrupt(); + // This will occur if session.close is called closing all consumers we may be blocked waiting for a receive + // so we need to let it know it is time to close. + if ((_messageListener != null) && _receiving.get()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Interrupting thread: " + _receivingThread); } + + _receivingThread.interrupt(); } } } - public abstract void sendCancel() throws JMSAMQException; + abstract void sendCancel() throws AMQException, FailoverException; /** * Called when you need to invalidate a consumer. Used for example when failover has occurred and the client has @@ -547,13 +595,14 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me { _closed.set(true); - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { if (_closedStack != null) { - _logger.trace(_consumerTag + " markClosed():" + Arrays - .asList(Thread.currentThread().getStackTrace()).subList(3, 8)); - _logger.trace(_consumerTag + " previously:" + _closedStack.toString()); + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + _logger.debug(_consumerTag + " markClosed():" + + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); + _logger.debug(_consumerTag + " previously:" + _closedStack.toString()); } else { @@ -570,9 +619,8 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me * 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) + void notifyMessage(UnprocessedMessage messageFrame) { final boolean debug = _logger.isDebugEnabled(); @@ -584,6 +632,7 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me try { AbstractJMSMessage jmsMessage = createJMSMessageFromUnprocessedMessage(messageFrame); + if (debug) { _logger.debug("Message is of type: " + jmsMessage.getClass().getName()); @@ -594,11 +643,9 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me // if (!_closed.get()) { - jmsMessage.setConsumer(this); - preDeliver(jmsMessage); - notifyMessage(jmsMessage, channelId); + notifyMessage(jmsMessage); } // else // { @@ -624,12 +671,8 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me public abstract AbstractJMSMessage createJMSMessageFromUnprocessedMessage(UnprocessedMessage<H, B> messageFrame) throws Exception; - - /** - * @param jmsMessage this message has already been processed so can't redo preDeliver - * @param channelId - */ - public void notifyMessage(AbstractJMSMessage jmsMessage, int channelId) + /** @param jmsMessage this message has already been processed so can't redo preDeliver */ + public void notifyMessage(AbstractJMSMessage jmsMessage) { try { @@ -700,26 +743,6 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me break; case Session.DUPS_OK_ACKNOWLEDGE: - if (++_outstanding >= _prefetchHigh) - { - _dups_ok_acknowledge_send = true; - } - - if (_outstanding <= _prefetchLow) - { - _dups_ok_acknowledge_send = false; - } - - if (_dups_ok_acknowledge_send) - { - if (!_session.isInRecovery()) - { - _session.acknowledgeMessage(msg.getDeliveryTag(), true); - } - } - - break; - case Session.AUTO_ACKNOWLEDGE: // we do not auto ack a message if the application code called recover() if (!_session.isInRecovery()) @@ -728,7 +751,6 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me } break; - case Session.SESSION_TRANSACTED: if (isNoConsume()) { @@ -743,22 +765,105 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me } } + + /** + * Acknowledge up to last message delivered (if any). Used when commiting. + * + * @return the lastDeliveryTag to acknowledge + */ + Long getLastDelivered() + { + if (!_receivedDeliveryTags.isEmpty()) + { + Long lastDeliveryTag = _receivedDeliveryTags.poll(); + + while (!_receivedDeliveryTags.isEmpty()) + { + lastDeliveryTag = _receivedDeliveryTags.poll(); + } + + assert _receivedDeliveryTags.isEmpty(); + + return lastDeliveryTag; + } + + return null; + } + + /** + * Acknowledge up to last message delivered (if any). Used when commiting. + */ + void acknowledgeDelivered() + { + synchronized(_commitLock) + { + ArrayList<Long> tagsToAck = new ArrayList<Long>(); + + while (!_receivedDeliveryTags.isEmpty()) + { + tagsToAck.add(_receivedDeliveryTags.poll()); + } + + Collections.sort(tagsToAck); + + long prevAcked = _lastAcked; + long oldAckPoint = -1; + + while(oldAckPoint != prevAcked) + { + oldAckPoint = prevAcked; + + Iterator<Long> tagsToAckIterator = tagsToAck.iterator(); + + while(tagsToAckIterator.hasNext() && tagsToAckIterator.next() == prevAcked+1) + { + tagsToAckIterator.remove(); + prevAcked++; + } + + Iterator<Long> previousAckIterator = _previouslyAcked.iterator(); + while(previousAckIterator.hasNext() && previousAckIterator.next() == prevAcked+1) + { + previousAckIterator.remove(); + prevAcked++; + } + + } + if(prevAcked != _lastAcked) + { + _session.acknowledgeMessage(prevAcked, true); + _lastAcked = prevAcked; + } + + Iterator<Long> tagsToAckIterator = tagsToAck.iterator(); + + while(tagsToAckIterator.hasNext()) + { + Long tag = tagsToAckIterator.next(); + _session.acknowledgeMessage(tag, false); + _previouslyAcked.add(tag); + } + } + } + + void notifyError(Throwable cause) { // synchronized (_closed) { _closed.set(true); - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); if (_closedStack != null) { - _logger.trace(_consumerTag + " notifyError():" + Arrays - .asList(Thread.currentThread().getStackTrace()).subList(3, 8)); - _logger.trace(_consumerTag + " previously" + _closedStack.toString()); + _logger.debug(_consumerTag + " notifyError():" + + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); + _logger.debug(_consumerTag + " previously" + _closedStack.toString()); } else { - _closedStack = Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 8); + _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1); } } } @@ -823,20 +928,13 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me return _noConsume; } - public void closeWhenNoMessages(boolean b) + public void rollback() { - _closeWhenNoMessages = b; - - if (_closeWhenNoMessages && _synchronousQueue.isEmpty() && _receiving.get() && (_messageListener != null)) - { - _receivingThread.interrupt(); - } - + rollbackPendingMessages(); } - public void rollback() + public void rollbackPendingMessages() { - // rollback pending messages if (_synchronousQueue.size() > 0) { if (_logger.isDebugEnabled()) @@ -847,6 +945,9 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me Iterator iterator = _synchronousQueue.iterator(); + int initialSize = _synchronousQueue.size(); + + boolean removed = false; while (iterator.hasNext()) { @@ -855,22 +956,30 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me { _session.rejectMessage(((AbstractJMSMessage) o), true); - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Rejected message:" + ((AbstractJMSMessage) o).getDeliveryTag()); + _logger.debug("Rejected message:" + ((AbstractJMSMessage) o).getDeliveryTag()); } iterator.remove(); + removed = true; } else { - _logger.error("Queue contained a :" + o - .getClass() + " unable to reject as it is not an AbstractJMSMessage. Will be cleared"); + _logger.error("Queue contained a :" + o.getClass() + + " unable to reject as it is not an AbstractJMSMessage. Will be cleared"); iterator.remove(); + removed = true; + } } + + if (removed && (initialSize == _synchronousQueue.size())) + { + _logger.error("Queue had content removed but didn't change in size." + initialSize); } + if (_synchronousQueue.size() != 0) { _logger.warn("Queue was not empty after rejecting all messages Remaining:" + _synchronousQueue.size()); @@ -883,7 +992,7 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me public String debugIdentity() { - return String.valueOf(_consumerTag); + return String.valueOf(_consumerTag) + "[" + System.identityHashCode(this) + "]"; } public void clearReceiveQueue() @@ -891,7 +1000,6 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me _synchronousQueue.clear(); } - public void start() { // do nothing as this is a 0_10 feature @@ -923,4 +1031,12 @@ public abstract class BasicMessageConsumer<H, B> extends Closeable implements Me { _session.addBindingKey(this,amqd,routingKey); } + + /** to be called when a failover has occured */ + public void failedOver() + { + clearReceiveQueue(); + // TGM FIXME: think this should just be removed + // clearUnackedMessages(); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java index 4f2d5d8c34..d92899087b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java @@ -117,7 +117,7 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By * @param jmsMessage this message has already been processed so can't redo preDeliver * @param channelId */ - public void notifyMessage(AbstractJMSMessage jmsMessage, int channelId) + public void notifyMessage(AbstractJMSMessage jmsMessage) { boolean messageOk = false; try @@ -147,7 +147,7 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By MessageCreditUnit.MESSAGE, 1); } _logger.debug("messageOk, trying to notify"); - super.notifyMessage(jmsMessage, channelId); + super.notifyMessage(jmsMessage); } } @@ -162,7 +162,7 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By { int channelId = getSession().getChannelId(); long deliveryId = message.getMessageTransferId(); - String consumerTag = getConsumerTag().toString(); + AMQShortString consumerTag = getConsumerTag(); AMQShortString exchange; AMQShortString routingKey; boolean redelivered = false; @@ -211,20 +211,13 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By * This method is invoked when this consumer is stopped. * It tells the broker to stop delivering messages to this consumer. */ - public void sendCancel() throws JMSAMQException + void sendCancel() throws AMQException { ((AMQSession_0_10) getSession()).getQpidSession().messageCancel(getConsumerTag().toString()); ((AMQSession_0_10) getSession()).getQpidSession().sync(); // confirm cancel getSession().confirmConsumerCancelled(getConsumerTag()); - try - { - ((AMQSession_0_10) getSession()).getCurrentException(); - } - catch (AMQException e) - { - throw new JMSAMQException("Problem when stopping consumer", e); - } + ((AMQSession_0_10) getSession()).getCurrentException(); } void notifyMessage(UnprocessedMessage messageFrame, int channelId) @@ -261,7 +254,7 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By } ((UnprocessedMessage_0_10) messageFrame).setReplyToURL(replyToUrl); } - super.notifyMessage(messageFrame, channelId); + super.notifyMessage(messageFrame); } protected void preApplicationProcessing(AbstractJMSMessage jmsMsg) throws JMSException @@ -486,5 +479,4 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<Struct[], By } return o; } - -}
\ No newline at end of file +} diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java index 1bc9e4d855..5414e25539 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java @@ -51,30 +51,17 @@ public class BasicMessageConsumer_0_8 extends BasicMessageConsumer<ContentHeader acknowledgeMode, noConsume, autoClose); } - public void sendCancel() throws JMSAMQException + void sendCancel() throws AMQException, FailoverException { - final AMQFrame cancelFrame = - BasicCancelBody.createAMQFrame(_channelId, _protocolHandler.getProtocolMajorVersion(), - _protocolHandler.getProtocolMinorVersion(), _consumerTag, // consumerTag - false); // nowait + BasicCancelBody body = getSession().getMethodRegistry().createBasicCancelBody(_consumerTag, false); - try - { - _protocolHandler.syncWrite(cancelFrame, BasicCancelOkBody.class); + final AMQFrame cancelFrame = body.generateFrame(_channelId); - if (_logger.isDebugEnabled()) - { - _logger.debug("CancelOk'd for consumer:" + debugIdentity()); - } + _protocolHandler.syncWrite(cancelFrame, BasicCancelOkBody.class); - } - catch (AMQException e) + if (_logger.isDebugEnabled()) { - throw new JMSAMQException("Error closing consumer: " + e, e); - } - catch (FailoverException e) - { - throw new JMSAMQException("FailoverException interrupted basic cancel.", e); + _logger.debug("CancelOk'd for consumer:" + debugIdentity()); } } @@ -86,5 +73,5 @@ public class BasicMessageConsumer_0_8 extends BasicMessageConsumer<ContentHeader messageFrame.getRoutingKey(), messageFrame.getContentHeader(), messageFrame.getBodies()); } - + }
\ No newline at end of file diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java index 4c45ec6638..5bb2955399 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java @@ -36,11 +36,18 @@ import javax.jms.StreamMessage; import javax.jms.TextMessage; import javax.jms.Topic; +import org.apache.mina.common.ByteBuffer; import org.apache.qpid.AMQException; import org.apache.qpid.client.message.AbstractJMSMessage; import org.apache.qpid.client.message.MessageConverter; import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.CompositeAMQDataBlock; import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ExchangeDeclareBody; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +58,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac private AMQConnection _connection; /** - * If true, messages will not get a timestamp. + * If true, messages will not get a timestamp. */ protected boolean _disableTimestamps; @@ -103,7 +110,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac private long _producerId; /** - * The session used to create this producer + * The session used to create this producer */ protected AMQSession _session; @@ -118,8 +125,8 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac private static final ContentBody[] NO_CONTENT_BODIES = new ContentBody[0]; protected BasicMessageProducer(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId, - AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory, - boolean waitUntilSent) + AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory, + boolean waitUntilSent) { _connection = connection; _destination = destination; @@ -146,7 +153,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac } } - public abstract void declareDestination(AMQDestination destination); + abstract void declareDestination(AMQDestination destination); public void setDisableMessageID(boolean b) throws JMSException { @@ -181,7 +188,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac if ((i != DeliveryMode.NON_PERSISTENT) && (i != DeliveryMode.PERSISTENT)) { throw new JMSException("DeliveryMode must be either NON_PERSISTENT or PERSISTENT. Value of " + i - + " is illegal"); + + " is illegal"); } _deliveryMode = i; @@ -293,7 +300,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac { validateDestination(destination); sendImpl((AMQDestination) destination, message, _deliveryMode, _messagePriority, _timeToLive, _mandatory, - _immediate); + _immediate); } } @@ -310,7 +317,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory) throws JMSException + boolean mandatory) throws JMSException { checkPreConditions(); checkDestination(destination); @@ -322,7 +329,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate) throws JMSException + boolean mandatory, boolean immediate) throws JMSException { checkPreConditions(); checkDestination(destination); @@ -334,7 +341,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac } public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException + boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException { checkPreConditions(); checkDestination(destination); @@ -342,7 +349,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac { validateDestination(destination); sendImpl((AMQDestination) destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, - waitUntilSent); + waitUntilSent); } } @@ -388,7 +395,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac else { throw new JMSException("Unable to send message, due to class conversion error: " - + message.getClass().getName()); + + message.getClass().getName()); } } } @@ -398,14 +405,19 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac if (!(destination instanceof AMQDestination)) { throw new JMSException("Unsupported destination class: " - + ((destination != null) ? destination.getClass() : null)); + + ((destination != null) ? destination.getClass() : null)); } - declareDestination((AMQDestination) destination); + AMQDestination amqDestination = (AMQDestination) destination; + if(!amqDestination.isExchangeExistsChecked()) + { + declareDestination(amqDestination); + amqDestination.setExchangeExistsChecked(true); + } } protected void sendImpl(AMQDestination destination, Message message, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate) throws JMSException + boolean mandatory, boolean immediate) throws JMSException { sendImpl(destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, _waitUntilSent); } @@ -420,16 +432,27 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac * @param timeToLive * @param mandatory * @param immediate + * * @throws JMSException */ protected void sendImpl(AMQDestination destination, Message origMessage, int deliveryMode, int priority, long timeToLive, - boolean mandatory, boolean immediate, boolean wait) throws JMSException + boolean mandatory, boolean immediate, boolean wait) throws JMSException { checkTemporaryDestination(destination); origMessage.setJMSDestination(destination); AbstractJMSMessage message = convertToNativeMessage(origMessage); + if (_transacted) + { + if (_session.hasFailedOver() && _session.isDirty()) + { + throw new JMSAMQException("Failover has occurred and session is dirty so unable to send.", + new AMQSessionDirtyException("Failover has occurred and session is dirty " + + "so unable to send.")); + } + } + if (_disableMessageId) { message.setJMSMessageID(null); @@ -456,14 +479,27 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac type = AMQDestination.UNKNOWN_TYPE; } - // message.getJmsHeaders().setInteger(CustomJMSXProperty.JMS_QPID_DESTTYPE.getShortStringName(), type); + sendMessage(destination, origMessage, message, deliveryMode, priority, timeToLive, mandatory, immediate, wait); + + if (message != origMessage) + { + _logger.debug("Updating original message"); + origMessage.setJMSPriority(message.getJMSPriority()); + origMessage.setJMSTimestamp(message.getJMSTimestamp()); + _logger.debug("Setting JMSExpiration:" + message.getJMSExpiration()); + origMessage.setJMSExpiration(message.getJMSExpiration()); + origMessage.setJMSMessageID(message.getJMSMessageID()); + } - sendMessage(destination, origMessage, message, deliveryMode, priority, timeToLive, - mandatory, immediate, wait); + if (_transacted) + { + _session.markDirty(); + } } - public abstract void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, int deliveryMode, - int priority, long timeToLive, boolean mandatory, boolean immediate, boolean wait)throws JMSException; + abstract void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, + int deliveryMode, int priority, long timeToLive, boolean mandatory, + boolean immediate, boolean wait)throws JMSException; private void checkTemporaryDestination(AMQDestination destination) throws JMSException { @@ -520,7 +556,7 @@ public abstract class BasicMessageProducer extends Closeable implements org.apac if ((_destination != null) && (suppliedDestination != null)) { throw new UnsupportedOperationException( - "This message producer was created with a Destination, therefore you cannot use an unidentified Destination"); + "This message producer was created with a Destination, therefore you cannot use an unidentified Destination"); } if (suppliedDestination == null) diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java index 2e31c43b4c..5fd6c23f68 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java @@ -55,7 +55,7 @@ public class BasicMessageProducer_0_10 extends BasicMessageProducer mandatory, waitUntilSent); } - public void declareDestination(AMQDestination destination) + void declareDestination(AMQDestination destination) { ((AMQSession_0_10) getSession()).getQpidSession().exchangeDeclare(destination.getExchangeName().toString(), destination.getExchangeClass().toString(), @@ -67,9 +67,9 @@ public class BasicMessageProducer_0_10 extends BasicMessageProducer /** * Sends a message to a given destination */ - public void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, - int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate, - boolean wait) throws JMSException + void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message, + int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate, + boolean wait) throws JMSException { message.prepareForSending(); if (message.get010Message() == null) diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java index 0e79069463..ff991b1a03 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java @@ -45,38 +45,37 @@ public class BasicMessageProducer_0_8 extends BasicMessageProducer super(connection, destination,transacted,channelId,session, protocolHandler, producerId, immediate, mandatory,waitUntilSent); } - public void declareDestination(AMQDestination destination) + void declareDestination(AMQDestination destination) { + + ExchangeDeclareBody body = getSession().getMethodRegistry().createExchangeDeclareBody(_session.getTicket(), + destination.getExchangeName(), + destination.getExchangeClass(), + false, + false, + false, + false, + true, + null); // Declare the exchange // Note that the durable and internal arguments are ignored since passive is set to false - // TODO: Be aware of possible changes to parameter order as versions change. - AMQFrame declare = - ExchangeDeclareBody.createAMQFrame(_channelId, _protocolHandler.getProtocolMajorVersion(), - _protocolHandler.getProtocolMinorVersion(), null, // arguments - false, // autoDelete - false, // durable - destination.getExchangeName(), // exchange - false, // internal - true, // nowait - false, // passive - _session.getTicket(), // ticket - destination.getExchangeClass()); // type + + AMQFrame declare = body.generateFrame(_channelId); + _protocolHandler.writeFrame(declare); } - public void sendMessage(AMQDestination destination, Message origMessage,AbstractJMSMessage message, - int deliveryMode,int priority, long timeToLive, boolean mandatory, boolean immediate, boolean wait) throws JMSException + void sendMessage(AMQDestination destination, Message origMessage,AbstractJMSMessage message, + int deliveryMode,int priority, long timeToLive, boolean mandatory, boolean immediate, + boolean wait) throws JMSException { -// AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - // Be aware of possible changes to parameter order as versions change. - AMQFrame publishFrame = - BasicPublishBody.createAMQFrame(_channelId, _protocolHandler.getProtocolMajorVersion(), - _protocolHandler.getProtocolMinorVersion(), destination.getExchangeName(), // exchange - immediate, // immediate - mandatory, // mandatory - destination.getRoutingKey(), // routingKey - _session.getTicket()); // ticket + BasicPublishBody body = getSession().getMethodRegistry().createBasicPublishBody(_session.getTicket(), + destination.getExchangeName(), + destination.getRoutingKey(), + mandatory, + immediate); + + AMQFrame publishFrame = body.generateFrame(_channelId); message.prepareForSending(); ByteBuffer payload = message.getData(); @@ -114,13 +113,13 @@ public class BasicMessageProducer_0_8 extends BasicMessageProducer _logger.debug("Sending content body frames to " + destination); } - // weight argument of zero indicates no child content headers, just bodies - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. + + // TODO: This is a hacky way of getting the AMQP class-id for the Basic class + int classIfForBasic = getSession().getMethodRegistry().createBasicQosOkBody().getClazz(); + AMQFrame contentHeaderFrame = ContentHeaderBody.createAMQFrame(_channelId, - BasicConsumeBody.getClazz(_protocolHandler.getProtocolMajorVersion(), - _protocolHandler.getProtocolMinorVersion()), 0, contentHeaderProperties, size); + classIfForBasic, 0, contentHeaderProperties, size); if (_logger.isDebugEnabled()) { _logger.debug("Sending content header frame to " + destination); @@ -129,17 +128,19 @@ public class BasicMessageProducer_0_8 extends BasicMessageProducer frames[0] = publishFrame; frames[1] = contentHeaderFrame; CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames); - _protocolHandler.writeFrame(compositeFrame, wait); - if (message != origMessage) + try + { + _session.checkFlowControl(); + } + catch (InterruptedException e) { - _logger.debug("Updating original message"); - origMessage.setJMSPriority(message.getJMSPriority()); - origMessage.setJMSTimestamp(message.getJMSTimestamp()); - _logger.debug("Setting JMSExpiration:" + message.getJMSExpiration()); - origMessage.setJMSExpiration(message.getJMSExpiration()); - origMessage.setJMSMessageID(message.getJMSMessageID()); + JMSException jmsEx = new JMSException("Interrupted while waiting for flow control to be removed"); + jmsEx.setLinkedException(e); + throw jmsEx; } + + _protocolHandler.writeFrame(compositeFrame, wait); } /** diff --git a/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java b/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java index a5e89ef4fc..8cb285c1ac 100644 --- a/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java +++ b/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java @@ -29,9 +29,10 @@ import org.apache.qpid.framing.AMQShortString; public enum CustomJMSXProperty
{
JMS_AMQP_NULL,
- JMS_QPID_DESTTYPE,
+ JMS_QPID_DESTTYPE,
JMSXGroupID,
- JMSXGroupSeq;
+ JMSXGroupSeq,
+ JMSXUserID;
private final AMQShortString _nameAsShortString;
@@ -47,7 +48,7 @@ public enum CustomJMSXProperty }
private static Enumeration _names;
-
+
public static synchronized Enumeration asEnumeration()
{
if(_names == null)
diff --git a/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java b/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java index 493e2b5ec0..27783bcacf 100644 --- a/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java +++ b/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java @@ -22,11 +22,9 @@ package org.apache.qpid.client; import javax.jms.Destination; -import javax.jms.IllegalStateException; import javax.jms.InvalidDestinationException; import javax.jms.JMSException; import javax.jms.Message; -import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.QueueSender; @@ -200,19 +198,19 @@ public class QueueSenderAdapter implements QueueSender } AMQDestination destination = (AMQDestination) queue; - if (!destination.isValidated() && checkQueueBeforePublish()) + if (!destination.isCheckedForQueueBinding() && checkQueueBeforePublish()) { if (_delegate.getSession().isStrictAMQP()) { _delegate._logger.warn("AMQP does not support destination validation before publish, "); - destination.setValidated(true); + destination.setCheckedForQueueBinding(true); } else { if (_delegate.isBound(destination)) { - destination.setValidated(true); + destination.setCheckedForQueueBinding(true); } else { diff --git a/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java b/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java index dd8ca8e197..29fa03fd1d 100644 --- a/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java +++ b/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java @@ -21,18 +21,18 @@ package org.apache.qpid.client; import org.apache.qpid.AMQException; -import org.apache.qpid.jms.TopicSubscriber; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.Topic; +import javax.jms.TopicSubscriber; /** * Wraps a MessageConsumer to fulfill the extended TopicSubscriber contract * */ -class TopicSubscriberAdaptor implements org.apache.qpid.jms.TopicSubscriber +class TopicSubscriberAdaptor implements TopicSubscriber { private final Topic _topic; private final BasicMessageConsumer _consumer; diff --git a/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java b/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java index fb2d72267b..76c899f565 100644 --- a/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java @@ -123,13 +123,17 @@ public class FailoverHandler implements Runnable // We wake up listeners. If they can handle failover, they will extend the // FailoverRetrySupport 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")); + _amqProtocolHandler.notifyFailoverStarting(); // 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()) { + //Clear the exception now that we have the failover mutex there can be no one else waiting for a frame so + // we can clear the exception. + _amqProtocolHandler.failoverInProgress(); + // 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 @@ -141,6 +145,9 @@ public class FailoverHandler implements Runnable _logger.info("Failover process veto-ed by client"); _amqProtocolHandler.setStateManager(existingStateManager); + + //todo: ritchiem these exceptions are useless... Would be better to attempt to propogate exception that + // prompted the failover event. if (_host != null) { _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Redirect was vetoed by client", null)); diff --git a/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java b/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java index 120a07f0fc..e756d7baf9 100644 --- a/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java +++ b/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java @@ -122,6 +122,13 @@ public class FailoverRetrySupport<T, E extends Exception> implements FailoverSup {
_log.debug("Failover exception caught during operation: " + e, e);
}
+ catch (IllegalStateException e)
+ {
+ if (!(e.getMessage().startsWith("Fail-over interupted no-op failover support")))
+ {
+ throw e;
+ }
+ }
}
}
}
diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java new file mode 100644 index 0000000000..a150d1446a --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java @@ -0,0 +1,36 @@ +package org.apache.qpid.client.handler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.AMQNoConsumersException;
+import org.apache.qpid.client.AMQNoRouteException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInvalidRoutingKeyException;
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.protocol.AMQConstant;
+
+public class AccessRequestOkMethodHandler implements StateAwareMethodListener<AccessRequestOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AccessRequestOkMethodHandler.class);
+
+ private static AccessRequestOkMethodHandler _handler = new AccessRequestOkMethodHandler();
+
+ public static AccessRequestOkMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AccessRequestOkBody method, int channelId)
+ throws AMQException
+ {
+ _logger.debug("AccessRequestOk method received");
+ final AMQProtocolSession session = stateManager.getProtocolSession();
+ session.setTicket(method.getTicket(), channelId);
+
+ }
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java index 8f0ee05b3e..e3e08667d8 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java @@ -25,12 +25,13 @@ import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.AMQMethodBody; import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicCancelOkMethodHandler implements StateAwareMethodListener +public class BasicCancelOkMethodHandler implements StateAwareMethodListener<BasicCancelOkBody> { private static final Logger _logger = LoggerFactory.getLogger(BasicCancelOkMethodHandler.class); @@ -44,16 +45,18 @@ public class BasicCancelOkMethodHandler implements StateAwareMethodListener private BasicCancelOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, BasicCancelOkBody body, int channelId) throws AMQException { - BasicCancelOkBody body = (BasicCancelOkBody) evt.getMethod(); + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) { - _logger.info("New BasicCancelOk method received for consumer:" + body.consumerTag); + _logger.info("New BasicCancelOk method received for consumer:" + body.getConsumerTag()); } - protocolSession.confirmConsumerCancelled(evt.getChannelId(), body.consumerTag); + session.confirmConsumerCancelled(channelId, body.getConsumerTag()); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java index 92ba6fd136..4deaa314ec 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java @@ -21,16 +21,16 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; +import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.framing.BasicDeliverBody; -import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicDeliverMethodHandler implements StateAwareMethodListener +public class BasicDeliverMethodHandler implements StateAwareMethodListener<BasicDeliverBody> { private static final Logger _logger = LoggerFactory.getLogger(BasicDeliverMethodHandler.class); @@ -41,18 +41,18 @@ public class BasicDeliverMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, BasicDeliverBody body, int channelId) throws AMQException { - BasicDeliverBody deliveryBody = (BasicDeliverBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); final UnprocessedMessage_0_8 msg = new UnprocessedMessage_0_8( - evt.getChannelId(), - deliveryBody.deliveryTag, - deliveryBody.consumerTag.asString(), - deliveryBody.getExchange(), - deliveryBody.getRoutingKey(), - deliveryBody.getRedelivered()); + channelId, + body.getDeliveryTag(), + body.getConsumerTag(), + body.getExchange(), + body.getRoutingKey(), + body.getRedelivered()); _logger.debug("New JmsDeliver method received"); - protocolSession.unprocessedMessageReceived(msg); + session.unprocessedMessageReceived(msg); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java index bb5a56e5bd..682c3ac2c1 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java @@ -22,15 +22,18 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; import org.apache.qpid.client.message.ReturnMessage; +import org.apache.qpid.client.message.UnprocessedMessage; +import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.framing.BasicReturnBody; import org.apache.qpid.protocol.AMQMethodEvent; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicReturnMethodHandler implements StateAwareMethodListener +public class BasicReturnMethodHandler implements StateAwareMethodListener<BasicReturnBody> { private static final Logger _logger = LoggerFactory.getLogger(BasicReturnMethodHandler.class); @@ -41,18 +44,20 @@ public class BasicReturnMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + + public void methodReceived(AMQStateManager stateManager, BasicReturnBody body, int channelId) + throws AMQException { - BasicReturnBody returnBody = (BasicReturnBody)evt.getMethod(); _logger.debug("New JmsBounce method received"); - final ReturnMessage msg = new ReturnMessage(evt.getChannelId(), - returnBody.getExchange(), - returnBody.getRoutingKey(), - returnBody.getReplyText(), - returnBody.getReplyCode() - ); - - protocolSession.unprocessedMessageReceived(msg); + final AMQProtocolSession session = stateManager.getProtocolSession(); + final ReturnMessage msg = new ReturnMessage(channelId, + body.getExchange(), + body.getRoutingKey(), + body.getReplyText(), + body.getReplyCode() + ); + + session.unprocessedMessageReceived(msg); } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java index 9ed3ef7a60..ee4cf14d58 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java @@ -38,7 +38,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelCloseMethodHandler implements StateAwareMethodListener +public class ChannelCloseMethodHandler implements StateAwareMethodListener<ChannelCloseBody> { private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseMethodHandler.class); @@ -49,22 +49,26 @@ public class ChannelCloseMethodHandler implements StateAwareMethodListener return _handler; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, ChannelCloseBody method, int channelId) throws AMQException { _logger.debug("ChannelClose method received"); - ChannelCloseBody method = (ChannelCloseBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); - AMQConstant errorCode = AMQConstant.getConstant(method.replyCode); - AMQShortString reason = method.replyText; + + AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode()); + AMQShortString reason = method.getReplyText(); if (_logger.isDebugEnabled()) { _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason); } - // TODO: Be aware of possible changes to parameter order as versions change. - AMQFrame frame = ChannelCloseOkBody.createAMQFrame(evt.getChannelId(), method.getMajor(), method.getMinor()); - protocolSession.writeFrame(frame); + + + ChannelCloseOkBody body = session.getMethodRegistry().createChannelCloseOkBody(); + AMQFrame frame = body.generateFrame(channelId); + session.writeFrame(frame); + if (errorCode != AMQConstant.REPLY_SUCCESS) { if (_logger.isDebugEnabled()) @@ -100,6 +104,11 @@ public class ChannelCloseMethodHandler implements StateAwareMethodListener } // fixme why is this only done when the close is expected... // should the above forced closes not also cause a close? - protocolSession.channelClosed(evt.getChannelId(), errorCode, String.valueOf(reason)); + // ---------- + // Closing the session only when it is expected allows the errors to be processed + // Calling this here will prevent failover. So we should do this for all exceptions + // that should never cause failover. Such as authentication errors. + + session.channelClosed(channelId, errorCode, String.valueOf(reason)); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java index e1fe2697e5..8d3277d4de 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java @@ -21,6 +21,7 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; @@ -29,7 +30,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelCloseOkMethodHandler implements StateAwareMethodListener +public class ChannelCloseOkMethodHandler implements StateAwareMethodListener<ChannelCloseOkBody> { private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseOkMethodHandler.class); @@ -40,11 +41,12 @@ public class ChannelCloseOkMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, ChannelCloseOkBody method, int channelId) throws AMQException { - _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId()); + _logger.info("Received channel-close-ok for channel-id " + channelId); + final AMQProtocolSession session = stateManager.getProtocolSession(); // todo this should do the local closure } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java new file mode 100644 index 0000000000..b47fe751d6 --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java @@ -0,0 +1,54 @@ +package org.apache.qpid.client.handler; + +import org.apache.qpid.framing.ChannelFlowBody; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ + +public class ChannelFlowMethodHandler implements StateAwareMethodListener<ChannelFlowBody> +{ + private static final Logger _logger = LoggerFactory.getLogger(ChannelFlowMethodHandler.class); + private static final ChannelFlowMethodHandler _instance = new ChannelFlowMethodHandler(); + + public static ChannelFlowMethodHandler getInstance() + { + return _instance; + } + + private ChannelFlowMethodHandler() + { } + + public void methodReceived(AMQStateManager stateManager, ChannelFlowBody body, int channelId) + throws AMQException + { + + final AMQProtocolSession session = stateManager.getProtocolSession(); + session.setFlowControl(channelId, body.getActive()); + } + + +} diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java index ca3f46d08b..96de8af54b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java @@ -30,7 +30,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelFlowOkMethodHandler implements StateAwareMethodListener +public class ChannelFlowOkMethodHandler implements StateAwareMethodListener<ChannelFlowOkBody> { private static final Logger _logger = LoggerFactory.getLogger(ChannelFlowOkMethodHandler.class); private static final ChannelFlowOkMethodHandler _instance = new ChannelFlowOkMethodHandler(); @@ -43,10 +43,12 @@ public class ChannelFlowOkMethodHandler implements StateAwareMethodListener private ChannelFlowOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ChannelFlowOkBody body, int channelId) + throws AMQException { - ChannelFlowOkBody method = (ChannelFlowOkBody) evt.getMethod(); - _logger.debug("Received Channel.Flow-Ok message, active = " + method.active); + + _logger.debug("Received Channel.Flow-Ok message, active = " + body.getActive()); } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java new file mode 100644 index 0000000000..de976b05bd --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java @@ -0,0 +1,528 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 java.util.Map;
+import java.util.HashMap;
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.AMQMethodNotImplementedException;
+
+
+public class ClientMethodDispatcherImpl implements MethodDispatcher
+{
+
+
+ private static final BasicCancelOkMethodHandler _basicCancelOkMethodHandler = BasicCancelOkMethodHandler.getInstance();
+ private static final BasicDeliverMethodHandler _basicDeliverMethodHandler = BasicDeliverMethodHandler.getInstance();
+ private static final BasicReturnMethodHandler _basicReturnMethodHandler = BasicReturnMethodHandler.getInstance();
+ private static final ChannelCloseMethodHandler _channelCloseMethodHandler = ChannelCloseMethodHandler.getInstance();
+ private static final ChannelFlowOkMethodHandler _channelFlowOkMethodHandler = ChannelFlowOkMethodHandler.getInstance();
+ private static final ConnectionCloseMethodHandler _connectionCloseMethodHandler = ConnectionCloseMethodHandler.getInstance();
+ private static final ConnectionOpenOkMethodHandler _connectionOpenOkMethodHandler = ConnectionOpenOkMethodHandler.getInstance();
+ private static final ConnectionRedirectMethodHandler _connectionRedirectMethodHandler = ConnectionRedirectMethodHandler.getInstance();
+ private static final ConnectionSecureMethodHandler _connectionSecureMethodHandler = ConnectionSecureMethodHandler.getInstance();
+ private static final ConnectionStartMethodHandler _connectionStartMethodHandler = ConnectionStartMethodHandler.getInstance();
+ private static final ConnectionTuneMethodHandler _connectionTuneMethodHandler = ConnectionTuneMethodHandler.getInstance();
+ private static final ExchangeBoundOkMethodHandler _exchangeBoundOkMethodHandler = ExchangeBoundOkMethodHandler.getInstance();
+ private static final QueueDeleteOkMethodHandler _queueDeleteOkMethodHandler = QueueDeleteOkMethodHandler.getInstance();
+
+
+
+ private static interface DispatcherFactory
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQStateManager stateManager);
+ }
+
+ private static final Map<ProtocolVersion, DispatcherFactory> _dispatcherFactories =
+ new HashMap<ProtocolVersion, DispatcherFactory>();
+
+ static
+ {
+ _dispatcherFactories.put(ProtocolVersion.v8_0,
+ new DispatcherFactory()
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ClientMethodDispatcherImpl_8_0(stateManager);
+ }
+ });
+
+ _dispatcherFactories.put(ProtocolVersion.v0_9,
+ new DispatcherFactory()
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ClientMethodDispatcherImpl_0_9(stateManager);
+ }
+ });
+
+ }
+
+
+ public static ClientMethodDispatcherImpl newMethodDispatcher(ProtocolVersion version, AMQStateManager stateManager)
+ {
+ DispatcherFactory factory = _dispatcherFactories.get(version);
+ return factory.createMethodDispatcher(stateManager);
+ }
+
+
+
+
+ private AMQStateManager _stateManager;
+
+ public ClientMethodDispatcherImpl(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ }
+
+
+ public AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+ public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException
+ {
+ _basicCancelOkMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicConsumeOk(BasicConsumeOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicDeliver(BasicDeliverBody body, int channelId) throws AMQException
+ {
+ _basicDeliverMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicGetEmpty(BasicGetEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicGetOk(BasicGetOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicQosOk(BasicQosOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicReturn(BasicReturnBody body, int channelId) throws AMQException
+ {
+ _basicReturnMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException
+ {
+ _channelCloseMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException
+ {
+ _channelFlowOkMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelOpenOk(ChannelOpenOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchConnectionClose(ConnectionCloseBody body, int channelId) throws AMQException
+ {
+ _connectionCloseMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionCloseOk(ConnectionCloseOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchConnectionOpenOk(ConnectionOpenOkBody body, int channelId) throws AMQException
+ {
+ _connectionOpenOkMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException
+ {
+ _connectionRedirectMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException
+ {
+ _connectionSecureMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException
+ {
+ _connectionStartMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException
+ {
+ _connectionTuneMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException
+ {
+ _queueDeleteOkMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchQueuePurgeOk(QueuePurgeOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamCancelOk(StreamCancelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamConsumeOk(StreamConsumeOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchAccessRequest(AccessRequestBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicAck(BasicAckBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicCancel(BasicCancelBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicConsume(BasicConsumeBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicGet(BasicGetBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicPublish(BasicPublishBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicQos(BasicQosBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicRecover(BasicRecoverBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchBasicReject(BasicRejectBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchChannelOpen(ChannelOpenBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchConnectionOpen(ConnectionOpenBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchConnectionSecureOk(ConnectionSecureOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchConnectionStartOk(ConnectionStartOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchConnectionTuneOk(ConnectionTuneOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchDtxSelect(DtxSelectBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchDtxStart(DtxStartBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchExchangeBound(ExchangeBoundBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchExchangeDeclare(ExchangeDeclareBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchExchangeDelete(ExchangeDeleteBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileAck(FileAckBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileCancel(FileCancelBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileConsume(FileConsumeBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFilePublish(FilePublishBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileQos(FileQosBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileReject(FileRejectBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchQueueBind(QueueBindBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchQueueDeclare(QueueDeclareBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchQueueDelete(QueueDeleteBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchQueuePurge(QueuePurgeBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchStreamCancel(StreamCancelBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchStreamConsume(StreamConsumeBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchStreamPublish(StreamPublishBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchStreamQos(StreamQosBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchTunnelRequest(TunnelRequestBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchTxCommit(TxCommitBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchTxRollback(TxRollbackBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchTxSelect(TxSelectBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchDtxSelectOk(DtxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchDtxStartOk(DtxStartOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchExchangeBoundOk(ExchangeBoundOkBody body, int channelId) throws AMQException
+ {
+ _exchangeBoundOkMethodHandler.methodReceived(_stateManager,body,channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDeclareOk(ExchangeDeclareOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchExchangeDeleteOk(ExchangeDeleteOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileCancelOk(FileCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileConsumeOk(FileConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileDeliver(FileDeliverBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileOpen(FileOpenBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileOpenOk(FileOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileQosOk(FileQosOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileReturn(FileReturnBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchFileStage(FileStageBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchQueueBindOk(QueueBindOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueDeclareOk(QueueDeclareOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamDeliver(StreamDeliverBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchStreamQosOk(StreamQosOkBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchStreamReturn(StreamReturnBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchTxCommitOk(TxCommitOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTxRollbackOk(TxRollbackOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTxSelectOk(TxSelectOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java new file mode 100644 index 0000000000..ae6d5e8283 --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java @@ -0,0 +1,155 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.*;
+import org.apache.qpid.framing.amqp_0_9.MethodDispatcher_0_9;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.AMQMethodNotImplementedException;
+
+
+public class ClientMethodDispatcherImpl_0_9 extends ClientMethodDispatcherImpl implements MethodDispatcher_0_9
+{
+ public ClientMethodDispatcherImpl_0_9(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ throw new AMQMethodNotImplementedException(body);
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java new file mode 100644 index 0000000000..6bd6874cde --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java @@ -0,0 +1,85 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.*;
+import org.apache.qpid.framing.amqp_8_0.MethodDispatcher_8_0;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.state.AMQStateManager;
+
+public class ClientMethodDispatcherImpl_8_0 extends ClientMethodDispatcherImpl implements MethodDispatcher_8_0
+{
+ public ClientMethodDispatcherImpl_8_0(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelAlert(ChannelAlertBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestContent(TestContentBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestContentOk(TestContentOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestInteger(TestIntegerBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestIntegerOk(TestIntegerOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestString(TestStringBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestStringOk(TestStringOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTable(TestTableBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTableOk(TestTableOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java index 752f44237d..950a3288fc 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java @@ -36,7 +36,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ConnectionCloseMethodHandler implements StateAwareMethodListener +public class ConnectionCloseMethodHandler implements StateAwareMethodListener<ConnectionCloseBody> { private static final Logger _logger = LoggerFactory.getLogger(ConnectionCloseMethodHandler.class); @@ -50,32 +50,35 @@ public class ConnectionCloseMethodHandler implements StateAwareMethodListener private ConnectionCloseMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionCloseBody method, int channelId) + throws AMQException { _logger.info("ConnectionClose frame received"); - ConnectionCloseBody method = (ConnectionCloseBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + // does it matter // stateManager.changeState(AMQState.CONNECTION_CLOSING); - AMQConstant errorCode = AMQConstant.getConstant(method.replyCode); - AMQShortString reason = method.replyText; + AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode()); + AMQShortString reason = method.getReplyText(); try { + + ConnectionCloseOkBody closeOkBody = session.getMethodRegistry().createConnectionCloseOkBody(); // TODO: check whether channel id of zero is appropriate // Be aware of possible changes to parameter order as versions change. - protocolSession.writeFrame(ConnectionCloseOkBody.createAMQFrame((short) 0, method.getMajor(), - method.getMinor())); + session.writeFrame(closeOkBody.generateFrame(0)); if (errorCode != AMQConstant.REPLY_SUCCESS) { - if (errorCode == AMQConstant.NOT_ALLOWED) + if (errorCode == AMQConstant.NOT_ALLOWED || (errorCode == AMQConstant.ACCESS_REFUSED)) { - _logger.info("Authentication Error:" + Thread.currentThread().getName()); + _logger.info("Error :" + errorCode +":"+ Thread.currentThread().getName()); - protocolSession.closeProtocolSession(); + // todo ritchiem : Why do this here when it is going to be done in the finally block? + session.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); @@ -94,9 +97,13 @@ public class ConnectionCloseMethodHandler implements StateAwareMethodListener { // this actually closes the connection in the case where it is not an error. - protocolSession.closeProtocolSession(); + session.closeProtocolSession(); + // ritchiem: Doing this though will cause any waiting connection start to be released without being able to + // see what the cause was. stateManager.changeState(AMQState.CONNECTION_CLOSED); } } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java index 2e0f273c32..fd7acac84f 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java @@ -21,13 +21,14 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionOpenOkBody; 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.protocol.AMQMethodEvent; -public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener +public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener<ConnectionOpenOkBody> { private static final ConnectionOpenOkMethodHandler _instance = new ConnectionOpenOkMethodHandler(); @@ -40,9 +41,11 @@ public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionOpenOkBody body, int channelId) + throws AMQException { stateManager.changeState(AMQState.CONNECTION_OPEN); } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java index 213c0eba6e..cac68c9467 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java @@ -30,7 +30,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ConnectionRedirectMethodHandler implements StateAwareMethodListener +public class ConnectionRedirectMethodHandler implements StateAwareMethodListener<ConnectionRedirectBody> { private static final Logger _logger = LoggerFactory.getLogger(ConnectionRedirectMethodHandler.class); @@ -46,13 +46,13 @@ public class ConnectionRedirectMethodHandler implements StateAwareMethodListener private ConnectionRedirectMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionRedirectBody method, int channelId) + throws AMQException { _logger.info("ConnectionRedirect frame received"); - ConnectionRedirectBody method = (ConnectionRedirectBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); - String host = method.host.toString(); + String host = method.getHost().toString(); // the host is in the form hostname:port with the port being optional int portIndex = host.indexOf(':'); @@ -68,6 +68,7 @@ public class ConnectionRedirectMethodHandler implements StateAwareMethodListener } - protocolSession.failover(host, port); + session.failover(host, port); } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java index b7776705fe..900aa2abac 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java @@ -32,7 +32,7 @@ import org.apache.qpid.framing.ConnectionSecureBody; import org.apache.qpid.framing.ConnectionSecureOkBody; import org.apache.qpid.protocol.AMQMethodEvent; -public class ConnectionSecureMethodHandler implements StateAwareMethodListener +public class ConnectionSecureMethodHandler implements StateAwareMethodListener<ConnectionSecureBody> { private static final ConnectionSecureMethodHandler _instance = new ConnectionSecureMethodHandler(); @@ -41,27 +41,26 @@ public class ConnectionSecureMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionSecureBody body, int channelId) + throws AMQException { - SaslClient client = protocolSession.getSaslClient(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + SaslClient client = session.getSaslClient(); if (client == null) { throw new AMQException(null, "No SASL client set up - cannot proceed with authentication", null); } - ConnectionSecureBody body = (ConnectionSecureBody) evt.getMethod(); + try { // Evaluate server challenge - byte[] response = client.evaluateChallenge(body.challenge); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - // Be aware of possible changes to parameter order as versions change. - AMQFrame responseFrame = ConnectionSecureOkBody.createAMQFrame(evt.getChannelId(), - body.getMajor(), body.getMinor(), - response); // response - protocolSession.writeFrame(responseFrame); + byte[] response = client.evaluateChallenge(body.getChallenge()); + + ConnectionSecureOkBody secureOkBody = session.getMethodRegistry().createConnectionSecureOkBody(response); + + session.writeFrame(secureOkBody.generateFrame(channelId)); } catch (SaslException e) { @@ -70,4 +69,6 @@ public class ConnectionSecureMethodHandler implements StateAwareMethodListener } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java index 59b493a6f7..d3746f137e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java @@ -48,7 +48,7 @@ import java.io.UnsupportedEncodingException; import java.util.HashSet; import java.util.StringTokenizer; -public class ConnectionStartMethodHandler implements StateAwareMethodListener +public class ConnectionStartMethodHandler implements StateAwareMethodListener<ConnectionStartBody> { private static final Logger _log = LoggerFactory.getLogger(ConnectionStartMethodHandler.class); @@ -62,15 +62,16 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener private ConnectionStartMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionStartBody body, int channelId) + throws AMQException { _log.debug("public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, " + "AMQMethodEvent evt): called"); - ConnectionStartBody body = (ConnectionStartBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + - ProtocolVersion pv = new ProtocolVersion((byte) body.versionMajor, (byte) body.versionMinor); + ProtocolVersion pv = new ProtocolVersion((byte) body.getVersionMajor(), (byte) body.getVersionMinor()); // For the purposes of interop, we can make the client accept the broker's version string. // If it does, it then internally records the version as being the latest one that it understands. @@ -83,26 +84,26 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener if (pv.isSupported()) { - protocolSession.setProtocolVersion(pv.getMajorVersion(), pv.getMinorVersion()); + session.setProtocolVersion(pv); try { // Used to hold the SASL mechanism to authenticate with. String mechanism; - if (body.mechanisms == null) + if (body.getMechanisms()== null) { throw new AMQException(null, "mechanism not specified in ConnectionStart method frame", null); } else { - mechanism = chooseMechanism(body.mechanisms); + mechanism = chooseMechanism(body.getMechanisms()); _log.debug("mechanism = " + mechanism); } if (mechanism == null) { - throw new AMQException(null, "No supported security mechanism found, passed: " + new String(body.mechanisms), null); + throw new AMQException(null, "No supported security mechanism found, passed: " + new String(body.getMechanisms()), null); } byte[] saslResponse; @@ -110,7 +111,7 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener { SaslClient sc = Sasl.createSaslClient(new String[] { mechanism }, null, "AMQP", "localhost", null, - createCallbackHandler(mechanism, protocolSession)); + createCallbackHandler(mechanism, session)); if (sc == null) { throw new AMQException(null, "Client SASL configuration error: no SaslClient could be created for mechanism " + mechanism @@ -118,21 +119,21 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener + " details of how to register non-standard SASL client providers.", null); } - protocolSession.setSaslClient(sc); + session.setSaslClient(sc); saslResponse = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null); } catch (SaslException e) { - protocolSession.setSaslClient(null); + session.setSaslClient(null); throw new AMQException(null, "Unable to create SASL client: " + e, e); } - if (body.locales == null) + if (body.getLocales() == null) { throw new AMQException(null, "Locales is not defined in Connection Start method", null); } - final String locales = new String(body.locales, "utf8"); + final String locales = new String(body.getLocales(), "utf8"); final StringTokenizer tokenizer = new StringTokenizer(locales, " "); String selectedLocale = null; if (tokenizer.hasMoreTokens()) @@ -148,23 +149,20 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener FieldTable clientProperties = FieldTableFactory.newFieldTable(); clientProperties.setString(new AMQShortString(ClientProperties.instance.toString()), - protocolSession.getClientID()); + session.getClientID()); clientProperties.setString(new AMQShortString(ClientProperties.product.toString()), QpidProperties.getProductName()); clientProperties.setString(new AMQShortString(ClientProperties.version.toString()), QpidProperties.getReleaseVersion()); clientProperties.setString(new AMQShortString(ClientProperties.platform.toString()), getFullSystemInfo()); + + ConnectionStartOkBody connectionStartOkBody = session.getMethodRegistry().createConnectionStartOkBody(clientProperties,new AMQShortString(mechanism),saslResponse,new AMQShortString(locales)); // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. // Be aware of possible changes to parameter order as versions change. - protocolSession.writeFrame(ConnectionStartOkBody.createAMQFrame(evt.getChannelId(), - protocolSession.getProtocolMajorVersion(), protocolSession.getProtocolMinorVersion(), - clientProperties, // clientProperties - new AMQShortString(selectedLocale), // locale - new AMQShortString(mechanism), // mechanism - saslResponse)); // response - + session.writeFrame(connectionStartOkBody.generateFrame(channelId)); + } catch (UnsupportedEncodingException e) { @@ -173,10 +171,10 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener } else { - _log.error("Broker requested Protocol [" + body.versionMajor + "-" + body.versionMinor + _log.error("Broker requested Protocol [" + body.getVersionMajor() + "-" + body.getVersionMinor() + "] which is not supported by this version of the client library"); - protocolSession.closeProtocolSession(); + session.closeProtocolSession(); } } @@ -235,4 +233,5 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener throw new AMQException(null, "Unable to create callback handler: " + e, e); } } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java index 68556991d7..fc0e40b745 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java @@ -26,17 +26,13 @@ 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.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ConnectionOpenBody; -import org.apache.qpid.framing.ConnectionTuneBody; -import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ConnectionTuneMethodHandler implements StateAwareMethodListener +public class ConnectionTuneMethodHandler implements StateAwareMethodListener<ConnectionTuneBody> { private static final Logger _logger = LoggerFactory.getLogger(ConnectionTuneMethodHandler.class); @@ -50,48 +46,41 @@ public class ConnectionTuneMethodHandler implements StateAwareMethodListener protected ConnectionTuneMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionTuneBody frame, int channelId) + throws AMQException { _logger.debug("ConnectionTune frame received"); - ConnectionTuneBody frame = (ConnectionTuneBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + final MethodRegistry methodRegistry = session.getMethodRegistry(); - ConnectionTuneParameters params = protocolSession.getConnectionTuneParameters(); + + 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)); - protocolSession.setConnectionTuneParameters(params); + params.setFrameMax(frame.getFrameMax()); + params.setChannelMax(frame.getChannelMax()); + params.setHeartbeat(Integer.getInteger("amqj.heartbeat.delay", frame.getHeartbeat())); + session.setConnectionTuneParameters(params); stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); - protocolSession.writeFrame(createTuneOkFrame(evt.getChannelId(), params, frame.getMajor(), frame.getMinor())); - String host = protocolSession.getAMQConnection().getVirtualHost(); + ConnectionTuneOkBody tuneOkBody = methodRegistry.createConnectionTuneOkBody(params.getChannelMax(), + params.getFrameMax(), + params.getHeartbeat()); + // Be aware of possible changes to parameter order as versions change. + session.writeFrame(tuneOkBody.generateFrame(channelId)); + + String host = session.getAMQConnection().getVirtualHost(); AMQShortString virtualHost = new AMQShortString("/" + host); - protocolSession.writeFrame(createConnectionOpenFrame(evt.getChannelId(), virtualHost, null, true, frame.getMajor(), - frame.getMinor())); - } + ConnectionOpenBody openBody = methodRegistry.createConnectionOpenBody(virtualHost,null,true); - protected AMQFrame createConnectionOpenFrame(int channel, AMQShortString path, AMQShortString capabilities, - boolean insist, byte major, byte minor) - { // Be aware of possible changes to parameter order as versions change. - return ConnectionOpenBody.createAMQFrame(channel, major, minor, // AMQP version (major, minor) - capabilities, // capabilities - insist, // insist - path); // virtualHost + session.writeFrame(openBody.generateFrame(channelId)); } - protected AMQFrame createTuneOkFrame(int channel, ConnectionTuneParameters params, byte major, byte minor) - { - // Be aware of possible changes to parameter order as versions change. - return ConnectionTuneOkBody.createAMQFrame(channel, major, minor, params.getChannelMax(), // channelMax - params.getFrameMax(), // frameMax - params.getHeartbeat()); // heartbeat - } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java index 862a9be8d4..8de40beb10 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; /** * @author Apache Software Foundation */ -public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener +public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener<ExchangeBoundOkBody> { private static final Logger _logger = LoggerFactory.getLogger(ExchangeBoundOkMethodHandler.class); private static final ExchangeBoundOkMethodHandler _instance = new ExchangeBoundOkMethodHandler(); @@ -46,14 +46,14 @@ public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener private ExchangeBoundOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ExchangeBoundOkBody body, int channelId) + throws AMQException { if (_logger.isDebugEnabled()) { - ExchangeBoundOkBody body = (ExchangeBoundOkBody) evt.getMethod(); - _logger.debug("Received Exchange.Bound-Ok message, response code: " + body.replyCode + " text: " - + body.replyText); + _logger.debug("Received Exchange.Bound-Ok message, response code: " + body.getReplyCode() + " text: " + + body.getReplyText()); } } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java index 65060d44d2..41225c0569 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; /** * @author Apache Software Foundation */ -public class QueueDeleteOkMethodHandler implements StateAwareMethodListener +public class QueueDeleteOkMethodHandler implements StateAwareMethodListener<QueueDeleteOkBody> { private static final Logger _logger = LoggerFactory.getLogger(QueueDeleteOkMethodHandler.class); private static final QueueDeleteOkMethodHandler _instance = new QueueDeleteOkMethodHandler(); @@ -46,13 +46,14 @@ public class QueueDeleteOkMethodHandler implements StateAwareMethodListener private QueueDeleteOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException - { + public void methodReceived(AMQStateManager stateManager, QueueDeleteOkBody body, int channelId) + throws AMQException + { if (_logger.isDebugEnabled()) { - QueueDeleteOkBody body = (QueueDeleteOkBody) evt.getMethod(); - _logger.debug("Received Queue.Delete-Ok message, message count: " + body.messageCount); + _logger.debug("Received Queue.Delete-Ok message, message count: " + body.getMessageCount()); } } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java index cfbf687401..f57f0ff252 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java @@ -63,8 +63,8 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach protected boolean _changedData = true; private Destination _destination; private JMSHeaderAdapter _headerAdapter; - private BasicMessageConsumer _consumer; - private boolean _strictAMQP; + private static final boolean STRICT_AMQP_COMPLIANCE = + Boolean.parseBoolean(System.getProperties().getProperty(AMQSession.STRICT_AMQP, AMQSession.STRICT_AMQP_DEFAULT)); /** * This is 0_10 specific @@ -108,6 +108,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach return _010message; } + protected AbstractJMSMessage(ByteBuffer data) { super(new BasicContentHeaderProperties()); @@ -122,8 +123,6 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach _changedData = (data == null); _headerAdapter = new JMSHeaderAdapter(((BasicContentHeaderProperties) _contentHeaderProperties).getHeaders()); - _strictAMQP = - Boolean.parseBoolean(System.getProperties().getProperty(AMQSession.STRICT_AMQP, AMQSession.STRICT_AMQP_DEFAULT)); } protected AbstractJMSMessage(long deliveryTag, BasicContentHeaderProperties contentHeader, AMQShortString exchange, @@ -171,7 +170,10 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach { if (getContentHeaderProperties().getMessageIdAsString() == null) { - getContentHeaderProperties().setMessageId("ID:" + UUID.randomUUID()); + StringBuilder b = new StringBuilder(39); + b.append("ID:"); + b.append(UUID.randomUUID()); + getContentHeaderProperties().setMessageId(b.toString()); } return getContentHeaderProperties().getMessageIdAsString(); @@ -351,7 +353,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public boolean getBooleanProperty(AMQShortString propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -361,7 +363,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public boolean getBooleanProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -371,7 +373,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public byte getByteProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -381,7 +383,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public byte[] getBytesProperty(AMQShortString propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -391,7 +393,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public short getShortProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -401,7 +403,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public int getIntProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -411,7 +413,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public long getLongProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -421,7 +423,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public float getFloatProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -431,7 +433,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public double getDoubleProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -441,12 +443,20 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public String getStringProperty(String propertyName) throws JMSException { - if (_strictAMQP) + //NOTE: if the JMSX Property is a non AMQP property then we must check _strictAMQP and throw as below. + if (propertyName.equals(CustomJMSXProperty.JMSXUserID.toString())) { - throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); + return ((BasicContentHeaderProperties) _contentHeaderProperties).getUserIdAsString(); } + else + { + if (STRICT_AMQP_COMPLIANCE) + { + throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); + } - return getJmsHeaders().getString(propertyName); + return getJmsHeaders().getString(propertyName); + } } public Object getObjectProperty(String propertyName) throws JMSException @@ -461,7 +471,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setBooleanProperty(AMQShortString propertyName, boolean b) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -472,7 +482,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setBooleanProperty(String propertyName, boolean b) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -483,7 +493,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setByteProperty(String propertyName, byte b) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -494,7 +504,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setBytesProperty(AMQShortString propertyName, byte[] bytes) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -505,7 +515,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setShortProperty(String propertyName, short i) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -523,7 +533,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setLongProperty(String propertyName, long l) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -534,7 +544,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setFloatProperty(String propertyName, float f) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -545,7 +555,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setDoubleProperty(String propertyName, double v) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -741,11 +751,6 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach } } - public void setConsumer(BasicMessageConsumer basicMessageConsumer) - { - _consumer = basicMessageConsumer; - } - public void receivedFromServer() { _changedData = false; diff --git a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java index 48992659f6..0dbad3726c 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java @@ -79,8 +79,17 @@ public abstract class AbstractJMSMessageFactory implements MessageFactory while (it.hasNext()) { ContentBody cb = (ContentBody) it.next(); - data.put(cb.payload); - cb.payload.release(); + final ByteBuffer payload = cb.payload; + if(payload.isDirect() || payload.isReadOnly()) + { + data.put(payload); + } + else + { + data.put(payload.array(), payload.arrayOffset(), payload.limit()); + } + + payload.release(); } data.flip(); diff --git a/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java index 1fb5e637c9..fed1f1c609 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java @@ -56,7 +56,11 @@ public class JMSMapMessage extends AbstractBytesTypedMessage implements javax.jm JMSMapMessage(ByteBuffer data) throws JMSException { super(data); // this instantiates a content header - populateMapFromData(); + if(data != null) + { + populateMapFromData(); + } + } JMSMapMessage(long messageNbr, BasicContentHeaderProperties contentHeader, AMQShortString exchange, AMQShortString routingKey, @@ -77,7 +81,7 @@ public class JMSMapMessage extends AbstractBytesTypedMessage implements javax.jm public String toBodyString() throws JMSException { - return _map.toString(); + return _map == null ? "" : _map.toString(); } public AMQShortString getMimeTypeAsShortString() diff --git a/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java index 593c7795b0..c866a5028e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java @@ -9,7 +9,7 @@ public class ReturnMessage extends UnprocessedMessage_0_8 public ReturnMessage(int channelId,AMQShortString exchange,AMQShortString routingKey,AMQShortString replyText,int replyCode) { - super(channelId,-1,"",exchange,routingKey,false); + super(channelId,-1,null,exchange,routingKey,false); _replyText = replyText; _replyCode = replyCode; } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java index 5cb943cd33..17efd7e24f 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java @@ -23,6 +23,20 @@ package org.apache.qpid.client.message; import java.util.List; import org.apache.qpid.framing.AMQShortString; +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.BasicMessageConsumer; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicDeliverBody; +import org.apache.qpid.framing.BasicReturnBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; /** @@ -36,12 +50,12 @@ public abstract class UnprocessedMessage<H,B> { private final int _channelId; private final long _deliveryId; - private final String _consumerTag; + private final AMQShortString _consumerTag; protected AMQShortString _exchange; protected AMQShortString _routingKey; protected boolean _redelivered; - public UnprocessedMessage(int channelId,long deliveryId,String consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) + public UnprocessedMessage(int channelId,long deliveryId,AMQShortString consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) { _channelId = channelId; _deliveryId = deliveryId; @@ -65,7 +79,7 @@ public abstract class UnprocessedMessage<H,B> return _deliveryId; } - public String getConsumerTag() + public AMQShortString getConsumerTag() { return _consumerTag; } @@ -84,14 +98,13 @@ public abstract class UnprocessedMessage<H,B> { return _redelivered; } - public abstract List<B> getBodies(); - + public abstract H getContentHeader(); - + // specific to 0_10 public String getReplyToURL() { return ""; - } + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java index 79d829ca39..09f41a9ba6 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java @@ -43,7 +43,7 @@ public class UnprocessedMessage_0_10 extends UnprocessedMessage<Struct[],ByteBuf /** List of ContentBody instances. Due to fragmentation you don't know how big this will be in general */ private List<ByteBuffer> _bodies = new ArrayList<ByteBuffer>(); - public UnprocessedMessage_0_10(int channelId,long deliveryId,String consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) + public UnprocessedMessage_0_10(int channelId,long deliveryId,AMQShortString consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) { super(channelId,deliveryId,consumerTag,exchange,routingKey,redelivered); } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java index 8e32de382b..78da8cdca2 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.BasicDeliverBody; +import org.apache.qpid.framing.BasicReturnBody; import org.apache.qpid.framing.ContentBody; import org.apache.qpid.framing.ContentHeaderBody; @@ -46,11 +47,22 @@ public class UnprocessedMessage_0_8 extends UnprocessedMessage<ContentHeaderBody /** List of ContentBody instances. Due to fragmentation you don't know how big this will be in general */ private List<ContentBody> _bodies; - public UnprocessedMessage_0_8(int channelId,long deliveryId,String consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) + public UnprocessedMessage_0_8(int channelId,long deliveryId,AMQShortString consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) { super(channelId,deliveryId,consumerTag,exchange,routingKey,redelivered); } + public UnprocessedMessage_0_8(int channelId, BasicReturnBody body) + { + //FIXME: TGM, SRSLY 4RL + super(channelId, 0, null, body.getExchange(), body.getRoutingKey(), false); + } + + public UnprocessedMessage_0_8(int channelId, BasicDeliverBody body) + { + super(channelId, body.getDeliveryTag(), body.getConsumerTag(), body.getExchange(), body.getRoutingKey(), false); + } + public void receiveBody(ContentBody body) { @@ -119,8 +131,8 @@ public class UnprocessedMessage_0_8 extends UnprocessedMessage<ContentHeaderBody } if(_deliverBody != null) { - buf.append("Delivery tag " + _deliverBody.deliveryTag); - buf.append("Consumer tag " + _deliverBody.consumerTag); + buf.append("Delivery tag " + _deliverBody.getDeliveryTag()); + buf.append("Consumer tag " + _deliverBody.getConsumerTag()); buf.append("Deliver Body " + _deliverBody); } diff --git a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java index 2c435aba6c..97b3660240 100644 --- a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java @@ -21,10 +21,15 @@ package org.apache.qpid.client.protocol; import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilterChain; import org.apache.mina.common.IoHandlerAdapter; import org.apache.mina.common.IoSession; +import org.apache.mina.filter.ReadThrottleFilterBuilder; import org.apache.mina.filter.SSLFilter; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.filter.codec.ProtocolCodecException; import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.executor.ExecutorFilter; import org.apache.qpid.AMQConnectionClosedException; import org.apache.qpid.AMQDisconnectedException; import org.apache.qpid.AMQException; @@ -39,16 +44,7 @@ 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.AMQBody; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ConnectionCloseBody; -import org.apache.qpid.framing.ConnectionCloseOkBody; -import org.apache.qpid.framing.ContentBody; -import org.apache.qpid.framing.ContentHeaderBody; -import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.*; import org.apache.qpid.pool.ReadWriteThreadModel; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQMethodEvent; @@ -57,7 +53,9 @@ import org.apache.qpid.ssl.SSLContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; import java.util.Iterator; +import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CountDownLatch; @@ -155,9 +153,19 @@ public class AMQProtocolHandler extends IoHandlerAdapter /** Used to provide a condition to wait upon for operations that are required to wait for failover to complete. */ private CountDownLatch _failoverLatch; + + /** The last failover exception that occured */ + private FailoverException _lastFailoverException; + /** Defines the default timeout to use for synchronous protocol commands. */ private final long DEFAULT_SYNC_TIMEOUT = 1000 * 30; + /** Default buffer size for pending messages reads */ + private static final String DEFAULT_READ_BUFFER_LIMIT = "262144"; + + /** Default buffer size for pending messages writes */ + private static final String DEFAULT_WRITE_BUFFER_LIMIT = "262144"; + /** * Creates a new protocol handler, associated with the specified client connection instance. * @@ -211,9 +219,34 @@ public class AMQProtocolHandler extends IoHandlerAdapter } catch (RuntimeException e) { - e.printStackTrace(); + _logger.error(e.getMessage(), e); } + if (Boolean.getBoolean("protectio")) + { + try + { + //Add IO Protection Filters + IoFilterChain chain = session.getFilterChain(); + + session.getFilterChain().addLast("tempExecutorFilterForFilterBuilder", new ExecutorFilter()); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER_LIMIT))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER_LIMIT))); + writefilter.attach(chain); + session.getFilterChain().remove("tempExecutorFilterForFilterBuilder"); + + _logger.info("Using IO Read/Write Filter Protection"); + } + catch (Exception e) + { + _logger.error("Unable to attach IO Read/Write Filter Protection :" + e.getMessage()); + } + } _protocolSession = new AMQProtocolSession(this, session, _connection, getStateManager()); _protocolSession.init(); } @@ -327,13 +360,27 @@ public class AMQProtocolHandler extends IoHandlerAdapter if (_failoverState == FailoverState.NOT_STARTED) { // if (!(cause instanceof AMQUndeliveredException) && (!(cause instanceof AMQAuthenticationException))) - if (cause instanceof AMQConnectionClosedException) + if ((cause instanceof AMQConnectionClosedException) || cause instanceof IOException) { _logger.info("Exception caught therefore going to attempt failover: " + cause, cause); // this will attemp failover sessionClosed(session); } + else + { + + if (cause instanceof ProtocolCodecException) + { + _logger.info("Protocol Exception caught NOT going to attempt failover as " + + "cause isn't AMQConnectionClosedException: " + cause, cause); + + AMQException amqe = new AMQException("Protocol handler error: " + cause, cause); + propagateExceptionToWaiters(amqe); + } + _connection.exceptionReceived(cause); + + } // FIXME Need to correctly handle other exceptions. Things like ... // if (cause instanceof AMQChannelClosedException) @@ -349,7 +396,7 @@ public class AMQProtocolHandler extends IoHandlerAdapter // 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(null, "Protocol handler error: " + cause, cause); + AMQException amqe = new AMQException("Protocol handler error: " + cause, cause); propagateExceptionToWaiters(amqe); _connection.exceptionReceived(cause); } @@ -364,7 +411,7 @@ public class AMQProtocolHandler extends IoHandlerAdapter */ public void propagateExceptionToWaiters(Exception e) { - getStateManager().error(e); + if (!_frameListeners.isEmpty()) { final Iterator it = _frameListeners.iterator(); @@ -376,98 +423,111 @@ public class AMQProtocolHandler extends IoHandlerAdapter } } - private static int _messageReceivedCount; - - public void messageReceived(IoSession session, Object message) throws Exception + public void notifyFailoverStarting() { - final boolean debug = _logger.isDebugEnabled(); - final long msgNumber = ++_messageReceivedCount; - - if (debug && ((msgNumber % 1000) == 0)) + // Set the last exception in the sync block to ensure the ordering with add. + // either this gets done and the add does the ml.error + // or the add completes first and the iterator below will do ml.error + synchronized (_frameListeners) { - _logger.debug("Received " + _messageReceivedCount + " protocol messages"); + _lastFailoverException = new FailoverException("Failing over about to start"); } - AMQFrame frame = (AMQFrame) message; + propagateExceptionToWaiters(_lastFailoverException); + } - final AMQBody bodyFrame = frame.getBodyFrame(); + public void failoverInProgress() + { + _lastFailoverException = null; + } - HeartbeatDiagnostics.received(bodyFrame instanceof HeartbeatBody); + private static int _messageReceivedCount; - switch (bodyFrame.getFrameType()) + public void messageReceived(IoSession session, Object message) throws Exception + { + if(message instanceof AMQFrame) { - case AMQMethodBody.TYPE: + final boolean debug = _logger.isDebugEnabled(); + final long msgNumber = ++_messageReceivedCount; - if (debug) - { - _logger.debug("(" + System.identityHashCode(this) + ")Method frame received: " + frame); - } + if (debug && ((msgNumber % 1000) == 0)) + { + _logger.debug("Received " + _messageReceivedCount + " protocol messages"); + } - final AMQMethodEvent<AMQMethodBody> evt = - new AMQMethodEvent<AMQMethodBody>(frame.getChannel(), (AMQMethodBody) bodyFrame); + AMQFrame frame = (AMQFrame) message; - try - { + final AMQBody bodyFrame = frame.getBodyFrame(); - boolean wasAnyoneInterested = getStateManager().methodReceived(evt); - if (!_frameListeners.isEmpty()) - { - Iterator it = _frameListeners.iterator(); - while (it.hasNext()) - { - final AMQMethodListener listener = (AMQMethodListener) it.next(); - wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; - } - } - - if (!wasAnyoneInterested) - { - throw new AMQException(null, "AMQMethodEvent " + evt + " was not processed by any listener. Listeners:" - + _frameListeners, null); - } - } - catch (AMQException e) - { - getStateManager().error(e); - if (!_frameListeners.isEmpty()) - { - Iterator it = _frameListeners.iterator(); - while (it.hasNext()) - { - final AMQMethodListener listener = (AMQMethodListener) it.next(); - listener.error(e); - } - } - - exceptionCaught(session, e); - } + HeartbeatDiagnostics.received(bodyFrame instanceof HeartbeatBody); - break; + bodyFrame.handle(frame.getChannel(),_protocolSession); - case ContentHeaderBody.TYPE: + _connection.bytesReceived(_protocolSession.getIoSession().getReadBytes()); + } + else if (message instanceof ProtocolInitiation) + { + // We get here if the server sends a response to our initial protocol header + // suggesting an alternate ProtocolVersion; the server will then close the + // connection. + ProtocolInitiation protocolInit = (ProtocolInitiation) message; + ProtocolVersion pv = protocolInit.checkVersion(); + getConnection().setProtocolVersion(pv); + + // get round a bug in old versions of qpid whereby the connection is not closed + _stateManager.changeState(AMQState.CONNECTION_CLOSED); + } + } - _protocolSession.messageContentHeaderReceived(frame.getChannel(), (ContentHeaderBody) bodyFrame); - break; + public void methodBodyReceived(final int channelId, final AMQBody bodyFrame, IoSession session)//, final IoSession session) + throws AMQException + { - case ContentBody.TYPE: + if (_logger.isDebugEnabled()) + { + _logger.debug("(" + System.identityHashCode(this) + ")Method frame received: " + bodyFrame); + } - _protocolSession.messageContentBodyReceived(frame.getChannel(), (ContentBody) bodyFrame); - break; + final AMQMethodEvent<AMQMethodBody> evt = + new AMQMethodEvent<AMQMethodBody>(channelId, (AMQMethodBody) bodyFrame); - case HeartbeatBody.TYPE: + try + { - if (debug) + boolean wasAnyoneInterested = getStateManager().methodReceived(evt); + if (!_frameListeners.isEmpty()) + { + //This iterator is safe from the error state as the frame listeners always add before they send so their + // will be ready and waiting for this response. + Iterator it = _frameListeners.iterator(); + while (it.hasNext()) { - _logger.debug("Received heartbeat"); + final AMQMethodListener listener = (AMQMethodListener) it.next(); + wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; } + } - break; - - default: + if (!wasAnyoneInterested) + { + throw new AMQException(null, "AMQMethodEvent " + evt + " was not processed by any listener. Listeners:" + + _frameListeners, null); + } + } + catch (AMQException e) + { + if (!_frameListeners.isEmpty()) + { + Iterator it = _frameListeners.iterator(); + while (it.hasNext()) + { + final AMQMethodListener listener = (AMQMethodListener) it.next(); + listener.error(e); + } + } + exceptionCaught(session, e); } - _connection.bytesReceived(_protocolSession.getIoSession().getReadBytes()); } private static int _messagesOut; @@ -506,6 +566,12 @@ public class AMQProtocolHandler extends IoHandlerAdapter getStateManager().attainState(s); } + public AMQState attainState(Set<AMQState> states) throws AMQException + { + return getStateManager().attainState(states); + } + + /** * Convenience method that writes a frame to the protocol session. Equivalent to calling * getProtocolSession().write(). @@ -547,7 +613,15 @@ public class AMQProtocolHandler extends IoHandlerAdapter { try { - _frameListeners.add(listener); + synchronized (_frameListeners) + { + if (_lastFailoverException != null) + { + throw _lastFailoverException; + } + + _frameListeners.add(listener); + } _protocolSession.writeFrame(frame); AMQMethodEvent e = listener.blockForFrame(timeout); @@ -556,10 +630,6 @@ public class AMQProtocolHandler extends IoHandlerAdapter // When control resumes before this line, a reply will have been received // that matches the criteria defined in the blocking listener } - catch (AMQException e) - { - throw e; - } finally { // If we don't removeKey the listener then no-one will @@ -600,16 +670,11 @@ public class AMQProtocolHandler extends IoHandlerAdapter { getStateManager().changeState(AMQState.CONNECTION_CLOSING); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - // Be aware of possible changes to parameter order as versions change. - final AMQFrame frame = - ConnectionCloseBody.createAMQFrame(0, _protocolSession.getProtocolMajorVersion(), - _protocolSession.getProtocolMinorVersion(), // AMQP version (major, minor) - 0, // classId - 0, // methodId - AMQConstant.REPLY_SUCCESS.getCode(), // replyCode - new AMQShortString("JMS client is closing the connection.")); // replyText + ConnectionCloseBody body = _protocolSession.getMethodRegistry().createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), // replyCode + new AMQShortString("JMS client is closing the connection."),0,0); + + + final AMQFrame frame = body.generateFrame(0); try { @@ -682,7 +747,10 @@ public class AMQProtocolHandler extends IoHandlerAdapter public void setStateManager(AMQStateManager stateManager) { _stateManager = stateManager; - _protocolSession.setStateManager(stateManager); + if (_protocolSession != null) + { + _protocolSession.setStateManager(stateManager); + } } public AMQProtocolSession getProtocolSession() @@ -709,4 +777,14 @@ public class AMQProtocolHandler extends IoHandlerAdapter { return _protocolSession.getProtocolMinorVersion(); } + + public MethodRegistry getMethodRegistry() + { + return getStateManager().getMethodRegistry(); + } + + public ProtocolVersion getProtocolVersion() + { + return _protocolSession.getProtocolVersion(); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java index f5008496d9..6e782e0bfc 100644 --- a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java +++ b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java @@ -21,40 +21,31 @@ package org.apache.qpid.client.protocol; import org.apache.commons.lang.StringUtils; - 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jms.JMSException; +import javax.security.sasl.SaslClient; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; 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.ReturnMessage; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.state.AMQStateManager; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ContentBody; -import org.apache.qpid.framing.ContentHeaderBody; -import org.apache.qpid.framing.MainRegistry; -import org.apache.qpid.framing.ProtocolInitiation; -import org.apache.qpid.framing.ProtocolVersion; -import org.apache.qpid.framing.VersionSpecificRegistry; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.jms.JMSException; -import javax.security.sasl.SaslClient; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import org.apache.qpid.client.handler.ClientMethodDispatcherImpl; /** * Wrapper for protocol session that provides type-safe access to session attributes. <p/> The underlying protocol @@ -95,18 +86,27 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession * 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. */ - protected ConcurrentMap<Integer,UnprocessedMessage_0_8> _channelId2UnprocessedMsgMap = new ConcurrentHashMap<Integer,UnprocessedMessage_0_8>(); + private final ConcurrentMap<Integer,UnprocessedMessage> _channelId2UnprocessedMsgMap = new ConcurrentHashMap<Integer,UnprocessedMessage>(); + private final UnprocessedMessage[] _channelId2UnprocessedMsgArray = new UnprocessedMessage[16]; /** Counter to ensure unique queue names */ protected int _queueId = 1; protected final Object _queueIdLock = new Object(); - private byte _protocolMinorVersion; - private byte _protocolMajorVersion; - private VersionSpecificRegistry _registry = - MainRegistry.getVersionSpecificRegistry(ProtocolVersion.getLatestSupportedVersion()); + private ProtocolVersion _protocolVersion; +// private VersionSpecificRegistry _registry = +// MainRegistry.getVersionSpecificRegistry(ProtocolVersion.getLatestSupportedVersion()); + + + private MethodRegistry _methodRegistry = + MethodRegistry.getMethodRegistry(ProtocolVersion.getLatestSupportedVersion()); + + + private MethodDispatcher _methodDispatcher; + private final AMQConnection _connection; + private static final int FAST_CHANNEL_ACCESS_MASK = 0xFFFFFFF0; public AMQProtocolSession(AMQProtocolHandler protocolHandler, IoSession protocolSession, AMQConnection connection) { @@ -126,6 +126,9 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession _minaProtocolSession.setWriteTimeout(LAST_WRITE_FUTURE_JOIN_TIMEOUT); _stateManager = stateManager; _stateManager.setProtocolSession(this); + _protocolVersion = connection.getProtocolVersion(); + _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(ProtocolVersion.getLatestSupportedVersion(), + stateManager); _connection = connection; } @@ -135,7 +138,7 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession // start the process of setting up the connection. This is the first place that // data is written to the server. - _minaProtocolSession.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); + _minaProtocolSession.write(new ProtocolInitiation(_connection.getProtocolVersion())); } public String getClientID() @@ -164,6 +167,8 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession public void setStateManager(AMQStateManager stateManager) { _stateManager = stateManager; + _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(_protocolVersion, + stateManager); } public String getVirtualHost() @@ -230,14 +235,25 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession * * @throws AMQException if this was not expected */ - public void unprocessedMessageReceived(UnprocessedMessage_0_8 message) throws AMQException + public void unprocessedMessageReceived(UnprocessedMessage message) throws AMQException { - _channelId2UnprocessedMsgMap.put(message.getChannelId(), message); + final int channelId = message.getChannelId(); + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + _channelId2UnprocessedMsgArray[channelId] = message; + } + else + { + _channelId2UnprocessedMsgMap.put(channelId, message); + } } - public void messageContentHeaderReceived(int channelId, ContentHeaderBody contentHeader) throws AMQException + public void contentHeaderReceived(int channelId, ContentHeaderBody contentHeader) throws AMQException { - UnprocessedMessage_0_8 msg = (UnprocessedMessage_0_8) _channelId2UnprocessedMsgMap.get(channelId); + final UnprocessedMessage msg = (channelId & FAST_CHANNEL_ACCESS_MASK) == 0 ? _channelId2UnprocessedMsgArray[channelId] + : _channelId2UnprocessedMsgMap.get(channelId); + + if (msg == null) { throw new AMQException(null, "Error: received content header without having received a BasicDeliver frame first", null); @@ -255,9 +271,19 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession } } - public void messageContentBodyReceived(int channelId, ContentBody contentBody) throws AMQException + public void contentBodyReceived(final int channelId, ContentBody contentBody) throws AMQException { - UnprocessedMessage_0_8 msg = _channelId2UnprocessedMsgMap.get(channelId); + UnprocessedMessage_0_8 msg; + final boolean fastAccess = (channelId & FAST_CHANNEL_ACCESS_MASK) == 0; + if(fastAccess) + { + msg = (UnprocessedMessage_0_8) _channelId2UnprocessedMsgArray[channelId]; + } + else + { + msg = (UnprocessedMessage_0_8) _channelId2UnprocessedMsgMap.get(channelId); + } + if (msg == null) { throw new AMQException(null, "Error: received content body without having received a JMSDeliver frame first", null); @@ -265,7 +291,14 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession if (msg.getContentHeader() == null) { - _channelId2UnprocessedMsgMap.remove(channelId); + if(fastAccess) + { + _channelId2UnprocessedMsgArray[channelId] = null; + } + else + { + _channelId2UnprocessedMsgMap.remove(channelId); + } throw new AMQException(null, "Error: received content body without having received a ContentHeader frame first", null); } @@ -285,6 +318,11 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession } } + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException + { + + } + /** * Deliver a message to the appropriate session, removing the unprocessed message from our map * @@ -295,7 +333,14 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession { AMQSession session = getSession(channelId); session.messageReceived(msg); - _channelId2UnprocessedMsgMap.remove(channelId); + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + _channelId2UnprocessedMsgArray[channelId] = null; + } + else + { + _channelId2UnprocessedMsgMap.remove(channelId); + } } protected AMQSession getSession(int channelId) @@ -440,26 +485,64 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession session.confirmConsumerCancelled(consumerTag); } - public void setProtocolVersion(final byte versionMajor, final byte versionMinor) + public void setProtocolVersion(final ProtocolVersion pv) { - _protocolMajorVersion = versionMajor; - _protocolMinorVersion = versionMinor; - _registry = MainRegistry.getVersionSpecificRegistry(versionMajor, versionMinor); + _protocolVersion = pv; + _methodRegistry = MethodRegistry.getMethodRegistry(pv); + _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(pv, _stateManager); + + // _registry = MainRegistry.getVersionSpecificRegistry(versionMajor, versionMinor); } public byte getProtocolMinorVersion() { - return _protocolMinorVersion; + return _protocolVersion.getMinorVersion(); } public byte getProtocolMajorVersion() { - return _protocolMajorVersion; + return _protocolVersion.getMajorVersion(); } - public VersionSpecificRegistry getRegistry() + public ProtocolVersion getProtocolVersion() { - return _registry; + return _protocolVersion; } +// public VersionSpecificRegistry getRegistry() +// { +// return _registry; +// } + + public MethodRegistry getMethodRegistry() + { + return _methodRegistry; + } + + public MethodDispatcher getMethodDispatcher() + { + return _methodDispatcher; + } + + + public void setTicket(int ticket, int channelId) + { + final AMQSession session = getSession(channelId); + session.setTicket(ticket); + } + public void setMethodDispatcher(MethodDispatcher methodDispatcher) + { + _methodDispatcher = methodDispatcher; + } + + public void setFlowControl(final int channelId, final boolean active) + { + final AMQSession session = getSession(channelId); + session.setFlowControl(active); + } + + public void methodFrameReceived(final int channel, final AMQMethodBody amqMethodBody) throws AMQException + { + _protocolHandler.methodBodyReceived(channel, amqMethodBody, _minaProtocolSession); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java new file mode 100644 index 0000000000..5185278eef --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java @@ -0,0 +1,32 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.AMQMethodBody;
+import org.apache.qpid.AMQException;
+
+public class AMQMethodNotImplementedException extends AMQException
+{
+ public AMQMethodNotImplementedException(AMQMethodBody body)
+ {
+ super(null, "Unexpected Method Received: " + body.getClass().getName(), null);
+ }
+}
diff --git a/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java index 4996f59345..d32d10542f 100644 --- a/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java +++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java @@ -24,8 +24,22 @@ 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 +public enum AMQState { + + CONNECTION_NOT_STARTED(1, "CONNECTION_NOT_STARTED"), + + CONNECTION_NOT_TUNED(2, "CONNECTION_NOT_TUNED"), + + CONNECTION_NOT_OPENED(3, "CONNECTION_NOT_OPENED"), + + CONNECTION_OPEN(4, "CONNECTION_OPEN"), + + CONNECTION_CLOSING(5, "CONNECTION_CLOSING"), + + CONNECTION_CLOSED(6, "CONNECTION_CLOSED"); + + private final int _id; private final String _name; @@ -41,16 +55,6 @@ public class AMQState 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/main/java/org/apache/qpid/client/state/AMQStateManager.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java index 9f430d76a7..eda1a1f5fd 100644 --- a/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java +++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java @@ -21,49 +21,22 @@ package org.apache.qpid.client.state; import org.apache.qpid.AMQException; -import org.apache.qpid.client.handler.BasicCancelOkMethodHandler; -import org.apache.qpid.client.handler.BasicDeliverMethodHandler; -import org.apache.qpid.client.handler.BasicReturnMethodHandler; -import org.apache.qpid.client.handler.ChannelCloseMethodHandler; -import org.apache.qpid.client.handler.ChannelCloseOkMethodHandler; -import org.apache.qpid.client.handler.ChannelFlowOkMethodHandler; -import org.apache.qpid.client.handler.ConnectionCloseMethodHandler; -import org.apache.qpid.client.handler.ConnectionOpenOkMethodHandler; -import org.apache.qpid.client.handler.ConnectionSecureMethodHandler; -import org.apache.qpid.client.handler.ConnectionStartMethodHandler; -import org.apache.qpid.client.handler.ConnectionTuneMethodHandler; -import org.apache.qpid.client.handler.ExchangeBoundOkMethodHandler; -import org.apache.qpid.client.handler.QueueDeleteOkMethodHandler; import org.apache.qpid.client.protocol.AMQProtocolSession; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.framing.BasicCancelOkBody; -import org.apache.qpid.framing.BasicDeliverBody; -import org.apache.qpid.framing.BasicReturnBody; -import org.apache.qpid.framing.ChannelCloseBody; -import org.apache.qpid.framing.ChannelCloseOkBody; -import org.apache.qpid.framing.ChannelFlowOkBody; -import org.apache.qpid.framing.ConnectionCloseBody; -import org.apache.qpid.framing.ConnectionOpenOkBody; -import org.apache.qpid.framing.ConnectionSecureBody; -import org.apache.qpid.framing.ConnectionStartBody; -import org.apache.qpid.framing.ConnectionTuneBody; -import org.apache.qpid.framing.ExchangeBoundOkBody; -import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQMethodEvent; import org.apache.qpid.protocol.AMQMethodListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; +import java.util.Set; 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 +public class AMQStateManager { private static final Logger _logger = LoggerFactory.getLogger(AMQStateManager.class); @@ -72,15 +45,15 @@ public class AMQStateManager implements AMQMethodListener /** 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. */ - protected final Map _state2HandlersMap = new HashMap(); - private final CopyOnWriteArraySet _stateListeners = new CopyOnWriteArraySet(); + private final Object _stateLock = new Object(); - private static final long MAXIMUM_STATE_WAIT_TIME = 30000L; + private static final long MAXIMUM_STATE_WAIT_TIME = Long.parseLong(System.getProperty("amqj.MaximumStateWait", "30000")); public AMQStateManager() { @@ -96,54 +69,11 @@ public class AMQStateManager implements AMQMethodListener { _protocolSession = protocolSession; _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(BasicCancelOkBody.class, BasicCancelOkMethodHandler.getInstance()); - frame2handlerMap.put(ChannelFlowOkBody.class, ChannelFlowOkMethodHandler.getInstance()); - frame2handlerMap.put(QueueDeleteOkBody.class, QueueDeleteOkMethodHandler.getInstance()); - frame2handlerMap.put(ExchangeBoundOkBody.class, ExchangeBoundOkMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); } + + public AMQState getCurrentState() { return _currentState; @@ -160,72 +90,17 @@ public class AMQStateManager implements AMQMethodListener } } - 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 <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException { - StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod()); - if (handler != null) - { - handler.methodReceived(this, _protocolSession, evt); - - return true; - } - return false; + B method = evt.getMethod(); + + // StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod()); + method.execute(_protocolSession.getMethodDispatcher(), evt.getChannelId()); + return true; } - protected StateAwareMethodListener findStateTransitionHandler(AMQState currentState, AMQMethodBody frame) - // throws IllegalStateTransitionException - { - final Class clazz = frame.getClass(); - if (_logger.isDebugEnabled()) - { - _logger.debug("Looking for state[" + currentState + "] 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 attainState(final AMQState s) throws AMQException { @@ -272,4 +147,46 @@ public class AMQStateManager implements AMQMethodListener { _protocolSession = session; } + + public MethodRegistry getMethodRegistry() + { + return getProtocolSession().getMethodRegistry(); + } + + public AMQState attainState(Set<AMQState> stateSet) throws AMQException + { + synchronized (_stateLock) + { + final long waitUntilTime = System.currentTimeMillis() + MAXIMUM_STATE_WAIT_TIME; + long waitTime = MAXIMUM_STATE_WAIT_TIME; + + while (!stateSet.contains(_currentState) && (waitTime > 0)) + { + try + { + _stateLock.wait(MAXIMUM_STATE_WAIT_TIME); + } + catch (InterruptedException e) + { + _logger.warn("Thread interrupted"); + } + + if (!stateSet.contains(_currentState)) + { + waitTime = waitUntilTime - System.currentTimeMillis(); + } + } + + if (!stateSet.contains(_currentState)) + { + _logger.warn("State not achieved within permitted time. Current state " + _currentState + + ", desired state: " + stateSet); + throw new AMQException(null, "State not achieved within permitted time. Current state " + _currentState + + ", desired state: " + stateSet, null); + } + return _currentState; + } + + + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java b/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java index b3932533ce..8c65f56af3 100644 --- a/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java +++ b/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java @@ -21,6 +21,7 @@ package org.apache.qpid.client.state; import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.protocol.AMQMethodEvent; @@ -29,8 +30,9 @@ import org.apache.qpid.protocol.AMQMethodEvent; * the opportunity to update state. * */ -public interface StateAwareMethodListener +public interface StateAwareMethodListener<B extends AMQMethodBody> { - void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, - AMQMethodEvent evt) throws AMQException; + + void methodReceived(AMQStateManager stateManager, B body, int channelId) throws AMQException; + } diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java index 5482e48699..b2f7ae8395 100644 --- a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java @@ -24,6 +24,7 @@ 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.ExistingSocketConnector; import org.apache.mina.transport.socket.nio.SocketConnectorConfig; import org.apache.mina.transport.socket.nio.SocketSessionConfig; @@ -36,6 +37,9 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class SocketTransportConnection implements ITransportConnection { @@ -83,8 +87,34 @@ public class SocketTransportConnection implements ITransportConnection _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()); - _logger.info("Attempting connection to " + address); + + final InetSocketAddress address; + + if (brokerDetail.getTransport().equals(BrokerDetails.SOCKET)) + { + address = null; + + Socket socket = TransportConnection.removeOpenSocket(brokerDetail.getHost()); + + if (socket != null) + { + _logger.info("Using existing Socket:" + socket); + + ((ExistingSocketConnector) ioConnector).setOpenSocket(socket); + } + else + { + throw new IllegalArgumentException("Active Socket must be provided for broker " + + "with 'socket://<SocketID>' transport:" + brokerDetail); + } + } + else + { + address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort()); + _logger.info("Attempting connection to " + address); + } + + ConnectFuture future = ioConnector.connect(address, protocolHandler); // wait for connection to complete diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java index 883cb2340a..a871c754b5 100644 --- a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java @@ -23,6 +23,8 @@ package org.apache.qpid.client.transport; import org.apache.mina.common.IoConnector; import org.apache.mina.common.IoHandlerAdapter; import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.transport.socket.nio.ExistingSocketConnector; +import org.apache.mina.transport.socket.nio.MultiThreadSocketConnector; import org.apache.mina.transport.socket.nio.SocketConnector; import org.apache.mina.transport.vmpipe.VmPipeAcceptor; import org.apache.mina.transport.vmpipe.VmPipeAddress; @@ -34,8 +36,10 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.net.Socket; + /** * The TransportConnection is a helper class responsible for connecting to an AMQ server. It sets up the underlying @@ -54,12 +58,25 @@ public class TransportConnection private static final int TCP = 0; private static final int VM = 1; + private static final int SOCKET = 2; private static Logger _logger = LoggerFactory.getLogger(TransportConnection.class); private static final String DEFAULT_QPID_SERVER = "org.apache.qpid.server.protocol.AMQPFastProtocolHandler"; - public static ITransportConnection getInstance(BrokerDetails details) throws AMQTransportConnectionException + private static Map<String, Socket> _openSocketRegister = new ConcurrentHashMap<String, Socket>(); + + public static void registerOpenSocket(String socketID, Socket openSocket) + { + _openSocketRegister.put(socketID, openSocket); + } + + public static Socket removeOpenSocket(String socketID) + { + return _openSocketRegister.remove(socketID); + } + + public static synchronized ITransportConnection getInstance(BrokerDetails details) throws AMQTransportConnectionException { int transport = getTransport(details.getTransport()); @@ -68,7 +85,7 @@ public class TransportConnection throw new AMQNoTransportForProtocolException(details, null, null); } - if (transport == _currentInstance) + /* if (transport == _currentInstance) { if (transport == VM) { @@ -83,13 +100,23 @@ public class TransportConnection } } - _currentInstance = transport; + _currentInstance = transport;*/ + ITransportConnection instance; switch (transport) { - + case SOCKET: + instance = + new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() + { + public IoConnector newSocketConnector() + { + return new ExistingSocketConnector(); + } + }); + break; case TCP: - _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() + instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() { public IoConnector newSocketConnector() { @@ -97,38 +124,44 @@ public class TransportConnection // FIXME - this needs to be sorted to use the new Mina MultiThread SA. if (Boolean.getBoolean("qpidnio")) { - _logger.error("Using Qpid NIO - sysproperty 'qpidnio' is set."); - // result = new org.apache.qpid.nio.SocketConnector(); // non-blocking connector + _logger.warn("Using Qpid MultiThreaded NIO - " + (System.getProperties().containsKey("qpidnio") + ? "Qpid NIO is new default" + : "Sysproperty 'qpidnio' is set")); + result = new MultiThreadSocketConnector(); } - // else - + else { _logger.info("Using Mina NIO"); 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(0); - return result; } }); break; - case VM: { - _instance = getVMTransport(details, Boolean.getBoolean("amqj.AutoCreateVMBroker")); + instance = getVMTransport(details, Boolean.getBoolean("amqj.AutoCreateVMBroker")); break; } + default: + // FIXME: TGM + throw new AMQNoTransportForProtocolException(details, null, null); } - return _instance; + return instance; } private static int getTransport(String transport) { + if (transport.equals(BrokerDetails.SOCKET)) + { + return SOCKET; + } + if (transport.equals(BrokerDetails.TCP)) { return TCP; @@ -151,7 +184,15 @@ public class TransportConnection { if (AutoCreate) { - createVMBroker(port); + if (AutoCreate) + { + createVMBroker(port); + } + else + { + throw new AMQVMBrokerCreationException(null, port, "VM Broker on port " + port + + " does not exist. Auto create disabled.", null); + } } else { @@ -281,16 +322,17 @@ public class TransportConnection public static void killAllVMBrokers() { _logger.info("Killing all VM Brokers"); - _acceptor.unbindAll(); - - Iterator keys = _inVmPipeAddress.keySet().iterator(); - - while (keys.hasNext()) + if (_acceptor != null) { - int id = (Integer) keys.next(); - _inVmPipeAddress.remove(id); + _acceptor.unbindAll(); } - + synchronized (_inVmPipeAddress) + { + _inVmPipeAddress.clear(); + } + _acceptor = null; + _currentInstance = -1; + _currentVMPort = -1; } public static void killVMBroker(int port) @@ -300,6 +342,8 @@ public class TransportConnection { _logger.info("Killing VM Broker:" + port); _inVmPipeAddress.remove(port); + // This does need to be sychronized as otherwise mina can hang + // if a new connection is made _acceptor.unbind(pipe); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java index d9137dc8b1..dca6efba67 100644 --- a/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java @@ -22,15 +22,12 @@ package org.apache.qpid.client.transport; import org.apache.mina.common.ConnectFuture; import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.transport.vmpipe.QpidVmPipeConnector; import org.apache.mina.transport.vmpipe.VmPipeAddress; import org.apache.mina.transport.vmpipe.VmPipeConnector; - import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.jms.BrokerDetails; -import org.apache.qpid.pool.PoolingFilter; -import org.apache.qpid.pool.ReferenceCountingExecutorService; import org.apache.qpid.pool.ReadWriteThreadModel; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,10 +46,10 @@ public class VmPipeTransportConnection implements ITransportConnection public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) throws IOException { - final VmPipeConnector ioConnector = new VmPipeConnector(); + final VmPipeConnector ioConnector = new QpidVmPipeConnector(); final IoServiceConfig cfg = ioConnector.getDefaultConfig(); - cfg.setThreadModel(ReadWriteThreadModel.getInstance()); + cfg.setThreadModel(ReadWriteThreadModel.getInstance()); final VmPipeAddress address = new VmPipeAddress(_port); _logger.info("Attempting connection to " + address); diff --git a/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java b/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java index 133285fe87..72ba16086d 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java +++ b/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java @@ -33,8 +33,10 @@ public interface BrokerDetails */ public static final String OPTIONS_RETRY = "retries"; public static final String OPTIONS_CONNECT_TIMEOUT = "connecttimeout"; + public static final String OPTIONS_CONNECT_DELAY = "connectdelay"; public static final int DEFAULT_PORT = 5672; + public static final String SOCKET = "socket"; public static final String TCP = "tcp"; public static final String VM = "vm"; diff --git a/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java b/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java index 11376467d7..da8cd4f750 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java +++ b/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java @@ -21,6 +21,7 @@ package org.apache.qpid.jms; import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ProtocolVersion; import java.util.List; @@ -45,7 +46,7 @@ public interface ConnectionURL public static final String OPTIONS_TEMPORARY_QUEUE_EXCHANGE = "temporaryQueueExchange"; public static final byte URL_0_8 = 1; public static final byte URL_0_10 = 2; - + String getURL(); String getFailoverMethod(); @@ -89,4 +90,5 @@ public interface ConnectionURL AMQShortString getTemporaryQueueExchangeName(); AMQShortString getTemporaryTopicExchangeName(); + } diff --git a/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java b/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java index 6ec883ff0b..8e3ccc3b02 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java +++ b/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java @@ -34,7 +34,6 @@ public class FailoverPolicy 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]; @@ -161,16 +160,7 @@ public class FailoverPolicy } else { - if ((now - _lastFailTime) >= DEFAULT_FAILOVER_TIMEOUT) - { - _logger.info("Failover timeout"); - - return false; - } - else - { - _lastMethodTime = now; - } + _lastMethodTime = now; } } else diff --git a/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java b/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java index b91fc2d960..b830c377b8 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java +++ b/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java @@ -50,4 +50,8 @@ public interface MessageProducer extends javax.jms.MessageProducer void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate) throws JMSException; + + void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, + boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException; + } diff --git a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java index 74a46de63b..9c172da6a2 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java +++ b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java @@ -22,7 +22,6 @@ package org.apache.qpid.jms.failover; import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.jms.ConnectionURL; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,34 +34,22 @@ public class FailoverRoundRobinServers implements FailoverMethod /** 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 - */ + /** 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 - */ + /** The number of times to retry connecting for each server */ private int _serverRetries; - /** - * The current number of retry attempts made - */ + /** The current number of retry attempts made */ private int _currentServerRetry; - /** - * The number of times to cycle through the servers - */ + /** The number of times to cycle through the servers */ private int _cycleRetries; - /** - * The current number of cycles performed. - */ + /** The current number of cycles performed. */ private int _currentCycleRetries; - /** - * Array of BrokerDetail used to make connections. - */ + /** Array of BrokerDetail used to make connections. */ private ConnectionURL _connectionDetails; public FailoverRoundRobinServers(ConnectionURL connectionDetails) @@ -128,6 +115,8 @@ public class FailoverRoundRobinServers implements FailoverMethod public BrokerDetails getNextBrokerDetails() { + boolean doDelay = false; + if (_currentBrokerIndex == (_connectionDetails.getBrokerCount() - 1)) { if (_currentServerRetry < _serverRetries) @@ -143,6 +132,7 @@ public class FailoverRoundRobinServers implements FailoverMethod else { _logger.info("Retrying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex)); + doDelay=true; } _currentServerRetry++; @@ -175,6 +165,7 @@ public class FailoverRoundRobinServers implements FailoverMethod else { _logger.info("Retrying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex)); + doDelay=true; } _currentServerRetry++; @@ -189,7 +180,28 @@ public class FailoverRoundRobinServers implements FailoverMethod } } - return _connectionDetails.getBrokerDetails(_currentBrokerIndex); + BrokerDetails broker = _connectionDetails.getBrokerDetails(_currentBrokerIndex); + + String delayStr = broker.getProperty(BrokerDetails.OPTIONS_CONNECT_DELAY); + if (delayStr != null && doDelay) + { + Long delay = Long.parseLong(delayStr); + _logger.info("Delay between connect retries:" + delay); + try + { + Thread.sleep(delay); + } + catch (InterruptedException ie) + { + return null; + } + } + else + { + _logger.info("No delay between connect retries, use tcp://host:port?connectdelay='value' to enable."); + } + + return broker; } public void setBroker(BrokerDetails broker) diff --git a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java index d59c1fb98d..b1bc3cee96 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java +++ b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java @@ -22,25 +22,23 @@ package org.apache.qpid.jms.failover; import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.jms.ConnectionURL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FailoverSingleServer implements FailoverMethod { + private static final Logger _logger = LoggerFactory.getLogger(FailoverSingleServer.class); + /** 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 - */ + /** The details of the Single Server */ private BrokerDetails _brokerDetail; - /** - * The number of times to retry connecting to the sever - */ + /** The number of times to retry connecting to the sever */ private int _retries; - /** - * The current number of attempts made to the server - */ + /** The current number of attempts made to the server */ private int _currentRetries; @@ -78,7 +76,7 @@ public class FailoverSingleServer implements FailoverMethod public BrokerDetails getCurrentBrokerDetails() { - return _brokerDetail; + return _brokerDetail; } public BrokerDetails getNextBrokerDetails() @@ -91,11 +89,29 @@ public class FailoverSingleServer implements FailoverMethod { if (_currentRetries < _retries) { - _currentRetries ++; + _currentRetries++; } + } + - return _brokerDetail; + String delayStr = _brokerDetail.getProperty(BrokerDetails.OPTIONS_CONNECT_DELAY); + if (delayStr != null && _currentRetries != 1) + { + Long delay = Long.parseLong(delayStr); + _logger.info("Delay between connect retries:" + delay); + try + { + + Thread.sleep(delay); + } + catch (InterruptedException ie) + { + _logger.info("No delay between connect retries, use tcp://host:port?connectdelay='value' to enable."); + return null; + } } + + return _brokerDetail; } public void setBroker(BrokerDetails broker) @@ -138,10 +154,10 @@ public class FailoverSingleServer implements FailoverMethod public String toString() { - return "SingleServer:\n"+ - "Max Retries:"+_retries+ - "\nCurrent Retry:"+_currentRetries+ - "\n"+_brokerDetail+"\n"; + return "SingleServer:\n" + + "Max Retries:" + _retries + + "\nCurrent Retry:" + _currentRetries + + "\n" + _brokerDetail + "\n"; } } diff --git a/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java b/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java index 42a168bad8..43ac56dee9 100644 --- a/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java +++ b/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java @@ -99,8 +99,6 @@ public class PropertiesFileInitialContextFactory implements InitialContextFactor _logger.warn("Unable to load property file specified in Provider_URL:" + environment.get(Context.PROVIDER_URL)); } - - createConnectionFactories(data, environment); createDestinations(data, environment); diff --git a/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java b/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java index a246352d8b..2fe01fc126 100644 --- a/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java +++ b/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java @@ -183,7 +183,7 @@ public class TestLargePublisher } catch (UnknownHostException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } catch (AMQException e) { diff --git a/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java b/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java index 33891142b5..37b4ff1498 100644 --- a/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java +++ b/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java @@ -133,7 +133,7 @@ public class TestPublisher } catch (JMSException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } } @@ -163,7 +163,7 @@ public class TestPublisher } catch (UnknownHostException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } catch (AMQException e) { diff --git a/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java b/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java index 8724c65b61..fe418535d6 100644 --- a/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java @@ -126,7 +126,7 @@ public class AMQQueueDeferredOrderingTest extends TestCase _logger.info("Consuming messages"); for (int i = 0; i < NUM_MESSAGES; i++) { - Message msg = consumer.receive(1500); + Message msg = consumer.receive(3000); assertNotNull("Message should not be null", msg); assertTrue("Message should be a text message", msg instanceof TextMessage); assertEquals("Message content does not match expected", Integer.toString(i), ((TextMessage) msg).getText()); diff --git a/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java b/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java index 75e50ee09b..136b9b94b6 100644 --- a/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java @@ -20,13 +20,17 @@ */ package org.apache.qpid.client; +import junit.framework.TestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.Connection; +import javax.jms.ConnectionFactory; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; @@ -34,7 +38,9 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.naming.Context; +import javax.naming.spi.InitialContextFactory; +import java.util.Hashtable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java b/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java index 2eb511f8cd..12b84b1495 100644 --- a/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java @@ -20,13 +20,17 @@ */ package org.apache.qpid.client; +import junit.framework.TestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.Connection; +import javax.jms.ConnectionFactory; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; @@ -34,6 +38,9 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.naming.Context; +import javax.naming.spi.InitialContextFactory; + +import java.util.Hashtable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java b/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java index 882915fb8f..c920499a07 100644 --- a/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java @@ -29,7 +29,16 @@ import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.jms.*; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; import javax.naming.Context; import javax.naming.spi.InitialContextFactory; @@ -65,12 +74,15 @@ public class ResetMessageListenerTest extends QpidTestCase private final CountDownLatch _allFirstMessagesSent = new CountDownLatch(2); // all messages Sent Lock private final CountDownLatch _allSecondMessagesSent = new CountDownLatch(2); // all messages Sent Lock private final CountDownLatch _allFirstMessagesSent010 = new CountDownLatch(MSG_COUNT); // all messages Sent Lock - private final CountDownLatch _allSecondMessagesSent010 = new CountDownLatch(MSG_COUNT); // all messages Sent Lock + private final CountDownLatch _allSecondMessagesSent010 = new CountDownLatch(MSG_COUNT); // all messages Sent Lock + + private String oldImmediatePrefetch; protected void setUp() throws Exception { super.setUp(); + oldImmediatePrefetch = System.getProperty(AMQSession.IMMEDIATE_PREFETCH); System.setProperty(AMQSession.IMMEDIATE_PREFETCH, "true"); _clientConnection = getConnection("guest", "guest"); @@ -109,8 +121,12 @@ public class ResetMessageListenerTest extends QpidTestCase { _clientConnection.close(); - _producerConnection.close(); super.tearDown(); + if (oldImmediatePrefetch == null) + { + oldImmediatePrefetch = AMQSession.IMMEDIATE_PREFETCH_DEFAULT; + } + System.setProperty(AMQSession.IMMEDIATE_PREFETCH, oldImmediatePrefetch); } public void testAsynchronousRecieve() @@ -238,7 +254,7 @@ public class ResetMessageListenerTest extends QpidTestCase try { - _allSecondMessagesSent.await(1000, TimeUnit.MILLISECONDS); + _allSecondMessagesSent.await(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java index 737daeb350..61ba3aad3a 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java @@ -60,6 +60,7 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener private final List<String> messages = new ArrayList<String>(); private int _count = 1; public String _connectionString = "vm://:1"; + private static final String USERNAME = "guest"; protected void setUp() throws Exception { @@ -171,7 +172,7 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener m.setJMSReplyTo(q); m.setStringProperty("TempQueue", q.toString()); - _logger.trace("Message:" + m); + _logger.debug("Message:" + m); Assert.assertEquals("Check temp queue has been set correctly", m.getJMSReplyTo().toString(), m.getStringProperty("TempQueue")); @@ -287,7 +288,14 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener ((AMQMessage) m).setVoidProperty(new AMQShortString("void")); Assert.assertTrue("Check void properties are correctly transported", - ((AMQMessage) m).getPropertyHeaders().containsKey("void")); + ((AMQMessage) m).getPropertyHeaders().containsKey("void")); + + //JMSXUserID + if (m.getStringProperty("JMSXUserID") != null) + { + Assert.assertEquals("Check 'JMSXUserID' is supported ", USERNAME, + m.getStringProperty("JMSXUserID")); + } } received.clear(); diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java index ed4f6041df..987b30ce28 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java @@ -21,7 +21,7 @@ package org.apache.qpid.test.unit.basic; import junit.framework.TestCase; - +import org.apache.qpid.AMQException; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.AMQDestination; import org.apache.qpid.client.AMQQueue; @@ -29,11 +29,14 @@ import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.BasicMessageProducer; import org.apache.qpid.client.transport.TransportConnection; import org.apache.qpid.testutil.QpidTestCase; +import org.apache.qpid.url.URLSyntaxException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jms.Connection; import javax.jms.DeliveryMode; +import javax.jms.InvalidSelectorException; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; @@ -47,11 +50,12 @@ public class SelectorTest extends QpidTestCase implements MessageListener private AMQSession _session; private int count; public String _connectionString = "vm://:1"; + private static final String INVALID_SELECTOR = "Cost LIKE 5"; protected void setUp() throws Exception { super.setUp(); - init((AMQConnection) getConnection("guest", "guest")); + init((AMQConnection) getConnection("guest", "guest")); } protected void tearDown() throws Exception @@ -59,19 +63,19 @@ public class SelectorTest extends QpidTestCase implements MessageListener super.tearDown(); } - private void init(AMQConnection connection) throws Exception + private void init(AMQConnection connection) throws JMSException { init(connection, new AMQQueue(connection, randomize("SessionStartTest"), true)); } - private void init(AMQConnection connection, AMQDestination destination) throws Exception + private void init(AMQConnection connection, AMQDestination destination) throws JMSException { _connection = connection; _destination = destination; connection.start(); String selector = null; - // selector = "Cost = 2 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT; + selector = "Cost = 2 AND \"property-with-hyphen\" = 'wibble'"; // selector = "JMSType = Special AND Cost = 2 AND AMQMessageID > 0 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT; _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE); @@ -79,13 +83,17 @@ public class SelectorTest extends QpidTestCase implements MessageListener _session.createConsumer(destination, selector).setMessageListener(this); } - public synchronized void test() throws JMSException, InterruptedException + public synchronized void test() throws Exception { try { + + init((AMQConnection) getConnection("guest", "guest", randomize("Client"))); + Message msg = _session.createTextMessage("Message"); msg.setJMSPriority(1); msg.setIntProperty("Cost", 2); + msg.setStringProperty("property-with-hyphen", "wibble"); msg.setJMSType("Special"); _logger.info("Sending Message:" + msg); @@ -105,10 +113,147 @@ public class SelectorTest extends QpidTestCase implements MessageListener // throw new RuntimeException("Did not get message!"); } } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + System.out.println("SUCCESS!!"); + } + } + catch (InterruptedException e) + { + _logger.debug("IE :" + e.getClass().getSimpleName() + ":" + e.getMessage()); + } + catch (URLSyntaxException e) + { + _logger.debug("URL:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + fail("Wrong exception"); + } + catch (AMQException e) + { + _logger.debug("AMQ:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + fail("Wrong exception"); + } + finally { - _session.close(); - _connection.close(); + if (_session != null) + { + _session.close(); + } + if (_connection != null) + { + _connection.close(); + } + } + } + + + public void testInvalidSelectors() throws Exception + { + Connection connection = null; + + try + { + connection = getConnection("guest", "guest", randomize("Client")); + _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE); + } + catch (JMSException e) + { + fail(e.getMessage()); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + catch (URLSyntaxException e) + { + fail("Error:" + e.getMessage()); + } + + //Try Creating a Browser + try + { + _session.createBrowser(_session.createQueue("Ping"), INVALID_SELECTOR); + } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + _logger.debug("SUCCESS!!"); + } + } + + //Try Creating a Consumer + try + { + _session.createConsumer(_session.createQueue("Ping"), INVALID_SELECTOR); + } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + _logger.debug("SUCCESS!!"); + } + } + + //Try Creating a Receiever + try + { + _session.createReceiver(_session.createQueue("Ping"), INVALID_SELECTOR); + } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + _logger.debug("SUCCESS!!"); + } + } + + finally + { + if (_session != null) + { + try + { + _session.close(); + } + catch (JMSException e) + { + fail("Error cleaning up:" + e.getMessage()); + } + } + if (_connection != null) + { + try + { + _connection.close(); + } + catch (JMSException e) + { + fail("Error cleaning up:" + e.getMessage()); + } + } } } @@ -127,9 +272,29 @@ public class SelectorTest extends QpidTestCase implements MessageListener public static void main(String[] argv) throws Exception { SelectorTest test = new SelectorTest(); - test._connectionString = (argv.length == 0) ? "localhost:5672" : argv[0]; - test.setUp(); - test.test(); + test._connectionString = (argv.length == 0) ? "localhost:3000" : argv[0]; + + try + { + while (true) + { + if (test._connectionString.contains("vm://:1")) + { + test.setUp(); + } + test.test(); + + if (test._connectionString.contains("vm://:1")) + { + test.tearDown(); + } + } + } + catch (Exception e) + { + System.err.println(e.getMessage()); + e.printStackTrace(); + } } public static junit.framework.Test suite() diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTest.java index 10c054a863..6f1ddebb0c 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTest.java @@ -35,9 +35,9 @@ import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; -public class CloseTests extends QpidTestCase +public class CloseTest extends QpidTestCase { - private static final Logger _logger = LoggerFactory.getLogger(CloseTests.class); + private static final Logger _logger = LoggerFactory.getLogger(CloseTest.class); private static final String BROKER = "vm://:1"; diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java index 6a4e01affd..965c22af4a 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java @@ -75,18 +75,11 @@ public class AMQSessionTest extends QpidTestCase public void testCreateDurableSubscriber() throws JMSException { - try - { - TopicSubscriber subscriber = _session.createDurableSubscriber(_topic, "mysubname"); - assertEquals("Topic names should match from durable TopicSubscriber", _topic.getTopicName(), subscriber.getTopic().getTopicName()); + TopicSubscriber subscriber = _session.createDurableSubscriber(_topic, "mysubname"); + assertEquals("Topic names should match from durable TopicSubscriber", _topic.getTopicName(), subscriber.getTopic().getTopicName()); - subscriber = _session.createDurableSubscriber(_topic, "mysubname", "abc", false); - assertEquals("Topic names should match from durable TopicSubscriber with selector", _topic.getTopicName(), subscriber.getTopic().getTopicName()); - } - catch (Throwable e) - { - e.printStackTrace(); - } + subscriber = _session.createDurableSubscriber(_topic, "mysubname2", "abc", false); + assertEquals("Topic names should match from durable TopicSubscriber with selector", _topic.getTopicName(), subscriber.getTopic().getTopicName()); } public void testCreateQueueReceiver() throws JMSException diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java index 85fcf6d95a..b6776a1a44 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java @@ -37,7 +37,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListener +public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListener<ChannelCloseBody> { private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseMethodHandlerNoCloseOk.class); @@ -48,14 +48,15 @@ public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListe return _handler; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, ChannelCloseBody method, int channelId) throws AMQException { _logger.debug("ChannelClose method received"); - ChannelCloseBody method = (ChannelCloseBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); - AMQConstant errorCode = AMQConstant.getConstant(method.replyCode); - AMQShortString reason = method.replyText; + + AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode()); + AMQShortString reason = method.getReplyText(); if (_logger.isDebugEnabled()) { _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason); @@ -95,6 +96,6 @@ public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListe } - protocolSession.channelClosed(evt.getChannelId(), errorCode, String.valueOf(reason)); + session.channelClosed(channelId, errorCode, String.valueOf(reason)); } } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java index 08d6b0bcab..45a9ca1dd6 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java @@ -26,17 +26,13 @@ import org.apache.qpid.AMQException; import org.apache.qpid.AMQTimeoutException; import org.apache.qpid.testutil.QpidTestCase; import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.handler.ClientMethodDispatcherImpl; import org.apache.qpid.client.failover.FailoverException; +import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.transport.TransportConnection; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ChannelCloseOkBody; -import org.apache.qpid.framing.ChannelOpenBody; -import org.apache.qpid.framing.ChannelOpenOkBody; -import org.apache.qpid.framing.ExchangeDeclareBody; -import org.apache.qpid.framing.ExchangeDeclareOkBody; +import org.apache.qpid.framing.*; import org.apache.qpid.jms.ConnectionListener; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.url.URLSyntaxException; @@ -53,6 +49,9 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, ConnectionListener { @@ -140,8 +139,11 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, /* close channel and send guff then send ok no errors + REMOVE TEST - The behaviour after server has sent close is undefined. + the server should be free to fail as it may wish to reclaim its resources + immediately after close. */ - public void testSendingMethodsAfterClose() throws Exception + /*public void testSendingMethodsAfterClose() throws Exception { // this is testing an 0.8 connection if(isBroker08()) @@ -167,7 +169,19 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, // Set StateManager to manager that ignores Close-oks AMQProtocolSession protocolSession= ((AMQConnection) _connection).getProtocolHandler().getProtocolSession(); - AMQStateManager newStateManager=new NoCloseOKStateManager(protocolSession); + + MethodDispatcher d = protocolSession.getMethodDispatcher(); + + MethodDispatcher wrappedDispatcher = (MethodDispatcher) + Proxy.newProxyInstance(d.getClass().getClassLoader(), + d.getClass().getInterfaces(), + new MethodDispatcherProxyHandler( + (ClientMethodDispatcherImpl) d)); + + protocolSession.setMethodDispatcher(wrappedDispatcher); + + + AMQStateManager newStateManager=new NoCloseOKStateManager(protocolSession); newStateManager.changeState(oldStateManager.getCurrentState()); ((AMQConnection) _connection).getProtocolHandler().setStateManager(newStateManager); @@ -257,7 +271,7 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, } } } - +*/ private void createChannelAndTest(int channel) throws FailoverException { // Create A channel @@ -284,10 +298,9 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, private void sendClose(int channel) { - AMQFrame frame = - ChannelCloseOkBody.createAMQFrame(channel, - ((AMQConnection) _connection).getProtocolHandler().getProtocolMajorVersion(), - ((AMQConnection) _connection).getProtocolHandler().getProtocolMinorVersion()); + ChannelCloseOkBody body = + ((AMQConnection) _connection).getProtocolHandler().getMethodRegistry().createChannelCloseOkBody(); + AMQFrame frame = body.generateFrame(channel); ((AMQConnection) _connection).getProtocolHandler().writeFrame(frame); } @@ -345,35 +358,43 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, private void declareExchange(int channelId, String _type, String _name, boolean nowait) throws AMQException, FailoverException { - AMQFrame exchangeDeclare = - ExchangeDeclareBody.createAMQFrame(channelId, - ((AMQConnection) _connection).getProtocolHandler().getProtocolMajorVersion(), - ((AMQConnection) _connection).getProtocolHandler().getProtocolMinorVersion(), null, // arguments - false, // autoDelete - false, // durable - new AMQShortString(_name), // exchange - false, // internal - nowait, // nowait - true, // passive - 0, // ticket - new AMQShortString(_type)); // type - - if (nowait) - { - ((AMQConnection) _connection).getProtocolHandler().writeFrame(exchangeDeclare); - } - else - { - ((AMQConnection) _connection).getProtocolHandler().syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class, - SYNC_TIMEOUT); - } + ExchangeDeclareBody body = + ((AMQConnection) _connection).getProtocolHandler() + .getMethodRegistry() + .createExchangeDeclareBody(0, + new AMQShortString(_name), + new AMQShortString(_type), + true, + false, + false, + false, + nowait, + null); + AMQFrame exchangeDeclare = body.generateFrame(channelId); + AMQProtocolHandler protocolHandler = ((AMQConnection) _connection).getProtocolHandler(); + + + if (nowait) + { + protocolHandler.writeFrame(exchangeDeclare); + } + else + { + protocolHandler.syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class, SYNC_TIMEOUT); + } + +// return null; +// } +// }, (AMQConnection)_connection).execute(); + } private void createChannel(int channelId) throws AMQException, FailoverException { - ((AMQConnection) _connection).getProtocolHandler().syncWrite(ChannelOpenBody.createAMQFrame(channelId, - ((AMQConnection) _connection).getProtocolHandler().getProtocolMajorVersion(), - ((AMQConnection) _connection).getProtocolHandler().getProtocolMinorVersion(), null), // outOfBand + ChannelOpenBody body = + ((AMQConnection) _connection).getProtocolHandler().getMethodRegistry().createChannelOpenBody(null); + + ((AMQConnection) _connection).getProtocolHandler().syncWrite(body.generateFrame(channelId), // outOfBand ChannelOpenOkBody.class); } @@ -402,4 +423,28 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, public void failoverComplete() { } + + private static final class MethodDispatcherProxyHandler implements InvocationHandler + { + private final ClientMethodDispatcherImpl _underlyingDispatcher; + private final ChannelCloseMethodHandlerNoCloseOk _handler = ChannelCloseMethodHandlerNoCloseOk.getInstance(); + + + public MethodDispatcherProxyHandler(ClientMethodDispatcherImpl dispatcher) + { + _underlyingDispatcher = dispatcher; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + if(method.getName().equals("dispatchChannelClose")) + { + _handler.methodReceived(_underlyingDispatcher.getStateManager(), + (ChannelCloseBody) args[0], (Integer)args[1]); + } + Method dispatcherMethod = _underlyingDispatcher.getClass().getMethod(method.getName(), method.getParameterTypes()); + return dispatcherMethod.invoke(_underlyingDispatcher, args); + + } + } } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java index d128f30727..c7eb745566 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java @@ -59,49 +59,7 @@ public class NoCloseOKStateManager extends AMQStateManager super(protocolSession); } - 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(); - // Use Test Handler for Close methods to not send Close-OKs - frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseMethodHandlerNoCloseOk.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(BasicCancelOkBody.class, BasicCancelOkMethodHandler.getInstance()); - frame2handlerMap.put(ChannelFlowOkBody.class, ChannelFlowOkMethodHandler.getInstance()); - frame2handlerMap.put(QueueDeleteOkBody.class, QueueDeleteOkMethodHandler.getInstance()); - frame2handlerMap.put(ExchangeBoundOkBody.class, ExchangeBoundOkMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); - } + } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java index 7eb74e2492..f856e8c20b 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java @@ -33,6 +33,7 @@ import org.apache.qpid.jms.Session; import junit.framework.TestCase; import javax.jms.Connection; +import javax.jms.JMSException; import javax.jms.QueueSession; import javax.jms.TopicSession; @@ -55,25 +56,30 @@ public class ConnectionTest extends TestCase TransportConnection.killVMBroker(1); } - public void testSimpleConnection() + public void testSimpleConnection() throws Exception { + AMQConnection conn = null; try { - AMQConnection conn = new AMQConnection(_broker, "guest", "guest", "fred", "test"); - conn.close(); + conn = new AMQConnection(_broker, "guest", "guest", "fred", "test"); } catch (Exception e) { fail("Connection to " + _broker + " should succeed. Reason: " + e); } + finally + { + conn.close(); + } } - public void testDefaultExchanges() + public void testDefaultExchanges() throws Exception { + AMQConnection conn = null; try { - AMQConnection conn = new AMQConnection("amqp://guest:guest@clientid/test?brokerlist='" + conn = new AMQConnection("amqp://guest:guest@clientid/test?brokerlist='" + _broker + "?retries='1''&defaultQueueExchange='test.direct'" + "&defaultTopicExchange='test.topic'" @@ -106,37 +112,53 @@ public class ConnectionTest extends TestCase topicSession.close(); - - conn.close(); } catch (Exception e) { fail("Connection to " + _broker + " should succeed. Reason: " + e); } + finally + { + conn.close(); + } } - //fixme AMQAuthenticationException is not propogaged - public void PasswordFailureConnection() throws Exception + //See QPID-771 + public void testPasswordFailureConnection() throws Exception { + AMQConnection conn = null; try { - new AMQConnection("amqp://guest:rubbishpassword@clientid/test?brokerlist='" + _broker + "?retries='1''"); + conn = new AMQConnection("amqp://guest:rubbishpassword@clientid/test?brokerlist='" + _broker + "?retries='1''"); fail("Connection should not be established password is wrong."); } catch (AMQException amqe) { - if (!(amqe instanceof AMQAuthenticationException)) + if (amqe.getCause().getClass() == Exception.class) + { + System.err.println("QPID-594 : WARNING RACE CONDITION. Unable to determine cause of Connection Failure."); + return; + } + + assertEquals("Exception was wrong type", JMSException.class, amqe.getCause().getClass()); + Exception linked = ((JMSException) amqe.getCause()).getLinkedException(); + assertEquals("Exception was wrong type", AMQAuthenticationException.class, linked.getClass()); + } + finally + { + if (conn != null) { - fail("Correct exception not thrown. Excpected 'AMQAuthenticationException' got: " + amqe); + conn.close(); } } } public void testConnectionFailure() throws Exception { + AMQConnection conn = null; try { - new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_NotRunning + "?retries='0''"); + conn = new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_NotRunning + "?retries='0''"); fail("Connection should not be established"); } catch (AMQException amqe) @@ -146,14 +168,22 @@ public class ConnectionTest extends TestCase fail("Correct exception not thrown. Excpected 'AMQConnectionException' got: " + amqe); } } + finally + { + if (conn != null) + { + conn.close(); + } + } } public void testUnresolvedHostFailure() throws Exception { + AMQConnection conn = null; try { - new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_BadDNS + "?retries='0''"); + conn = new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_BadDNS + "?retries='0''"); fail("Connection should not be established"); } catch (AMQException amqe) @@ -163,6 +193,38 @@ public class ConnectionTest extends TestCase fail("Correct exception not thrown. Excpected 'AMQUnresolvedAddressException' got: " + amqe); } } + finally + { + if (conn != null) + { + conn.close(); + } + } + + } + + public void testUnresolvedVirtualHostFailure() throws Exception + { + AMQConnection conn = null; + try + { + conn = new AMQConnection("amqp://guest:guest@clientid/rubbishhost?brokerlist='" + _broker + "?retries='0''"); + fail("Connection should not be established"); + } + catch (AMQException amqe) + { + if (!(amqe instanceof AMQConnectionFailureException)) + { + fail("Correct exception not thrown. Excpected 'AMQConnectionFailureException' got: " + amqe); + } + } + finally + { + if (conn != null) + { + conn.close(); + } + } } public void testClientIdCannotBeChanged() throws Exception @@ -180,7 +242,10 @@ public class ConnectionTest extends TestCase } finally { - connection.close(); + if (connection != null) + { + connection.close(); + } } } @@ -188,7 +253,14 @@ public class ConnectionTest extends TestCase { Connection connection = new AMQConnection(_broker, "guest", "guest", null, "test"); - assertNotNull(connection.getClientID()); + try + { + assertNotNull(connection.getClientID()); + } + finally + { + connection.close(); + } connection.close(); } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java index bfbba61913..27adc4dd77 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java @@ -473,6 +473,25 @@ public class ConnectionURLTest extends TestCase } + public void testSocketProtocol() throws URLSyntaxException + { + String url = "amqp://guest:guest@id/test" + "?brokerlist='socket://VM-Unique-socketID'"; + + try + { + AMQConnectionURL curl = new AMQConnectionURL(url); + assertNotNull(curl); + assertEquals(1, curl.getBrokerCount()); + assertNotNull(curl.getBrokerDetails(0)); + assertEquals(BrokerDetails.SOCKET, curl.getBrokerDetails(0).getTransport()); + assertEquals("VM-Unique-socketID", curl.getBrokerDetails(0).getHost()); + assertEquals("URL does not toString as expected", url, curl.toString()); + } + catch (URLSyntaxException e) + { + fail(e.getMessage()); + } + } public static junit.framework.Test suite() { diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java index 3776ff767f..4cdd7dd7e8 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java @@ -27,8 +27,9 @@ import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.testutil.QpidTestCase; -public class AMQProtocolSessionTest extends TestCase +public class AMQProtocolSessionTest extends QpidTestCase { private static class AMQProtSession extends AMQProtocolSession { @@ -50,7 +51,7 @@ public class AMQProtocolSessionTest extends TestCase } //private Strings for test values and expected results - private String _brokenAddress; + private String _brokenAddress = "tcp://myAddress;:";; private String _generatedAddress; private String _emptyAddress; private String _generatedAddress_2; @@ -64,7 +65,7 @@ public class AMQProtocolSessionTest extends TestCase super.setUp(); //don't care about the values set here apart from the dummy IoSession - _testSession = new AMQProtSession(null,new TestIoSession(),null); + _testSession = new AMQProtSession(null,new TestIoSession(), (AMQConnection) getConnection("guest", "guest")); //initialise addresses for test and expected results _port = 123; diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java index e78c295ce5..d25986d991 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java @@ -29,8 +29,8 @@ import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import uk.co.thebadgerset.junit.concurrency.TestRunnable;
-import uk.co.thebadgerset.junit.concurrency.ThreadTestCoordinator;
+import org.apache.qpid.junit.concurrency.TestRunnable;
+import org.apache.qpid.junit.concurrency.ThreadTestCoordinator;
import javax.jms.Connection;
import javax.jms.Message;
diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTests.java b/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java index af19db5128..9caba63fe4 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTests.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java @@ -25,7 +25,7 @@ import org.apache.qpid.testutil.QpidTestCase; * Crash Recovery tests for durable subscription * */ -public class DurableSubscriberTests extends QpidTestCase +public class DurableSubscriberTest extends QpidTestCase { private final String _topicName = "durableSubscriberTopic"; diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java index c4b94a6f36..6883a09f1b 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java @@ -39,6 +39,7 @@ import javax.jms.MessageProducer; import javax.jms.ObjectMessage; import javax.jms.Queue; import javax.jms.Session; +import java.util.Enumeration; /** * @author Apache Software Foundation @@ -84,6 +85,12 @@ public class JMSPropertiesTest extends QpidTestCase sentMsg.setJMSType(JMS_TYPE); sentMsg.setJMSReplyTo(JMS_REPLY_TO); + String JMSXGroupID_VALUE = "group"; + sentMsg.setStringProperty("JMSXGroupID", JMSXGroupID_VALUE); + + int JMSXGroupSeq_VALUE = 1; + sentMsg.setIntProperty("JMSXGroupSeq", JMSXGroupSeq_VALUE); + // send it producer.send(sentMsg); @@ -100,6 +107,30 @@ public class JMSPropertiesTest extends QpidTestCase // assertEquals("JMS Delivery Mode mismatch",sentMsg.getJMSDeliveryMode(),rm.getJMSDeliveryMode()); assertEquals("JMS Type mismatch", sentMsg.getJMSType(), rm.getJMSType()); assertEquals("JMS Reply To mismatch", sentMsg.getJMSReplyTo(), rm.getJMSReplyTo()); + assertTrue("JMSMessageID Does not start ID:", rm.getJMSMessageID().startsWith("ID:")); + + //Validate that the JMSX values are correct + assertEquals("JMSXGroupID is not as expected:", JMSXGroupID_VALUE, rm.getStringProperty("JMSXGroupID")); + assertEquals("JMSXGroupSeq is not as expected:", JMSXGroupSeq_VALUE, rm.getIntProperty("JMSXGroupSeq")); + + boolean JMSXGroupID_Available = false; + boolean JMSXGroupSeq_Available = false; + Enumeration props = con.getMetaData().getJMSXPropertyNames(); + while (props.hasMoreElements()) + { + String name = (String) props.nextElement(); + if (name.equals("JMSXGroupID")) + { + JMSXGroupID_Available = true; + } + if (name.equals("JMSXGroupSeq")) + { + JMSXGroupSeq_Available = true; + } + } + + assertTrue("JMSXGroupID not available.",JMSXGroupID_Available); + assertTrue("JMSXGroupSeq not available.",JMSXGroupSeq_Available); con.close(); } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java index 23bd2a960a..50ed1dee9e 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java @@ -41,6 +41,14 @@ import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.TopicSubscriber; +/** + * @todo Code to check that a consumer gets only one particular method could be factored into a re-usable method (as + * a static on a base test helper class, e.g. TestUtils. + * + * @todo Code to create test end-points using session per connection, or all sessions on one connection, to be factored + * out to make creating this test variation simpler. Want to make this variation available through LocalCircuit, + * driven by the test model. + */ public class DurableSubscriptionTest extends QpidTestCase { private static final Logger _logger = LoggerFactory.getLogger(DurableSubscriptionTest.class); @@ -113,12 +121,26 @@ public class DurableSubscriptionTest extends QpidTestCase con.close(); } - public void testDurability() throws Exception + public void testDurabilityAUTOACK() throws Exception { + durabilityImpl(Session.AUTO_ACKNOWLEDGE); + } + public void testDurabilityNOACKSessionPerConnection() throws Exception + { + durabilityImplSessionPerConnection(AMQSession.NO_ACKNOWLEDGE); + } + + public void testDurabilityAUTOACKSessionPerConnection() throws Exception + { + durabilityImplSessionPerConnection(Session.AUTO_ACKNOWLEDGE); + } + + private void durabilityImpl(int ackMode) throws Exception + { AMQConnection con = (AMQConnection) getConnection("guest", "guest"); - AMQTopic topic = new AMQTopic(con, "MyDurableSubscriptionTestTopic"); - Session session1 = con.createSession(false, AMQSession.NO_ACKNOWLEDGE); + AMQTopic topic = new AMQTopic(con, "MyTopic"); + Session session1 = con.createSession(false, ackMode); MessageConsumer consumer1 = session1.createConsumer(topic); Session sessionProd = con.createSession(false, AMQSession.NO_ACKNOWLEDGE); @@ -144,10 +166,82 @@ public class DurableSubscriptionTest extends QpidTestCase consumer2.close(); - Session session3 = con.createSession(false, AMQSession.NO_ACKNOWLEDGE); + producer.send(session1.createTextMessage("B")); + + _logger.info("Receive message on consumer 1 :expecting B"); + msg = consumer1.receive(500); + assertNotNull("Consumer 1 should get message 'B'.", msg); + assertEquals("Incorrect Message recevied on consumer1.", "B", ((TextMessage) msg).getText()); + _logger.info("Receive message on consumer 1 :expecting null"); + msg = consumer1.receive(500); + assertNull("There should be no more messages for consumption on consumer1.", msg); + + Session session3 = con.createSession(false, ackMode); MessageConsumer consumer3 = session3.createDurableSubscriber(topic, "MySubscription"); - producer.send(session1.createTextMessage("B")); + _logger.info("Receive message on consumer 3 :expecting B"); + msg = consumer3.receive(500); + assertNotNull("Consumer 3 should get message 'B'.", msg); + assertEquals("Incorrect Message recevied on consumer4.", "B", ((TextMessage) msg).getText()); + _logger.info("Receive message on consumer 3 :expecting null"); + msg = consumer3.receive(500); + assertNull("There should be no more messages for consumption on consumer3.", msg); + + consumer1.close(); + consumer3.close(); + + con.close(); + } + + private void durabilityImplSessionPerConnection(int ackMode) throws Exception + { + Message msg; + // Create producer. + AMQConnection con0 = (AMQConnection) getConnection("guest", "guest"); + con0.start(); + Session session0 = con0.createSession(false, ackMode); + + AMQTopic topic = new AMQTopic(con0, "MyTopic"); + + Session sessionProd = con0.createSession(false, ackMode); + MessageProducer producer = sessionProd.createProducer(topic); + + // Create consumer 1. + AMQConnection con1 = (AMQConnection) getConnection("guest", "guest"); + con1.start(); + Session session1 = con1.createSession(false, ackMode); + + MessageConsumer consumer1 = session0.createConsumer(topic); + + // Create consumer 2. + AMQConnection con2 = (AMQConnection) getConnection("guest", "guest"); + con2.start(); + Session session2 = con2.createSession(false, ackMode); + + TopicSubscriber consumer2 = session2.createDurableSubscriber(topic, "MySubscription"); + + // Send message and check that both consumers get it and only it. + producer.send(session0.createTextMessage("A")); + + msg = consumer1.receive(500); + assertNotNull("Message should be available", msg); + assertEquals("Message Text doesn't match", "A", ((TextMessage) msg).getText()); + msg = consumer1.receive(500); + assertNull("There should be no more messages for consumption on consumer1.", msg); + + msg = consumer2.receive(); + assertNotNull(msg); + assertEquals("Consumer 2 should also received the first msg.", "A", ((TextMessage) msg).getText()); + msg = consumer2.receive(500); + assertNull("There should be no more messages for consumption on consumer2.", msg); + + // Detach the durable subscriber. + consumer2.close(); + session2.close(); + con2.close(); + + // Send message and receive on open consumer. + producer.send(session0.createTextMessage("B")); _logger.info("Receive message on consumer 1 :expecting B"); msg = consumer1.receive(1000); @@ -156,17 +250,27 @@ public class DurableSubscriptionTest extends QpidTestCase msg = consumer1.receive(1000); assertEquals(null, msg); + // Re-attach a new consumer to the durable subscription, and check that it gets the message that it missed. + AMQConnection con3 = (AMQConnection) getConnection("guest", "guest"); + con3.start(); + Session session3 = con3.createSession(false, ackMode); + + TopicSubscriber consumer3 = session3.createDurableSubscriber(topic, "MySubscription"); + _logger.info("Receive message on consumer 3 :expecting B"); - msg = consumer3.receive(1000); - assertEquals("B", ((TextMessage) msg).getText()); + msg = consumer3.receive(500); + assertNotNull("Consumer 3 should get message 'B'.", msg); + assertEquals("Incorrect Message recevied on consumer4.", "B", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 3 :expecting null"); - msg = consumer3.receive(1000); - assertEquals(null, msg); - // we need to unsubscribe as the session is NO_ACKNOWLEDGE - // messages for the durable subscriber are not deleted so the test cannot - // be run twice in a row - session2.unsubscribe("MySubscription"); - con.close(); + msg = consumer3.receive(500); + assertNull("There should be no more messages for consumption on consumer3.", msg); + + consumer1.close(); + consumer3.close(); + + con0.close(); + con1.close(); + con3.close(); } public static junit.framework.Test suite() diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java index 495cc98f31..f2f35644c6 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java @@ -409,9 +409,9 @@ public class CommitRollbackTest extends QpidTestCase } else { - _logger.warn("Got 2, message prefetched wasn't cleared or messages was in transit when rollback occured"); + _logger.warn("Got 2, message prefetched wasn't cleared or messages was in transit when rollback occured"); assertFalse("Already received message two", _gottwo); - + assertFalse("Already received message redelivered two", _gottwoRedelivered); _gottwo = true; } } @@ -419,6 +419,13 @@ public class CommitRollbackTest extends QpidTestCase verifyMessages(_consumer.receive(1000)); } + /** + * This test sends two messages receives on of them but doesn't ack it. + * The consumer is then closed + * the first message should be returned as redelivered. + * the second message should be delivered normally. + * @throws Exception + */ public void testSend2ThenCloseAfter1andTryAgain() throws Exception { assertTrue("session is not transacted", _session.getTransacted()); @@ -437,6 +444,7 @@ public class CommitRollbackTest extends QpidTestCase assertTrue("Messasge is marked as redelivered" + result, !result.getJMSRedelivered()); _logger.info("Closing Consumer"); + _consumer.close(); _logger.info("Creating New consumer"); diff --git a/java/common/build.xml b/java/common/build.xml index af27eb8d8f..6172a680ec 100644 --- a/java/common/build.xml +++ b/java/common/build.xml @@ -26,8 +26,8 @@ <property name="generated.package" value="org/apache/qpid/framing" /> <property name="generated.dir" location="${module.precompiled}/${generated.package}" /> <property name="xml.spec.dir" location="${project.root}/../specs" /> - <property name="xml.spec.deps" value="amqp.0-8.xml cluster.0-8.xml" /> - <property name="xml.spec.list" value="${xml.spec.dir}/amqp.0-8.xml ${xml.spec.dir}/cluster.0-8.xml" /> + <property name="xml.spec.deps" value="amqp.0-8.xml amqp.0-9.xml" /> + <property name="xml.spec.list" value="${xml.spec.dir}/amqp.0-8.xml ${xml.spec.dir}/amqp.0-9.xml" /> <property name="mllib.dir" value="${project.root}/../python" /> <property name="gentools.timestamp" location="${generated.dir}/gentools.timestamp" /> <property name="jython.timestamp" location="${generated.dir}/jython.timestamp" /> @@ -72,6 +72,8 @@ <fileset dir="${gentools.home}/lib"> <include name="**/*.jar"/> </fileset> + <pathelement path="${gentools.home}/lib/velocity-1.4.jar" /> + <pathelement path="${gentools.home}/lib/velocity-dep-1.4.jar" /> </classpath> </java> <touch file="${gentools.timestamp}" /> diff --git a/java/common/pom.xml b/java/common/pom.xml index ee3ae1b0a7..714087d843 100644 --- a/java/common/pom.xml +++ b/java/common/pom.xml @@ -139,18 +139,6 @@ <dependencies> <dependency> - <groupId>commons-configuration</groupId> - <artifactId>commons-configuration</artifactId> - </dependency> - - <dependency> - <groupId>commons-cli</groupId> - <artifactId>commons-cli</artifactId> - </dependency> - - - - <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.4.0</version> diff --git a/java/common/protocol-version.xml b/java/common/protocol-version.xml index 2ed7dbff23..ee9db6049b 100644 --- a/java/common/protocol-version.xml +++ b/java/common/protocol-version.xml @@ -27,18 +27,23 @@ <property name="generated.dir" location="${generated.path}/${generated.package}" /> <property name="generated.timestamp" location="${generated.dir}/timestamp" /> <property name="xml.spec.dir" location="${topDirectoryLocation}/../specs" /> - <property name="xml.spec.deps" value="amqp.0-8.xml cluster.0-8.xml" /> - <property name="xml.spec.list" value="${xml.spec.dir}/amqp.0-8.xml ${xml.spec.dir}/cluster.0-8.xml" /> + <property name="xml.spec.deps" value="amqp.0-8.xml amqp.0-9.xml" /> + <property name="xml.spec.list" value="${xml.spec.dir}/amqp.0-8.xml ${xml.spec.dir}/amqp.0-9.xml" /> + <property name="template.dir" value="${topDirectoryLocation}/common/templates" /> + - <target name="generate" depends="compile_generator,check_generate_deps" unless="generation.notRequired"> + <!--<target name="generate" depends="compile_generator,check_generate_deps" unless="generation.notRequired">--> + <target name="generate" depends="compile_generator" unless="generation.notRequired"> <mkdir dir="${generated.dir}"/> <java classname="org.apache.qpid.gentools.Main" fork="true" dir="${gentools.home}/src" failonerror="true"> - <arg line="-j -o ${generated.dir} -t ${topDirectoryLocation}/common/templates ${xml.spec.list}" /> + <arg line="-j -o ${generated.dir} -t ${template.dir} ${xml.spec.list}" /> <classpath> <pathelement path="${gentools.home}/src" /> - <fileset dir="${gentools.home}/lib"> - <include name="**/*.jar"/> - </fileset> + <fileset dir="${gentools.home}/lib"> + <include name="**/*.jar"/> + </fileset> + <pathelement path="${gentools.home}/lib/velocity-1.4.jar" /> + <pathelement path="${gentools.home}/lib/velocity-dep-1.4.jar" /> </classpath> </java> <touch file="${generated.timestamp}" /> @@ -47,7 +52,8 @@ <target name="check_generate_deps"> <uptodate property="generation.notRequired" targetfile="${generated.timestamp}"> <srcfiles dir="${xml.spec.dir}" includes="${xml.spec.deps}" /> - </uptodate> + <srcfiles dir="${template.dir}" includes="**/*.vm **/*.tmpl" /> + </uptodate> </target> <target name="compile_generator"> diff --git a/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java b/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java new file mode 100644 index 0000000000..bed80d5954 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java @@ -0,0 +1,528 @@ +package org.apache.mina.common; + +import org.apache.mina.common.ByteBuffer; + +import java.nio.*; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class FixedSizeByteBufferAllocator implements ByteBufferAllocator +{ + + + private static final int MINIMUM_CAPACITY = 1; + + public FixedSizeByteBufferAllocator () + { + } + + public ByteBuffer allocate( int capacity, boolean direct ) + { + java.nio.ByteBuffer nioBuffer; + if( direct ) + { + nioBuffer = java.nio.ByteBuffer.allocateDirect( capacity ); + } + else + { + nioBuffer = java.nio.ByteBuffer.allocate( capacity ); + } + return new FixedSizeByteBuffer( nioBuffer ); + } + + public ByteBuffer wrap( java.nio.ByteBuffer nioBuffer ) + { + return new FixedSizeByteBuffer( nioBuffer ); + } + + public void dispose() + { + } + + + + private static final class FixedSizeByteBuffer extends ByteBuffer + { + private java.nio.ByteBuffer buf; + private int refCount = 1; + private int mark = -1; + + + protected FixedSizeByteBuffer( java.nio.ByteBuffer buf ) + { + this.buf = buf; + buf.order( ByteOrder.BIG_ENDIAN ); + refCount = 1; + } + + public synchronized void acquire() + { + if( refCount <= 0 ) + { + throw new IllegalStateException( "Already released buffer." ); + } + + refCount ++; + } + + public void release() + { + synchronized( this ) + { + if( refCount <= 0 ) + { + refCount = 0; + throw new IllegalStateException( + "Already released buffer. You released the buffer too many times." ); + } + + refCount --; + if( refCount > 0) + { + return; + } + } + } + + public java.nio.ByteBuffer buf() + { + return buf; + } + + public boolean isPooled() + { + return false; + } + + public void setPooled( boolean pooled ) + { + } + + public ByteBuffer duplicate() { + return new FixedSizeByteBuffer( this.buf.duplicate() ); + } + + public ByteBuffer slice() { + return new FixedSizeByteBuffer( this.buf.slice() ); + } + + public ByteBuffer asReadOnlyBuffer() { + return new FixedSizeByteBuffer( this.buf.asReadOnlyBuffer() ); + } + + public byte[] array() + { + return buf.array(); + } + + public int arrayOffset() + { + return buf.arrayOffset(); + } + + public boolean isDirect() + { + return buf.isDirect(); + } + + public boolean isReadOnly() + { + return buf.isReadOnly(); + } + + public int capacity() + { + return buf.capacity(); + } + + public ByteBuffer capacity( int newCapacity ) + { + if( newCapacity > capacity() ) + { + // Allocate a new buffer and transfer all settings to it. + int pos = position(); + int limit = limit(); + ByteOrder bo = order(); + + capacity0( newCapacity ); + buf.limit( limit ); + if( mark >= 0 ) + { + buf.position( mark ); + buf.mark(); + } + buf.position( pos ); + buf.order( bo ); + } + + return this; + } + + protected void capacity0( int requestedCapacity ) + { + int newCapacity = MINIMUM_CAPACITY; + while( newCapacity < requestedCapacity ) + { + newCapacity <<= 1; + } + + java.nio.ByteBuffer oldBuf = this.buf; + java.nio.ByteBuffer newBuf; + if( isDirect() ) + { + newBuf = java.nio.ByteBuffer.allocateDirect( newCapacity ); + } + else + { + newBuf = java.nio.ByteBuffer.allocate( newCapacity ); + } + + newBuf.clear(); + oldBuf.clear(); + newBuf.put( oldBuf ); + this.buf = newBuf; + } + + + + public boolean isAutoExpand() + { + return false; + } + + public ByteBuffer setAutoExpand( boolean autoExpand ) + { + if(autoExpand) throw new IllegalArgumentException(); + else return this; + } + + public ByteBuffer expand( int pos, int expectedRemaining ) + { + int end = pos + expectedRemaining; + if( end > capacity() ) + { + // The buffer needs expansion. + capacity( end ); + } + + if( end > limit() ) + { + // We call limit() directly to prevent StackOverflowError + buf.limit( end ); + } + return this; + } + + public int position() + { + return buf.position(); + } + + public ByteBuffer position( int newPosition ) + { + + buf.position( newPosition ); + if( mark > newPosition ) + { + mark = -1; + } + return this; + } + + public int limit() + { + return buf.limit(); + } + + public ByteBuffer limit( int newLimit ) + { + buf.limit( newLimit ); + if( mark > newLimit ) + { + mark = -1; + } + return this; + } + + public ByteBuffer mark() + { + buf.mark(); + mark = position(); + return this; + } + + public int markValue() + { + return mark; + } + + public ByteBuffer reset() + { + buf.reset(); + return this; + } + + public ByteBuffer clear() + { + buf.clear(); + mark = -1; + return this; + } + + public ByteBuffer flip() + { + buf.flip(); + mark = -1; + return this; + } + + public ByteBuffer rewind() + { + buf.rewind(); + mark = -1; + return this; + } + + public byte get() + { + return buf.get(); + } + + public ByteBuffer put( byte b ) + { + buf.put( b ); + return this; + } + + public byte get( int index ) + { + return buf.get( index ); + } + + public ByteBuffer put( int index, byte b ) + { + buf.put( index, b ); + return this; + } + + public ByteBuffer get( byte[] dst, int offset, int length ) + { + buf.get( dst, offset, length ); + return this; + } + + public ByteBuffer put( java.nio.ByteBuffer src ) + { + buf.put( src ); + return this; + } + + public ByteBuffer put( byte[] src, int offset, int length ) + { + buf.put( src, offset, length ); + return this; + } + + public ByteBuffer compact() + { + buf.compact(); + mark = -1; + return this; + } + + public ByteOrder order() + { + return buf.order(); + } + + public ByteBuffer order( ByteOrder bo ) + { + buf.order( bo ); + return this; + } + + public char getChar() + { + return buf.getChar(); + } + + public ByteBuffer putChar( char value ) + { + buf.putChar( value ); + return this; + } + + public char getChar( int index ) + { + return buf.getChar( index ); + } + + public ByteBuffer putChar( int index, char value ) + { + buf.putChar( index, value ); + return this; + } + + public CharBuffer asCharBuffer() + { + return buf.asCharBuffer(); + } + + public short getShort() + { + return buf.getShort(); + } + + public ByteBuffer putShort( short value ) + { + buf.putShort( value ); + return this; + } + + public short getShort( int index ) + { + return buf.getShort( index ); + } + + public ByteBuffer putShort( int index, short value ) + { + buf.putShort( index, value ); + return this; + } + + public ShortBuffer asShortBuffer() + { + return buf.asShortBuffer(); + } + + public int getInt() + { + return buf.getInt(); + } + + public ByteBuffer putInt( int value ) + { + buf.putInt( value ); + return this; + } + + public int getInt( int index ) + { + return buf.getInt( index ); + } + + public ByteBuffer putInt( int index, int value ) + { + buf.putInt( index, value ); + return this; + } + + public IntBuffer asIntBuffer() + { + return buf.asIntBuffer(); + } + + public long getLong() + { + return buf.getLong(); + } + + public ByteBuffer putLong( long value ) + { + buf.putLong( value ); + return this; + } + + public long getLong( int index ) + { + return buf.getLong( index ); + } + + public ByteBuffer putLong( int index, long value ) + { + buf.putLong( index, value ); + return this; + } + + public LongBuffer asLongBuffer() + { + return buf.asLongBuffer(); + } + + public float getFloat() + { + return buf.getFloat(); + } + + public ByteBuffer putFloat( float value ) + { + buf.putFloat( value ); + return this; + } + + public float getFloat( int index ) + { + return buf.getFloat( index ); + } + + public ByteBuffer putFloat( int index, float value ) + { + buf.putFloat( index, value ); + return this; + } + + public FloatBuffer asFloatBuffer() + { + return buf.asFloatBuffer(); + } + + public double getDouble() + { + return buf.getDouble(); + } + + public ByteBuffer putDouble( double value ) + { + buf.putDouble( value ); + return this; + } + + public double getDouble( int index ) + { + return buf.getDouble( index ); + } + + public ByteBuffer putDouble( int index, double value ) + { + buf.putDouble( index, value ); + return this; + } + + public DoubleBuffer asDoubleBuffer() + { + return buf.asDoubleBuffer(); + } + + + } + + +} diff --git a/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java b/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java new file mode 100644 index 0000000000..c515263317 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.common.support; + +import org.apache.mina.common.IoFuture; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoFutureListener; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * A default implementation of {@link org.apache.mina.common.IoFuture}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 440259 $, $Date: 2006-09-05 14:01:47 +0900 (í™”, 05 9ì›” 2006) $ + */ +public class DefaultIoFuture implements IoFuture +{ + private final IoSession session; + private final Object lock; + private List listeners; + private Object result; + private boolean ready; + + + /** + * Creates a new instance. + * + * @param session an {@link IoSession} which is associated with this future + */ + public DefaultIoFuture( IoSession session ) + { + this.session = session; + this.lock = this; + } + + /** + * Creates a new instance which uses the specified object as a lock. + */ + public DefaultIoFuture( IoSession session, Object lock ) + { + if( lock == null ) + { + throw new NullPointerException( "lock" ); + } + this.session = session; + this.lock = lock; + } + + public IoSession getSession() + { + return session; + } + + public Object getLock() + { + return lock; + } + + public void join() + { + synchronized( lock ) + { + while( !ready ) + { + try + { + lock.wait(); + } + catch( InterruptedException e ) + { + } + } + } + } + + public boolean join( long timeoutInMillis ) + { + long startTime = ( timeoutInMillis <= 0 ) ? 0 : System + .currentTimeMillis(); + long waitTime = timeoutInMillis; + + synchronized( lock ) + { + if( ready ) + { + return ready; + } + else if( waitTime <= 0 ) + { + return ready; + } + + for( ;; ) + { + try + { + lock.wait( waitTime ); + } + catch( InterruptedException e ) + { + } + + if( ready ) + return true; + else + { + waitTime = timeoutInMillis - ( System.currentTimeMillis() - startTime ); + if( waitTime <= 0 ) + { + return ready; + } + } + } + } + } + + public boolean isReady() + { + synchronized( lock ) + { + return ready; + } + } + + /** + * Sets the result of the asynchronous operation, and mark it as finished. + */ + protected void setValue( Object newValue ) + { + synchronized( lock ) + { + // Allow only once. + if( ready ) + { + return; + } + + result = newValue; + ready = true; + lock.notifyAll(); + + notifyListeners(); + } + } + + /** + * Returns the result of the asynchronous operation. + */ + protected Object getValue() + { + synchronized( lock ) + { + return result; + } + } + + public void addListener( IoFutureListener listener ) + { + if( listener == null ) + { + throw new NullPointerException( "listener" ); + } + + synchronized( lock ) + { + if(listeners == null) + { + listeners = new ArrayList(); + } + listeners.add( listener ); + if( ready ) + { + listener.operationComplete( this ); + } + } + } + + public void removeListener( IoFutureListener listener ) + { + if( listener == null ) + { + throw new NullPointerException( "listener" ); + } + + synchronized( lock ) + { + listeners.remove( listener ); + } + } + + private void notifyListeners() + { + synchronized( lock ) + { + + if(listeners != null) + { + + for( Iterator i = listeners.iterator(); i.hasNext(); ) { + ( ( IoFutureListener ) i.next() ).operationComplete( this ); + } + } + } + } +} + + + diff --git a/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java b/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java new file mode 100644 index 0000000000..47f19aa76d --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java @@ -0,0 +1,48 @@ +package org.apache.mina.filter; + +import org.apache.mina.common.IoFilter;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +public class WriteBufferFullExeception extends RuntimeException +{ + private IoFilter.WriteRequest _writeRequest; + + public WriteBufferFullExeception() + { + this(null); + } + + public WriteBufferFullExeception(IoFilter.WriteRequest writeRequest) + { + _writeRequest = writeRequest; + } + + + public void setWriteRequest(IoFilter.WriteRequest writeRequest) + { + _writeRequest = writeRequest; + } + + public IoFilter.WriteRequest getWriteRequest() + { + return _writeRequest; + } +} diff --git a/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java b/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java new file mode 100644 index 0000000000..4e9db9071a --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.filter; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.DefaultIoFilterChainBuilder; +import org.apache.mina.common.IoFilterAdapter; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.executor.ExecutorFilter; + +import java.util.Iterator; +import java.util.List; + +/** + * This filter will turn the asynchronous filterWrite method in to a blocking send when there are more than + * the prescribed number of messages awaiting filterWrite. It should be used in conjunction with the + * {@link ReadThrottleFilterBuilder} on a server as the blocking writes will allow the read thread to + * cause an Out of Memory exception due to a back log of unprocessed messages. + * + * This is should only be viewed as a temporary work around for DIRMINA-302. + * + * A true solution should not be implemented as a filter as this issue will always occur. On a machine + * where the network is slower than the local producer. + * + * Suggested improvement is to allow implementation of policices on what to do when buffer is full. + * + * They could be: + * Block - As this does + * Wait on a given Future - to drain more of the queue.. in essence this filter with high/low watermarks + * Throw Exception - through the client filterWrite() method to allow them to get immediate feedback on buffer state + * + * <p/> + * <p>Usage: + * <p/> + * <pre><code> + * DefaultFilterChainBuilder builder = ... + * WriteBufferLimitFilterBuilder filter = new WriteBufferLimitFilterBuilder(); + * filter.attach( builder ); + * </code></pre> + * <p/> + * or + * <p/> + * <pre><code> + * IoFilterChain chain = ... + * WriteBufferLimitFilterBuilder filter = new WriteBufferLimitFilterBuilder(); + * filter.attach( chain ); + * </code></pre> + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class WriteBufferLimitFilterBuilder +{ + public static final String PENDING_SIZE = WriteBufferLimitFilterBuilder.class.getName() + ".pendingSize"; + + private static int DEFAULT_CONNECTION_BUFFER_MESSAGE_COUNT = 5000; + + private volatile boolean throwNotBlock = false; + + private volatile int maximumConnectionBufferCount; + private volatile long maximumConnectionBufferSize; + + private final Object _blockLock = new Object(); + + private int _blockWaiters = 0; + + + public WriteBufferLimitFilterBuilder() + { + this(DEFAULT_CONNECTION_BUFFER_MESSAGE_COUNT); + } + + public WriteBufferLimitFilterBuilder(int maxWriteBufferSize) + { + setMaximumConnectionBufferCount(maxWriteBufferSize); + } + + + /** + * Set the maximum amount pending items in the writeQueue for a given session. + * Changing the value will only take effect when new data is received for a + * connection, including existing connections. Default value is 5000 msgs. + * + * @param maximumConnectionBufferCount New buffer size. Must be > 0 + */ + public void setMaximumConnectionBufferCount(int maximumConnectionBufferCount) + { + this.maximumConnectionBufferCount = maximumConnectionBufferCount; + this.maximumConnectionBufferSize = 0; + } + + public void setMaximumConnectionBufferSize(long maximumConnectionBufferSize) + { + this.maximumConnectionBufferSize = maximumConnectionBufferSize; + this.maximumConnectionBufferCount = 0; + } + + /** + * Attach this filter to the specified filter chain. It will search for the ThreadPoolFilter, and attach itself + * before and after that filter. + * + * @param chain {@link IoFilterChain} to attach self to. + */ + public void attach(IoFilterChain chain) + { + String name = getThreadPoolFilterEntryName(chain.getAll()); + + chain.addBefore(name, getClass().getName() + ".sendlimit", new SendLimit()); + } + + /** + * Attach this filter to the specified builder. It will search for the + * {@link ExecutorFilter}, and attach itself before and after that filter. + * + * @param builder {@link DefaultIoFilterChainBuilder} to attach self to. + */ + public void attach(DefaultIoFilterChainBuilder builder) + { + String name = getThreadPoolFilterEntryName(builder.getAll()); + + builder.addBefore(name, getClass().getName() + ".sendlimit", new SendLimit()); + } + + private String getThreadPoolFilterEntryName(List entries) + { + Iterator i = entries.iterator(); + + while (i.hasNext()) + { + IoFilterChain.Entry entry = (IoFilterChain.Entry) i.next(); + + if (entry.getFilter().getClass().isAssignableFrom(ExecutorFilter.class)) + { + return entry.getName(); + } + } + + throw new IllegalStateException("Chain does not contain a ExecutorFilter"); + } + + + public class SendLimit extends IoFilterAdapter + { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception + { + try + { + waitTillSendAllowed(session); + } + catch (WriteBufferFullExeception wbfe) + { + nextFilter.exceptionCaught(session, wbfe); + } + + if (writeRequest.getMessage() instanceof ByteBuffer) + { + increasePendingWriteSize(session, (ByteBuffer) writeRequest.getMessage()); + } + + nextFilter.filterWrite(session, writeRequest); + } + + private void increasePendingWriteSize(IoSession session, ByteBuffer message) + { + synchronized (session) + { + Long pendingSize = getScheduledWriteBytes(session) + message.remaining(); + session.setAttribute(PENDING_SIZE, pendingSize); + } + } + + private boolean sendAllowed(IoSession session) + { + if (session.isClosing()) + { + return true; + } + + int lmswm = maximumConnectionBufferCount; + long lmswb = maximumConnectionBufferSize; + + return (lmswm == 0 || session.getScheduledWriteRequests() < lmswm) + && (lmswb == 0 || getScheduledWriteBytes(session) < lmswb); + } + + private long getScheduledWriteBytes(IoSession session) + { + synchronized (session) + { + Long i = (Long) session.getAttribute(PENDING_SIZE); + return null == i ? 0 : i; + } + } + + private void waitTillSendAllowed(IoSession session) + { + synchronized (_blockLock) + { + if (throwNotBlock) + { + throw new WriteBufferFullExeception(); + } + + _blockWaiters++; + + while (!sendAllowed(session)) + { + try + { + _blockLock.wait(); + } + catch (InterruptedException e) + { + // Ignore. + } + } + _blockWaiters--; + } + } + + public void messageSent(NextFilter nextFilter, IoSession session, Object message) throws Exception + { + if (message instanceof ByteBuffer) + { + decrementPendingWriteSize(session, (ByteBuffer) message); + } + notifyWaitingWriters(); + nextFilter.messageSent(session, message); + } + + private void decrementPendingWriteSize(IoSession session, ByteBuffer message) + { + synchronized (session) + { + session.setAttribute(PENDING_SIZE, getScheduledWriteBytes(session) - message.remaining()); + } + } + + private void notifyWaitingWriters() + { + synchronized (_blockLock) + { + if (_blockWaiters != 0) + { + _blockLock.notifyAll(); + } + } + + } + + }//SentLimit + + +} diff --git a/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java b/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java new file mode 100644 index 0000000000..3f7e206cb4 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.filter.codec; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoSession; + +/** + * A {@link ProtocolDecoder} that cumulates the content of received + * buffers to a <em>cumulative buffer</em> to help users implement decoders. + * <p> + * If the received {@link ByteBuffer} is only a part of a message. + * decoders should cumulate received buffers to make a message complete or + * to postpone decoding until more buffers arrive. + * <p> + * Here is an example decoder that decodes CRLF terminated lines into + * <code>Command</code> objects: + * <pre> + * public class CRLFTerminatedCommandLineDecoder + * extends CumulativeProtocolDecoder { + * + * private Command parseCommand(ByteBuffer in) { + * // Convert the bytes in the specified buffer to a + * // Command object. + * ... + * } + * + * protected boolean doDecode(IoSession session, ByteBuffer in, + * ProtocolDecoderOutput out) + * throws Exception { + * + * // Remember the initial position. + * int start = in.position(); + * + * // Now find the first CRLF in the buffer. + * byte previous = 0; + * while (in.hasRemaining()) { + * byte current = in.get(); + * + * if (previous == '\r' && current == '\n') { + * // Remember the current position and limit. + * int position = in.position(); + * int limit = in.limit(); + * try { + * in.position(start); + * in.limit(position); + * // The bytes between in.position() and in.limit() + * // now contain a full CRLF terminated line. + * out.write(parseCommand(in.slice())); + * } finally { + * // Set the position to point right after the + * // detected line and set the limit to the old + * // one. + * in.position(position); + * in.limit(limit); + * } + * // Decoded one line; CumulativeProtocolDecoder will + * // call me again until I return false. So just + * // return true until there are no more lines in the + * // buffer. + * return true; + * } + * + * previous = current; + * } + * + * // Could not find CRLF in the buffer. Reset the initial + * // position to the one we recorded above. + * in.position(start); + * + * return false; + * } + * } + * </pre> + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public abstract class OurCumulativeProtocolDecoder extends ProtocolDecoderAdapter { + + private static final String BUFFER = OurCumulativeProtocolDecoder.class + .getName() + + ".Buffer"; + + /** + * Creates a new instance. + */ + protected OurCumulativeProtocolDecoder() { + } + + /** + * Cumulates content of <tt>in</tt> into internal buffer and forwards + * decoding request to {@link #doDecode(IoSession, ByteBuffer, ProtocolDecoderOutput)}. + * <tt>doDecode()</tt> is invoked repeatedly until it returns <tt>false</tt> + * and the cumulative buffer is NOT compacted after decoding ends. + * + * @throws IllegalStateException if your <tt>doDecode()</tt> returned + * <tt>true</tt> not consuming the cumulative buffer. + */ + public void decode(IoSession session, ByteBuffer in, + ProtocolDecoderOutput out) throws Exception { + boolean usingSessionBuffer = true; + ByteBuffer buf = (ByteBuffer) session.getAttribute(BUFFER); + // If we have a session buffer, append data to that; otherwise + // use the buffer read from the network directly. + if (buf != null) { + buf.put(in); + buf.flip(); + } else { + buf = in; + usingSessionBuffer = false; + } + + for (;;) { + int oldPos = buf.position(); + boolean decoded = doDecode(session, buf, out); + if (decoded) { + if (buf.position() == oldPos) { + throw new IllegalStateException( + "doDecode() can't return true when buffer is not consumed."); + } + + if (!buf.hasRemaining()) { + break; + } + } else { + break; + } + } + + + // if there is any data left that cannot be decoded, we store + // it in a buffer in the session and next time this decoder is + // invoked the session buffer gets appended to + if (buf.hasRemaining()) { + storeRemainingInSession(buf, session); + } else { + if (usingSessionBuffer) + removeSessionBuffer(session); + } + } + + /** + * Implement this method to consume the specified cumulative buffer and + * decode its content into message(s). + * + * @param in the cumulative buffer + * @return <tt>true</tt> if and only if there's more to decode in the buffer + * and you want to have <tt>doDecode</tt> method invoked again. + * Return <tt>false</tt> if remaining data is not enough to decode, + * then this method will be invoked again when more data is cumulated. + * @throws Exception if cannot decode <tt>in</tt>. + */ + protected abstract boolean doDecode(IoSession session, ByteBuffer in, + ProtocolDecoderOutput out) throws Exception; + + /** + * Releases the cumulative buffer used by the specified <tt>session</tt>. + * Please don't forget to call <tt>super.dispose( session )</tt> when + * you override this method. + */ + public void dispose(IoSession session) throws Exception { + removeSessionBuffer(session); + } + + private void removeSessionBuffer(IoSession session) { + ByteBuffer buf = (ByteBuffer) session.removeAttribute(BUFFER); + if (buf != null) { + buf.release(); + } + } + + private void storeRemainingInSession(ByteBuffer buf, IoSession session) { + ByteBuffer remainingBuf = ByteBuffer.allocate(buf.capacity()); + remainingBuf.setAutoExpand(true); + remainingBuf.order(buf.order()); + remainingBuf.put(buf); + session.setAttribute(BUFFER, remainingBuf); + } +} diff --git a/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java b/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java new file mode 100644 index 0000000000..b8c6f29720 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java @@ -0,0 +1,440 @@ +package org.apache.mina.filter.codec; + + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ + +import org.apache.mina.common.*; +import org.apache.mina.common.support.DefaultWriteFuture; +import org.apache.mina.filter.codec.support.SimpleProtocolDecoderOutput; +import org.apache.mina.util.SessionLog; +import org.apache.mina.util.Queue; + + +public class QpidProtocolCodecFilter extends IoFilterAdapter +{ + public static final String ENCODER = QpidProtocolCodecFilter.class.getName() + ".encoder"; + public static final String DECODER = QpidProtocolCodecFilter.class.getName() + ".decoder"; + + private static final Class[] EMPTY_PARAMS = new Class[0]; + private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap( new byte[0] ); + + private final ProtocolCodecFactory factory; + + public QpidProtocolCodecFilter( ProtocolCodecFactory factory ) + { + if( factory == null ) + { + throw new NullPointerException( "factory" ); + } + this.factory = factory; + } + + public QpidProtocolCodecFilter( final ProtocolEncoder encoder, final ProtocolDecoder decoder ) + { + if( encoder == null ) + { + throw new NullPointerException( "encoder" ); + } + if( decoder == null ) + { + throw new NullPointerException( "decoder" ); + } + + this.factory = new ProtocolCodecFactory() + { + public ProtocolEncoder getEncoder() + { + return encoder; + } + + public ProtocolDecoder getDecoder() + { + return decoder; + } + }; + } + + public QpidProtocolCodecFilter( final Class encoderClass, final Class decoderClass ) + { + if( encoderClass == null ) + { + throw new NullPointerException( "encoderClass" ); + } + if( decoderClass == null ) + { + throw new NullPointerException( "decoderClass" ); + } + if( !ProtocolEncoder.class.isAssignableFrom( encoderClass ) ) + { + throw new IllegalArgumentException( "encoderClass: " + encoderClass.getName() ); + } + if( !ProtocolDecoder.class.isAssignableFrom( decoderClass ) ) + { + throw new IllegalArgumentException( "decoderClass: " + decoderClass.getName() ); + } + try + { + encoderClass.getConstructor( EMPTY_PARAMS ); + } + catch( NoSuchMethodException e ) + { + throw new IllegalArgumentException( "encoderClass doesn't have a public default constructor." ); + } + try + { + decoderClass.getConstructor( EMPTY_PARAMS ); + } + catch( NoSuchMethodException e ) + { + throw new IllegalArgumentException( "decoderClass doesn't have a public default constructor." ); + } + + this.factory = new ProtocolCodecFactory() + { + public ProtocolEncoder getEncoder() throws Exception + { + return ( ProtocolEncoder ) encoderClass.newInstance(); + } + + public ProtocolDecoder getDecoder() throws Exception + { + return ( ProtocolDecoder ) decoderClass.newInstance(); + } + }; + } + + public void onPreAdd( IoFilterChain parent, String name, IoFilter.NextFilter nextFilter ) throws Exception + { + if( parent.contains( ProtocolCodecFilter.class ) ) + { + throw new IllegalStateException( "A filter chain cannot contain more than one QpidProtocolCodecFilter." ); + } + } + + public void messageReceived( IoFilter.NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + if( !( message instanceof ByteBuffer ) ) + { + nextFilter.messageReceived( session, message ); + return; + } + + ByteBuffer in = ( ByteBuffer ) message; + ProtocolDecoder decoder = getDecoder( session ); + ProtocolDecoderOutput decoderOut = getDecoderOut( session, nextFilter ); + + try + { + decoder.decode( session, in, decoderOut ); + } + catch( Throwable t ) + { + ProtocolDecoderException pde; + if( t instanceof ProtocolDecoderException ) + { + pde = ( ProtocolDecoderException ) t; + } + else + { + pde = new ProtocolDecoderException( t ); + } + pde.setHexdump( in.getHexDump() ); + throw pde; + } + finally + { + // Dispose the decoder if this session is connectionless. + if( session.getTransportType().isConnectionless() ) + { + disposeDecoder( session ); + } + + // Release the read buffer. + in.release(); + + decoderOut.flush(); + } + } + + public void messageSent( IoFilter.NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + if( message instanceof HiddenByteBuffer ) + { + return; + } + + if( !( message instanceof MessageByteBuffer ) ) + { + nextFilter.messageSent( session, message ); + return; + } + + nextFilter.messageSent( session, ( ( MessageByteBuffer ) message ).message ); + } + + public void filterWrite( IoFilter.NextFilter nextFilter, IoSession session, IoFilter.WriteRequest writeRequest ) throws Exception + { + Object message = writeRequest.getMessage(); + if( message instanceof ByteBuffer ) + { + nextFilter.filterWrite( session, writeRequest ); + return; + } + + ProtocolEncoder encoder = getEncoder( session ); + ProtocolEncoderOutputImpl encoderOut = getEncoderOut( session, nextFilter, writeRequest ); + + try + { + encoder.encode( session, message, encoderOut ); + encoderOut.flush(); + nextFilter.filterWrite( + session, + new IoFilter.WriteRequest( + new MessageByteBuffer( writeRequest.getMessage() ), + writeRequest.getFuture(), writeRequest.getDestination() ) ); + } + catch( Throwable t ) + { + ProtocolEncoderException pee; + if( t instanceof ProtocolEncoderException ) + { + pee = ( ProtocolEncoderException ) t; + } + else + { + pee = new ProtocolEncoderException( t ); + } + throw pee; + } + finally + { + // Dispose the encoder if this session is connectionless. + if( session.getTransportType().isConnectionless() ) + { + disposeEncoder( session ); + } + } + } + + public void sessionClosed( IoFilter.NextFilter nextFilter, IoSession session ) throws Exception + { + // Call finishDecode() first when a connection is closed. + ProtocolDecoder decoder = getDecoder( session ); + ProtocolDecoderOutput decoderOut = getDecoderOut( session, nextFilter ); + try + { + decoder.finishDecode( session, decoderOut ); + } + catch( Throwable t ) + { + ProtocolDecoderException pde; + if( t instanceof ProtocolDecoderException ) + { + pde = ( ProtocolDecoderException ) t; + } + else + { + pde = new ProtocolDecoderException( t ); + } + throw pde; + } + finally + { + // Dispose all. + disposeEncoder( session ); + disposeDecoder( session ); + + decoderOut.flush(); + } + + nextFilter.sessionClosed( session ); + } + + private ProtocolEncoder getEncoder( IoSession session ) throws Exception + { + ProtocolEncoder encoder = ( ProtocolEncoder ) session.getAttribute( ENCODER ); + if( encoder == null ) + { + encoder = factory.getEncoder(); + session.setAttribute( ENCODER, encoder ); + } + return encoder; + } + + private ProtocolEncoderOutputImpl getEncoderOut( IoSession session, IoFilter.NextFilter nextFilter, IoFilter.WriteRequest writeRequest ) + { + return new ProtocolEncoderOutputImpl( session, nextFilter, writeRequest ); + } + + private ProtocolDecoder getDecoder( IoSession session ) throws Exception + { + ProtocolDecoder decoder = ( ProtocolDecoder ) session.getAttribute( DECODER ); + if( decoder == null ) + { + decoder = factory.getDecoder(); + session.setAttribute( DECODER, decoder ); + } + return decoder; + } + + private ProtocolDecoderOutput getDecoderOut( IoSession session, IoFilter.NextFilter nextFilter ) + { + return new SimpleProtocolDecoderOutput( session, nextFilter ); + } + + private void disposeEncoder( IoSession session ) + { + ProtocolEncoder encoder = ( ProtocolEncoder ) session.removeAttribute( ENCODER ); + if( encoder == null ) + { + return; + } + + try + { + encoder.dispose( session ); + } + catch( Throwable t ) + { + SessionLog.warn( + session, + "Failed to dispose: " + encoder.getClass().getName() + + " (" + encoder + ')' ); + } + } + + private void disposeDecoder( IoSession session ) + { + ProtocolDecoder decoder = ( ProtocolDecoder ) session.removeAttribute( DECODER ); + if( decoder == null ) + { + return; + } + + try + { + decoder.dispose( session ); + } + catch( Throwable t ) + { + SessionLog.warn( + session, + "Falied to dispose: " + decoder.getClass().getName() + + " (" + decoder + ')' ); + } + } + + private static class HiddenByteBuffer extends ByteBufferProxy + { + private HiddenByteBuffer( ByteBuffer buf ) + { + super( buf ); + } + } + + private static class MessageByteBuffer extends ByteBufferProxy + { + private final Object message; + + private MessageByteBuffer( Object message ) + { + super( EMPTY_BUFFER ); + this.message = message; + } + + public void acquire() + { + // no-op since we are wraping a zero-byte buffer, this instance is to just curry the message + } + + public void release() + { + // no-op since we are wraping a zero-byte buffer, this instance is to just curry the message + } + } + + private static class ProtocolEncoderOutputImpl implements ProtocolEncoderOutput + { + private ByteBuffer buffer; + + private final IoSession session; + private final IoFilter.NextFilter nextFilter; + private final IoFilter.WriteRequest writeRequest; + + public ProtocolEncoderOutputImpl( IoSession session, IoFilter.NextFilter nextFilter, IoFilter.WriteRequest writeRequest ) + { + this.session = session; + this.nextFilter = nextFilter; + this.writeRequest = writeRequest; + } + + + + public void write( ByteBuffer buf ) + { + if(buffer != null) + { + flush(); + } + buffer = buf; + } + + public void mergeAll() + { + } + + public WriteFuture flush() + { + WriteFuture future = null; + if( buffer == null ) + { + return null; + } + else + { + ByteBuffer buf = buffer; + // Flush only when the buffer has remaining. + if( buf.hasRemaining() ) + { + future = doFlush( buf ); + } + + } + + return future; + } + + + protected WriteFuture doFlush( ByteBuffer buf ) + { + WriteFuture future = new DefaultWriteFuture( session ); + nextFilter.filterWrite( + session, + new IoFilter.WriteRequest( + buf, + future, writeRequest.getDestination() ) ); + return future; + } + } +} + diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java new file mode 100644 index 0000000000..e5360d32e0 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.BaseIoAcceptor; +import org.apache.mina.util.Queue; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.NamePreservingRunnable; +import edu.emory.mathcs.backport.java.util.concurrent.Executor; + +/** + * {@link IoAcceptor} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketAcceptor extends SocketAcceptor +{ + /** + * @noinspection StaticNonFinalField + */ + private static volatile int nextId = 0; + + private final Executor executor; + private final Object lock = new Object(); + private final int id = nextId ++; + private final String threadName = "SocketAcceptor-" + id; + private final Map channels = new HashMap(); + + private final Queue registerQueue = new Queue(); + private final Queue cancelQueue = new Queue(); + + private final MultiThreadSocketIoProcessor[] ioProcessors; + private final int processorCount; + + /** + * @noinspection FieldAccessedSynchronizedAndUnsynchronized + */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + + /** + * Create an acceptor with a single processing thread using a NewThreadExecutor + */ + public MultiThreadSocketAcceptor() + { + this( 1, new NewThreadExecutor() ); + } + + /** + * Create an acceptor with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public MultiThreadSocketAcceptor( int processorCount, Executor executor ) + { + if( processorCount < 1 ) + { + throw new IllegalArgumentException( "Must have at least one processor" ); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new MultiThreadSocketIoProcessor[processorCount]; + + for( int i = 0; i < processorCount; i++ ) + { + ioProcessors[i] = new MultiThreadSocketIoProcessor( "SocketAcceptorIoProcessor-" + id + "." + i, executor ); + } + } + + + /** + * Binds to the specified <code>address</code> and handles incoming connections with the specified + * <code>handler</code>. Backlog value is configured to the value of <code>backlog</code> property. + * + * @throws IOException if failed to bind + */ + public void bind( SocketAddress address, IoHandler handler, IoServiceConfig config ) throws IOException + { + if( handler == null ) + { + throw new NullPointerException( "handler" ); + } + + if( address != null && !( address instanceof InetSocketAddress ) ) + { + throw new IllegalArgumentException( "Unexpected address type: " + address.getClass() ); + } + + if( config == null ) + { + config = getDefaultConfig(); + } + + RegistrationRequest request = new RegistrationRequest( address, handler, config ); + + synchronized( registerQueue ) + { + registerQueue.push( request ); + } + + startupWorker(); + + selector.wakeup(); + + synchronized( request ) + { + while( !request.done ) + { + try + { + request.wait(); + } + catch( InterruptedException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + + if( request.exception != null ) + { + throw request.exception; + } + } + + + private synchronized void startupWorker() throws IOException + { + synchronized( lock ) + { + if( worker == null ) + { + selector = Selector.open(); + worker = new Worker(); + + executor.execute( new NamePreservingRunnable( worker ) ); + } + } + } + + public void unbind( SocketAddress address ) + { + if( address == null ) + { + throw new NullPointerException( "address" ); + } + + CancellationRequest request = new CancellationRequest( address ); + + try + { + startupWorker(); + } + catch( IOException e ) + { + // IOException is thrown only when Worker thread is not + // running and failed to open a selector. We simply throw + // IllegalArgumentException here because we can simply + // conclude that nothing is bound to the selector. + throw new IllegalArgumentException( "Address not bound: " + address ); + } + + synchronized( cancelQueue ) + { + cancelQueue.push( request ); + } + + selector.wakeup(); + + synchronized( request ) + { + while( !request.done ) + { + try + { + request.wait(); + } + catch( InterruptedException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + + if( request.exception != null ) + { + request.exception.fillInStackTrace(); + + throw request.exception; + } + } + + + private class Worker implements Runnable + { + public void run() + { + Thread.currentThread().setName(MultiThreadSocketAcceptor.this.threadName ); + + for( ; ; ) + { + try + { + int nKeys = selector.select(); + + registerNew(); + + if( nKeys > 0 ) + { + processSessions( selector.selectedKeys() ); + } + + cancelKeys(); + + if( selector.keys().isEmpty() ) + { + synchronized( lock ) + { + if( selector.keys().isEmpty() && + registerQueue.isEmpty() && + cancelQueue.isEmpty() ) + { + worker = null; + try + { + selector.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + finally + { + selector = null; + } + break; + } + } + } + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + + try + { + Thread.sleep( 1000 ); + } + catch( InterruptedException e1 ) + { + ExceptionMonitor.getInstance().exceptionCaught( e1 ); + } + } + } + } + + private void processSessions( Set keys ) throws IOException + { + Iterator it = keys.iterator(); + while( it.hasNext() ) + { + SelectionKey key = ( SelectionKey ) it.next(); + + it.remove(); + + if( !key.isAcceptable() ) + { + continue; + } + + ServerSocketChannel ssc = ( ServerSocketChannel ) key.channel(); + + SocketChannel ch = ssc.accept(); + + if( ch == null ) + { + continue; + } + + boolean success = false; + try + { + + RegistrationRequest req = ( RegistrationRequest ) key.attachment(); + + MultiThreadSocketSessionImpl session = new MultiThreadSocketSessionImpl( + MultiThreadSocketAcceptor.this, nextProcessor(), getListeners(), + req.config, ch, req.handler, req.address ); + + // New Interface +// SocketSessionImpl session = new SocketSessionImpl( +// SocketAcceptor.this, nextProcessor(), getListeners(), +// req.config, ch, req.handler, req.address ); + + + getFilterChainBuilder().buildFilterChain( session.getFilterChain() ); + req.config.getFilterChainBuilder().buildFilterChain( session.getFilterChain() ); + req.config.getThreadModel().buildFilterChain( session.getFilterChain() ); + session.getIoProcessor().addNew( session ); + success = true; + } + catch( Throwable t ) + { + ExceptionMonitor.getInstance().exceptionCaught( t ); + } + finally + { + if( !success ) + { + ch.close(); + } + } + } + } + } + + private MultiThreadSocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + + private void registerNew() + { + if( registerQueue.isEmpty() ) + { + return; + } + + for( ; ; ) + { + RegistrationRequest req; + + synchronized( registerQueue ) + { + req = ( RegistrationRequest ) registerQueue.pop(); + } + + if( req == null ) + { + break; + } + + ServerSocketChannel ssc = null; + + try + { + ssc = ServerSocketChannel.open(); + ssc.configureBlocking( false ); + + // Configure the server socket, + SocketAcceptorConfig cfg; + if( req.config instanceof SocketAcceptorConfig ) + { + cfg = ( SocketAcceptorConfig ) req.config; + } + else + { + cfg = ( SocketAcceptorConfig ) getDefaultConfig(); + } + + ssc.socket().setReuseAddress( cfg.isReuseAddress() ); + ssc.socket().setReceiveBufferSize( + ( ( SocketSessionConfig ) cfg.getSessionConfig() ).getReceiveBufferSize() ); + + // and bind. + ssc.socket().bind( req.address, cfg.getBacklog() ); + if( req.address == null || req.address.getPort() == 0 ) + { + req.address = ( InetSocketAddress ) ssc.socket().getLocalSocketAddress(); + } + ssc.register( selector, SelectionKey.OP_ACCEPT, req ); + + synchronized( channels ) + { + channels.put( req.address, ssc ); + } + + getListeners().fireServiceActivated( + this, req.address, req.handler, req.config ); + } + catch( IOException e ) + { + req.exception = e; + } + finally + { + synchronized( req ) + { + req.done = true; + + req.notifyAll(); + } + + if( ssc != null && req.exception != null ) + { + try + { + ssc.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + } + } + + + private void cancelKeys() + { + if( cancelQueue.isEmpty() ) + { + return; + } + + for( ; ; ) + { + CancellationRequest request; + + synchronized( cancelQueue ) + { + request = ( CancellationRequest ) cancelQueue.pop(); + } + + if( request == null ) + { + break; + } + + ServerSocketChannel ssc; + synchronized( channels ) + { + ssc = ( ServerSocketChannel ) channels.remove( request.address ); + } + + // close the channel + try + { + if( ssc == null ) + { + request.exception = new IllegalArgumentException( "Address not bound: " + request.address ); + } + else + { + SelectionKey key = ssc.keyFor( selector ); + request.registrationRequest = ( RegistrationRequest ) key.attachment(); + key.cancel(); + + selector.wakeup(); // wake up again to trigger thread death + + ssc.close(); + } + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + finally + { + synchronized( request ) + { + request.done = true; + request.notifyAll(); + } + + if( request.exception == null ) + { + getListeners().fireServiceDeactivated( + this, request.address, + request.registrationRequest.handler, + request.registrationRequest.config ); + } + } + } + } + + private static class RegistrationRequest + { + private InetSocketAddress address; + private final IoHandler handler; + private final IoServiceConfig config; + private IOException exception; + private boolean done; + + private RegistrationRequest( SocketAddress address, IoHandler handler, IoServiceConfig config ) + { + this.address = ( InetSocketAddress ) address; + this.handler = handler; + this.config = config; + } + } + + + private static class CancellationRequest + { + private final SocketAddress address; + private boolean done; + private RegistrationRequest registrationRequest; + private RuntimeException exception; + + private CancellationRequest( SocketAddress address ) + { + this.address = address; + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java new file mode 100644 index 0000000000..b1612840db --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.Queue; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; + +/** + * {@link IoConnector} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketConnector extends SocketConnector +{ + /** @noinspection StaticNonFinalField */ + private static volatile int nextId = 0; + + private final Object lock = new Object(); + private final int id = nextId++; + private final String threadName = "SocketConnector-" + id; + private SocketConnectorConfig defaultConfig = new SocketConnectorConfig(); + private final Queue connectQueue = new Queue(); + private final MultiThreadSocketIoProcessor[] ioProcessors; + private final int processorCount; + private final Executor executor; + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + private int workerTimeout = 60; // 1 min. + + /** Create a connector with a single processing thread using a NewThreadExecutor */ + public MultiThreadSocketConnector() + { + this(1, new NewThreadExecutor()); + } + + /** + * Create a connector with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public MultiThreadSocketConnector(int processorCount, Executor executor) + { + if (processorCount < 1) + { + throw new IllegalArgumentException("Must have at least one processor"); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new MultiThreadSocketIoProcessor[processorCount]; + + for (int i = 0; i < processorCount; i++) + { + ioProcessors[i] = new MultiThreadSocketIoProcessor("SocketConnectorIoProcessor-" + id + "." + i, executor); + } + } + + /** + * How many seconds to keep the connection thread alive between connection requests + * + * @return Number of seconds to keep connection thread alive + */ + public int getWorkerTimeout() + { + return workerTimeout; + } + + /** + * Set how many seconds the connection worker thread should remain alive once idle before terminating itself. + * + * @param workerTimeout Number of seconds to keep thread alive. Must be >=0 + */ + public void setWorkerTimeout(int workerTimeout) + { + if (workerTimeout < 0) + { + throw new IllegalArgumentException("Must be >= 0"); + } + this.workerTimeout = workerTimeout; + } + + public ConnectFuture connect(SocketAddress address, IoHandler handler, IoServiceConfig config) + { + return connect(address, null, handler, config); + } + + public ConnectFuture connect(SocketAddress address, SocketAddress localAddress, + IoHandler handler, IoServiceConfig config) + { + if (address == null) + { + throw new NullPointerException("address"); + } + if (handler == null) + { + throw new NullPointerException("handler"); + } + + if (!(address instanceof InetSocketAddress)) + { + throw new IllegalArgumentException("Unexpected address type: " + + address.getClass()); + } + + if (localAddress != null && !(localAddress instanceof InetSocketAddress)) + { + throw new IllegalArgumentException("Unexpected local address type: " + + localAddress.getClass()); + } + + if (config == null) + { + config = getDefaultConfig(); + } + + SocketChannel ch = null; + boolean success = false; + try + { + ch = SocketChannel.open(); + ch.socket().setReuseAddress(true); + if (localAddress != null) + { + ch.socket().bind(localAddress); + } + + ch.configureBlocking(false); + + if (ch.connect(address)) + { + DefaultConnectFuture future = new DefaultConnectFuture(); + newSession(ch, handler, config, future); + success = true; + return future; + } + + success = true; + } + catch (IOException e) + { + return DefaultConnectFuture.newFailedFuture(e); + } + finally + { + if (!success && ch != null) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + + ConnectionRequest request = new ConnectionRequest(ch, handler, config); + synchronized (lock) + { + try + { + startupWorker(); + } + catch (IOException e) + { + try + { + ch.close(); + } + catch (IOException e2) + { + ExceptionMonitor.getInstance().exceptionCaught(e2); + } + + return DefaultConnectFuture.newFailedFuture(e); + } + } + + synchronized (connectQueue) + { + connectQueue.push(request); + } + selector.wakeup(); + + return request; + } + + private synchronized void startupWorker() throws IOException + { + if (worker == null) + { + selector = Selector.open(); + worker = new Worker(); + executor.execute(new NamePreservingRunnable(worker)); + } + } + + private void registerNew() + { + if (connectQueue.isEmpty()) + { + return; + } + + for (; ;) + { + ConnectionRequest req; + synchronized (connectQueue) + { + req = (ConnectionRequest) connectQueue.pop(); + } + + if (req == null) + { + break; + } + + SocketChannel ch = req.channel; + try + { + ch.register(selector, SelectionKey.OP_CONNECT, req); + } + catch (IOException e) + { + req.setException(e); + } + } + } + + private void processSessions(Set keys) + { + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isConnectable()) + { + continue; + } + + SocketChannel ch = (SocketChannel) key.channel(); + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + boolean success = false; + try + { + ch.finishConnect(); + newSession(ch, entry.handler, entry.config, entry); + success = true; + } + catch (Throwable e) + { + entry.setException(e); + } + finally + { + key.cancel(); + if (!success) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + keys.clear(); + } + + private void processTimedOutSessions(Set keys) + { + long currentTime = System.currentTimeMillis(); + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isValid()) + { + continue; + } + + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + if (currentTime >= entry.deadline) + { + entry.setException(new ConnectException()); + try + { + key.channel().close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + key.cancel(); + } + } + } + } + + private void newSession(SocketChannel ch, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + MultiThreadSocketSessionImpl session = + new MultiThreadSocketSessionImpl(this, nextProcessor(), getListeners(), + config, ch, handler, ch.socket().getRemoteSocketAddress()); + + //new interface +// SocketSessionImpl session = new SocketSessionImpl( +// this, nextProcessor(), getListeners(), +// config, ch, handler, ch.socket().getRemoteSocketAddress() ); + try + { + getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getThreadModel().buildFilterChain(session.getFilterChain()); + } + catch (Throwable e) + { + throw (IOException) new IOException("Failed to create a session.").initCause(e); + } + + // Set the ConnectFuture of the specified session, which will be + // removed and notified by AbstractIoFilterChain eventually. + session.setAttribute( AbstractIoFilterChain.CONNECT_FUTURE, connectFuture ); + + // Forward the remaining process to the SocketIoProcessor. + session.getIoProcessor().addNew(session); + } + + private MultiThreadSocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + private class Worker implements Runnable + { + private long lastActive = System.currentTimeMillis(); + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketConnector.this.threadName); + + for (; ;) + { + try + { + int nKeys = selector.select(1000); + + registerNew(); + + if (nKeys > 0) + { + processSessions(selector.selectedKeys()); + } + + processTimedOutSessions(selector.keys()); + + if (selector.keys().isEmpty()) + { + if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) + { + synchronized (lock) + { + if (selector.keys().isEmpty() && + connectQueue.isEmpty()) + { + worker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + break; + } + } + } + } + else + { + lastActive = System.currentTimeMillis(); + } + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + } + } + + private class ConnectionRequest extends DefaultConnectFuture + { + private final SocketChannel channel; + private final long deadline; + private final IoHandler handler; + private final IoServiceConfig config; + + private ConnectionRequest(SocketChannel channel, IoHandler handler, IoServiceConfig config) + { + this.channel = channel; + long timeout; + if (config instanceof IoConnectorConfig) + { + timeout = ((IoConnectorConfig) config).getConnectTimeoutMillis(); + } + else + { + timeout = ((IoConnectorConfig) getDefaultConfig()).getConnectTimeoutMillis(); + } + this.deadline = System.currentTimeMillis() + timeout; + this.handler = handler; + this.config = config; + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java new file mode 100644 index 0000000000..67b8c8d820 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import java.io.IOException; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.util.Queue; + +/** + * An {@link IoFilterChain} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + */ +class MultiThreadSocketFilterChain extends AbstractIoFilterChain { + + MultiThreadSocketFilterChain( IoSession parent ) + { + super( parent ); + } + + protected void doWrite( IoSession session, WriteRequest writeRequest ) + { + MultiThreadSocketSessionImpl s = (MultiThreadSocketSessionImpl) session; + Queue writeRequestQueue = s.getWriteRequestQueue(); + + // SocketIoProcessor.doFlush() will reset it after write is finished + // because the buffer will be passed with messageSent event. + ( ( ByteBuffer ) writeRequest.getMessage() ).mark(); + synchronized( writeRequestQueue ) + { + writeRequestQueue.push( writeRequest ); + if( writeRequestQueue.size() == 1 && session.getTrafficMask().isWritable() ) + { + // Notify SocketIoProcessor only when writeRequestQueue was empty. + s.getIoProcessor().flush( s ); + } + } + } + + protected void doClose( IoSession session ) throws IOException + { + MultiThreadSocketSessionImpl s = (MultiThreadSocketSessionImpl) session; + s.getIoProcessor().remove( s ); + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java new file mode 100644 index 0000000000..c23ad8686f --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java @@ -0,0 +1,1026 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.WriteTimeoutException; +import org.apache.mina.util.IdentityHashSet; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.Queue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Performs all I/O operations for sockets which is connected or bound. This class is used by MINA internally. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $, + */ +class MultiThreadSocketIoProcessor extends SocketIoProcessor +{ + Logger _logger = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class); + Logger _loggerRead = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class + ".Reader"); + Logger _loggerWrite = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class + ".Writer"); + + private static final long SELECTOR_TIMEOUT = 1000L; + + private int MAX_READ_BYTES_PER_SESSION = 524288; //512K + private int MAX_FLUSH_BYTES_PER_SESSION = 524288; //512K + + private final Object readLock = new Object(); + private final Object writeLock = new Object(); + + private final String threadName; + private final Executor executor; + + private ReentrantLock trafficMaskUpdateLock = new ReentrantLock(); + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private volatile Selector selector, writeSelector; + + private final Queue newSessions = new Queue(); + private final Queue removingSessions = new Queue(); + private final BlockingQueue flushingSessions = new LinkedBlockingQueue(); + private final IdentityHashSet flushingSessionsSet = new IdentityHashSet(); + + private final Queue trafficControllingSessions = new Queue(); + + private ReadWorker readWorker; + private WriteWorker writeWorker; + private long lastIdleReadCheckTime = System.currentTimeMillis(); + private long lastIdleWriteCheckTime = System.currentTimeMillis(); + + MultiThreadSocketIoProcessor(String threadName, Executor executor) + { + super(threadName, executor); + this.threadName = threadName; + this.executor = executor; + } + + void addNew(SocketSessionImpl session) throws IOException + { + synchronized (newSessions) + { + newSessions.push(session); + } + + startupWorker(); + + selector.wakeup(); + writeSelector.wakeup(); + } + + void remove(SocketSessionImpl session) throws IOException + { + scheduleRemove(session); + startupWorker(); + selector.wakeup(); + } + + private void startupWorker() throws IOException + { + synchronized (readLock) + { + if (readWorker == null) + { + selector = Selector.open(); + readWorker = new ReadWorker(); + executor.execute(new NamePreservingRunnable(readWorker)); + } + } + + synchronized (writeLock) + { + if (writeWorker == null) + { + writeSelector = Selector.open(); + writeWorker = new WriteWorker(); + executor.execute(new NamePreservingRunnable(writeWorker)); + } + } + + } + + void flush(SocketSessionImpl session) + { + scheduleFlush(session); + Selector selector = this.writeSelector; + + if (selector != null) + { + selector.wakeup(); + } + } + + void updateTrafficMask(SocketSessionImpl session) + { + scheduleTrafficControl(session); + Selector selector = this.selector; + if (selector != null) + { + selector.wakeup(); + } + } + + private void scheduleRemove(SocketSessionImpl session) + { + synchronized (removingSessions) + { + removingSessions.push(session); + } + } + + private void scheduleFlush(SocketSessionImpl session) + { + synchronized (flushingSessionsSet) + { + //if flushingSessions grows to contain Integer.MAX_VALUE sessions + // then this will fail. + if (flushingSessionsSet.add(session)) + { + flushingSessions.offer(session); + } + } + } + + private void scheduleTrafficControl(SocketSessionImpl session) + { + synchronized (trafficControllingSessions) + { + trafficControllingSessions.push(session); + } + } + + private void doAddNewReader() throws InterruptedException + { + if (newSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (newSessions) + { + session = (MultiThreadSocketSessionImpl) newSessions.peek(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + + + try + { + + ch.configureBlocking(false); + session.setSelectionKey(ch.register(selector, + SelectionKey.OP_READ, + session)); + + //System.out.println("ReadDebug:"+"Awaiting Registration"); + session.awaitRegistration(); + sessionCreated(session); + } + catch (IOException e) + { + // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute + // and call ConnectFuture.setException(). + session.getFilterChain().fireExceptionCaught(session, e); + } + } + } + + + private void doAddNewWrite() throws InterruptedException + { + if (newSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (newSessions) + { + session = (MultiThreadSocketSessionImpl) newSessions.peek(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + + try + { + ch.configureBlocking(false); + synchronized (flushingSessionsSet) + { + flushingSessionsSet.add(session); + } + + session.setWriteSelectionKey(ch.register(writeSelector, + SelectionKey.OP_WRITE, + session)); + + //System.out.println("WriteDebug:"+"Awaiting Registration"); + session.awaitRegistration(); + sessionCreated(session); + } + catch (IOException e) + { + + // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute + // and call ConnectFuture.setException(). + session.getFilterChain().fireExceptionCaught(session, e); + } + } + } + + + private void sessionCreated(SocketSessionImpl sessionParam) throws InterruptedException + { + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) sessionParam; + synchronized (newSessions) + { + if (!session.created()) + { + _logger.debug("Popping new session"); + newSessions.pop(); + + // AbstractIoFilterChain.CONNECT_FUTURE is cleared inside here + // in AbstractIoFilterChain.fireSessionOpened(). + session.getServiceListeners().fireSessionCreated(session); + + session.doneCreation(); + } + } + } + + private void doRemove() + { + if (removingSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (removingSessions) + { + session = (MultiThreadSocketSessionImpl) removingSessions.pop(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + SelectionKey key = session.getReadSelectionKey(); + SelectionKey writeKey = session.getWriteSelectionKey(); + + // Retry later if session is not yet fully initialized. + // (In case that Session.close() is called before addSession() is processed) + if (key == null || writeKey == null) + { + scheduleRemove(session); + break; + } + // skip if channel is already closed + if (!key.isValid() || !writeKey.isValid()) + { + continue; + } + + try + { + //System.out.println("ReadDebug:"+"Removing Session: " + System.identityHashCode(session)); + synchronized (readLock) + { + key.cancel(); + } + synchronized (writeLock) + { + writeKey.cancel(); + } + ch.close(); + } + catch (IOException e) + { + session.getFilterChain().fireExceptionCaught(session, e); + } + finally + { + releaseWriteBuffers(session); + session.getServiceListeners().fireSessionDestroyed(session); + } + } + } + + private void processRead(Set selectedKeys) + { + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) key.attachment(); + + synchronized (readLock) + { + if (key.isValid() && key.isReadable() && session.getTrafficMask().isReadable()) + { + read(session); + } + } + + } + + selectedKeys.clear(); + } + + private void processWrite(Set selectedKeys) + { + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + + synchronized (writeLock) + { + if (key.isValid() && key.isWritable() && session.getTrafficMask().isWritable()) + { + + // Clear OP_WRITE + key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); + + synchronized (flushingSessionsSet) + { + flushingSessions.offer(session); + } + } + } + } + + selectedKeys.clear(); + } + + private void read(SocketSessionImpl session) + { + + //if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Starting read for Session:" + System.identityHashCode(session)); + } + + int totalReadBytes = 0; + + while (totalReadBytes <= MAX_READ_BYTES_PER_SESSION) + { + ByteBuffer buf = ByteBuffer.allocate(session.getReadBufferSize()); + SocketChannel ch = session.getChannel(); + + try + { + buf.clear(); + + int readBytes = 0; + int ret; + + try + { + while ((ret = ch.read(buf.buf())) > 0) + { + readBytes += ret; + totalReadBytes += ret; + } + } + finally + { + buf.flip(); + } + + + if (readBytes > 0) + { + session.increaseReadBytes(readBytes); + + session.getFilterChain().fireMessageReceived(session, buf); + buf = null; + } + + if (ret <= 0) + { + if (ret == 0) + { + if (readBytes == session.getReadBufferSize()) + { + continue; + } + } + else + { + scheduleRemove(session); + } + + break; + } + } + catch (Throwable e) + { + if (e instanceof IOException) + { + scheduleRemove(session); + } + session.getFilterChain().fireExceptionCaught(session, e); + + //Stop Reading this session. + return; + } + finally + { + if (buf != null) + { + buf.release(); + } + } + }//for + + // if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Read for Session:" + System.identityHashCode(session) + " got: " + totalReadBytes); + } + } + + + private void notifyReadIdleness() + { + // process idle sessions + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastIdleReadCheckTime) >= 1000) + { + lastIdleReadCheckTime = currentTime; + Set keys = selector.keys(); + if (keys != null) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + notifyReadIdleness(session, currentTime); + } + } + } + } + + private void notifyWriteIdleness() + { + // process idle sessions + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastIdleWriteCheckTime) >= 1000) + { + lastIdleWriteCheckTime = currentTime; + Set keys = writeSelector.keys(); + if (keys != null) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + notifyWriteIdleness(session, currentTime); + } + } + } + } + + private void notifyReadIdleness(SocketSessionImpl session, long currentTime) + { + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, + Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.READER_IDLE), + IdleStatus.READER_IDLE, + Math.max(session.getLastReadTime(), session.getLastIdleTime(IdleStatus.READER_IDLE))); + + notifyWriteTimeout(session, currentTime, session + .getWriteTimeoutInMillis(), session.getLastWriteTime()); + } + + private void notifyWriteIdleness(SocketSessionImpl session, long currentTime) + { + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, + Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.WRITER_IDLE), + IdleStatus.WRITER_IDLE, + Math.max(session.getLastWriteTime(), session.getLastIdleTime(IdleStatus.WRITER_IDLE))); + + notifyWriteTimeout(session, currentTime, session + .getWriteTimeoutInMillis(), session.getLastWriteTime()); + } + + private void notifyIdleness0(SocketSessionImpl session, long currentTime, + long idleTime, IdleStatus status, + long lastIoTime) + { + if (idleTime > 0 && lastIoTime != 0 + && (currentTime - lastIoTime) >= idleTime) + { + session.increaseIdleCount(status); + session.getFilterChain().fireSessionIdle(session, status); + } + } + + private void notifyWriteTimeout(SocketSessionImpl session, + long currentTime, + long writeTimeout, long lastIoTime) + { + + MultiThreadSocketSessionImpl sesh = (MultiThreadSocketSessionImpl) session; + SelectionKey key = sesh.getWriteSelectionKey(); + + synchronized (writeLock) + { + if (writeTimeout > 0 + && (currentTime - lastIoTime) >= writeTimeout + && key != null && key.isValid() + && (key.interestOps() & SelectionKey.OP_WRITE) != 0) + { + session.getFilterChain().fireExceptionCaught(session, new WriteTimeoutException()); + } + } + } + + private SocketSessionImpl getNextFlushingSession() + { + return (SocketSessionImpl) flushingSessions.poll(); + } + + private void releaseSession(SocketSessionImpl session) + { + synchronized (session.getWriteRequestQueue()) + { + synchronized (flushingSessionsSet) + { + if (session.getScheduledWriteRequests() > 0) + { + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Reflush" + System.identityHashCode(session)); + } + flushingSessions.offer(session); + } + else + { + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Releasing session " + System.identityHashCode(session)); + } + flushingSessionsSet.remove(session); + } + } + } + } + + private void releaseWriteBuffers(SocketSessionImpl session) + { + Queue writeRequestQueue = session.getWriteRequestQueue(); + WriteRequest req; + + //Should this be synchronized? + synchronized (writeRequestQueue) + { + while ((req = (WriteRequest) writeRequestQueue.pop()) != null) + { + try + { + ((ByteBuffer) req.getMessage()).release(); + } + catch (IllegalStateException e) + { + session.getFilterChain().fireExceptionCaught(session, e); + } + finally + { + req.getFuture().setWritten(false); + } + } + } + } + + private void doFlush() + { + MultiThreadSocketSessionImpl session; + + while ((session = (MultiThreadSocketSessionImpl) getNextFlushingSession()) != null) + { + if (!session.isConnected()) + { + releaseWriteBuffers(session); + releaseSession(session); + continue; + } + + SelectionKey key = session.getWriteSelectionKey(); + // Retry later if session is not yet fully initialized. + // (In case that Session.write() is called before addSession() is processed) + if (key == null) + { + scheduleFlush(session); + releaseSession(session); + continue; + } + // skip if channel is already closed + if (!key.isValid()) + { + releaseSession(session); + continue; + } + + try + { + if (doFlush(session)) + { + releaseSession(session); + } + } + catch (IOException e) + { + releaseSession(session); + scheduleRemove(session); + session.getFilterChain().fireExceptionCaught(session, e); + } + + } + + } + + private boolean doFlush(SocketSessionImpl sessionParam) throws IOException + { + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) sessionParam; + // Clear OP_WRITE + SelectionKey key = session.getWriteSelectionKey(); + synchronized (writeLock) + { + key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); + } + SocketChannel ch = session.getChannel(); + Queue writeRequestQueue = session.getWriteRequestQueue(); + + long totalFlushedBytes = 0; + while (true) + { + WriteRequest req; + + synchronized (writeRequestQueue) + { + req = (WriteRequest) writeRequestQueue.first(); + } + + if (req == null) + { + break; + } + + ByteBuffer buf = (ByteBuffer) req.getMessage(); + if (buf.remaining() == 0) + { + synchronized (writeRequestQueue) + { + writeRequestQueue.pop(); + } + + session.increaseWrittenMessages(); + + buf.reset(); + session.getFilterChain().fireMessageSent(session, req); + continue; + } + + + int writtenBytes = 0; + + // Reported as DIRMINA-362 + //note: todo: fixme: Not sure it is important but if we see NoyYetConnected exceptions or 100% CPU in the kernel then this is it. + if (key.isWritable()) + { + writtenBytes = ch.write(buf.buf()); + totalFlushedBytes += writtenBytes; + } + + if (writtenBytes > 0) + { + session.increaseWrittenBytes(writtenBytes); + } + + if (buf.hasRemaining() || (totalFlushedBytes <= MAX_FLUSH_BYTES_PER_SESSION)) + { + // Kernel buffer is full + synchronized (writeLock) + { + key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); + } + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Written BF: " + (session.getWrittenBytes() - totalFlushedBytes) + " bytes"); + } + return false; + } + } + + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Written : " + (session.getWrittenBytes() - totalFlushedBytes) + " bytes"); + } + return true; + } + + private void doUpdateTrafficMask() + { + if (trafficControllingSessions.isEmpty() || trafficMaskUpdateLock.isLocked()) + { + return; + } + + // Synchronize over entire operation as this method should be called + // from both read and write thread and we don't want the order of the + // updates to get changed. + trafficMaskUpdateLock.lock(); + try + { + for (; ;) + { + MultiThreadSocketSessionImpl session; + + session = (MultiThreadSocketSessionImpl) trafficControllingSessions.pop(); + + if (session == null) + { + break; + } + + SelectionKey key = session.getReadSelectionKey(); + // Retry later if session is not yet fully initialized. + // (In case that Session.suspend??() or session.resume??() is + // called before addSession() is processed) + if (key == null) + { + scheduleTrafficControl(session); + break; + } + // skip if channel is already closed + if (!key.isValid()) + { + continue; + } + + // The normal is OP_READ and, if there are write requests in the + // session's write queue, set OP_WRITE to trigger flushing. + + //Sset to Read and Write if there is nothing then the cost + // is one loop through the flusher. + int ops = SelectionKey.OP_READ; + + // Now mask the preferred ops with the mask of the current session + int mask = session.getTrafficMask().getInterestOps(); + synchronized (readLock) + { + key.interestOps(ops & mask); + } + //Change key to the WriteSelection Key + key = session.getWriteSelectionKey(); + if (key != null && key.isValid()) + { + Queue writeRequestQueue = session.getWriteRequestQueue(); + synchronized (writeRequestQueue) + { + if (!writeRequestQueue.isEmpty()) + { + ops = SelectionKey.OP_WRITE; + synchronized (writeLock) + { + key.interestOps(ops & mask); + } + } + } + } + } + } + finally + { + trafficMaskUpdateLock.unlock(); + } + + } + + private class WriteWorker implements Runnable + { + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketIoProcessor.this.threadName + "Writer"); + + //System.out.println("WriteDebug:"+"Startup"); + for (; ;) + { + try + { + int nKeys = writeSelector.select(SELECTOR_TIMEOUT); + + doAddNewWrite(); + doUpdateTrafficMask(); + + if (nKeys > 0) + { + //System.out.println("WriteDebug:"+nKeys + " keys from writeselector"); + processWrite(writeSelector.selectedKeys()); + } + else + { + //System.out.println("WriteDebug:"+"No keys from writeselector"); + } + + doRemove(); + notifyWriteIdleness(); + + if (flushingSessionsSet.size() > 0) + { + doFlush(); + } + + if (writeSelector.keys().isEmpty()) + { + synchronized (writeLock) + { + + if (writeSelector.keys().isEmpty() && newSessions.isEmpty()) + { + writeWorker = null; + try + { + writeSelector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + writeSelector = null; + } + + break; + } + } + } + + } + catch (Throwable t) + { + ExceptionMonitor.getInstance().exceptionCaught(t); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + //System.out.println("WriteDebug:"+"Shutdown"); + } + + } + + private class ReadWorker implements Runnable + { + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketIoProcessor.this.threadName + "Reader"); + + //System.out.println("ReadDebug:"+"Startup"); + for (; ;) + { + try + { + int nKeys = selector.select(SELECTOR_TIMEOUT); + + doAddNewReader(); + doUpdateTrafficMask(); + + if (nKeys > 0) + { + //System.out.println("ReadDebug:"+nKeys + " keys from selector"); + + processRead(selector.selectedKeys()); + } + else + { + //System.out.println("ReadDebug:"+"No keys from selector"); + } + + + doRemove(); + notifyReadIdleness(); + + if (selector.keys().isEmpty()) + { + + synchronized (readLock) + { + if (selector.keys().isEmpty() && newSessions.isEmpty()) + { + readWorker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + + break; + } + } + } + } + catch (Throwable t) + { + ExceptionMonitor.getInstance().exceptionCaught(t); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + //System.out.println("ReadDebug:"+"Shutdown"); + } + + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java new file mode 100644 index 0000000000..043d4800b6 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.support.BaseIoSessionConfig; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +/** + * An {@link IoConnectorConfig} for {@link SocketConnector}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketSessionConfigImpl extends org.apache.mina.transport.socket.nio.SocketSessionConfigImpl +{ + private static boolean SET_RECEIVE_BUFFER_SIZE_AVAILABLE = false; + private static boolean SET_SEND_BUFFER_SIZE_AVAILABLE = false; + private static boolean GET_TRAFFIC_CLASS_AVAILABLE = false; + private static boolean SET_TRAFFIC_CLASS_AVAILABLE = false; + + private static boolean DEFAULT_REUSE_ADDRESS; + private static int DEFAULT_RECEIVE_BUFFER_SIZE; + private static int DEFAULT_SEND_BUFFER_SIZE; + private static int DEFAULT_TRAFFIC_CLASS; + private static boolean DEFAULT_KEEP_ALIVE; + private static boolean DEFAULT_OOB_INLINE; + private static int DEFAULT_SO_LINGER; + private static boolean DEFAULT_TCP_NO_DELAY; + + static + { + initialize(); + } + + private static void initialize() + { + Socket socket = null; + + socket = new Socket(); + + try + { + DEFAULT_REUSE_ADDRESS = socket.getReuseAddress(); + DEFAULT_RECEIVE_BUFFER_SIZE = socket.getReceiveBufferSize(); + DEFAULT_SEND_BUFFER_SIZE = socket.getSendBufferSize(); + DEFAULT_KEEP_ALIVE = socket.getKeepAlive(); + DEFAULT_OOB_INLINE = socket.getOOBInline(); + DEFAULT_SO_LINGER = socket.getSoLinger(); + DEFAULT_TCP_NO_DELAY = socket.getTcpNoDelay(); + + // Check if setReceiveBufferSize is supported. + try + { + socket.setReceiveBufferSize(DEFAULT_RECEIVE_BUFFER_SIZE); + SET_RECEIVE_BUFFER_SIZE_AVAILABLE = true; + } + catch( SocketException e ) + { + SET_RECEIVE_BUFFER_SIZE_AVAILABLE = false; + } + + // Check if setSendBufferSize is supported. + try + { + socket.setSendBufferSize(DEFAULT_SEND_BUFFER_SIZE); + SET_SEND_BUFFER_SIZE_AVAILABLE = true; + } + catch( SocketException e ) + { + SET_SEND_BUFFER_SIZE_AVAILABLE = false; + } + + // Check if getTrafficClass is supported. + try + { + DEFAULT_TRAFFIC_CLASS = socket.getTrafficClass(); + GET_TRAFFIC_CLASS_AVAILABLE = true; + } + catch( SocketException e ) + { + GET_TRAFFIC_CLASS_AVAILABLE = false; + DEFAULT_TRAFFIC_CLASS = 0; + } + } + catch( SocketException e ) + { + throw new ExceptionInInitializerError(e); + } + finally + { + if( socket != null ) + { + try + { + socket.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + public static boolean isSetReceiveBufferSizeAvailable() { + return SET_RECEIVE_BUFFER_SIZE_AVAILABLE; + } + + public static boolean isSetSendBufferSizeAvailable() { + return SET_SEND_BUFFER_SIZE_AVAILABLE; + } + + public static boolean isGetTrafficClassAvailable() { + return GET_TRAFFIC_CLASS_AVAILABLE; + } + + public static boolean isSetTrafficClassAvailable() { + return SET_TRAFFIC_CLASS_AVAILABLE; + } + + private boolean reuseAddress = DEFAULT_REUSE_ADDRESS; + private int receiveBufferSize = DEFAULT_RECEIVE_BUFFER_SIZE; + private int sendBufferSize = DEFAULT_SEND_BUFFER_SIZE; + private int trafficClass = DEFAULT_TRAFFIC_CLASS; + private boolean keepAlive = DEFAULT_KEEP_ALIVE; + private boolean oobInline = DEFAULT_OOB_INLINE; + private int soLinger = DEFAULT_SO_LINGER; + private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY; + + /** + * Creates a new instance. + */ + MultiThreadSocketSessionConfigImpl() + { + } + + public boolean isReuseAddress() + { + return reuseAddress; + } + + public void setReuseAddress( boolean reuseAddress ) + { + this.reuseAddress = reuseAddress; + } + + public int getReceiveBufferSize() + { + return receiveBufferSize; + } + + public void setReceiveBufferSize( int receiveBufferSize ) + { + this.receiveBufferSize = receiveBufferSize; + } + + public int getSendBufferSize() + { + return sendBufferSize; + } + + public void setSendBufferSize( int sendBufferSize ) + { + this.sendBufferSize = sendBufferSize; + } + + public int getTrafficClass() + { + return trafficClass; + } + + public void setTrafficClass( int trafficClass ) + { + this.trafficClass = trafficClass; + } + + public boolean isKeepAlive() + { + return keepAlive; + } + + public void setKeepAlive( boolean keepAlive ) + { + this.keepAlive = keepAlive; + } + + public boolean isOobInline() + { + return oobInline; + } + + public void setOobInline( boolean oobInline ) + { + this.oobInline = oobInline; + } + + public int getSoLinger() + { + return soLinger; + } + + public void setSoLinger( int soLinger ) + { + this.soLinger = soLinger; + } + + public boolean isTcpNoDelay() + { + return tcpNoDelay; + } + + public void setTcpNoDelay( boolean tcpNoDelay ) + { + this.tcpNoDelay = tcpNoDelay; + } + + +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java new file mode 100644 index 0000000000..be4a2d289d --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java @@ -0,0 +1,488 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.socket.nio; + +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoService; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.RuntimeIOException; +import org.apache.mina.common.TransportType; +import org.apache.mina.common.support.BaseIoSessionConfig; +import org.apache.mina.common.support.IoServiceListenerSupport; +import org.apache.mina.util.Queue; + +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An {@link IoSession} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +class MultiThreadSocketSessionImpl extends SocketSessionImpl +{ + private final IoService manager; + private final IoServiceConfig serviceConfig; + private final SocketSessionConfig config = new SessionConfigImpl(); + private final MultiThreadSocketIoProcessor ioProcessor; + private final MultiThreadSocketFilterChain filterChain; + private final SocketChannel ch; + private final Queue writeRequestQueue; + private final IoHandler handler; + private final SocketAddress remoteAddress; + private final SocketAddress localAddress; + private final SocketAddress serviceAddress; + private final IoServiceListenerSupport serviceListeners; + private SelectionKey readKey, writeKey; + private int readBufferSize; + private CountDownLatch registeredReadyLatch = new CountDownLatch(2); + private AtomicBoolean created = new AtomicBoolean(false); + + /** + * Creates a new instance. + */ + MultiThreadSocketSessionImpl( IoService manager, + SocketIoProcessor ioProcessor, + IoServiceListenerSupport listeners, + IoServiceConfig serviceConfig, + SocketChannel ch, + IoHandler defaultHandler, + SocketAddress serviceAddress ) + { + super(manager, ioProcessor, listeners, serviceConfig, ch,defaultHandler,serviceAddress); + this.manager = manager; + this.serviceListeners = listeners; + this.ioProcessor = (MultiThreadSocketIoProcessor) ioProcessor; + this.filterChain = new MultiThreadSocketFilterChain(this); + this.ch = ch; + this.writeRequestQueue = new Queue(); + this.handler = defaultHandler; + this.remoteAddress = ch.socket().getRemoteSocketAddress(); + this.localAddress = ch.socket().getLocalSocketAddress(); + this.serviceAddress = serviceAddress; + this.serviceConfig = serviceConfig; + + // Apply the initial session settings + IoSessionConfig sessionConfig = serviceConfig.getSessionConfig(); + if( sessionConfig instanceof SocketSessionConfig ) + { + SocketSessionConfig cfg = ( SocketSessionConfig ) sessionConfig; + this.config.setKeepAlive( cfg.isKeepAlive() ); + this.config.setOobInline( cfg.isOobInline() ); + this.config.setReceiveBufferSize( cfg.getReceiveBufferSize() ); + this.readBufferSize = cfg.getReceiveBufferSize(); + this.config.setReuseAddress( cfg.isReuseAddress() ); + this.config.setSendBufferSize( cfg.getSendBufferSize() ); + this.config.setSoLinger( cfg.getSoLinger() ); + this.config.setTcpNoDelay( cfg.isTcpNoDelay() ); + + if( this.config.getTrafficClass() != cfg.getTrafficClass() ) + { + this.config.setTrafficClass( cfg.getTrafficClass() ); + } + } + } + + void awaitRegistration() throws InterruptedException + { + registeredReadyLatch.countDown(); + + registeredReadyLatch.await(); + } + + boolean created() throws InterruptedException + { + return created.get(); + } + + void doneCreation() + { + created.getAndSet(true); + } + + public IoService getService() + { + return manager; + } + + public IoServiceConfig getServiceConfig() + { + return serviceConfig; + } + + public IoSessionConfig getConfig() + { + return config; + } + + SocketIoProcessor getIoProcessor() + { + return ioProcessor; + } + + public IoFilterChain getFilterChain() + { + return filterChain; + } + + SocketChannel getChannel() + { + return ch; + } + + IoServiceListenerSupport getServiceListeners() + { + return serviceListeners; + } + + SelectionKey getSelectionKey() + { + return readKey; + } + + SelectionKey getReadSelectionKey() + { + return readKey; + } + + SelectionKey getWriteSelectionKey() + { + return writeKey; + } + + void setSelectionKey(SelectionKey key) + { + this.readKey = key; + } + + void setWriteSelectionKey(SelectionKey key) + { + this.writeKey = key; + } + + public IoHandler getHandler() + { + return handler; + } + + protected void close0() + { + filterChain.fireFilterClose( this ); + } + + Queue getWriteRequestQueue() + { + return writeRequestQueue; + } + + /** + @return int Number of write scheduled write requests + @deprecated + */ + public int getScheduledWriteMessages() + { + return getScheduledWriteRequests(); + } + + public int getScheduledWriteRequests() + { + synchronized( writeRequestQueue ) + { + return writeRequestQueue.size(); + } + } + + public int getScheduledWriteBytes() + { + synchronized( writeRequestQueue ) + { + return writeRequestQueue.byteSize(); + } + } + + protected void write0( WriteRequest writeRequest ) + { + filterChain.fireFilterWrite( this, writeRequest ); + } + + public TransportType getTransportType() + { + return TransportType.SOCKET; + } + + public SocketAddress getRemoteAddress() + { + //This is what I had previously +// return ch.socket().getRemoteSocketAddress(); + return remoteAddress; + } + + public SocketAddress getLocalAddress() + { + //This is what I had previously +// return ch.socket().getLocalSocketAddress(); + return localAddress; + } + + public SocketAddress getServiceAddress() + { + return serviceAddress; + } + + protected void updateTrafficMask() + { + this.ioProcessor.updateTrafficMask( this ); + } + + int getReadBufferSize() + { + return readBufferSize; + } + + private class SessionConfigImpl extends BaseIoSessionConfig implements SocketSessionConfig + { + public boolean isKeepAlive() + { + try + { + return ch.socket().getKeepAlive(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setKeepAlive( boolean on ) + { + try + { + ch.socket().setKeepAlive( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isOobInline() + { + try + { + return ch.socket().getOOBInline(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setOobInline( boolean on ) + { + try + { + ch.socket().setOOBInline( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isReuseAddress() + { + try + { + return ch.socket().getReuseAddress(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setReuseAddress( boolean on ) + { + try + { + ch.socket().setReuseAddress( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public int getSoLinger() + { + try + { + return ch.socket().getSoLinger(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setSoLinger( int linger ) + { + try + { + if( linger < 0 ) + { + ch.socket().setSoLinger( false, 0 ); + } + else + { + ch.socket().setSoLinger( true, linger ); + } + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isTcpNoDelay() + { + try + { + return ch.socket().getTcpNoDelay(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setTcpNoDelay( boolean on ) + { + try + { + ch.socket().setTcpNoDelay( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public int getTrafficClass() + { + if( SocketSessionConfigImpl.isGetTrafficClassAvailable() ) + { + try + { + return ch.socket().getTrafficClass(); + } + catch( SocketException e ) + { + // Throw an exception only when setTrafficClass is also available. + if( SocketSessionConfigImpl.isSetTrafficClassAvailable() ) + { + throw new RuntimeIOException( e ); + } + } + } + + return 0; + } + + public void setTrafficClass( int tc ) + { + if( SocketSessionConfigImpl.isSetTrafficClassAvailable() ) + { + try + { + ch.socket().setTrafficClass( tc ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + + public int getSendBufferSize() + { + try + { + return ch.socket().getSendBufferSize(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setSendBufferSize( int size ) + { + if( SocketSessionConfigImpl.isSetSendBufferSizeAvailable() ) + { + try + { + ch.socket().setSendBufferSize( size ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + + public int getReceiveBufferSize() + { + try + { + return ch.socket().getReceiveBufferSize(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setReceiveBufferSize( int size ) + { + if( SocketSessionConfigImpl.isSetReceiveBufferSizeAvailable() ) + { + try + { + ch.socket().setReceiveBufferSize( size ); + MultiThreadSocketSessionImpl.this.readBufferSize = size; + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java b/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java new file mode 100644 index 0000000000..a23e546af5 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.mina.transport.vmpipe; + +import java.io.IOException; +import java.net.SocketAddress; + +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.common.support.BaseIoConnector; +import org.apache.mina.common.support.BaseIoConnectorConfig; +import org.apache.mina.common.support.BaseIoSessionConfig; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.transport.vmpipe.support.VmPipe; +import org.apache.mina.transport.vmpipe.support.VmPipeIdleStatusChecker; +import org.apache.mina.transport.vmpipe.support.VmPipeSessionImpl; +import org.apache.mina.util.AnonymousSocketAddress; + +/** + * Connects to {@link IoHandler}s which is bound on the specified + * {@link VmPipeAddress}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class QpidVmPipeConnector extends VmPipeConnector +{ + private static final IoSessionConfig CONFIG = new BaseIoSessionConfig() {}; + private final IoServiceConfig defaultConfig = new BaseIoConnectorConfig() + { + public IoSessionConfig getSessionConfig() + { + return CONFIG; + } + }; + + /** + * Creates a new instance. + */ + public QpidVmPipeConnector() + { + } + + public ConnectFuture connect( SocketAddress address, IoHandler handler, IoServiceConfig config ) + { + return connect( address, null, handler, config ); + } + + public ConnectFuture connect( SocketAddress address, SocketAddress localAddress, IoHandler handler, IoServiceConfig config ) + { + if( address == null ) + throw new NullPointerException( "address" ); + if( handler == null ) + throw new NullPointerException( "handler" ); + if( ! ( address instanceof VmPipeAddress ) ) + throw new IllegalArgumentException( + "address must be VmPipeAddress." ); + + if( config == null ) + { + config = getDefaultConfig(); + } + + VmPipe entry = ( VmPipe ) VmPipeAcceptor.boundHandlers.get( address ); + if( entry == null ) + { + return DefaultConnectFuture.newFailedFuture( + new IOException( "Endpoint unavailable: " + address ) ); + } + + DefaultConnectFuture future = new DefaultConnectFuture(); + VmPipeSessionImpl localSession = + new VmPipeSessionImpl( + this, + config, + getListeners(), + new Object(), // lock + new AnonymousSocketAddress(), + handler, + entry ); + + // initialize acceptor session + VmPipeSessionImpl remoteSession = localSession.getRemoteSession(); + try + { + IoFilterChain filterChain = remoteSession.getFilterChain(); + entry.getAcceptor().getFilterChainBuilder().buildFilterChain( filterChain ); + entry.getConfig().getFilterChainBuilder().buildFilterChain( filterChain ); + entry.getConfig().getThreadModel().buildFilterChain( filterChain ); + + // The following sentences don't throw any exceptions. + entry.getListeners().fireSessionCreated( remoteSession ); + VmPipeIdleStatusChecker.getInstance().addSession( remoteSession ); + } + catch( Throwable t ) + { + ExceptionMonitor.getInstance().exceptionCaught( t ); + remoteSession.close(); + } + + + // initialize connector session + try + { + IoFilterChain filterChain = localSession.getFilterChain(); + this.getFilterChainBuilder().buildFilterChain( filterChain ); + config.getFilterChainBuilder().buildFilterChain( filterChain ); + config.getThreadModel().buildFilterChain( filterChain ); + + // The following sentences don't throw any exceptions. + localSession.setAttribute( AbstractIoFilterChain.CONNECT_FUTURE, future ); + getListeners().fireSessionCreated( localSession ); + VmPipeIdleStatusChecker.getInstance().addSession( localSession); + } + catch( Throwable t ) + { + future.setException( t ); + } + + + + return future; + } + + public IoServiceConfig getDefaultConfig() + { + return defaultConfig; + } +}
\ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/AMQChannelException.java b/java/common/src/main/java/org/apache/qpid/AMQChannelException.java index 19f5035e33..f6f596da95 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQChannelException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQChannelException.java @@ -20,9 +20,7 @@ */ package org.apache.qpid; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQConstant; /** @@ -55,7 +53,7 @@ public class AMQChannelException extends AMQException public AMQFrame getCloseFrame(int channel) { - return ChannelCloseBody.createAMQFrame(channel, major, minor, _classId, _methodId, getErrorCode().getCode(), - new AMQShortString(getMessage())); + MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); + return new AMQFrame(channel, reg.createChannelCloseBody(getErrorCode().getCode(), new AMQShortString(getMessage()),_classId,_methodId)); } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java b/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java index ba9f69a05c..afd415b1eb 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java @@ -21,9 +21,7 @@ package org.apache.qpid; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQConstant; /** @@ -61,7 +59,12 @@ public class AMQConnectionException extends AMQException public AMQFrame getCloseFrame(int channel) { - return ConnectionCloseBody.createAMQFrame(channel, major, minor, _classId, _methodId, getErrorCode().getCode(), - new AMQShortString(getMessage())); + MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); + return new AMQFrame(channel, + reg.createConnectionCloseBody(getErrorCode().getCode(), + new AMQShortString(getMessage()), + _classId, + _methodId)); + } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java b/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java index b60f130652..6cdd57d6f2 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java @@ -1,40 +1,47 @@ -/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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;
-
-/**
- * AMQConnectionFailureException indicates that a connection to a broker could not be formed.
- *
- * <p/><table id="crc"><caption>CRC Card</caption>
- * <tr><th> Responsibilities <th> Collaborations
- * <tr><td> Represents failure to connect to a broker.
- * </table>
- *
- * @todo Not an AMQP exception as no status code.
- */
-public class AMQConnectionFailureException extends AMQException
-{
- public AMQConnectionFailureException(String message, Throwable cause)
- {
- super(null, message, cause);
- }
-}
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQConnectionFailureException indicates that a connection to a broker could not be formed. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents failure to connect to a broker. + * </table> + * + * @todo Not an AMQP exception as no status code. + */ +public class AMQConnectionFailureException extends AMQException +{ + public AMQConnectionFailureException(String message, Throwable cause) + { + super(null, message, cause); + } + + public AMQConnectionFailureException(AMQConstant errorCode, String message, Throwable cause) + { + super(errorCode, message, cause); + } +} diff --git a/java/common/src/main/java/org/apache/qpid/AMQException.java b/java/common/src/main/java/org/apache/qpid/AMQException.java index 6cbb98fd86..eda532b64e 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQException.java @@ -20,6 +20,8 @@ */ package org.apache.qpid; +import javax.management.JMException; + import org.apache.qpid.protocol.AMQConstant; /** @@ -38,7 +40,7 @@ public class AMQException extends Exception { /** Holds the AMQ error code constant associated with this exception. */ private AMQConstant _errorCode; - + /** * Creates an exception with an optional error code, optional message and optional underlying cause. * @@ -52,6 +54,28 @@ public class AMQException extends Exception _errorCode = errorCode; } + /* + * Deprecated constructors brought from M2.1 + */ + @Deprecated + public AMQException (String msg) + { + this(null, (msg == null) ? "" : msg); + } + + @Deprecated + public AMQException (AMQConstant errorCode, String msg) + { + this(errorCode, (msg == null) ? "" : msg, null); + } + + @Deprecated + public AMQException(String msg, Throwable cause) + { + this(null, msg, cause); + } + + /** * Gets the AMQ protocol exception code associated with this exception. * @@ -61,4 +85,9 @@ public class AMQException extends Exception { return _errorCode; } + + public boolean isHardError() + { + return true; + } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java b/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java index 15c8bea0a4..baca2a4773 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java @@ -36,4 +36,10 @@ public class AMQInvalidArgumentException extends AMQException { super(AMQConstant.INVALID_ARGUMENT, message, cause); } + + public boolean isHardError() + { + return false; + } + } diff --git a/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java b/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java index 1502c0efc5..01a569b693 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java @@ -45,4 +45,10 @@ public class AMQUndeliveredException extends AMQException { return _bounced; } + + public boolean isHardError() + { + return false; + } + } diff --git a/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java b/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java index 02ae3cb089..7eef73f337 100644 --- a/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java +++ b/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java @@ -22,6 +22,7 @@ package org.apache.qpid.codec; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; import org.apache.mina.filter.codec.CumulativeProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; @@ -48,6 +49,9 @@ import org.apache.qpid.framing.ProtocolInitiation; */ public class AMQDecoder extends CumulativeProtocolDecoder { + + private static final String BUFFER = AMQDecoder.class.getName() + ".Buffer"; + /** Holds the 'normal' AMQP data decoder. */ private AMQDataBlockDecoder _dataBlockDecoder = new AMQDataBlockDecoder(); @@ -56,6 +60,7 @@ public class AMQDecoder extends CumulativeProtocolDecoder /** Flag to indicate whether this decoder needs to handle protocol initiation. */ private boolean _expectProtocolInitiation; + private boolean firstDecode = true; /** * Creates a new AMQP decoder. @@ -81,14 +86,24 @@ public class AMQDecoder extends CumulativeProtocolDecoder */ protected boolean doDecode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception { - if (_expectProtocolInitiation) + + boolean decoded; + if (_expectProtocolInitiation + || (firstDecode + && (in.remaining() > 0) + && (in.get(in.position()) == (byte)'A'))) { - return doDecodePI(session, in, out); + decoded = doDecodePI(session, in, out); } else { - return doDecodeDataBlock(session, in, out); + decoded = doDecodeDataBlock(session, in, out); } + if(firstDecode && decoded) + { + firstDecode = false; + } + return decoded; } /** @@ -160,4 +175,97 @@ public class AMQDecoder extends CumulativeProtocolDecoder { _expectProtocolInitiation = expectProtocolInitiation; } + + + /** + * Cumulates content of <tt>in</tt> into internal buffer and forwards + * decoding request to {@link #doDecode(IoSession, ByteBuffer, ProtocolDecoderOutput)}. + * <tt>doDecode()</tt> is invoked repeatedly until it returns <tt>false</tt> + * and the cumulative buffer is compacted after decoding ends. + * + * @throws IllegalStateException if your <tt>doDecode()</tt> returned + * <tt>true</tt> not consuming the cumulative buffer. + */ + public void decode( IoSession session, ByteBuffer in, + ProtocolDecoderOutput out ) throws Exception + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + // if we have a session buffer, append data to that otherwise + // use the buffer read from the network directly + if( buf != null ) + { + buf.put( in ); + buf.flip(); + } + else + { + buf = in; + } + + for( ;; ) + { + int oldPos = buf.position(); + boolean decoded = doDecode( session, buf, out ); + if( decoded ) + { + if( buf.position() == oldPos ) + { + throw new IllegalStateException( + "doDecode() can't return true when buffer is not consumed." ); + } + + if( !buf.hasRemaining() ) + { + break; + } + } + else + { + break; + } + } + + // if there is any data left that cannot be decoded, we store + // it in a buffer in the session and next time this decoder is + // invoked the session buffer gets appended to + if ( buf.hasRemaining() ) + { + storeRemainingInSession( buf, session ); + } + else + { + removeSessionBuffer( session ); + } + } + + /** + * Releases the cumulative buffer used by the specified <tt>session</tt>. + * Please don't forget to call <tt>super.dispose( session )</tt> when + * you override this method. + */ + public void dispose( IoSession session ) throws Exception + { + removeSessionBuffer( session ); + } + + private void removeSessionBuffer(IoSession session) + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + if( buf != null ) + { + buf.release(); + session.removeAttribute( BUFFER ); + } + } + + private static final SimpleByteBufferAllocator SIMPLE_BYTE_BUFFER_ALLOCATOR = new SimpleByteBufferAllocator(); + + private void storeRemainingInSession(ByteBuffer buf, IoSession session) + { + ByteBuffer remainingBuf = SIMPLE_BYTE_BUFFER_ALLOCATOR.allocate( buf.remaining(), false ); + remainingBuf.setAutoExpand( true ); + remainingBuf.put( buf ); + session.setAttribute( BUFFER, remainingBuf ); + } + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java b/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java index c497717870..fe04155bb8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java @@ -1,39 +1,40 @@ -/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.framing;
-
-import org.apache.mina.common.ByteBuffer;
-
-public abstract class AMQBody
-{
- public abstract byte getFrameType();
-
- /**
- * Get the size of the body
- * @return unsigned short
- */
- protected abstract int getSize();
-
- protected abstract void writePayload(ByteBuffer buffer);
-
- protected abstract void populateFromBuffer(ByteBuffer buffer, long size)
- throws AMQFrameDecodingException, AMQProtocolVersionException;
-}
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.framing; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; + +public interface AMQBody +{ + public byte getFrameType(); + + /** + * Get the size of the body + * @return unsigned short + */ + public abstract int getSize(); + + public void writePayload(ByteBuffer buffer); + + void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession) throws AMQException; +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java b/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java index 11f505fd4b..02a46f3748 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java @@ -27,7 +27,7 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock private final int _channel; private final AMQBody _bodyFrame; - + public static final byte FRAME_END_BYTE = (byte) 0xCE; public AMQFrame(final int channel, final AMQBody bodyFrame) @@ -47,13 +47,19 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock return 1 + 2 + 4 + _bodyFrame.getSize() + 1; } + public static final int getFrameOverhead() + { + return 1 + 2 + 4 + 1; + } + + public void writePayload(ByteBuffer buffer) { buffer.put(_bodyFrame.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, _channel); EncodingUtils.writeUnsignedInteger(buffer, _bodyFrame.getSize()); _bodyFrame.writePayload(buffer); - buffer.put((byte) 0xCE); + buffer.put(FRAME_END_BYTE); } public final int getChannel() @@ -66,10 +72,54 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock return _bodyFrame; } - - public String toString() { return "Frame channelId: " + _channel + ", bodyFrame: " + String.valueOf(_bodyFrame); } + + public static void writeFrame(ByteBuffer buffer, final int channel, AMQBody body) + { + buffer.put(body.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body.getSize()); + body.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2) + { + buffer.put(body1.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); + body1.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); + body2.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2, AMQBody body3) + { + buffer.put(body1.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); + body1.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); + body2.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body3.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body3.getSize()); + body3.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java b/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java index 843b6a1e8c..2373edb478 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java @@ -38,4 +38,10 @@ public class AMQFrameDecodingException extends AMQException { super(errorCode, message, cause); } + + public AMQFrameDecodingException(AMQConstant errorCode, String message) + { + super(errorCode, message, null); + } + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java index 0982847aac..4763b22290 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java @@ -1,132 +1,83 @@ -/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.framing;
-
-import org.apache.mina.common.ByteBuffer;
-import org.apache.qpid.AMQChannelException;
-import org.apache.qpid.AMQConnectionException;
-import org.apache.qpid.protocol.AMQConstant;
-
-public abstract class AMQMethodBody extends AMQBody
-{
- public static final byte TYPE = 1;
-
- /** AMQP version */
- protected byte major;
- protected byte minor;
-
- public byte getMajor()
- {
- return major;
- }
-
- public byte getMinor()
- {
- return minor;
- }
-
- public AMQMethodBody(byte major, byte minor)
- {
- this.major = major;
- this.minor = minor;
- }
-
- /** unsigned short */
- protected abstract int getBodySize();
-
- /** @return unsigned short */
- protected abstract int getClazz();
-
- /** @return unsigned short */
- protected abstract int getMethod();
-
- protected abstract void writeMethodPayload(ByteBuffer buffer);
-
- public byte getFrameType()
- {
- return TYPE;
- }
-
- protected int getSize()
- {
- return 2 + 2 + getBodySize();
- }
-
- protected void writePayload(ByteBuffer buffer)
- {
- EncodingUtils.writeUnsignedShort(buffer, getClazz());
- EncodingUtils.writeUnsignedShort(buffer, getMethod());
- writeMethodPayload(buffer);
- }
-
- protected abstract void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException;
-
- protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException
- {
- populateMethodBodyFromBuffer(buffer);
- }
-
- public String toString()
- {
- StringBuffer buf = new StringBuffer(getClass().getName());
- buf.append("[ Class: ").append(getClazz());
- buf.append(" Method: ").append(getMethod()).append(']');
- return buf.toString();
- }
-
- /**
- * Creates an AMQChannelException for the corresponding body type (a channel exception should include the class and
- * method ids of the body it resulted from).
- */
-
- /**
- * Convenience Method to create a channel not found exception
- *
- * @param channelId The channel id that is not found
- *
- * @return new AMQChannelException
- */
- public AMQChannelException getChannelNotFoundException(int channelId)
- {
- return getChannelException(AMQConstant.NOT_FOUND, "Channel not found for id:" + channelId);
- }
-
- public AMQChannelException getChannelException(AMQConstant code, String message)
- {
- return new AMQChannelException(code, message, getClazz(), getMethod(), major, minor, null);
- }
-
- public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause)
- {
- return new AMQChannelException(code, message, getClazz(), getMethod(), major, minor, cause);
- }
-
- public AMQConnectionException getConnectionException(AMQConstant code, String message)
- {
- return new AMQConnectionException(code, message, getClazz(), getMethod(), major, minor, null);
- }
-
- public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause)
- {
- return new AMQConnectionException(code, message, getClazz(), getMethod(), major, minor, cause);
- }
-
-}
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.framing; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +public interface AMQMethodBody extends AMQBody +{ + public static final byte TYPE = 1; + + /** AMQP version */ + public byte getMajor(); + + public byte getMinor(); + + + + /** @return unsigned short */ + public int getClazz(); + + /** @return unsigned short */ + public int getMethod(); + + public void writeMethodPayload(ByteBuffer buffer); + + + public int getSize(); + + public void writePayload(ByteBuffer buffer); + + //public abstract void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException; + + //public void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException; + + public AMQFrame generateFrame(int channelId); + + public String toString(); + + + + /** + * Convenience Method to create a channel not found exception + * + * @param channelId The channel id that is not found + * + * @return new AMQChannelException + */ + public AMQChannelException getChannelNotFoundException(int channelId); + + public AMQChannelException getChannelException(AMQConstant code, String message); + + public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause); + + public AMQConnectionException getConnectionException(AMQConstant code, String message); + + + public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause); + + + public boolean execute(MethodDispatcher methodDispatcher, int channelId) throws AMQException; +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java index cf85bdab31..1a7022c11b 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java @@ -40,7 +40,6 @@ public class AMQMethodBodyFactory implements BodyFactory public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException { - return _protocolSession.getRegistry().get((short) in.getUnsignedShort(), (short) in.getUnsignedShort(), in, - bodySize); + return _protocolSession.getMethodRegistry().convertToBody(in, bodySize); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java new file mode 100644 index 0000000000..64af717342 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java @@ -0,0 +1,96 @@ +package org.apache.qpid.framing;
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQChannelException;
+import org.apache.qpid.AMQConnectionException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+
+public abstract class AMQMethodBodyImpl implements AMQMethodBody
+{
+ public static final byte TYPE = 1;
+
+ public AMQMethodBodyImpl()
+ {
+ }
+
+ public byte getFrameType()
+ {
+ return TYPE;
+ }
+
+
+ /** unsigned short */
+ abstract protected int getBodySize();
+
+
+ public AMQFrame generateFrame(int channelId)
+ {
+ return new AMQFrame(channelId, this);
+ }
+
+ /**
+ * Creates an AMQChannelException for the corresponding body type (a channel exception should include the class and
+ * method ids of the body it resulted from).
+ */
+
+ /**
+ * Convenience Method to create a channel not found exception
+ *
+ * @param channelId The channel id that is not found
+ *
+ * @return new AMQChannelException
+ */
+ public AMQChannelException getChannelNotFoundException(int channelId)
+ {
+ return getChannelException(AMQConstant.NOT_FOUND, "Channel not found for id:" + channelId);
+ }
+
+ public AMQChannelException getChannelException(AMQConstant code, String message)
+ {
+ return new AMQChannelException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), null);
+ }
+
+ public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause)
+ {
+ return new AMQChannelException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), cause);
+ }
+
+ public AMQConnectionException getConnectionException(AMQConstant code, String message)
+ {
+ return new AMQConnectionException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), null);
+ }
+
+ public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause)
+ {
+ return new AMQConnectionException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), cause);
+ }
+
+ public void handle(final int channelId, final AMQVersionAwareProtocolSession session) throws AMQException
+ {
+ session.methodFrameReceived(channelId, this);
+ }
+
+}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java index 359efe7eb7..0030742e94 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java @@ -26,6 +26,5 @@ import org.apache.mina.common.ByteBuffer; public abstract interface AMQMethodBodyInstanceFactory
{
- public AMQMethodBody newInstance(byte major, byte minor, ByteBuffer buffer, long size) throws AMQFrameDecodingException;
- public AMQMethodBody newInstance(byte major, byte minor, int clazzID, int methodID, ByteBuffer buffer, long size) throws AMQFrameDecodingException;
+ public AMQMethodBody newInstance(ByteBuffer buffer, long size) throws AMQFrameDecodingException;
}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java b/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java index ee63f2c83d..a747aaeda7 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java @@ -1,444 +1,721 @@ -/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.framing;
-
-import org.apache.mina.common.ByteBuffer;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.lang.ref.WeakReference;
-
-/**
- * A short string is a representation of an AMQ Short String
- * Short strings differ from the Java String class by being limited to on ASCII characters (0-127)
- * and thus can be held more effectively in a byte buffer.
- *
- */
-public final class AMQShortString implements CharSequence, Comparable<AMQShortString>
-{
-
- private static final ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>> _localInternMap =
- new ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>>()
- {
- protected Map<AMQShortString, WeakReference<AMQShortString>> initialValue()
- {
- return new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>();
- };
- };
-
- private static final Map<AMQShortString, WeakReference<AMQShortString>> _globalInternMap =
- new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>();
-
- private static final Logger _logger = LoggerFactory.getLogger(AMQShortString.class);
-
- private final ByteBuffer _data;
- private int _hashCode;
- private final int _length;
- private static final char[] EMPTY_CHAR_ARRAY = new char[0];
- private char[] chars;
- private String str;
-
- public AMQShortString(byte[] data)
- {
-
- _data = ByteBuffer.wrap(data);
- _length = data.length;
- }
-
- public AMQShortString(String data)
- {
- this((data == null) ? EMPTY_CHAR_ARRAY : data.toCharArray());
- if (data != null)
- {
- _hashCode = data.hashCode();
- }
- }
-
- public AMQShortString(char[] data)
- {
- if (data == null)
- {
- throw new NullPointerException("Cannot create AMQShortString with null char[]");
- }
-
- final int length = data.length;
- final byte[] stringBytes = new byte[length];
- for (int i = 0; i < length; i++)
- {
- stringBytes[i] = (byte) (0xFF & data[i]);
- }
-
- _data = ByteBuffer.wrap(stringBytes);
- _data.rewind();
- _length = length;
-
- }
-
- public AMQShortString(CharSequence charSequence)
- {
- final int length = charSequence.length();
- final byte[] stringBytes = new byte[length];
- int hash = 0;
- for (int i = 0; i < length; i++)
- {
- stringBytes[i] = ((byte) (0xFF & charSequence.charAt(i)));
- hash = (31 * hash) + stringBytes[i];
-
- }
-
- _data = ByteBuffer.wrap(stringBytes);
- _data.rewind();
- _hashCode = hash;
- _length = length;
-
- }
-
- private AMQShortString(ByteBuffer data)
- {
- _data = data;
- _length = data.limit();
-
- }
-
- /**
- * Get the length of the short string
- * @return length of the underlying byte array
- */
- public int length()
- {
- return _length;
- }
-
- public char charAt(int index)
- {
-
- return (char) _data.get(index);
-
- }
-
- public CharSequence subSequence(int start, int end)
- {
- return new CharSubSequence(start, end);
- }
-
- public int writeToByteArray(byte[] encoding, int pos)
- {
- final int size = length();
- encoding[pos++] = (byte) length();
- for (int i = 0; i < size; i++)
- {
- encoding[pos++] = _data.get(i);
- }
-
- return pos;
- }
-
- public static AMQShortString readFromByteArray(byte[] byteEncodedDestination, int pos)
- {
-
- final byte len = byteEncodedDestination[pos];
- if (len == 0)
- {
- return null;
- }
-
- ByteBuffer data = ByteBuffer.wrap(byteEncodedDestination, pos + 1, len).slice();
-
- return new AMQShortString(data);
- }
-
- public static AMQShortString readFromBuffer(ByteBuffer buffer)
- {
- final short length = buffer.getUnsigned();
- if (length == 0)
- {
- return null;
- }
- else
- {
- ByteBuffer data = buffer.slice();
- data.limit(length);
- data.rewind();
- buffer.skip(length);
-
- return new AMQShortString(data);
- }
- }
-
- public byte[] getBytes()
- {
-
- if (_data.buf().hasArray() && (_data.arrayOffset() == 0))
- {
- return _data.array();
- }
- else
- {
- final int size = length();
- byte[] b = new byte[size];
- ByteBuffer buf = _data.duplicate();
- buf.rewind();
- buf.get(b);
-
- return b;
- }
-
- }
-
- public void writeToBuffer(ByteBuffer buffer)
- {
-
- final int size = length();
- if (size != 0)
- {
-
- buffer.put((byte) size);
- if (_data.buf().hasArray())
- {
- buffer.put(_data.array(), _data.arrayOffset(), length());
- }
- else
- {
-
- for (int i = 0; i < size; i++)
- {
-
- buffer.put(_data.get(i));
- }
- }
- }
- else
- {
- // really writing out unsigned byte
- buffer.put((byte) 0);
- }
-
- }
-
- private final class CharSubSequence implements CharSequence
- {
- private final int _offset;
- private final int _end;
-
- public CharSubSequence(final int offset, final int end)
- {
- _offset = offset;
- _end = end;
- }
-
- public int length()
- {
- return _end - _offset;
- }
-
- public char charAt(int index)
- {
- return AMQShortString.this.charAt(index + _offset);
- }
-
- public CharSequence subSequence(int start, int end)
- {
- return new CharSubSequence(start + _offset, end + _offset);
- }
- }
-
- public char[] asChars()
- {
- if (chars == null)
- {
- final int size = length();
- chars = new char[size];
-
- for (int i = 0; i < size; i++)
- {
- chars[i] = (char) _data.get(i);
- }
- }
-
- return chars;
- }
-
- public String asString()
- {
- if (str == null)
- {
- str = new String(asChars());
- }
-
- return str;
- }
-
- public boolean equals(Object o)
- {
- if (o == null)
- {
- return false;
- }
-
- if (o == this)
- {
- return true;
- }
-
- if (o instanceof AMQShortString)
- {
-
- final AMQShortString otherString = (AMQShortString) o;
-
- if ((_hashCode != 0) && (otherString._hashCode != 0) && (_hashCode != otherString._hashCode))
- {
- return false;
- }
-
- return _data.equals(otherString._data);
-
- }
-
- return (o instanceof CharSequence) && equals((CharSequence) o);
-
- }
-
- public boolean equals(CharSequence s)
- {
- if (s == null)
- {
- return false;
- }
-
- if (s.length() != length())
- {
- return false;
- }
-
- for (int i = 0; i < length(); i++)
- {
- if (charAt(i) != s.charAt(i))
- {
- return false;
- }
- }
-
- return true;
- }
-
- public int hashCode()
- {
- int hash = _hashCode;
- if (hash == 0)
- {
- final int size = length();
-
- for (int i = 0; i < size; i++)
- {
- hash = (31 * hash) + _data.get(i);
- }
-
- _hashCode = hash;
- }
-
- return hash;
- }
-
- public void setDirty()
- {
- _hashCode = 0;
- }
-
- public String toString()
- {
- return asString();
- }
-
- public int compareTo(AMQShortString name)
- {
- if (name == null)
- {
- return 1;
- }
- else
- {
-
- if (name.length() < length())
- {
- return -name.compareTo(this);
- }
-
- for (int i = 0; i < length(); i++)
- {
- final byte d = _data.get(i);
- final byte n = name._data.get(i);
- if (d < n)
- {
- return -1;
- }
-
- if (d > n)
- {
- return 1;
- }
- }
-
- return (length() == name.length()) ? 0 : -1;
- }
- }
-
- public AMQShortString intern()
- {
-
- hashCode();
-
- Map<AMQShortString, WeakReference<AMQShortString>> localMap =
- _localInternMap.get();
-
- WeakReference<AMQShortString> ref = localMap.get(this);
- AMQShortString internString;
-
- if(ref != null)
- {
- internString = ref.get();
- if(internString != null)
- {
- return internString;
- }
- }
-
-
- synchronized(_globalInternMap)
- {
-
- ref = _globalInternMap.get(this);
- if((ref == null) || ((internString = ref.get()) == null))
- {
- internString = new AMQShortString(getBytes());
- ref = new WeakReference(internString);
- _globalInternMap.put(internString, ref);
- }
-
- }
- localMap.put(internString, ref);
- return internString;
-
- }
-}
+/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.framing; + +import org.apache.mina.common.ByteBuffer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.lang.ref.WeakReference; + +/** + * A short string is a representation of an AMQ Short String + * Short strings differ from the Java String class by being limited to on ASCII characters (0-127) + * and thus can be held more effectively in a byte buffer. + * + */ +public final class AMQShortString implements CharSequence, Comparable<AMQShortString> +{ + private static final byte MINUS = (byte)'-'; + private static final byte ZERO = (byte) '0'; + + + + private final class TokenizerImpl implements AMQShortStringTokenizer + { + private final byte _delim; + private int _count = -1; + private int _pos = 0; + + public TokenizerImpl(final byte delim) + { + _delim = delim; + } + + public int countTokens() + { + if(_count == -1) + { + _count = 1 + AMQShortString.this.occurences(_delim); + } + return _count; + } + + public AMQShortString nextToken() + { + if(_pos <= AMQShortString.this.length()) + { + int nextDelim = AMQShortString.this.indexOf(_delim, _pos); + if(nextDelim == -1) + { + nextDelim = AMQShortString.this.length(); + } + + AMQShortString nextToken = AMQShortString.this.substring(_pos, nextDelim++); + _pos = nextDelim; + return nextToken; + } + else + { + return null; + } + } + + public boolean hasMoreTokens() + { + return _pos <= AMQShortString.this.length(); + } + } + + private AMQShortString substring(final int from, final int to) + { + return new AMQShortString(_data, from+_offset, to+_offset); + } + + + private static final ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>> _localInternMap = + new ThreadLocal<Map<AMQShortString, WeakReference<AMQShortString>>>() + { + protected Map<AMQShortString, WeakReference<AMQShortString>> initialValue() + { + return new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>(); + }; + }; + + private static final Map<AMQShortString, WeakReference<AMQShortString>> _globalInternMap = + new WeakHashMap<AMQShortString, WeakReference<AMQShortString>>(); + + private static final Logger _logger = LoggerFactory.getLogger(AMQShortString.class); + + private final byte[] _data; + private final int _offset; + private int _hashCode; + private final int _length; + private static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + public static final AMQShortString EMPTY_STRING = new AMQShortString((String)null); + + public AMQShortString(byte[] data) + { + + _data = data.clone(); + _length = data.length; + _offset = 0; + } + + public AMQShortString(byte[] data, int pos) + { + final int size = data[pos++]; + final byte[] dataCopy = new byte[size]; + System.arraycopy(data,pos,dataCopy,0,size); + _length = size; + _data = dataCopy; + _offset = 0; + } + + public AMQShortString(String data) + { + this((data == null) ? EMPTY_CHAR_ARRAY : data.toCharArray()); + + } + + public AMQShortString(char[] data) + { + if (data == null) + { + throw new NullPointerException("Cannot create AMQShortString with null char[]"); + } + + final int length = data.length; + final byte[] stringBytes = new byte[length]; + int hash = 0; + for (int i = 0; i < length; i++) + { + stringBytes[i] = (byte) (0xFF & data[i]); + hash = (31 * hash) + stringBytes[i]; + } + _hashCode = hash; + _data = stringBytes; + + _length = length; + _offset = 0; + + } + + public AMQShortString(CharSequence charSequence) + { + final int length = charSequence.length(); + final byte[] stringBytes = new byte[length]; + int hash = 0; + for (int i = 0; i < length; i++) + { + stringBytes[i] = ((byte) (0xFF & charSequence.charAt(i))); + hash = (31 * hash) + stringBytes[i]; + + } + + _data = stringBytes; + _hashCode = hash; + _length = length; + _offset = 0; + + } + + private AMQShortString(ByteBuffer data, final int length) + { + if(data.isDirect() || data.isReadOnly()) + { + byte[] dataBytes = new byte[length]; + data.get(dataBytes); + _data = dataBytes; + _offset = 0; + } + else + { + + _data = data.array(); + _offset = data.arrayOffset() + data.position(); + data.skip(length); + + } + _length = length; + + } + + private AMQShortString(final byte[] data, final int from, final int to) + { + _offset = from; + _length = to - from; + _data = data; + } + + public AMQShortString shrink() + { + if(_data.length != _length) + { + byte[] dataBytes = new byte[_length]; + System.arraycopy(_data,_offset,dataBytes,0,_length); + return new AMQShortString(dataBytes,0,_length); + } + else + { + return this; + } + } + + + /** + * Get the length of the short string + * @return length of the underlying byte array + */ + public int length() + { + return _length; + } + + public char charAt(int index) + { + + return (char) _data[_offset + index]; + + } + + public CharSequence subSequence(int start, int end) + { + return new CharSubSequence(start, end); + } + + public int writeToByteArray(byte[] encoding, int pos) + { + final int size = length(); + encoding[pos++] = (byte) size; + System.arraycopy(_data,_offset,encoding,pos,size); + return pos+size; + } + + public static AMQShortString readFromByteArray(byte[] byteEncodedDestination, int pos) + { + + + final AMQShortString shortString = new AMQShortString(byteEncodedDestination, pos); + if(shortString.length() == 0) + { + return null; + } + else + { + return shortString; + } + } + + public static AMQShortString readFromBuffer(ByteBuffer buffer) + { + final short length = buffer.getUnsigned(); + if (length == 0) + { + return null; + } + else + { + + return new AMQShortString(buffer, length); + } + } + + public byte[] getBytes() + { + if(_offset == 0 && _length == _data.length) + { + return _data.clone(); + } + else + { + byte[] data = new byte[_length]; + System.arraycopy(_data,_offset,data,0,_length); + return data; + } + } + + public void writeToBuffer(ByteBuffer buffer) + { + + final int size = length(); + //buffer.setAutoExpand(true); + buffer.put((byte) size); + buffer.put(_data, _offset, size); + + } + + public boolean endsWith(String s) + { + return endsWith(new AMQShortString(s)); + } + + + public boolean endsWith(AMQShortString otherString) + { + + if (otherString.length() > length()) + { + return false; + } + + + int thisLength = length(); + int otherLength = otherString.length(); + + for (int i = 1; i <= otherLength; i++) + { + if (charAt(thisLength - i) != otherString.charAt(otherLength - i)) + { + return false; + } + } + return true; + } + + public boolean startsWith(String s) + { + return startsWith(new AMQShortString(s)); + } + + public boolean startsWith(AMQShortString otherString) + { + + if (otherString.length() > length()) + { + return false; + } + + for (int i = 0; i < otherString.length(); i++) + { + if (charAt(i) != otherString.charAt(i)) + { + return false; + } + } + + return true; + + } + + public boolean startsWith(CharSequence otherString) + { + if (otherString.length() > length()) + { + return false; + } + + for (int i = 0; i < otherString.length(); i++) + { + if (charAt(i) != otherString.charAt(i)) + { + return false; + } + } + + return true; + } + + + private final class CharSubSequence implements CharSequence + { + private final int _sequenceOffset; + private final int _end; + + public CharSubSequence(final int offset, final int end) + { + _sequenceOffset = offset; + _end = end; + } + + public int length() + { + return _end - _sequenceOffset; + } + + public char charAt(int index) + { + return AMQShortString.this.charAt(index + _sequenceOffset); + } + + public CharSequence subSequence(int start, int end) + { + return new CharSubSequence(start + _sequenceOffset, end + _sequenceOffset); + } + } + + public char[] asChars() + { + final int size = length(); + final char[] chars = new char[size]; + + for (int i = 0; i < size; i++) + { + chars[i] = (char) _data[i + _offset]; + } + + return chars; + } + + public String asString() + { + return new String(asChars()); + } + + public boolean equals(Object o) + { + + + if(o instanceof AMQShortString) + { + return equals((AMQShortString)o); + } + if(o instanceof CharSequence) + { + return equals((CharSequence)o); + } + + if (o == null) + { + return false; + } + + if (o == this) + { + return true; + } + + + return false; + + } + + public boolean equals(final AMQShortString otherString) + { + if (otherString == this) + { + return true; + } + + if (otherString == null) + { + return false; + } + + if ((_hashCode != 0) && (otherString._hashCode != 0) && (_hashCode != otherString._hashCode)) + { + return false; + } + + return (_offset == 0 && otherString._offset == 0 && _length == _data.length && otherString._length == otherString._data.length && Arrays.equals(_data,otherString._data)) + || Arrays.equals(getBytes(),otherString.getBytes()); + + } + + public boolean equals(CharSequence s) + { + if(s instanceof AMQShortString) + { + return equals((AMQShortString)s); + } + + if (s == null) + { + return false; + } + + if (s.length() != length()) + { + return false; + } + + for (int i = 0; i < length(); i++) + { + if (charAt(i) != s.charAt(i)) + { + return false; + } + } + + return true; + } + + public int hashCode() + { + int hash = _hashCode; + if (hash == 0) + { + final int size = length(); + + for (int i = 0; i < size; i++) + { + hash = (31 * hash) + _data[i+_offset]; + } + + _hashCode = hash; + } + + return hash; + } + + public void setDirty() + { + _hashCode = 0; + } + + public String toString() + { + return asString(); + } + + public int compareTo(AMQShortString name) + { + if (name == null) + { + return 1; + } + else + { + + if (name.length() < length()) + { + return -name.compareTo(this); + } + + for (int i = 0; i < length(); i++) + { + final byte d = _data[i+_offset]; + final byte n = name._data[i+name._offset]; + if (d < n) + { + return -1; + } + + if (d > n) + { + return 1; + } + } + + return (length() == name.length()) ? 0 : -1; + } + } + + + public AMQShortStringTokenizer tokenize(byte delim) + { + return new TokenizerImpl(delim); + } + + + public AMQShortString intern() + { + + hashCode(); + + Map<AMQShortString, WeakReference<AMQShortString>> localMap = + _localInternMap.get(); + + WeakReference<AMQShortString> ref = localMap.get(this); + AMQShortString internString; + + if(ref != null) + { + internString = ref.get(); + if(internString != null) + { + return internString; + } + } + + + synchronized(_globalInternMap) + { + + ref = _globalInternMap.get(this); + if((ref == null) || ((internString = ref.get()) == null)) + { + internString = shrink(); + ref = new WeakReference(internString); + _globalInternMap.put(internString, ref); + } + + } + localMap.put(internString, ref); + return internString; + + } + + private int occurences(final byte delim) + { + int count = 0; + final int end = _offset + _length; + for(int i = _offset ; i < end ; i++ ) + { + if(_data[i] == delim) + { + count++; + } + } + return count; + } + + private int indexOf(final byte val, final int pos) + { + + for(int i = pos; i < length(); i++) + { + if(_data[_offset+i] == val) + { + return i; + } + } + return -1; + } + + + public static AMQShortString join(final Collection<AMQShortString> terms, + final AMQShortString delim) + { + if(terms.size() == 0) + { + return EMPTY_STRING; + } + + int size = delim.length() * (terms.size() - 1); + for(AMQShortString term : terms) + { + size += term.length(); + } + + byte[] data = new byte[size]; + int pos = 0; + final byte[] delimData = delim._data; + final int delimOffset = delim._offset; + final int delimLength = delim._length; + + + for(AMQShortString term : terms) + { + + if(pos!=0) + { + System.arraycopy(delimData, delimOffset,data,pos, delimLength); + pos+=delimLength; + } + System.arraycopy(term._data,term._offset,data,pos,term._length); + pos+=term._length; + } + + + + return new AMQShortString(data,0,size); + } + + public int toIntValue() + { + int pos = _offset; + int val = 0; + + + boolean isNegative = (_data[pos] == MINUS); + if(isNegative) + { + pos++; + } + + final int end = _length + _offset; + + while(pos < end) + { + int digit = (int) (_data[pos++] - ZERO); + if((digit < 0) || (digit > 9)) + { + throw new NumberFormatException("\""+toString()+"\" is not a valid number"); + } + val = val * 10; + val += digit; + } + if(isNegative) + { + val = val * -1; + } + return val; + } + + public boolean contains(final byte b) + { + final int end = _length + _offset; + for(int i = _offset; i < end; i++) + { + if(_data[i] == b) + { + return true; + } + } + return false; //To change body of created methods use File | Settings | File Templates. + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java b/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java new file mode 100644 index 0000000000..e2db8906a1 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java @@ -0,0 +1,31 @@ +package org.apache.qpid.framing; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public interface AMQShortStringTokenizer +{ + + public int countTokens(); + + public AMQShortString nextToken(); + + boolean hasMoreTokens(); +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java index 6dda91a488..2c356d072c 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java @@ -23,12 +23,24 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer;
import java.math.BigDecimal;
-import java.math.BigInteger;
+/**
+ * AMQType is a type that represents the different possible AMQP field table types. It provides operations for each
+ * of the types to perform tasks such as calculating the size of an instance of the type, converting types between AMQP
+ * and Java native types, and reading and writing instances of AMQP types in binary formats to and from byte buffers.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Get the equivalent one byte identifier for a type.
+ * <tr><td> Calculate the size of an instance of an AMQP parameter type. <td> {@link EncodingUtils}
+ * <tr><td> Convert an instance of an AMQP parameter into a compatable Java object tagged with its AMQP type.
+ * <td> {@link AMQTypedValue}
+ * <tr><td> Write an instance of an AMQP parameter type to a byte buffer. <td> {@link EncodingUtils}
+ * <tr><td> Read an instance of an AMQP parameter from a byte buffer. <td> {@link EncodingUtils}
+ * </table>
+ */
public enum AMQType
{
- //AMQP FieldTable Wire Types
-
LONG_STRING('S')
{
public int getEncodingSize(Object value)
@@ -36,7 +48,6 @@ public enum AMQType return EncodingUtils.encodedLongStringLength((String) value);
}
-
public String toNativeValue(Object value)
{
if (value != null)
@@ -58,12 +69,10 @@ public enum AMQType {
return EncodingUtils.readLongString(buffer);
}
-
},
INTEGER('i')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.unsignedIntegerLength();
@@ -89,12 +98,11 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Long.valueOf((String)value);
+ return Long.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to int.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int.");
}
}
@@ -111,22 +119,21 @@ public enum AMQType DECIMAL('D')
{
-
public int getEncodingSize(Object value)
{
- return EncodingUtils.encodedByteLength()+ EncodingUtils.encodedIntegerLength();
+ return EncodingUtils.encodedByteLength() + EncodingUtils.encodedIntegerLength();
}
public Object toNativeValue(Object value)
{
- if(value instanceof BigDecimal)
+ if (value instanceof BigDecimal)
{
return (BigDecimal) value;
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to BigDecimal.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to BigDecimal.");
}
}
@@ -150,7 +157,8 @@ public enum AMQType int unscaled = EncodingUtils.readInteger(buffer);
BigDecimal bd = new BigDecimal(unscaled);
- return bd.setScale(places);
+
+ return bd.setScale(places);
}
},
@@ -163,14 +171,14 @@ public enum AMQType public Object toNativeValue(Object value)
{
- if(value instanceof Long)
+ if (value instanceof Long)
{
return (Long) value;
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to timestamp.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to timestamp.");
}
}
@@ -179,37 +187,97 @@ public enum AMQType EncodingUtils.writeLong(buffer, (Long) value);
}
-
public Object readValueFromBuffer(ByteBuffer buffer)
{
return EncodingUtils.readLong(buffer);
}
},
+ /**
+ * Implements the field table type. The native value of a field table type will be an instance of
+ * {@link FieldTable}, which itself may contain name/value pairs encoded as {@link AMQTypedValue}s.
+ */
FIELD_TABLE('F')
{
+ /**
+ * Calculates the size of an instance of the type in bytes.
+ *
+ * @param value An instance of the type.
+ *
+ * @return The size of the instance of the type in bytes.
+ */
public int getEncodingSize(Object value)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ // Ensure that the value is a FieldTable.
+ if (!(value instanceof FieldTable))
+ {
+ throw new IllegalArgumentException("Value is not a FieldTable.");
+ }
+
+ FieldTable ftValue = (FieldTable) value;
+
+ // Loop over all name/value pairs adding up size of each. FieldTable itself keeps track of its encoded
+ // size as entries are added, so no need to loop over all explicitly.
+ // EncodingUtils calculation of the encoded field table lenth, will include 4 bytes for its 'size' field.
+ return EncodingUtils.encodedFieldTableLength(ftValue);
}
+ /**
+ * Converts an instance of the type to an equivalent Java native representation.
+ *
+ * @param value An instance of the type.
+ *
+ * @return An equivalent Java native representation.
+ */
public Object toNativeValue(Object value)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ // Ensure that the value is a FieldTable.
+ if (!(value instanceof FieldTable))
+ {
+ throw new IllegalArgumentException("Value is not a FieldTable.");
+ }
+
+ return (FieldTable) value;
}
+ /**
+ * Writes an instance of the type to a specified byte buffer.
+ *
+ * @param value An instance of the type.
+ * @param buffer The byte buffer to write it to.
+ */
public void writeValueImpl(Object value, ByteBuffer buffer)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ // Ensure that the value is a FieldTable.
+ if (!(value instanceof FieldTable))
+ {
+ throw new IllegalArgumentException("Value is not a FieldTable.");
+ }
+
+ FieldTable ftValue = (FieldTable) value;
+
+ // Loop over all name/values writing out into buffer.
+ ftValue.writeToBuffer(buffer);
}
+ /**
+ * Reads an instance of the type from a specified byte buffer.
+ *
+ * @param buffer The byte buffer to write it to.
+ *
+ * @return An instance of the type.
+ */
public Object readValueFromBuffer(ByteBuffer buffer)
{
- // TODO : fixme
- throw new UnsupportedOperationException();
+ try
+ {
+ // Read size of field table then all name/value pairs.
+ return EncodingUtils.readFieldTable(buffer);
+ }
+ catch (AMQFrameDecodingException e)
+ {
+ throw new IllegalArgumentException("Unable to read field table from buffer.", e);
+ }
}
},
@@ -220,7 +288,6 @@ public enum AMQType return 0;
}
-
public Object toNativeValue(Object value)
{
if (value == null)
@@ -229,14 +296,13 @@ public enum AMQType }
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to null String.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to null String.");
}
}
public void writeValueImpl(Object value, ByteBuffer buffer)
- {
- }
+ { }
public Object readValueFromBuffer(ByteBuffer buffer)
{
@@ -244,8 +310,6 @@ public enum AMQType }
},
- // Extended types
-
BINARY('x')
{
public int getEncodingSize(Object value)
@@ -253,21 +317,19 @@ public enum AMQType return EncodingUtils.encodedLongstrLength((byte[]) value);
}
-
public Object toNativeValue(Object value)
{
- if((value instanceof byte[]) || (value == null))
+ if ((value instanceof byte[]) || (value == null))
{
return value;
}
else
{
- throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName() +
- ") cannot be converted to byte[]");
+ throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName()
+ + ") cannot be converted to byte[]");
}
}
-
public void writeValueImpl(Object value, ByteBuffer buffer)
{
EncodingUtils.writeLongstr(buffer, (byte[]) value);
@@ -277,7 +339,6 @@ public enum AMQType {
return EncodingUtils.readLongstr(buffer);
}
-
},
ASCII_STRING('c')
@@ -287,7 +348,6 @@ public enum AMQType return EncodingUtils.encodedLongStringLength((String) value);
}
-
public String toNativeValue(Object value)
{
if (value != null)
@@ -309,7 +369,6 @@ public enum AMQType {
return EncodingUtils.readLongString(buffer);
}
-
},
WIDE_STRING('C')
@@ -320,7 +379,6 @@ public enum AMQType return EncodingUtils.encodedLongStringLength((String) value);
}
-
public String toNativeValue(Object value)
{
if (value != null)
@@ -351,7 +409,6 @@ public enum AMQType return EncodingUtils.encodedBooleanLength();
}
-
public Object toNativeValue(Object value)
{
if (value instanceof Boolean)
@@ -360,12 +417,12 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Boolean.valueOf((String)value);
+ return Boolean.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to boolean.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to boolean.");
}
}
@@ -374,7 +431,6 @@ public enum AMQType EncodingUtils.writeBoolean(buffer, (Boolean) value);
}
-
public Object readValueFromBuffer(ByteBuffer buffer)
{
return EncodingUtils.readBoolean(buffer);
@@ -388,7 +444,6 @@ public enum AMQType return EncodingUtils.encodedCharLength();
}
-
public Character toNativeValue(Object value)
{
if (value instanceof Character)
@@ -401,8 +456,8 @@ public enum AMQType }
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to char.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to char.");
}
}
@@ -415,7 +470,6 @@ public enum AMQType {
return EncodingUtils.readChar(buffer);
}
-
},
BYTE('b')
@@ -425,7 +479,6 @@ public enum AMQType return EncodingUtils.encodedByteLength();
}
-
public Byte toNativeValue(Object value)
{
if (value instanceof Byte)
@@ -434,12 +487,12 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Byte.valueOf((String)value);
+ return Byte.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to byte.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to byte.");
}
}
@@ -456,13 +509,11 @@ public enum AMQType SHORT('s')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.encodedShortLength();
}
-
public Short toNativeValue(Object value)
{
if (value instanceof Short)
@@ -475,16 +526,13 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Short.valueOf((String)value);
+ return Short.valueOf((String) value);
}
-
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to short.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to short.");
}
-
-
}
public void writeValueImpl(Object value, ByteBuffer buffer)
@@ -521,12 +569,11 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Integer.valueOf((String)value);
+ return Integer.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to int.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int.");
}
}
@@ -543,7 +590,6 @@ public enum AMQType LONG('l')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.encodedLongLength();
@@ -551,7 +597,7 @@ public enum AMQType public Object toNativeValue(Object value)
{
- if(value instanceof Long)
+ if (value instanceof Long)
{
return (Long) value;
}
@@ -569,12 +615,12 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Long.valueOf((String)value);
+ return Long.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to long.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to long.");
}
}
@@ -583,7 +629,6 @@ public enum AMQType EncodingUtils.writeLong(buffer, (Long) value);
}
-
public Object readValueFromBuffer(ByteBuffer buffer)
{
return EncodingUtils.readLong(buffer);
@@ -597,7 +642,6 @@ public enum AMQType return EncodingUtils.encodedFloatLength();
}
-
public Float toNativeValue(Object value)
{
if (value instanceof Float)
@@ -606,12 +650,12 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Float.valueOf((String)value);
+ return Float.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to float.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to float.");
}
}
@@ -628,13 +672,11 @@ public enum AMQType DOUBLE('d')
{
-
public int getEncodingSize(Object value)
{
return EncodingUtils.encodedDoubleLength();
}
-
public Double toNativeValue(Object value)
{
if (value instanceof Double)
@@ -647,12 +689,12 @@ public enum AMQType }
else if ((value instanceof String) || (value == null))
{
- return Double.valueOf((String)value);
+ return Double.valueOf((String) value);
}
else
{
- throw new NumberFormatException("Cannot convert: " + value + "(" +
- value.getClass().getName() + ") to double.");
+ throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName()
+ + ") to double.");
}
}
@@ -667,35 +709,87 @@ public enum AMQType }
};
+ /** Holds the defined one byte identifier for the type. */
private final byte _identifier;
+ /**
+ * Creates an instance of an AMQP type from its defined one byte identifier.
+ *
+ * @param identifier The one byte identifier for the type.
+ */
AMQType(char identifier)
{
_identifier = (byte) identifier;
}
+ /**
+ * Extracts the byte identifier for the typ.
+ *
+ * @return The byte identifier for the typ.
+ */
public final byte identifier()
{
return _identifier;
}
-
+ /**
+ * Calculates the size of an instance of the type in bytes.
+ *
+ * @param value An instance of the type.
+ *
+ * @return The size of the instance of the type in bytes.
+ */
public abstract int getEncodingSize(Object value);
+ /**
+ * Converts an instance of the type to an equivalent Java native representation.
+ *
+ * @param value An instance of the type.
+ *
+ * @return An equivalent Java native representation.
+ */
public abstract Object toNativeValue(Object value);
+ /**
+ * Converts an instance of the type to an equivalent Java native representation, packaged as an
+ * {@link AMQTypedValue} tagged with its AMQP type.
+ *
+ * @param value An instance of the type.
+ *
+ * @return An equivalent Java native representation, tagged with its AMQP type.
+ */
public AMQTypedValue asTypedValue(Object value)
{
return new AMQTypedValue(this, toNativeValue(value));
}
+ /**
+ * Writes an instance of the type to a specified byte buffer, preceded by its one byte identifier. As the type and
+ * value are both written, this provides a fully encoded description of a parameters type and value.
+ *
+ * @param value An instance of the type.
+ * @param buffer The byte buffer to write it to.
+ */
public void writeToBuffer(Object value, ByteBuffer buffer)
{
- buffer.put((byte)identifier());
+ buffer.put(identifier());
writeValueImpl(value, buffer);
}
+ /**
+ * Writes an instance of the type to a specified byte buffer.
+ *
+ * @param value An instance of the type.
+ * @param buffer The byte buffer to write it to.
+ */
abstract void writeValueImpl(Object value, ByteBuffer buffer);
+ /**
+ * Reads an instance of the type from a specified byte buffer.
+ *
+ * @param buffer The byte buffer to write it to.
+ *
+ * @return An instance of the type.
+ */
abstract Object readValueFromBuffer(ByteBuffer buffer);
}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java index 7193580884..e5b1fad9a8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java @@ -18,23 +18,40 @@ * under the License.
*
*/
-
package org.apache.qpid.framing;
import org.apache.mina.common.ByteBuffer;
+/**
+ * AMQTypedValue combines together a native Java Object value, and an {@link AMQType}, as a fully typed AMQP parameter
+ * value. It provides the ability to read and write fully typed parameters to and from byte buffers. It also provides
+ * the ability to create such parameters from Java native value and a type tag or to extract the native value and type
+ * from one.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create a fully typed AMQP value from a native type and a type tag. <td> {@link AMQType}
+ * <tr><td> Create a fully typed AMQP value from a binary representation in a byte buffer. <td> {@link AMQType}
+ * <tr><td> Write a fully typed AMQP value to a binary representation in a byte buffer. <td> {@link AMQType}
+ * <tr><td> Extract the type from a fully typed AMQP value.
+ * <tr><td> Extract the value from a fully typed AMQP value.
+ * </table>
+ */
public class AMQTypedValue
{
+ /** The type of the value. */
private final AMQType _type;
- private final Object _value;
+ /** The Java native representation of the AMQP typed value. */
+ private final Object _value;
public AMQTypedValue(AMQType type, Object value)
{
- if(type == null)
+ if (type == null)
{
throw new NullPointerException("Cannot create a typed value with null type");
}
+
_type = type;
_value = type.toNativeValue(value);
}
@@ -42,10 +59,9 @@ public class AMQTypedValue private AMQTypedValue(AMQType type, ByteBuffer buffer)
{
_type = type;
- _value = type.readValueFromBuffer( buffer );
+ _value = type.readValueFromBuffer(buffer);
}
-
public AMQType getType()
{
return _type;
@@ -56,10 +72,9 @@ public class AMQTypedValue return _value;
}
-
public void writeToBuffer(ByteBuffer buffer)
{
- _type.writeToBuffer(_value,buffer);
+ _type.writeToBuffer(_value, buffer);
}
public int getEncodingSize()
@@ -70,11 +85,12 @@ public class AMQTypedValue public static AMQTypedValue readFromBuffer(ByteBuffer buffer)
{
AMQType type = AMQTypeMap.getType(buffer.get());
+
return new AMQTypedValue(type, buffer);
}
public String toString()
{
- return "["+getType()+": "+getValue()+"]";
+ return "[" + getType() + ": " + getValue() + "]";
}
}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java index 5ec62ede93..94030f383e 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java @@ -24,7 +24,6 @@ import org.apache.mina.common.ByteBuffer; public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock { - private ByteBuffer _encodedBlock; private AMQDataBlock[] _blocks; @@ -33,27 +32,12 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD _blocks = blocks; } - /** - * The encoded block will be logically first before the AMQDataBlocks which are encoded - * into the buffer afterwards. - * @param encodedBlock already-encoded data - * @param blocks some blocks to be encoded. - */ - public CompositeAMQDataBlock(ByteBuffer encodedBlock, AMQDataBlock[] blocks) - { - this(blocks); - _encodedBlock = encodedBlock; - } public AMQDataBlock[] getBlocks() { return _blocks; } - public ByteBuffer getEncodedBlock() - { - return _encodedBlock; - } public long getSize() { @@ -62,20 +46,11 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD { frameSize += _blocks[i].getSize(); } - if (_encodedBlock != null) - { - _encodedBlock.rewind(); - frameSize += _encodedBlock.remaining(); - } return frameSize; } public void writePayload(ByteBuffer buffer) { - if (_encodedBlock != null) - { - buffer.put(_encodedBlock); - } for (int i = 0; i < _blocks.length; i++) { _blocks[i].writePayload(buffer); @@ -91,7 +66,7 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD else { StringBuilder buf = new StringBuilder(this.getClass().getName()); - buf.append("{encodedBlock=").append(_encodedBlock); + buf.append("{"); for (int i = 0 ; i < _blocks.length; i++) { buf.append(" ").append(i).append("=[").append(_blocks[i].toString()).append("]"); diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java b/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java index be38695384..9d39f8aa86 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java @@ -21,8 +21,10 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; -public class ContentBody extends AMQBody +public class ContentBody implements AMQBody { public static final byte TYPE = 3; @@ -63,11 +65,24 @@ public class ContentBody extends AMQBody { if (payload != null) { - ByteBuffer copy = payload.duplicate(); - buffer.put(copy.rewind()); + if(payload.isDirect() || payload.isReadOnly()) + { + ByteBuffer copy = payload.duplicate(); + buffer.put(copy.rewind()); + } + else + { + buffer.put(payload.array(),payload.arrayOffset(),payload.limit()); + } } } + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.contentBodyReceived(channelId, this); + } + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException { if (size > 0) diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java index 02631a5f88..83e5a7e341 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java @@ -21,8 +21,10 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; -public class ContentHeaderBody extends AMQBody +public class ContentHeaderBody implements AMQBody { public static final byte TYPE = 2; @@ -110,6 +112,12 @@ public class ContentHeaderBody extends AMQBody properties.writePropertyListPayload(buffer); } + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.contentHeaderReceived(channelId, this); + } + public static AMQFrame createAMQFrame(int channelId, int classId, int weight, BasicContentHeaderProperties properties, long bodySize) { diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java index 712eb437db..46189b63d7 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java @@ -22,6 +22,8 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.amqp_8_0.BasicConsumeBodyImpl; + public class ContentHeaderPropertiesFactory { private static final ContentHeaderPropertiesFactory _instance = new ContentHeaderPropertiesFactory(); @@ -43,7 +45,7 @@ public class ContentHeaderPropertiesFactory // AMQP version change: "Hardwired" version to major=8, minor=0 // TODO: Change so that the actual version is obtained from // the ProtocolInitiation object for this session. - if (classId == BasicConsumeBody.getClazz((byte)8, (byte)0)) + if (classId == BasicConsumeBodyImpl.CLASS_ID) { properties = new BasicContentHeaderProperties(); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java new file mode 100644 index 0000000000..f6795ff200 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java @@ -0,0 +1,50 @@ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public abstract class DeferredDataBlock extends AMQDataBlock +{ + private AMQDataBlock _underlyingDataBlock; + + + public long getSize() + { + if(_underlyingDataBlock == null) + { + _underlyingDataBlock = createAMQDataBlock(); + } + return _underlyingDataBlock.getSize(); + } + + public void writePayload(ByteBuffer buffer) + { + if(_underlyingDataBlock == null) + { + _underlyingDataBlock = createAMQDataBlock(); + } + _underlyingDataBlock.writePayload(buffer); + } + + abstract protected AMQDataBlock createAMQDataBlock(); + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java index ccba8bd41e..6425f8c591 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java +++ b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java @@ -229,7 +229,11 @@ public class EncodingUtils encodedString[i] = (byte) cha[i]; } - writeBytes(buffer, encodedString); + // TODO: check length fits in an unsigned byte + writeUnsignedByte(buffer, (short)encodedString.length); + buffer.put(encodedString); + + } else { @@ -928,15 +932,15 @@ public class EncodingUtils public static byte[] readBytes(ByteBuffer buffer) { - short length = buffer.getUnsigned(); + long length = buffer.getUnsignedInt(); if (length == 0) { return null; } else { - byte[] dataBytes = new byte[length]; - buffer.get(dataBytes, 0, length); + byte[] dataBytes = new byte[(int)length]; + buffer.get(dataBytes, 0, (int)length); return dataBytes; } @@ -947,13 +951,14 @@ public class EncodingUtils if (data != null) { // TODO: check length fits in an unsigned byte - writeUnsignedByte(buffer, (short) data.length); + writeUnsignedInteger(buffer, (long)data.length); buffer.put(data); } else - { + { // really writing out unsigned byte - buffer.put((byte) 0); + //buffer.put((byte) 0); + writeUnsignedInteger(buffer, 0L); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java index 3438770450..9ba9b53b13 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java @@ -18,7 +18,6 @@ * under the License. * */ - package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; @@ -360,6 +359,41 @@ public class FieldTable } } + /** + * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name. + * + * @param string The name of the parameter to get the associated FieldTable value for. + * + * @return The associated FieldTable value, or <tt>null</tt> if the associated value is not of FieldTable type or + * not present in the field table at all. + */ + public FieldTable getFieldTable(String string) + { + return getFieldTable(new AMQShortString(string)); + } + + /** + * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name. + * + * @param string The name of the parameter to get the associated FieldTable value for. + * + * @return The associated FieldTable value, or <tt>null</tt> if the associated value is not of FieldTable type or + * not present in the field table at all. + */ + public FieldTable getFieldTable(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + + if ((value != null) && (value.getType() == AMQType.FIELD_TABLE)) + { + return (FieldTable) value.getValue(); + } + else + { + return null; + } + } + public Object getObject(String string) { return getObject(new AMQShortString(string)); @@ -568,6 +602,32 @@ public class FieldTable return setProperty(string, AMQType.VOID.asTypedValue(null)); } + /** + * Associates a nested field table with the specified parameter name. + * + * @param string The name of the parameter to store in the table. + * @param ftValue The field table value to associate with the parameter name. + * + * @return The stored value. + */ + public Object setFieldTable(String string, FieldTable ftValue) + { + return setFieldTable(new AMQShortString(string), ftValue); + } + + /** + * Associates a nested field table with the specified parameter name. + * + * @param string The name of the parameter to store in the table. + * @param ftValue The field table value to associate with the parameter name. + * + * @return The stored value. + */ + public Object setFieldTable(AMQShortString string, FieldTable ftValue) + { + return setProperty(string, AMQType.FIELD_TABLE.asTypedValue(ftValue)); + } + public Object setObject(AMQShortString string, Object object) { if (object instanceof Boolean) @@ -706,12 +766,15 @@ public class FieldTable public void writeToBuffer(ByteBuffer buffer) { - final boolean trace = _logger.isTraceEnabled(); + final boolean trace = _logger.isDebugEnabled(); if (trace) { - _logger.trace("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "..."); - _logger.trace(_properties.toString()); + _logger.debug("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "..."); + if (_properties != null) + { + _logger.debug(_properties.toString()); + } } EncodingUtils.writeUnsignedInteger(buffer, getEncodedSize()); @@ -894,14 +957,21 @@ public class FieldTable if (_encodedForm != null) { + if(buffer.isDirect() || buffer.isReadOnly()) + { + ByteBuffer encodedForm = _encodedForm.duplicate(); - if (_encodedForm.position() != 0) + if (encodedForm.position() != 0) + { + encodedForm.flip(); + } + + buffer.put(encodedForm); + } + else { - _encodedForm.flip(); + buffer.put(_encodedForm.array(),_encodedForm.arrayOffset(),(int)_encodedSize); } - // _encodedForm.limit((int)getEncodedSize()); - - buffer.put(_encodedForm); } else if (_properties != null) { @@ -915,11 +985,11 @@ public class FieldTable final Map.Entry<AMQShortString, AMQTypedValue> me = it.next(); try { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + me.getValue().getValue()); - _logger.trace("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); + _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); } // Write the actual parameter name @@ -928,12 +998,12 @@ public class FieldTable } catch (Exception e) { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Exception thrown:" + e); - _logger.trace("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + _logger.debug("Exception thrown:" + e); + _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + me.getValue().getValue()); - _logger.trace("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); + _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); } throw new RuntimeException(e); @@ -945,7 +1015,7 @@ public class FieldTable private void setFromBuffer(ByteBuffer buffer, long length) throws AMQFrameDecodingException { - final boolean trace = _logger.isTraceEnabled(); + final boolean trace = _logger.isDebugEnabled(); if (length > 0) { @@ -961,7 +1031,7 @@ public class FieldTable if (trace) { - _logger.trace("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType() + _logger.debug("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType() + "', key '" + key + "', value '" + value.getValue() + "'"); } @@ -976,7 +1046,7 @@ public class FieldTable if (trace) { - _logger.trace("FieldTable::FieldTable(buffer," + length + "): Done."); + _logger.debug("FieldTable::FieldTable(buffer," + length + "): Done."); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java index 7246c4a1cf..15a43345b5 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java @@ -21,8 +21,10 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; -public class HeartbeatBody extends AMQBody +public class HeartbeatBody implements AMQBody { public static final byte TYPE = 8; public static AMQFrame FRAME = new HeartbeatBody().toFrame(); @@ -46,15 +48,21 @@ public class HeartbeatBody extends AMQBody return TYPE; } - protected int getSize() + public int getSize() { return 0;//heartbeats we generate have no payload } - protected void writePayload(ByteBuffer buffer) + public void writePayload(ByteBuffer buffer) { } + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.heartbeatBodyReceived(channelId, this); + } + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException { if(size > 0) diff --git a/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java b/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java index 4c253b9973..3ac17e9204 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java @@ -139,7 +139,7 @@ public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQData } } - public void checkVersion() throws AMQException + public ProtocolVersion checkVersion() throws AMQException { if(_protocolHeader.length != 4) @@ -180,6 +180,7 @@ public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQData throw new AMQProtocolVersionException("Protocol version " + _protocolMajor + "." + _protocolMinor + " not suppoerted by this version of the Qpid broker.", null); } + return pv; } public String toString() diff --git a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java index 26c048e34a..f8cf3f3011 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java @@ -25,7 +25,7 @@ import org.apache.mina.common.ByteBuffer; public class SmallCompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock
{
- private ByteBuffer _encodedBlock;
+ private AMQDataBlock _firstFrame;
private AMQDataBlock _block;
@@ -40,10 +40,10 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl * @param encodedBlock already-encoded data
* @param block a block to be encoded.
*/
- public SmallCompositeAMQDataBlock(ByteBuffer encodedBlock, AMQDataBlock block)
+ public SmallCompositeAMQDataBlock(AMQDataBlock encodedBlock, AMQDataBlock block)
{
this(block);
- _encodedBlock = encodedBlock;
+ _firstFrame = encodedBlock;
}
public AMQDataBlock getBlock()
@@ -51,28 +51,28 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl return _block;
}
- public ByteBuffer getEncodedBlock()
+ public AMQDataBlock getFirstFrame()
{
- return _encodedBlock;
+ return _firstFrame;
}
public long getSize()
{
long frameSize = _block.getSize();
- if (_encodedBlock != null)
+ if (_firstFrame != null)
{
- _encodedBlock.rewind();
- frameSize += _encodedBlock.remaining();
+
+ frameSize += _firstFrame.getSize();
}
return frameSize;
}
public void writePayload(ByteBuffer buffer)
{
- if (_encodedBlock != null)
+ if (_firstFrame != null)
{
- buffer.put(_encodedBlock);
+ _firstFrame.writePayload(buffer);
}
_block.writePayload(buffer);
@@ -87,7 +87,7 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl else
{
StringBuilder buf = new StringBuilder(this.getClass().getName());
- buf.append("{encodedBlock=").append(_encodedBlock);
+ buf.append("{encodedBlock=").append(_firstFrame);
buf.append(" _block=[").append(_block.toString()).append("]");
diff --git a/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java b/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java index 6006e9793c..516d0c569c 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java +++ b/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java @@ -182,7 +182,7 @@ public class VersionSpecificRegistry + " method " + methodID + ".", null);
}
- return bodyFactory.newInstance(_protocolMajorVersion, _protocolMinorVersion, classID, methodID, in, size);
+ return bodyFactory.newInstance( in, size);
}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java b/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java index 706499c1b0..49c28bb06b 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java +++ b/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java @@ -27,6 +27,8 @@ public interface MessagePublishInfo public AMQShortString getExchange();
+ public void setExchange(AMQShortString exchange);
+
public boolean isImmediate();
public boolean isMandatory();
diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java new file mode 100644 index 0000000000..3c5cb74773 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java @@ -0,0 +1,209 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.amqp_0_9;
+
+import org.apache.qpid.framing.EncodingUtils;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.AMQFrameDecodingException;
+import org.apache.qpid.framing.Content;
+
+import org.apache.mina.common.ByteBuffer;
+
+public abstract class AMQMethodBody_0_9 extends org.apache.qpid.framing.AMQMethodBodyImpl
+{
+
+ public byte getMajor()
+ {
+ return 0;
+ }
+
+ public byte getMinor()
+ {
+ return 9;
+ }
+
+ public int getSize()
+ {
+ return 2 + 2 + getBodySize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ EncodingUtils.writeUnsignedShort(buffer, getClazz());
+ EncodingUtils.writeUnsignedShort(buffer, getMethod());
+ writeMethodPayload(buffer);
+ }
+
+
+ protected byte readByte(ByteBuffer buffer)
+ {
+ return buffer.get();
+ }
+
+ protected AMQShortString readAMQShortString(ByteBuffer buffer)
+ {
+ return EncodingUtils.readAMQShortString(buffer);
+ }
+
+ protected int getSizeOf(AMQShortString string)
+ {
+ return EncodingUtils.encodedShortStringLength(string);
+ }
+
+ protected void writeByte(ByteBuffer buffer, byte b)
+ {
+ buffer.put(b);
+ }
+
+ protected void writeAMQShortString(ByteBuffer buffer, AMQShortString string)
+ {
+ EncodingUtils.writeShortStringBytes(buffer, string);
+ }
+
+ protected int readInt(ByteBuffer buffer)
+ {
+ return buffer.getInt();
+ }
+
+ protected void writeInt(ByteBuffer buffer, int i)
+ {
+ buffer.putInt(i);
+ }
+
+ protected FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException
+ {
+ return EncodingUtils.readFieldTable(buffer);
+ }
+
+ protected int getSizeOf(FieldTable table)
+ {
+ return EncodingUtils.encodedFieldTableLength(table); //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected void writeFieldTable(ByteBuffer buffer, FieldTable table)
+ {
+ EncodingUtils.writeFieldTableBytes(buffer, table);
+ }
+
+ protected long readLong(ByteBuffer buffer)
+ {
+ return buffer.getLong();
+ }
+
+ protected void writeLong(ByteBuffer buffer, long l)
+ {
+ buffer.putLong(l);
+ }
+
+ protected int getSizeOf(byte[] response)
+ {
+ return (response == null) ? 4 :response.length + 4;
+ }
+
+ protected void writeBytes(ByteBuffer buffer, byte[] data)
+ {
+ EncodingUtils.writeBytes(buffer,data);
+ }
+
+ protected byte[] readBytes(ByteBuffer buffer)
+ {
+ return EncodingUtils.readBytes(buffer);
+ }
+
+ protected short readShort(ByteBuffer buffer)
+ {
+ return EncodingUtils.readShort(buffer);
+ }
+
+ protected void writeShort(ByteBuffer buffer, short s)
+ {
+ EncodingUtils.writeShort(buffer, s);
+ }
+
+ protected Content readContent(ByteBuffer buffer)
+ {
+ return null; //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected int getSizeOf(Content body)
+ {
+ return 0; //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected void writeContent(ByteBuffer buffer, Content body)
+ {
+ //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected byte readBitfield(ByteBuffer buffer)
+ {
+ return readByte(buffer); //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected int readUnsignedShort(ByteBuffer buffer)
+ {
+ return buffer.getUnsignedShort(); //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected void writeBitfield(ByteBuffer buffer, byte bitfield0)
+ {
+ buffer.put(bitfield0);
+ }
+
+ protected void writeUnsignedShort(ByteBuffer buffer, int s)
+ {
+ EncodingUtils.writeUnsignedShort(buffer, s);
+ }
+
+ protected long readUnsignedInteger(ByteBuffer buffer)
+ {
+ return buffer.getUnsignedInt();
+ }
+ protected void writeUnsignedInteger(ByteBuffer buffer, long i)
+ {
+ EncodingUtils.writeUnsignedInteger(buffer, i);
+ }
+
+
+ protected short readUnsignedByte(ByteBuffer buffer)
+ {
+ return buffer.getUnsigned();
+ }
+
+ protected void writeUnsignedByte(ByteBuffer buffer, short unsignedByte)
+ {
+ EncodingUtils.writeUnsignedByte(buffer, unsignedByte);
+ }
+
+ protected long readTimestamp(ByteBuffer buffer)
+ {
+ return EncodingUtils.readTimestamp(buffer);
+ }
+
+ protected void writeTimestamp(ByteBuffer buffer, long t)
+ {
+ EncodingUtils.writeTimestamp(buffer, t);
+ }
+
+
+}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java new file mode 100644 index 0000000000..2fd4f70138 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java @@ -0,0 +1,172 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.amqp_0_9;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.framing.abstraction.AbstractMethodConverter;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_9.*;
+import org.apache.qpid.framing.amqp_0_9.BasicPublishBodyImpl;
+
+public class MethodConverter_0_9 extends AbstractMethodConverter implements ProtocolVersionMethodConverter
+{
+ private int _basicPublishClassId;
+ private int _basicPublishMethodId;
+
+ public MethodConverter_0_9()
+ {
+ super((byte)0,(byte)9);
+
+
+ }
+
+ public AMQBody convertToBody(ContentChunk contentChunk)
+ {
+ if(contentChunk instanceof ContentChunk_0_9)
+ {
+ return ((ContentChunk_0_9)contentChunk).toBody();
+ }
+ else
+ {
+ return new ContentBody(contentChunk.getData());
+ }
+ }
+
+ public ContentChunk convertToContentChunk(AMQBody body)
+ {
+ final ContentBody contentBodyChunk = (ContentBody) body;
+
+ return new ContentChunk_0_9(contentBodyChunk);
+
+ }
+
+ public void configure()
+ {
+
+ _basicPublishClassId = org.apache.qpid.framing.amqp_0_9.BasicPublishBodyImpl.CLASS_ID;
+ _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID;
+
+ }
+
+ public MessagePublishInfo convertToInfo(AMQMethodBody methodBody)
+ {
+ final BasicPublishBody publishBody = ((BasicPublishBody) methodBody);
+
+ final AMQShortString exchange = publishBody.getExchange();
+ final AMQShortString routingKey = publishBody.getRoutingKey();
+
+ return new MethodConverter_0_9.MessagePublishInfoImpl(exchange,
+ publishBody.getImmediate(),
+ publishBody.getMandatory(),
+ routingKey);
+
+ }
+
+ public AMQMethodBody convertToBody(MessagePublishInfo info)
+ {
+
+ return new BasicPublishBodyImpl(0,
+ info.getExchange(),
+ info.getRoutingKey(),
+ info.isMandatory(),
+ info.isImmediate()) ;
+
+ }
+
+ private static class MessagePublishInfoImpl implements MessagePublishInfo
+ {
+ private AMQShortString _exchange;
+ private final boolean _immediate;
+ private final boolean _mandatory;
+ private final AMQShortString _routingKey;
+
+ public MessagePublishInfoImpl(final AMQShortString exchange,
+ final boolean immediate,
+ final boolean mandatory,
+ final AMQShortString routingKey)
+ {
+ _exchange = exchange;
+ _immediate = immediate;
+ _mandatory = mandatory;
+ _routingKey = routingKey;
+ }
+
+ public AMQShortString getExchange()
+ {
+ return _exchange;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ _exchange = exchange;
+ }
+
+ public boolean isImmediate()
+ {
+ return _immediate;
+ }
+
+ public boolean isMandatory()
+ {
+ return _mandatory;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+ }
+
+ private static class ContentChunk_0_9 implements ContentChunk
+ {
+ private final ContentBody _contentBodyChunk;
+
+ public ContentChunk_0_9(final ContentBody contentBodyChunk)
+ {
+ _contentBodyChunk = contentBodyChunk;
+ }
+
+ public int getSize()
+ {
+ return _contentBodyChunk.getSize();
+ }
+
+ public ByteBuffer getData()
+ {
+ return _contentBodyChunk.payload;
+ }
+
+ public void reduceToFit()
+ {
+ _contentBodyChunk.reduceBufferToFit();
+ }
+
+ public AMQBody toBody()
+ {
+ return _contentBodyChunk;
+ }
+ }
+}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java new file mode 100644 index 0000000000..2b7c9534a9 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java @@ -0,0 +1,209 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.amqp_8_0;
+
+import org.apache.qpid.framing.EncodingUtils;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.AMQFrameDecodingException;
+import org.apache.qpid.framing.Content;
+
+import org.apache.mina.common.ByteBuffer;
+
+public abstract class AMQMethodBody_8_0 extends org.apache.qpid.framing.AMQMethodBodyImpl
+{
+
+ public byte getMajor()
+ {
+ return 8;
+ }
+
+ public byte getMinor()
+ {
+ return 0;
+ }
+
+ public int getSize()
+ {
+ return 2 + 2 + getBodySize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ EncodingUtils.writeUnsignedShort(buffer, getClazz());
+ EncodingUtils.writeUnsignedShort(buffer, getMethod());
+ writeMethodPayload(buffer);
+ }
+
+
+ protected byte readByte(ByteBuffer buffer)
+ {
+ return buffer.get();
+ }
+
+ protected AMQShortString readAMQShortString(ByteBuffer buffer)
+ {
+ return EncodingUtils.readAMQShortString(buffer);
+ }
+
+ protected int getSizeOf(AMQShortString string)
+ {
+ return EncodingUtils.encodedShortStringLength(string);
+ }
+
+ protected void writeByte(ByteBuffer buffer, byte b)
+ {
+ buffer.put(b);
+ }
+
+ protected void writeAMQShortString(ByteBuffer buffer, AMQShortString string)
+ {
+ EncodingUtils.writeShortStringBytes(buffer, string);
+ }
+
+ protected int readInt(ByteBuffer buffer)
+ {
+ return buffer.getInt();
+ }
+
+ protected void writeInt(ByteBuffer buffer, int i)
+ {
+ buffer.putInt(i);
+ }
+
+ protected FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException
+ {
+ return EncodingUtils.readFieldTable(buffer);
+ }
+
+ protected int getSizeOf(FieldTable table)
+ {
+ return EncodingUtils.encodedFieldTableLength(table); //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected void writeFieldTable(ByteBuffer buffer, FieldTable table)
+ {
+ EncodingUtils.writeFieldTableBytes(buffer, table);
+ }
+
+ protected long readLong(ByteBuffer buffer)
+ {
+ return buffer.getLong();
+ }
+
+ protected void writeLong(ByteBuffer buffer, long l)
+ {
+ buffer.putLong(l);
+ }
+
+ protected int getSizeOf(byte[] response)
+ {
+ return (response == null) ? 4 : response.length + 4;
+ }
+
+ protected void writeBytes(ByteBuffer buffer, byte[] data)
+ {
+ EncodingUtils.writeBytes(buffer,data);
+ }
+
+ protected byte[] readBytes(ByteBuffer buffer)
+ {
+ return EncodingUtils.readBytes(buffer);
+ }
+
+ protected short readShort(ByteBuffer buffer)
+ {
+ return EncodingUtils.readShort(buffer);
+ }
+
+ protected void writeShort(ByteBuffer buffer, short s)
+ {
+ EncodingUtils.writeShort(buffer, s);
+ }
+
+ protected Content readContent(ByteBuffer buffer)
+ {
+ return null; //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected int getSizeOf(Content body)
+ {
+ return 0; //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected void writeContent(ByteBuffer buffer, Content body)
+ {
+ //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected byte readBitfield(ByteBuffer buffer)
+ {
+ return readByte(buffer); //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected int readUnsignedShort(ByteBuffer buffer)
+ {
+ return buffer.getUnsignedShort(); //To change body of created methods use File | Settings | File Templates.
+ }
+
+ protected void writeBitfield(ByteBuffer buffer, byte bitfield0)
+ {
+ buffer.put(bitfield0);
+ }
+
+ protected void writeUnsignedShort(ByteBuffer buffer, int s)
+ {
+ EncodingUtils.writeUnsignedShort(buffer, s);
+ }
+
+ protected long readUnsignedInteger(ByteBuffer buffer)
+ {
+ return buffer.getUnsignedInt();
+ }
+ protected void writeUnsignedInteger(ByteBuffer buffer, long i)
+ {
+ EncodingUtils.writeUnsignedInteger(buffer, i);
+ }
+
+
+ protected short readUnsignedByte(ByteBuffer buffer)
+ {
+ return buffer.getUnsigned();
+ }
+
+ protected void writeUnsignedByte(ByteBuffer buffer, short unsignedByte)
+ {
+ EncodingUtils.writeUnsignedByte(buffer, unsignedByte);
+ }
+
+ protected long readTimestamp(ByteBuffer buffer)
+ {
+ return EncodingUtils.readTimestamp(buffer);
+ }
+
+ protected void writeTimestamp(ByteBuffer buffer, long t)
+ {
+ EncodingUtils.writeTimestamp(buffer, t);
+ }
+
+
+}
diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java new file mode 100644 index 0000000000..b1be49a350 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java @@ -0,0 +1,151 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.framing.amqp_8_0;
+
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.framing.abstraction.ContentChunk;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.AbstractMethodConverter;
+import org.apache.qpid.framing.amqp_8_0.BasicPublishBodyImpl;
+import org.apache.qpid.framing.*;
+
+import org.apache.mina.common.ByteBuffer;
+
+public class MethodConverter_8_0 extends AbstractMethodConverter implements ProtocolVersionMethodConverter
+{
+ private int _basicPublishClassId;
+ private int _basicPublishMethodId;
+
+ public MethodConverter_8_0()
+ {
+ super((byte)8,(byte)0);
+
+
+ }
+
+ public AMQBody convertToBody(ContentChunk contentChunk)
+ {
+ return new ContentBody(contentChunk.getData());
+ }
+
+ public ContentChunk convertToContentChunk(AMQBody body)
+ {
+ final ContentBody contentBodyChunk = (ContentBody) body;
+
+ return new ContentChunk()
+ {
+
+ public int getSize()
+ {
+ return contentBodyChunk.getSize();
+ }
+
+ public ByteBuffer getData()
+ {
+ return contentBodyChunk.payload;
+ }
+
+ public void reduceToFit()
+ {
+ contentBodyChunk.reduceBufferToFit();
+ }
+ };
+
+ }
+
+ public void configure()
+ {
+
+ _basicPublishClassId = BasicPublishBodyImpl.CLASS_ID;
+ _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID;
+
+ }
+
+ public MessagePublishInfo convertToInfo(AMQMethodBody methodBody)
+ {
+ final BasicPublishBody publishBody = ((BasicPublishBody) methodBody);
+
+ final AMQShortString exchange = publishBody.getExchange();
+ final AMQShortString routingKey = publishBody.getRoutingKey();
+
+ return new MessagePublishInfoImpl(exchange == null ? null : exchange.intern(),
+ publishBody.getImmediate(),
+ publishBody.getMandatory(),
+ routingKey == null ? null : routingKey.intern());
+
+ }
+
+ public AMQMethodBody convertToBody(MessagePublishInfo info)
+ {
+
+ return new BasicPublishBodyImpl(0,
+ info.getExchange(),
+ info.getRoutingKey(),
+ info.isMandatory(),
+ info.isImmediate()) ;
+
+ }
+
+ private static class MessagePublishInfoImpl implements MessagePublishInfo
+ {
+ private AMQShortString _exchange;
+ private final boolean _immediate;
+ private final boolean _mandatory;
+ private final AMQShortString _routingKey;
+
+ public MessagePublishInfoImpl(final AMQShortString exchange,
+ final boolean immediate,
+ final boolean mandatory,
+ final AMQShortString routingKey)
+ {
+ _exchange = exchange;
+ _immediate = immediate;
+ _mandatory = mandatory;
+ _routingKey = routingKey;
+ }
+
+ public AMQShortString getExchange()
+ {
+ return _exchange;
+ }
+
+ public void setExchange(AMQShortString exchange)
+ {
+ _exchange = exchange;
+ }
+
+ public boolean isImmediate()
+ {
+ return _immediate;
+ }
+
+ public boolean isMandatory()
+ {
+ return _mandatory;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+ }
+}
diff --git a/java/common/src/main/java/org/apache/qpid/pool/Job.java b/java/common/src/main/java/org/apache/qpid/pool/Job.java index ba3c5d03fa..b2a09ac592 100644 --- a/java/common/src/main/java/org/apache/qpid/pool/Job.java +++ b/java/common/src/main/java/org/apache/qpid/pool/Job.java @@ -94,21 +94,23 @@ public class Job implements Runnable /** * Sequentially processes, up to the maximum number per job, the aggregated continuations in enqueued in this job. */ - void processAll() + boolean processAll() { // limit the number of events processed in one run - for (int i = 0; i < _maxEvents; i++) + int i = _maxEvents; + while( --i != 0 ) { Event e = _eventQueue.poll(); if (e == null) { - break; + return true; } else { e.process(_session); } } + return false; } /** @@ -144,9 +146,15 @@ public class Job implements Runnable */ public void run() { - processAll(); - deactivate(); - _completionHandler.completed(_session, this); + if(processAll()) + { + deactivate(); + _completionHandler.completed(_session, this); + } + else + { + _completionHandler.notCompleted(_session, this); + } } /** @@ -158,5 +166,7 @@ public class Job implements Runnable static interface JobCompletionHandler { public void completed(IoSession session, Job job); + + public void notCompleted(final IoSession session, final Job job); } } diff --git a/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java b/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java index d0dfb1adcf..2912e54662 100644 --- a/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java +++ b/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java @@ -30,6 +30,8 @@ import org.apache.qpid.pool.Event.CloseEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ExecutorService; /** * PoolingFilter, is a no-op pass through filter that hands all events down the Mina filter chain by default. As it @@ -83,9 +85,6 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo /** Used for debugging purposes. */ private static final Logger _logger = LoggerFactory.getLogger(PoolingFilter.class); - /** Holds a mapping from Mina sessions to batched jobs for execution. */ - private final ConcurrentMap<IoSession, Job> _jobs = new ConcurrentHashMap<IoSession, Job>(); - /** Holds the managed reference to obtain the executor for the batched jobs. */ private final ReferenceCountingExecutorService _poolReference; @@ -93,7 +92,9 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo private final String _name; /** Defines the maximum number of events that will be batched into a single job. */ - private final int _maxEvents = Integer.getInteger("amqj.server.read_write_pool.max_events", 10); + static final int MAX_JOB_EVENTS = Integer.getInteger("amqj.server.read_write_pool.max_events", 10); + + private final int _maxEvents; /** * Creates a named pooling filter, on the specified shared thread pool. @@ -101,10 +102,11 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo * @param refCountingPool The thread pool reference. * @param name The identifying name of the filter type. */ - public PoolingFilter(ReferenceCountingExecutorService refCountingPool, String name) + public PoolingFilter(ReferenceCountingExecutorService refCountingPool, String name, int maxEvents) { _poolReference = refCountingPool; _name = name; + _maxEvents = maxEvents; } /** @@ -159,20 +161,34 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo /** * Adds an {@link Event} to a {@link Job}, triggering the execution of the job if it is not already running. * - * @param session The Mina session to work in. + * @param job The job. * @param event The event to hand off asynchronously. */ - void fireAsynchEvent(IoSession session, Event event) + void fireAsynchEvent(Job job, Event event) { - Job job = getJobForSession(session); + // job.acquire(); //prevents this job being removed from _jobs job.add(event); - // Additional checks on pool to check that it hasn't shutdown. - // The alternative is to catch the RejectedExecutionException that will result from executing on a shutdown pool - if (job.activate() && (_poolReference.getPool() != null) && !_poolReference.getPool().isShutdown()) + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) { - _poolReference.getPool().execute(job); + return; + } + + // rather than perform additional checks on pool to check that it hasn't shutdown. + // catch the RejectedExecutionException that will result from executing on a shutdown pool + if (job.activate()) + { + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } } } @@ -185,7 +201,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void createNewJobForSession(IoSession session) { - Job job = new Job(session, this, _maxEvents); + Job job = new Job(session, this, MAX_JOB_EVENTS); session.setAttribute(_name, job); } @@ -196,7 +212,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo * * @return The Job for this filter to place asynchronous events into. */ - private Job getJobForSession(IoSession session) + public Job getJobForSession(IoSession session) { return (Job) session.getAttribute(_name); } @@ -210,17 +226,57 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void completed(IoSession session, Job job) { + + if (!job.isComplete()) { + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) + { + return; + } + + // ritchiem : 2006-12-13 Do we need to perform the additional checks here? // Can the pool be shutdown at this point? - if (job.activate() && (_poolReference.getPool() != null) && !_poolReference.getPool().isShutdown()) + if (job.activate()) { - _poolReference.getPool().execute(job); + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + } } } + public void notCompleted(IoSession session, Job job) + { + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) + { + return; + } + + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + + } + + + /** * No-op pass through filter to the next filter in the chain. * @@ -377,7 +433,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public AsynchReadPoolingFilter(ReferenceCountingExecutorService refCountingPool, String name) { - super(refCountingPool, name); + super(refCountingPool, name, Integer.getInteger("amqj.server.read_write_pool.max_read_events", MAX_JOB_EVENTS)); } /** @@ -389,8 +445,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void messageReceived(NextFilter nextFilter, final IoSession session, Object message) { - - fireAsynchEvent(session, new Event.ReceivedEvent(nextFilter, message)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new Event.ReceivedEvent(nextFilter, message)); } /** @@ -401,7 +457,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void sessionClosed(final NextFilter nextFilter, final IoSession session) { - fireAsynchEvent(session, new CloseEvent(nextFilter)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new CloseEvent(nextFilter)); } } @@ -419,7 +476,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public AsynchWritePoolingFilter(ReferenceCountingExecutorService refCountingPool, String name) { - super(refCountingPool, name); + super(refCountingPool, name, Integer.getInteger("amqj.server.read_write_pool.max_write_events", MAX_JOB_EVENTS)); } /** @@ -431,7 +488,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) { - fireAsynchEvent(session, new Event.WriteEvent(nextFilter, writeRequest)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new Event.WriteEvent(nextFilter, writeRequest)); } /** @@ -442,7 +500,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void sessionClosed(final NextFilter nextFilter, final IoSession session) { - fireAsynchEvent(session, new CloseEvent(nextFilter)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new CloseEvent(nextFilter)); } } } diff --git a/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java b/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java index 2fbeeda1d4..5a7679a972 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java @@ -21,6 +21,7 @@ package org.apache.qpid.protocol; import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.AMQException; /** * AMQMethodListener is a listener that receives notifications of AMQP methods. The methods are packaged as events in @@ -57,7 +58,7 @@ public interface AMQMethodListener * * @todo Consider narrowing the exception. */ - <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws Exception; + <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException; /** * Notifies the listener of an error on the event context to which it is listening. The listener should perform diff --git a/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java b/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java index 7c1d6fdaa0..b56a05f725 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java @@ -20,7 +20,8 @@ */
package org.apache.qpid.protocol;
-import org.apache.qpid.framing.VersionSpecificRegistry;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.AMQException;
/**
* AMQVersionAwareProtocolSession is implemented by all AMQP session classes, that need to provide an awareness to
@@ -42,5 +43,15 @@ public interface AMQVersionAwareProtocolSession extends AMQProtocolWriter, Proto *
* @return The method registry for a specific version of the AMQP.
*/
- public VersionSpecificRegistry getRegistry();
+// public VersionSpecificRegistry getRegistry();
+
+ MethodRegistry getMethodRegistry();
+
+
+ public void methodFrameReceived(int channelId, AMQMethodBody body) throws AMQException;
+ public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException;
+ public void contentBodyReceived(int channelId, ContentBody body) throws AMQException;
+ public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException;
+
+
}
diff --git a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java index 60a7f30185..dea80cdcf4 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java @@ -20,6 +20,8 @@ */
package org.apache.qpid.protocol;
+import org.apache.qpid.framing.ProtocolVersion;
+
/**
* ProtocolVersionAware is implemented by all AMQP handling classes, that need to provide an awareness to callers of
* the version of the AMQP protocol that they are able to handle.
@@ -32,6 +34,7 @@ package org.apache.qpid.protocol; public interface ProtocolVersionAware
{
/**
+ * @deprecated
* Reports the AMQP minor version, that the implementer can handle.
*
* @return The AMQP minor version.
@@ -39,9 +42,12 @@ public interface ProtocolVersionAware public byte getProtocolMinorVersion();
/**
+ * @deprecated
* Reports the AMQP major version, that the implementer can handle.
*
* @return The AMQP major version.
*/
public byte getProtocolMajorVersion();
+
+ public ProtocolVersion getProtocolVersion();
}
diff --git a/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java b/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java index 461cf9591d..633cf4fe3a 100644 --- a/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java +++ b/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java @@ -215,6 +215,14 @@ public class ConcurrentLinkedMessageQueueAtomicSize<E> extends ConcurrentLinkedQ public void remove() { last.remove(); + if(last == _mainIterator) + { + _size.decrementAndGet(); + } + else + { + _messageHeadSize.decrementAndGet(); + } } }; } diff --git a/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java new file mode 100644 index 0000000000..b93dc46741 --- /dev/null +++ b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java @@ -0,0 +1,396 @@ +/* + * + * 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.mina.SocketIOTest; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.CloseFuture; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; + +public class IOWriterClient implements Runnable +{ + private static final Logger _logger = LoggerFactory.getLogger(IOWriterClient.class); + + public static int DEFAULT_TEST_SIZE = 2; + + private IoSession _session; + + private long _startTime; + + private long[] _chunkTimes; + + public int _chunkCount = 200000; + + private int _chunkSize = 1024; + + private CountDownLatch _notifier; + + private int _maximumWriteQueueLength; + + static public int _PORT = IOWriterServer._PORT; + + public void run() + { + _logger.info("Starting to send " + _chunkCount + " buffers of " + _chunkSize + "B"); + _startTime = System.currentTimeMillis(); + _notifier = new CountDownLatch(1); + + for (int i = 0; i < _chunkCount; i++) + { + ByteBuffer buf = ByteBuffer.allocate(_chunkSize, false); + byte check = (byte) (i % 128); + buf.put(check); + buf.fill((byte) 88, buf.remaining()); + buf.flip(); + + _session.write(buf); + } + + long _sentall = System.currentTimeMillis(); + long _receivedall = _sentall; + try + { + _logger.info("All buffers sent; waiting for receipt from server"); + _notifier.await(); + _receivedall = System.currentTimeMillis(); + } + catch (InterruptedException e) + { + //Ignore + } + _logger.info("Completed"); + _logger.info("Total time waiting for server after last write: " + (_receivedall - _sentall)); + + long totalTime = System.currentTimeMillis() - _startTime; + + _logger.info("Total time: " + totalTime); + _logger.info("MB per second: " + (int) ((1.0 * _chunkSize * _chunkCount) / totalTime)); + long lastChunkTime = _startTime; + double average = 0; + for (int i = 0; i < _chunkTimes.length; i++) + { + if (i == 0) + { + average = _chunkTimes[i] - _startTime; + } + else + { + long delta = _chunkTimes[i] - lastChunkTime; + if (delta != 0) + { + average = (average + delta) / 2; + } + } + lastChunkTime = _chunkTimes[i]; + } + _logger.info("Average chunk time: " + average + "ms"); + _logger.info("Maximum WriteRequestQueue size: " + _maximumWriteQueueLength); + + CloseFuture cf = _session.close(); + _logger.info("Closing session"); + cf.join(); + } + + private class WriterHandler extends IoHandlerAdapter + { + private int _chunksReceived = 0; + + private int _partialBytesRead = 0; + + private byte _partialCheckNumber; + + private int _totalBytesReceived = 0; + + private int _receivedCount = 0; + private int _sentCount = 0; + private static final String DEFAULT_READ_BUFFER = "262144"; + private static final String DEFAULT_WRITE_BUFFER = "262144"; + + public void sessionCreated(IoSession session) throws Exception + { + IoFilterChain chain = session.getFilterChain(); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER))); + + writefilter.attach(chain); + } + + public void messageSent(IoSession session, Object message) throws Exception + { + _maximumWriteQueueLength = Math.max(session.getScheduledWriteRequests(), _maximumWriteQueueLength); + + if (_logger.isDebugEnabled()) + { + ++_sentCount; + if (_sentCount % 1000 == 0) + { + _logger.debug("Sent count " + _sentCount + ":WQueue" + session.getScheduledWriteRequests()); + + } + } + } + + public void messageReceived(IoSession session, Object message) throws Exception + { + if (_logger.isDebugEnabled()) + { + ++_receivedCount; + + if (_receivedCount % 1000 == 0) + { + _logger.debug("Receieved count " + _receivedCount); + } + } + + ByteBuffer result = (ByteBuffer) message; + _totalBytesReceived += result.remaining(); + int size = result.remaining(); + long now = System.currentTimeMillis(); + if (_partialBytesRead > 0) + { + int offset = _chunkSize - _partialBytesRead; + if (size >= offset) + { + _chunkTimes[_chunksReceived++] = now; + result.position(offset); + } + else + { + // have not read even one chunk, including the previous partial bytes + _partialBytesRead += size; + return; + } + } + + + int chunkCount = result.remaining() / _chunkSize; + + for (int i = 0; i < chunkCount; i++) + { + _chunkTimes[_chunksReceived++] = now; + byte check = result.get(); + _logger.debug("Check number " + check + " read"); + if (check != (byte) ((_chunksReceived - 1) % 128)) + { + _logger.error("Check number " + check + " read when expected " + (_chunksReceived % 128)); + } + _logger.debug("Chunk times recorded"); + + try + { + result.skip(_chunkSize - 1); + } + catch (IllegalArgumentException e) + { + _logger.error("Position was: " + result.position()); + _logger.error("Tried to skip to: " + (_chunkSize * i)); + _logger.error("limit was; " + result.limit()); + } + } + _logger.debug("Chunks received now " + _chunksReceived); + _logger.debug("Bytes received: " + _totalBytesReceived); + _partialBytesRead = result.remaining(); + + if (_partialBytesRead > 0) + { + _partialCheckNumber = result.get(); + } + + + if (_chunksReceived >= _chunkCount) + { + _notifier.countDown(); + } + + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + _logger.error("Error: " + cause, cause); + } + } + + public void startWriter() throws IOException, InterruptedException + { + + _maximumWriteQueueLength = 0; + + IoConnector ioConnector = null; + + if (Boolean.getBoolean("multinio")) + { + _logger.warn("Using MultiThread NIO"); + ioConnector = new org.apache.mina.transport.socket.nio.MultiThreadSocketConnector(); + } + else + { + _logger.warn("Using MINA NIO"); + ioConnector = new org.apache.mina.transport.socket.nio.SocketConnector(); + } + + SocketSessionConfig scfg = (SocketSessionConfig) ioConnector.getDefaultConfig().getSessionConfig(); + scfg.setTcpNoDelay(true); + scfg.setSendBufferSize(32768); + scfg.setReceiveBufferSize(32768); + + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + + + final InetSocketAddress address = new InetSocketAddress("localhost", _PORT); + _logger.info("Attempting connection to " + address); + + //Old mina style +// ioConnector.setHandler(new WriterHandler()); +// ConnectFuture future = ioConnector.connect(address); + ConnectFuture future = ioConnector.connect(address, new WriterHandler()); + // wait for connection to complete + future.join(); + _logger.info("Connection completed"); + // we call getSession which throws an IOException if there has been an error connecting + _session = future.getSession(); + + _chunkTimes = new long[_chunkCount]; + Thread t = new Thread(this); + t.start(); + t.join(); + _logger.info("Test Complete"); + } + + + public void test1k() throws IOException, InterruptedException + { + _logger.info("Starting 1k test"); + _chunkSize = 1024; + startWriter(); + } + + + public void test2k() throws IOException, InterruptedException + { + _logger.info("Starting 2k test"); + _chunkSize = 2048; + startWriter(); + } + + + public void test4k() throws IOException, InterruptedException + { + _logger.info("Starting 4k test"); + _chunkSize = 4096; + startWriter(); + } + + + public void test8k() throws IOException, InterruptedException + { + _logger.info("Starting 8k test"); + _chunkSize = 8192; + startWriter(); + } + + + public void test16k() throws IOException, InterruptedException + { + _logger.info("Starting 16k test"); + _chunkSize = 16384; + startWriter(); + } + + + public void test32k() throws IOException, InterruptedException + { + _logger.info("Starting 32k test"); + _chunkSize = 32768; + startWriter(); + } + + + public static int getIntArg(String[] args, int index, int defaultValue) + { + if (args.length > index) + { + try + { + return Integer.parseInt(args[index]); + } + catch (NumberFormatException e) + { + //Do nothing + } + } + return defaultValue; + } + + public static void main(String[] args) throws IOException, InterruptedException + { + _PORT = getIntArg(args, 0, _PORT); + + int test = getIntArg(args, 1, DEFAULT_TEST_SIZE); + + IOWriterClient w = new IOWriterClient(); + w._chunkCount = getIntArg(args, 2, w._chunkCount); + switch (test) + { + case 0: + w.test1k(); + w.test2k(); + w.test4k(); + w.test8k(); + w.test16k(); + w.test32k(); + break; + case 1: + w.test1k(); + break; + case 2: + w.test2k(); + break; + case 4: + w.test4k(); + break; + case 8: + w.test8k(); + break; + case 16: + w.test16k(); + break; + case 32: + w.test32k(); + break; + } + } +} diff --git a/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java new file mode 100644 index 0000000000..423e98c67b --- /dev/null +++ b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java @@ -0,0 +1,157 @@ +/* + * + * 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.mina.SocketIOTest; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; + +/** Tests MINA socket performance. This acceptor simply reads data from the network and writes it back again. */ +public class IOWriterServer +{ + private static final Logger _logger = LoggerFactory.getLogger(IOWriterServer.class); + + static public int _PORT = 9999; + + private static final String DEFAULT_READ_BUFFER = "262144"; + private static final String DEFAULT_WRITE_BUFFER = "262144"; + + + private static class TestHandler extends IoHandlerAdapter + { + private int _sentCount = 0; + + private int _bytesSent = 0; + + private int _receivedCount = 0; + + public void sessionCreated(IoSession ioSession) throws java.lang.Exception + { + IoFilterChain chain = ioSession.getFilterChain(); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER))); + + writefilter.attach(chain); + + } + + public void messageReceived(IoSession session, Object message) throws Exception + { + ((ByteBuffer) message).acquire(); + session.write(message); + + if (_logger.isDebugEnabled()) + { + _bytesSent += ((ByteBuffer) message).remaining(); + + _sentCount++; + + if (_sentCount % 1000 == 0) + { + _logger.debug("Bytes sent: " + _bytesSent); + } + } + } + + public void messageSent(IoSession session, Object message) throws Exception + { + if (_logger.isDebugEnabled()) + { + ++_receivedCount; + + if (_receivedCount % 1000 == 0) + { + _logger.debug("Receieved count " + _receivedCount); + } + } + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + _logger.error("Error: " + cause, cause); + } + } + + public void startAcceptor() throws IOException + { + IoAcceptor acceptor; + if (Boolean.getBoolean("multinio")) + { + _logger.warn("Using MultiThread NIO"); + acceptor = new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(); + } + else + { + _logger.warn("Using MINA NIO"); + acceptor = new org.apache.mina.transport.socket.nio.SocketAcceptor(); + } + + + SocketSessionConfig sc = (SocketSessionConfig) acceptor.getDefaultConfig().getSessionConfig(); + sc.setTcpNoDelay(true); + sc.setSendBufferSize(32768); + sc.setReceiveBufferSize(32768); + + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + + //The old mina style +// acceptor.setLocalAddress(new InetSocketAddress(_PORT)); +// acceptor.setHandler(new TestHandler()); +// acceptor.bind(); + acceptor.bind(new InetSocketAddress(_PORT), new TestHandler()); + + _logger.info("Bound on port " + _PORT + ":" + _logger.isDebugEnabled()); + _logger.debug("debug on"); + } + + public static void main(String[] args) throws IOException + { + + if (args.length > 0) + { + try + { + _PORT = Integer.parseInt(args[0]); + } + catch (NumberFormatException e) + { + //IGNORE so use default port 9999; + } + } + + IOWriterServer a = new IOWriterServer(); + a.startAcceptor(); + } +} diff --git a/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java b/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java new file mode 100644 index 0000000000..92e7ce0a80 --- /dev/null +++ b/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.framing; + +import junit.framework.TestCase; +public class AMQShortStringTest extends TestCase +{ + + public static final AMQShortString HELLO = new AMQShortString("Hello"); + public static final AMQShortString HELL = new AMQShortString("Hell"); + public static final AMQShortString GOODBYE = new AMQShortString("Goodbye"); + public static final AMQShortString GOOD = new AMQShortString("Good"); + public static final AMQShortString BYE = new AMQShortString("BYE"); + + public void testStartsWith() + { + assertTrue(HELLO.startsWith(HELL)); + + assertFalse(HELL.startsWith(HELLO)); + + assertTrue(GOODBYE.startsWith(GOOD)); + + assertFalse(GOOD.startsWith(GOODBYE)); + } + + public void testEndWith() + { + assertFalse(HELL.endsWith(HELLO)); + + assertTrue(GOODBYE.endsWith(new AMQShortString("bye"))); + + assertFalse(GOODBYE.endsWith(BYE)); + } + + + public void testTokenize() + { + AMQShortString dotSeparatedWords = new AMQShortString("this.is.a.test.with.1.2.3.-numbers-and-then--dashes-"); + AMQShortStringTokenizer dotTokenizer = dotSeparatedWords.tokenize((byte) '.'); + + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("this"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("is"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("a"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("test"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("with"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 1); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 2); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 3); + assertTrue(dotTokenizer.hasMoreTokens()); + AMQShortString dashString = dotTokenizer.nextToken(); + assertEquals(new AMQShortString("-numbers-and-then--dashes-"),(dashString)); + + AMQShortStringTokenizer dashTokenizer = dashString.tokenize((byte)'-'); + assertEquals(dashTokenizer.countTokens(), 7); + + AMQShortString[] expectedResults = new AMQShortString[] + { AMQShortString.EMPTY_STRING, + new AMQShortString("numbers"), + new AMQShortString("and"), + new AMQShortString("then"), + AMQShortString.EMPTY_STRING, + new AMQShortString("dashes"), + AMQShortString.EMPTY_STRING }; + + for(int i = 0; i < 7; i++) + { + assertTrue(dashTokenizer.hasMoreTokens()); + assertEquals(dashTokenizer.nextToken(), expectedResults[i]); + } + + assertFalse(dotTokenizer.hasMoreTokens()); + } + + + public void testEquals() + { + assertEquals(GOODBYE, new AMQShortString("Goodbye")); + assertEquals(new AMQShortString("A"), new AMQShortString("A")); + assertFalse(new AMQShortString("A").equals(new AMQShortString("a"))); + } + + +} diff --git a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java index e63b0df770..007da7423e 100644 --- a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java +++ b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java @@ -1,21 +1,21 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 * - * 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. + * 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.framing; @@ -439,6 +439,60 @@ public class PropertyFieldTableTest extends TestCase Assert.assertEquals("Hello", table1.getString("value")); } + /** Check that a nested field table parameter correctly encodes and decodes to a byte buffer. */ + public void testNestedFieldTable() + { + byte[] testBytes = new byte[] { 0, 1, 2, 3, 4, 5 }; + + FieldTable outerTable = new FieldTable(); + FieldTable innerTable = new FieldTable(); + + // Put some stuff in the inner table. + innerTable.setBoolean("bool", true); + innerTable.setByte("byte", Byte.MAX_VALUE); + innerTable.setBytes("bytes", testBytes); + innerTable.setChar("char", 'c'); + innerTable.setDouble("double", Double.MAX_VALUE); + innerTable.setFloat("float", Float.MAX_VALUE); + innerTable.setInteger("int", Integer.MAX_VALUE); + innerTable.setLong("long", Long.MAX_VALUE); + innerTable.setShort("short", Short.MAX_VALUE); + innerTable.setString("string", "hello"); + innerTable.setString("null-string", null); + + // Put the inner table in the outer one. + outerTable.setFieldTable("innerTable", innerTable); + + // Write the outer table into the buffer. + final ByteBuffer buffer = ByteBuffer.allocate((int) outerTable.getEncodedSize() + 4); + outerTable.writeToBuffer(buffer); + buffer.flip(); + + // Extract the table back from the buffer again. + try + { + FieldTable extractedOuterTable = EncodingUtils.readFieldTable(buffer); + + FieldTable extractedTable = extractedOuterTable.getFieldTable("innerTable"); + + Assert.assertEquals((Boolean) true, extractedTable.getBoolean("bool")); + Assert.assertEquals((Byte) Byte.MAX_VALUE, extractedTable.getByte("byte")); + assertBytesEqual(testBytes, extractedTable.getBytes("bytes")); + Assert.assertEquals((Character) 'c', extractedTable.getCharacter("char")); + Assert.assertEquals(Double.MAX_VALUE, extractedTable.getDouble("double")); + Assert.assertEquals(Float.MAX_VALUE, extractedTable.getFloat("float")); + Assert.assertEquals((Integer) Integer.MAX_VALUE, extractedTable.getInteger("int")); + Assert.assertEquals((Long) Long.MAX_VALUE, extractedTable.getLong("long")); + Assert.assertEquals((Short) Short.MAX_VALUE, extractedTable.getShort("short")); + Assert.assertEquals("hello", extractedTable.getString("string")); + Assert.assertEquals(null, extractedTable.getString("null-string")); + } + catch (AMQFrameDecodingException e) + { + fail("Failed to decode field table with nested inner table."); + } + } + public void testValues() { FieldTable table = new FieldTable(); diff --git a/java/common/templates/method/MethodBodyClass.tmpl b/java/common/templates/method/MethodBodyClass.tmpl deleted file mode 100644 index eb730fd891..0000000000 --- a/java/common/templates/method/MethodBodyClass.tmpl +++ /dev/null @@ -1,183 +0,0 @@ -&{${CLASS}${METHOD}Body.java} -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - * - */ - -/* - * This file is auto-generated by ${GENERATOR} - do not modify. - * Supported AMQP versions: -%{VLIST} * ${major}-${minor} - */ - -package org.apache.qpid.framing; - -import java.util.HashMap; - -import org.apache.mina.common.ByteBuffer; - -public class ${CLASS}${METHOD}Body extends AMQMethodBody implements EncodableAMQDataBlock -{ - private static final AMQMethodBodyInstanceFactory factory = new AMQMethodBodyInstanceFactory() - { - public AMQMethodBody newInstance(byte major, byte minor, ByteBuffer in, long size) throws AMQFrameDecodingException - { - return new ${CLASS}${METHOD}Body(major, minor, in); - } - - public AMQMethodBody newInstance(byte major, byte minor, int clazzID, int methodID, ByteBuffer in, long size) throws AMQFrameDecodingException - { - return new ${CLASS}${METHOD}Body(major, minor, clazzID, methodID, in); - } - - }; - - public static AMQMethodBodyInstanceFactory getFactory() - { - return factory; - } - - public static HashMap<Integer, Integer> classIdMap = new HashMap<Integer, Integer>(); - public static HashMap<Integer, Integer> methodIdMap = new HashMap<Integer, Integer>(); - - private static void registerMethodId(byte major, byte minor, int methodId) - { - methodIdMap.put((0xff & (int) major) | ((0xff & (int) minor)<<8), methodId); - } - - private static void registerClassId(byte major, byte minor, int classId) - { - classIdMap.put((0xff & (int) major) | ((0xff & (int) minor)<<8), classId); - } - - - static - { - - ${CLASS_ID_INIT} - ${METHOD_ID_INIT} - - } - - // Fields declared in specification -%{FLIST} ${field_declaration} - - private final int _clazz; - private final int _method; - - - // Constructor - - public ${CLASS}${METHOD}Body(byte major, byte minor, ByteBuffer buffer) throws AMQFrameDecodingException - { - this(major, minor, getClazz(major,minor), getMethod(major,minor), buffer); - } - - public ${CLASS}${METHOD}Body(byte major, byte minor, int clazzID, int methodID, ByteBuffer buffer) throws AMQFrameDecodingException - { - - super(major, minor); - _clazz = clazzID; - _method = methodID; - %{FLIST} ${mb_field_decode} - } - public ${CLASS}${METHOD}Body(byte major, byte minor, int clazzID, int methodID - %{FLIST} ${mb_field_parameter_list} - ) - { - super(major, minor); - _clazz = getClazz(major,minor); - _method = getMethod(major,minor); - %{FLIST} ${mb_field_body_initialize} - } - - public int getClazz() - { - return _clazz; - } - - public int getMethod() - { - return _method; - } - - public static int getClazz(byte major, byte minor) - { - return classIdMap.get((0xff & (int) major) | ((0xff & (int) minor)<<8)); - } - - public static int getMethod(byte major, byte minor) - { - return methodIdMap.get((0xff & (int) major) | ((0xff & (int) minor)<<8)); - } - - - // Field methods -%{FLIST} ${mb_field_get_method} - - protected int getBodySize() - { - int size = 0; -%{FLIST} ${mb_field_size} - return size; - } - - protected void writeMethodPayload(ByteBuffer buffer) - { -%{FLIST} ${mb_field_encode} - } - - public void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException - { -%{FLIST} ${mb_field_decode} - } - - public String toString() - { - StringBuffer buf = new StringBuffer(super.toString()); -%{FLIST} ${mb_field_to_string} - return buf.toString(); - } - - public static AMQFrame createAMQFrame(int channelId, byte major, byte minor -%{FLIST} ${mb_field_parameter_list} - ) - { - return createAMQFrame(channelId, major, minor, getClazz(major,minor), getMethod(major,minor) -%{FLIST} ${mb_field_passed_parameter_list} - ); - - - - } - - public static AMQFrame createAMQFrame(int channelId, byte major, byte minor, int clazzID, int methodID -%{FLIST} ${mb_field_parameter_list} - ) - { - ${CLASS}${METHOD}Body body = new ${CLASS}${METHOD}Body(major, minor, clazzID, methodID -%{FLIST} ${mb_field_passed_parameter_list} - ); - - - AMQFrame frame = new AMQFrame(channelId, body); - return frame; - } - -} diff --git a/java/common/templates/method/MethodBodyInterface.vm b/java/common/templates/method/MethodBodyInterface.vm new file mode 100644 index 0000000000..d5feba12de --- /dev/null +++ b/java/common/templates/method/MethodBodyInterface.vm @@ -0,0 +1,62 @@ +#macro( UpperCamel $name ) +#set( $name = "${name.substring(0,1).toUpperCase()}${name.substring(1)}" ) +#end +#macro( toUpperCamel $name )${name.substring(0,1).toUpperCase()}${name.substring(1)}#end + + + +#set( $amqp_ClassName = $amqpClass.Name) +#UpperCamel( $amqp_ClassName ) +#set( $amqp_MethodName = $amqpMethod.Name ) +#UpperCamel( $amqp_MethodName ) +#set( $javaClassName = "${amqp_ClassName}${amqp_MethodName}Body" ) + + +#set( $filename = "${javaClassName}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by ${generator} - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + + +package org.apache.qpid.framing; + + +public interface ${javaClassName} extends EncodableAMQDataBlock, AMQMethodBody +{ + + +#foreach( $field in $amqpMethod.Fields ) + +#if( $amqpMethod.isCommon( $field ) ) + public $field.ConsistentNativeType get#toUpperCamel( ${field.Name} )(); +#end +#end + + + +} diff --git a/java/common/templates/method/version/MethodBodyClass.vm b/java/common/templates/method/version/MethodBodyClass.vm new file mode 100644 index 0000000000..9b2ba0fa39 --- /dev/null +++ b/java/common/templates/method/version/MethodBodyClass.vm @@ -0,0 +1,209 @@ +#macro( UpperCamel $name ) +#set( $name = "${name.substring(0,1).toUpperCase()}${name.substring(1)}" ) +#end +#macro( toUpperCamel $name )${name.substring(0,1).toUpperCase()}${name.substring(1)}#end + + + +#set( $amqp_ClassName = $amqpClass.Name) +#UpperCamel( $amqp_ClassName ) +#set( $amqp_MethodName = $amqpMethod.Name ) +#UpperCamel( $amqp_MethodName ) +#set( $javaClassName = "${amqp_ClassName}${amqp_MethodName}BodyImpl" ) +#set( $interfaceName = "${amqp_ClassName}${amqp_MethodName}Body" ) +#set( $amqpPackageName = "amqp_$version.getMajor()_$version.getMinor()" ) + +#set( $filename = "${amqpPackageName}/${javaClassName}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by ${generator} - do not modify. + * Supported AMQP version: + * $version.getMajor()-$version.getMinor() + */ + +#set( $clazz = $amqpClass.asSingleVersionClass( $version ) ) +#set( $method = $amqpMethod.asSingleVersionMethod( $version ) ) + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import java.util.HashMap; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.*; +import org.apache.qpid.AMQException; + +public class ${javaClassName} extends AMQMethodBody_$version.getMajor()_$version.getMinor() implements $interfaceName +{ + private static final AMQMethodBodyInstanceFactory FACTORY_INSTANCE = new AMQMethodBodyInstanceFactory() + { + public AMQMethodBody newInstance(ByteBuffer in, long size) throws AMQFrameDecodingException + { + return new ${javaClassName}(in); + } + + + }; + + + public static AMQMethodBodyInstanceFactory getFactory() + { + return FACTORY_INSTANCE; + } + + public static int CLASS_ID = $clazz.ClassId; + + public static int METHOD_ID = $method.MethodId; + + + + // Fields declared in specification +#foreach( $field in $method.ConsolidatedFields ) + private final $field.NativeType _$field.getName(); // $field.UnderlyingFields +#end + + + // Constructor + + public ${javaClassName}(ByteBuffer buffer) throws AMQFrameDecodingException + { +#foreach( $field in $method.ConsolidatedFields ) + _$field.Name = read$field.getEncodingType()( buffer ); +#end + } + + public ${javaClassName}( +#foreach( $field in $method.FieldList ) +#if( $velocityCount == $method.getFieldList().size() ) + $field.NativeType $field.Name +#else + $field.NativeType $field.Name, +#end +#end + ) + { +#set( $consolidatedFieldName = "" ) +#foreach( $field in $method.FieldList ) +#if( $method.isConsolidated( $field.Name ) ) +#if( !$method.getConsolidatedFieldName( $field.Name ).equals( $consolidatedFieldName ) ) +#if( !$consolidatedFieldName.equals("") ) + _$consolidatedFieldName = $consolidatedFieldName; // 1 +#end +#set( $consolidatedFieldName = $method.getConsolidatedFieldName( $field.Name ) ) + byte $consolidatedFieldName = (byte)0; +#end + if( $field.Name ) + { + $consolidatedFieldName = (byte) (((int) $consolidatedFieldName) | (1 << $method.getPositionInBitField( $field.Name ))); + } +#if( $velocityCount == $method.getFieldList().size()) + _$consolidatedFieldName = $consolidatedFieldName; +#else + +#end +#else +#if( !$consolidatedFieldName.equals("") ) + _$consolidatedFieldName = $consolidatedFieldName; +#end +#set( $consolidatedFieldName = "" ) + _$field.Name = $field.Name; +#end +#end + } + + public int getClazz() + { + return CLASS_ID; + } + + public int getMethod() + { + return METHOD_ID; + } + + +#foreach( $field in $method.FieldList ) + public final $field.NativeType get#toUpperCamel( ${field.Name} )() + { +#if( $method.isConsolidated( $field.Name ) ) + return (((int)(_$method.getConsolidatedFieldName( $field.Name ))) & ( 1 << $method.getPositionInBitField( $field.Name ))) != 0; +#else + return _$field.Name; +#end + } +#end + + protected int getBodySize() + { +#set( $fixedSize = 0 ) +#foreach( $field in $method.ConsolidatedFields ) +#if( $field.isFixedSize() ) +#set( $fixedSize = $fixedSize + $field.Size ) +#end +#end + int size = $fixedSize; +#foreach( $field in $method.ConsolidatedFields ) +#if( ! $field.isFixedSize() ) + size += getSizeOf( _$field.Name ); +#end +#end + return size; + } + + public void writeMethodPayload(ByteBuffer buffer) + { +#foreach( $field in $method.ConsolidatedFields ) + write$field.getEncodingType()( buffer, _$field.Name ); +#end + } + + public boolean execute(MethodDispatcher dispatcher, int channelId) throws AMQException + { +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) +#if( $amqpMethod.inAllVersions() ) + return dispatcher.dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(this, channelId); +#else + return ((MethodDispatcher_$version.getMajor()_$version.getMinor())dispatcher).dispatch${amqp_ClassName}${amqpMethodNameUpperCamel}(this, channelId); + +#end + + } + + + public String toString() + { + StringBuilder buf = new StringBuilder("[$javaClassName: "); +#foreach( $field in $method.FieldList ) + buf.append( "$field.Name=" ); + buf.append( get#toUpperCamel( $field.Name )() ); +#if( $velocityCount != $method.FieldList.size() ) + buf.append( ", " ); +#end +#end + buf.append("]"); + return buf.toString(); + } + + +} diff --git a/java/common/templates/model/ClientMethodDispatcherInterface.vm b/java/common/templates/model/ClientMethodDispatcherInterface.vm new file mode 100644 index 0000000000..9e4aee7dee --- /dev/null +++ b/java/common/templates/model/ClientMethodDispatcherInterface.vm @@ -0,0 +1,56 @@ +#set( $filename = "ClientMethodDispatcher.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.qpid.AMQException; + + +public interface ClientMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#if( $amqpMethodVersions.inAllVersions() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isClientMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end +#end + +}
\ No newline at end of file diff --git a/java/common/templates/model/MethodDispatcherInterface.vm b/java/common/templates/model/MethodDispatcherInterface.vm new file mode 100644 index 0000000000..ff14715fef --- /dev/null +++ b/java/common/templates/model/MethodDispatcherInterface.vm @@ -0,0 +1,39 @@ +#set( $filename = "MethodDispatcher.java")
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+/*
+ * This file is auto-generated by $generator - do not modify.
+ * Supported AMQP version:
+ #foreach( $supportedVersion in $model.VersionSet )
+ * $supportedVersion.getMajor()-$supportedVersion.getMinor()
+ #end
+ */
+
+package org.apache.qpid.framing;
+
+import org.apache.qpid.AMQException;
+
+
+public interface MethodDispatcher extends
+ ClientMethodDispatcher, ServerMethodDispatcher
+{
+}
\ No newline at end of file diff --git a/java/common/templates/model/MethodRegistryClass.tmpl b/java/common/templates/model/MethodRegistryClass.tmpl deleted file mode 100644 index 474d9e31d1..0000000000 --- a/java/common/templates/model/MethodRegistryClass.tmpl +++ /dev/null @@ -1,159 +0,0 @@ -&{MainRegistry.java}
-/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you 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.
- *
- */
-
-/*
- * This file is auto-generated by ${GENERATOR} - do not modify.
- * Supported AMQP versions:
-%{VLIST} * ${major}-${minor}
- */
-
-package org.apache.qpid.framing;
-
-import java.util.HashMap;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.mina.common.ByteBuffer;
-
-public class MainRegistry
-{
- private static final HashMap<Long, AMQMethodBodyInstanceFactory> classIDMethodIDVersionBodyMap = new HashMap<Long, AMQMethodBodyInstanceFactory>();
-
-
- private static final Logger _log = LoggerFactory.getLogger(MainRegistry.class);
-
-
- private static final int DEFAULT_MINOR_VERSION_COUNT = 10;
- private static final int DEFAULT_MAJOR_VERSION_COUNT = 10;
-
- private static VersionSpecificRegistry[][] _specificRegistries = new VersionSpecificRegistry[DEFAULT_MAJOR_VERSION_COUNT][];
-
- static
- {
-%{CLIST} ${reg_map_put_method}
-
- configure();
- }
-
- public static AMQMethodBody get(short classID, short methodID, byte major, byte minor, ByteBuffer in, long size)
- throws AMQFrameDecodingException
- {
- VersionSpecificRegistry registry = getVersionSpecificRegistry(major, minor);
- AMQMethodBodyInstanceFactory bodyFactory = registry.getMethodBody(classID,methodID);
-
- if (bodyFactory == null)
- {
- throw new AMQFrameDecodingException(null,
- "Unable to find a suitable decoder for class " + classID + " and method " +
- methodID + " in AMQP version " + major + "-" + minor + ".", null);
- }
- return bodyFactory.newInstance(major, minor, in, size);
-
-
- }
-
-
- public static VersionSpecificRegistry getVersionSpecificRegistry(ProtocolVersion pv)
- {
- return getVersionSpecificRegistry(pv.getMajorVersion(), pv.getMinorVersion());
- }
- public static VersionSpecificRegistry getVersionSpecificRegistry(byte major, byte minor)
- {
- try
- {
- return _specificRegistries[(int)major][(int)minor];
- }
- catch (IndexOutOfBoundsException e)
- {
- return null;
- }
- catch (NullPointerException e)
- {
- return null;
- }
-
-
- }
-
- private static VersionSpecificRegistry addVersionSpecificRegistry(byte major, byte minor)
- {
- VersionSpecificRegistry[][] registries = _specificRegistries;
- if(major >= registries.length)
- {
- _specificRegistries = new VersionSpecificRegistry[(int)major + 1][];
- System.arraycopy(registries, 0, _specificRegistries, 0, registries.length);
- registries = _specificRegistries;
- }
- if(registries[major] == null)
- {
- registries[major] = new VersionSpecificRegistry[ minor >= DEFAULT_MINOR_VERSION_COUNT ? minor + 1 : DEFAULT_MINOR_VERSION_COUNT ];
- }
- else if(registries[major].length <= minor)
- {
- VersionSpecificRegistry[] minorArray = registries[major];
- registries[major] = new VersionSpecificRegistry[ minor + 1 ];
- System.arraycopy(minorArray, 0, registries[major], 0, minorArray.length);
-
- }
-
- VersionSpecificRegistry newRegistry = new VersionSpecificRegistry(major,minor);
-
- registries[major][minor] = newRegistry;
-
- return newRegistry;
- }
-
- private static void registerMethod(short classID, short methodID, byte major, byte minor, AMQMethodBodyInstanceFactory instanceFactory )
- {
- VersionSpecificRegistry registry = getVersionSpecificRegistry(major,minor);
- if(registry == null)
- {
- registry = addVersionSpecificRegistry(major,minor);
-
- }
-
- registry.registerMethod(classID, methodID, instanceFactory);
-
- }
-
-
- private static void configure()
- {
- for(int i = 0 ; i < _specificRegistries.length; i++)
- {
- VersionSpecificRegistry[] registries = _specificRegistries[i];
- if(registries != null)
- {
- for(int j = 0 ; j < registries.length; j++)
- {
- VersionSpecificRegistry registry = registries[j];
-
- if(registry != null)
- {
- registry.configure();
- }
- }
- }
- }
-
- }
-
-}
diff --git a/java/common/templates/model/MethodRegistryClass.vm b/java/common/templates/model/MethodRegistryClass.vm new file mode 100644 index 0000000000..759e5e4a42 --- /dev/null +++ b/java/common/templates/model/MethodRegistryClass.vm @@ -0,0 +1,104 @@ +#set( $filename = "MethodRegistry.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; + +import java.util.Map; +import java.util.HashMap; + + +public abstract class MethodRegistry +{ + private static final Map<ProtocolVersion, MethodRegistry> _registries = + new HashMap<ProtocolVersion, MethodRegistry>(); + +#foreach( $supportedVersion in $model.VersionSet ) + +#set( $verName = "_$supportedVersion.getMajor()_$supportedVersion.getMinor()" ) +#set( $regPackage = "org.apache.qpid.framing.amqp$verName" ) + public static final MethodRegistry registry_$supportedVersion.getMajor()_$supportedVersion.getMinor() = + new ${regPackage}.MethodRegistry${verName}(); + +#end + + + public abstract AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException; + + public abstract int getMaxClassId(); + + public abstract int getMaxMethodId(int classId); + + + protected MethodRegistry(ProtocolVersion pv) + { + _registries.put(pv, this); + } + + public static MethodRegistry getMethodRegistry(ProtocolVersion pv) + { + return _registries.get(pv); + } + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#if( $amqpMethodVersions.isVersionInterfaceConsistent() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) + +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public abstract ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body create${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body( +#foreach( $field in $amqpMethod.FieldList ) +#if( $velocityCount == $amqpMethod.getFieldList().size() ) + final $field.NativeType $field.Name +#else + final $field.NativeType $field.Name, +#end +#end + ); + + +#end +#end +#end + + + public abstract ProtocolVersionMethodConverter getProtocolVersionMethodConverter(); + +}
\ No newline at end of file diff --git a/java/common/templates/model/ProtocolVersionListClass.tmpl b/java/common/templates/model/ProtocolVersionListClass.vm index 4a2592d11b..9ac6adfdf5 100644 --- a/java/common/templates/model/ProtocolVersionListClass.tmpl +++ b/java/common/templates/model/ProtocolVersionListClass.vm @@ -1,4 +1,4 @@ -&{ProtocolVersion.java} +#set( $filename = "ProtocolVersion.java" ) /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -21,9 +21,11 @@ */ /* -* This file is auto-generated by ${GENERATOR} - do not modify. +* This file is auto-generated by $generator - do not modify. * Supported AMQP versions: -%{VLIST} * ${major}-${minor} +#foreach( $version in $model.getVersionSet() ) +* $version.getMajor()-$version.getMinor() +#end */ package org.apache.qpid.framing; @@ -31,6 +33,8 @@ package org.apache.qpid.framing; import java.util.SortedSet; import java.util.Collections; import java.util.TreeSet; +import java.util.Map; +import java.util.HashMap; public class ProtocolVersion implements Comparable @@ -59,15 +63,33 @@ public class ProtocolVersion implements Comparable public int compareTo(Object o) { ProtocolVersion pv = (ProtocolVersion) o; - if(getMajorVersion() > pv.getMajorVersion()) + + /* + * 0-8 has it's major and minor numbers the wrong way round (it's actually 8-0)... + * so we need to deal with that case specially + */ + + if((_majorVersion == (byte) 8) && (_minorVersion == (byte) 0)) + { + ProtocolVersion fixedThis = new ProtocolVersion(_minorVersion, _majorVersion); + return fixedThis.compareTo(pv); + } + + if((pv.getMajorVersion() == (byte) 8) && (pv.getMinorVersion() == (byte) 0)) + { + ProtocolVersion fixedOther = new ProtocolVersion(pv.getMinorVersion(), pv.getMajorVersion()); + return this.compareTo(fixedOther); + } + + if(_majorVersion > pv.getMajorVersion()) { return 1; } - else if(getMajorVersion() < pv.getMajorVersion()) + else if(_majorVersion < pv.getMajorVersion()) { return -1; } - else if(getMajorVersion() > pv.getMajorVersion()) + else if(_minorVersion > pv.getMinorVersion()) { return 1; } @@ -104,14 +126,34 @@ public class ProtocolVersion implements Comparable } private static final SortedSet<ProtocolVersion> _supportedVersions; - + private static final Map<String, ProtocolVersion> _nameToVersionMap = + new HashMap<String, ProtocolVersion>(); + private static final ProtocolVersion _defaultVersion; + + +#foreach( $version in $model.getVersionSet() ) +#set( $versionId = "v$version.getMajor()_$version.getMinor()" ) + public static final ProtocolVersion $versionId = new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor()); +#end + static { SortedSet<ProtocolVersion> versions = new TreeSet<ProtocolVersion>(); -%{VLIST} versions.add(new ProtocolVersion((byte)${major},(byte)${minor})); - +#foreach( $version in $model.getVersionSet() ) +#set( $versionId = "v$version.getMajor()_$version.getMinor()" ) + versions.add($versionId); + _nameToVersionMap.put("${version.getMajor()}-${version.getMinor()}", $versionId); +#end _supportedVersions = Collections.unmodifiableSortedSet(versions); + + + ProtocolVersion systemDefinedVersion = + _nameToVersionMap.get(System.getProperty("org.apache.qpid.amqp_version")); + + _defaultVersion = (systemDefinedVersion == null) + ? getLatestSupportedVersion() + : systemDefinedVersion; } @@ -121,7 +163,16 @@ public class ProtocolVersion implements Comparable } + + public static ProtocolVersion parse(String name) + { + return _nameToVersionMap.get(name); + } + public static ProtocolVersion defaultProtocolVersion() + { + return _defaultVersion; + } } diff --git a/java/common/templates/model/ServerMethodDispatcherInterface.vm b/java/common/templates/model/ServerMethodDispatcherInterface.vm new file mode 100644 index 0000000000..b80d6027b7 --- /dev/null +++ b/java/common/templates/model/ServerMethodDispatcherInterface.vm @@ -0,0 +1,56 @@ +#set( $filename = "ServerMethodDispatcher.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.qpid.AMQException; + + +public interface ServerMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#if( $amqpMethodVersions.inAllVersions() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isServerMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end +#end + +}
\ No newline at end of file diff --git a/java/common/templates/model/AmqpConstantsClass.tmpl b/java/common/templates/model/version/AmqpConstantsClass.vm index 8d459f2977..8d459f2977 100644 --- a/java/common/templates/model/AmqpConstantsClass.tmpl +++ b/java/common/templates/model/version/AmqpConstantsClass.vm diff --git a/java/common/templates/model/version/ClientMethodDispatcherInterface.vm b/java/common/templates/model/version/ClientMethodDispatcherInterface.vm new file mode 100644 index 0000000000..80705c1a39 --- /dev/null +++ b/java/common/templates/model/version/ClientMethodDispatcherInterface.vm @@ -0,0 +1,55 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/ClientMethodDispatcher_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; + + +public interface ClientMethodDispatcher_${version.getMajor()}_${version.getMinor()} extends ClientMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isClientMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end + +}
\ No newline at end of file diff --git a/java/common/templates/model/version/MethodDispatcherInterface.vm b/java/common/templates/model/version/MethodDispatcherInterface.vm new file mode 100644 index 0000000000..8a7b667a91 --- /dev/null +++ b/java/common/templates/model/version/MethodDispatcherInterface.vm @@ -0,0 +1,43 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/MethodDispatcher_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; + + +public interface MethodDispatcher_${version.getMajor()}_${version.getMinor()} + extends MethodDispatcher, + ServerMethodDispatcher_${version.getMajor()}_${version.getMinor()}, + ClientMethodDispatcher_${version.getMajor()}_${version.getMinor()} +{ + +}
\ No newline at end of file diff --git a/java/common/templates/model/version/MethodRegistryClass.vm b/java/common/templates/model/version/MethodRegistryClass.vm new file mode 100644 index 0000000000..277605e34b --- /dev/null +++ b/java/common/templates/model/version/MethodRegistryClass.vm @@ -0,0 +1,193 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/MethodRegistry_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + * $version.getMajor()-$version.getMinor() + */ + +package org.apache.qpid.framing.amqp_${version.getMajor()}_${version.getMinor()}; + +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; + + +public class MethodRegistry_$version.getMajor()_$version.getMinor() extends MethodRegistry +{ + + private static final Logger _log = LoggerFactory.getLogger(MethodRegistry.class); + + private ProtocolVersionMethodConverter _protocolVersionConverter = new MethodConverter_$version.getMajor()_$version.getMinor()(); + +#set( $specificModel = $model.asSingleVersionModel() ) + + +#set( $maxClassId = $specificModel.getMaximumClassId()+1 ) + private final AMQMethodBodyInstanceFactory[][] _factories = new AMQMethodBodyInstanceFactory[$maxClassId][]; + + public MethodRegistry_$version.getMajor()_$version.getMinor()() + { + this(new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor())); + } + + public MethodRegistry_$version.getMajor()_$version.getMinor()(ProtocolVersion pv) + { + super(pv); +#foreach( $amqpClass in $specificModel.getClassList() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + + + + // Register method body instance factories for the $amqpClassNameUpperCamel class. + +#set( $maxMethodId = $amqpClass.getMaximumMethodId()+1 ) + _factories[$amqpClass.getClassId()] = new AMQMethodBodyInstanceFactory[$maxMethodId]; + +#foreach( $amqpMethod in $amqpClass.getMethodList() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + _factories[$amqpClass.getClassId()][$amqpMethod.getMethodId()] = ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl.getFactory(); +#end + +#end + + + } + + + public AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException + { + int classId = in.getUnsignedShort(); + int methodId = in.getUnsignedShort(); + + AMQMethodBodyInstanceFactory bodyFactory; + try + { + bodyFactory = _factories[classId][methodId]; + } + catch(NullPointerException e) + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Class " + classId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + } + catch(IndexOutOfBoundsException e) + { + if(classId >= _factories.length) + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Class " + classId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + + } + else + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Method " + methodId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + + } + } + + + if (bodyFactory == null) + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Method " + methodId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + } + + + return bodyFactory.newInstance(in, size); + + + } + + + public int getMaxClassId() + { + return $specificModel.getMaximumClassId(); + } + + public int getMaxMethodId(int classId) + { + return _factories[classId].length - 1; + } + + + +#foreach( $amqpClass in $specificModel.getClassList() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + + +#foreach( $amqpMethod in $amqpClass.getMethodList() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body create${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body( +#foreach( $field in $amqpMethod.FieldList ) +#if( $velocityCount == $amqpMethod.getFieldList().size() ) + final $field.NativeType $field.Name +#else + final $field.NativeType $field.Name, +#end +#end + ) + { + return new ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl( +#foreach( $field in $amqpMethod.FieldList ) +#if( $velocityCount == $amqpMethod.getFieldList().size() ) + $field.Name +#else + $field.Name, +#end +#end + ); + } + +#end + +#end + + + public ProtocolVersionMethodConverter getProtocolVersionMethodConverter() + { + return _protocolVersionConverter; + } + + +} diff --git a/java/common/templates/model/version/ServerMethodDispatcherInterface.vm b/java/common/templates/model/version/ServerMethodDispatcherInterface.vm new file mode 100644 index 0000000000..db388fcc65 --- /dev/null +++ b/java/common/templates/model/version/ServerMethodDispatcherInterface.vm @@ -0,0 +1,55 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/ServerMethodDispatcher_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; + + +public interface ServerMethodDispatcher_${version.getMajor()}_${version.getMinor()} extends ServerMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isServerMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end + +}
\ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index a4e4de5057..084358a6e8 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -48,6 +48,12 @@ under the License. <url>scp://people.apache.org/www/people.apache.org/repo/m2-snapshot-repository</url> </snapshotRepository> + <repository> + <id>apache.incubating</id> + <name>Apache Incubating Repository</name> + <url>scp://people.apache.org/www/people.apache.org/repo/m2-incubating-repository</url> + </repository> + <!-- <snapshotRepository> <uniqueVersion>true</uniqueVersion> @@ -147,19 +153,20 @@ under the License. </properties> <modules> - <module>plugins</module> <module>common</module> <module>broker</module> <module>client</module> + <module>plugins</module> <module>systests</module> <module>perftests</module> <module>integrationtests</module> <module>management/eclipse-plugin</module> <module>client/example</module> <module>client-java14</module> + <module>junit-toolkit</module> + <module>junit-toolkit-maven-plugin</module> </modules> - <build> <resources> @@ -395,37 +402,14 @@ under the License. </executions> </plugin> + <!-- <plugin> - <groupId>uk.co.thebadgerset</groupId> + <groupId>org.apache.qpid</groupId> <artifactId>junit-toolkit-maven-plugin</artifactId> - <version>0.6-SNAPSHOT</version> + <version>0.7.1-SNAPSHOT</version> </plugin> - - <!-- Disabled as plugin crashes on the systest module. - Also, the resulting NOTICE file doesn't include all license info due to missing data in dependant poms. - - <plugin> - <artifactId>maven-remote-resources-plugin</artifactId> - <version>1.0-alpha-5</version> - <executions> - <execution> - <goals> - <goal>process</goal> - </goals> - <configuration> - <resourceBundles> - <resourceBundle>org.apache:apache-incubator-disclaimer-resource-bundle:1.1</resourceBundle> - <resourceBundle>org.apache:apache-jar-resource-bundle:1.2</resourceBundle> - </resourceBundles> - <properties> - <addLicense>true</addLicense> - <projectName>Apache Qpid</projectName> - </properties> - </configuration> - </execution> - </executions> - </plugin--> - </plugins> + --> + </plugins> </pluginManagement> <defaultGoal>install</defaultGoal> @@ -502,18 +486,23 @@ under the License. <dependency> <groupId>org.apache.mina</groupId> <artifactId>mina-core</artifactId> - <version>1.0.0</version> + <version>1.0.1</version> </dependency> <dependency> <groupId>org.apache.mina</groupId> <artifactId>mina-filter-ssl</artifactId> - <version>1.0.0</version> + <version>1.0.1</version> </dependency> <!-- <dependency> + <groupId>org.apache.derby</groupId> + <artifactId>derby</artifactId> + <version>10.3.2.1</version> + </dependency> + <dependency> <groupId>org.apache.mina</groupId> <artifactId>mina-java5</artifactId> - <version>1.1.5</version> + <version>1.0.1</version> </dependency> --> <dependency> @@ -537,12 +526,14 @@ under the License. <scope>test</scope> </dependency> + <!-- <dependency> - <groupId>uk.co.thebadgerset</groupId> + <groupId>org.apache.qpid</groupId> <artifactId>junit-toolkit</artifactId> - <version>0.6-SNAPSHOT</version> + <version>0.7.1-SNAPSHOT</version> <scope>compile</scope> </dependency> + --> <!-- Qpid Version Dependencies --> <dependency> @@ -580,6 +571,16 @@ under the License. <artifactId>qpid-mgmt-client</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.qpid</groupId> + <artifactId>junit-toolkit</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.apache.qpid</groupId> + <artifactId>junit-toolkit-maven-plugin</artifactId> + <version>${project.version}</version> + </dependency> </dependencies> </dependencyManagement> @@ -676,25 +677,28 @@ under the License. </snapshots> </repository> --> - <repository> - <id>junit-toolkit.snapshots</id> - <name>JUnit Toolkit SNAPSHOT Repository</name> - <url>http://junit-toolkit.svn.sourceforge.net/svnroot/junit-toolkit/snapshots/</url> - <snapshots> - <enabled>true</enabled> - </snapshots> - </repository> - <repository> - <id>Codehaus Snapshots</id> - <url>http://snapshots.repository.codehaus.org/</url> - <snapshots> - <enabled>true</enabled> - </snapshots> - <releases> - <enabled>false</enabled> - </releases> - </repository> -</repositories> + <repository> + <id>Codehaus Snapshots</id> + <url>http://snapshots.repository.codehaus.org/</url> + <snapshots> + <enabled>true</enabled> + </snapshots> + <releases> + <enabled>false</enabled> + </releases> + </repository> + + <!-- + <repository> + <id>junittoolkit.snapshots</id> + <url>http://junit-toolkit.svn.sourceforge.net/svnroot/junit-toolkit/snapshots/</url> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + --> + + </repositories> <pluginRepositories> <pluginRepository> @@ -715,6 +719,16 @@ under the License. </snapshots> </pluginRepository> + <!-- + <pluginRepository> + <id>junittoolkit.plugin.snapshots</id> + <url>http://junit-toolkit.svn.sourceforge.net/svnroot/junit-toolkit/snapshots/</url> + <snapshots> + <enabled>true</enabled> + </snapshots> + </pluginRepository> + --> + </pluginRepositories> <profiles> diff --git a/java/systests/build.xml b/java/systests/build.xml index 4a45bfb28e..b84cb9fc32 100644 --- a/java/systests/build.xml +++ b/java/systests/build.xml @@ -19,7 +19,7 @@ - --> <project name="System Tests" default="build"> - <property name="module.depends" value="client broker common"/> + <property name="module.depends" value="client broker common junit-toolkit"/> <property name="module.dist" value="false"/> <import file="../module.xml"/> diff --git a/java/systests/etc/bin/fail.py b/java/systests/etc/bin/fail.py new file mode 100644 index 0000000000..517f31d075 --- /dev/null +++ b/java/systests/etc/bin/fail.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. + +import os +import re +import datetime + +from optparse import OptionParser + +BASE_CMD = "mvn -Dskip.python.test=true %s test" + +def main(): + parser = OptionParser() + parser.add_option("-t", "--test", dest="test", + action="store", type="string", + help="run specific tests") + parser.add_option("-c", "--continuous", dest="continuous", + action="store_true", default=False, + help="run tests after failures, don't stop") + + + (options, args) = parser.parse_args() + + # determine command to run + if (options.test != None): + cmd = (BASE_CMD % ("-Dtest="+options.test)) + else: + cmd = (BASE_CMD % ("")) + + run_forever = options.continuous + + + failed_runs = [] + iteration = 0 + fail_match = re.compile("BUILD SUCCESSFUL") + done = False + + while (run_forever or not (len(failed_runs) > 0)): + iteration = iteration + 1 + if (run_forever): + extra_text = (", %d failures so far: %s:" % (len(failed_runs), failed_runs)) + else: + extra_text = "" + print ("%s Test run %d%s" % (datetime.datetime.today().isoformat(), iteration, extra_text)) + (child_stdin, child_stdout_and_stderr) = os.popen4(cmd) + output = child_stdout_and_stderr.read() + child_stdin.close() + child_stdout_and_stderr.close() + matches = fail_match.search(output) + if (matches == None): + failed_runs.append(iteration) + output_name = ("test-run-%d.out" % (iteration)) + #write testouput + test_output = file(output_name, "w") + test_output.write(output) + test_output.close() + #tar test-output and surefire reports together + find_stdout = os.popen("find . -type d -name surefire-reports") + surefire_dirs = find_stdout.read().replace('\n', ' ') + find_stdout.close() + tarcmd = ("tar -zcf test-failures-%d.tar.gz %s %s" % (iteration, output_name, surefire_dirs)) + tar_stdout = os.popen(tarcmd) + tar_output = tar_stdout.read() + tar_exitstatus = tar_stdout.close() + print ("Something failed! Check %s" % (output_name)) + if (tar_exitstatus != None): + print ("tar exited abornmally, aborting\n %s" % (tar_output)) + run_forever = False + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/java/systests/pom.xml b/java/systests/pom.xml index 64af96150a..49f088ec1a 100644 --- a/java/systests/pom.xml +++ b/java/systests/pom.xml @@ -1,20 +1,20 @@ <!-- - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you 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 - + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. + + 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. --> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> @@ -57,16 +57,16 @@ </dependency> <dependency> - <groupId>uk.co.thebadgerset</groupId> + <groupId>org.apache.qpid</groupId> <artifactId>junit-toolkit</artifactId> </dependency> <!-- Test Dependencies --> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-log4j12</artifactId> - <version>1.4.0</version> - <scope>test</scope> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>1.4.0</version> + <scope>test</scope> </dependency> </dependencies> @@ -82,7 +82,97 @@ <includes> <include>**/*Test.class</include> </includes> + + <systemProperties> + <property> + <name>example.plugin.target</name> + <value>${basedir}/${topDirectoryLocation}/plugins/target</value> + </property> + <property> + <name>QPID_EXAMPLE_HOME</name> + <value>${basedir}/${topDirectoryLocation}/broker</value> + </property> + <property> + <name>QPID_HOME</name> + <value>${basedir}/${topDirectoryLocation}/broker</value> + </property> + </systemProperties> + + <excludes> + <exclude>**/testcases/ImmediateMessageTest.class</exclude> + <exclude>**/testcases/MandatoryMessageTest.class</exclude> + <exclude>**/testcases/RollbackTest.class</exclude> + <exclude>**/testcases/TTLTest.class</exclude> + <exclude>**/testcases/FailoverTest.class</exclude> + </excludes> + </configuration> + </plugin> + + <!-- Runs the framework based tests against an in-vm broker. --> + <plugin> + <groupId>org.apache.qpid</groupId> + <artifactId>junit-toolkit-maven-plugin</artifactId> + + <configuration> + <systemproperties> + <property> + <name>log4j.configuration</name> + <value>${log4j.configuration}</value> + </property> + </systemproperties> + + <testrunner>org.apache.qpid.junit.extensions.TKTestRunner</testrunner> + + <testrunneroptions> + <option>-X:decorators "org.apache.qpid.test.framework.qpid.InVMBrokerDecorator:org.apache.qpid.test.framework.qpid.AMQPFeatureDecorator"</option> + <!--<option>-d30S</option>--> + <option>-o ${basedir}/target/surefire-reports</option> + <option>--xml</option> + </testrunneroptions> + + <testrunnerproperties> + <property> + <name>notApplicableAssertion</name> + <value>warn</value> + </property> + </testrunnerproperties> + + <commands> + <AMQBrokerManagerMBeanTest>-n AMQBrokerManagerMBeanTest org.apache.qpid.server.AMQBrokerManagerMBeanTest </AMQBrokerManagerMBeanTest> + <TxAckTest>-n TxAckTest org.apache.qpid.server.ack.TxAckTest </TxAckTest> + <!--<HeadersExchangeTest>-n HeadersExchangeTest org.apache.qpid.server.exchange.HeadersExchangeTest </HeadersExchangeTest>--> + <ReturnUnroutableMandatoryMessageTest>-n ReturnUnroutableMandatoryMessageTest org.apache.qpid.server.exchange.ReturnUnroutableMandatoryMessageTest </ReturnUnroutableMandatoryMessageTest> + <!--<FailoverMethodTest>-n FailoverMethodTest org.apache.qpid.server.failover.FailoverMethodTest </FailoverMethodTest>--> + <DeadlockTest>-n DeadlockTest org.apache.qpid.server.failure.DeadlockTest </DeadlockTest> + <!--<PluginTest>-n PluginTest org.apache.qpid.server.plugins.PluginTest </PluginTest>--> + <AMQProtocolSessionMBeanTest>-n AMQProtocolSessionMBeanTest org.apache.qpid.server.protocol.AMQProtocolSessionMBeanTest </AMQProtocolSessionMBeanTest> + <MaxChannelsTest>-n MaxChannelsTest org.apache.qpid.server.protocol.MaxChannelsTest </MaxChannelsTest> + <AckTest>-n AckTest org.apache.qpid.server.queue.AckTest </AckTest> + <MessageReturnTest>-n MessageReturnTest org.apache.qpid.server.queue.MessageReturnTest </MessageReturnTest> + <QueueDepthWithSelectorTest>-n QueueDepthWithSelectorTest org.apache.qpid.server.queue.QueueDepthWithSelectorTest </QueueDepthWithSelectorTest> + <!--<SubscriptionManagerTest>-n SubscriptionManagerTest org.apache.qpid.server.queue.SubscriptionManagerTest </SubscriptionManagerTest>--> + <SubscriptionSetTest>-n SubscriptionSetTest org.apache.qpid.server.queue.SubscriptionSetTest </SubscriptionSetTest> + <TimeToLiveTest>-n TimeToLiveTest org.apache.qpid.server.queue.TimeToLiveTest </TimeToLiveTest> + <TxnBufferTest>-n TxnBufferTest org.apache.qpid.server.txn.TxnBufferTest </TxnBufferTest> + <!--<TxnTest>-n TxnTest org.apache.qpid.server.txn.TxnTest </TxnTest>--> + <!--QueueBrowserTest>-n QueueBrowserTest org.apache.qpid.test.client.QueueBrowserTest </QueueBrowserTest--> + + <!--<Immediate-Message-Test>-n Immediate-Test -s[1] org.apache.qpid.test.testcases.ImmediateMessageTest</Immediate-Message-Test>--> + <!--<Mandatory-Message-Test>-n Mandatory-Test -s[1] org.apache.qpid.test.testcases.MandatoryMessageTest</Mandatory-Message-Test>--> + <!--<Rollback-Test>-n Rollback-Test -s[1] org.apache.qpid.test.testcases.RollbackTest</Rollback-Test>--> + </commands> + </configuration> + + <executions> + <execution> + <id>framework_tests</id> + <phase>test</phase> + <goals> + <goal>tktest</goal> + </goals> + </execution> + </executions> </plugin> </plugins> @@ -109,3 +199,4 @@ </build> </project> + diff --git a/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java b/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java index fd28c2f77e..7fd53a64fd 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java @@ -28,6 +28,8 @@ import org.apache.qpid.framing.abstraction.MessagePublishInfo; import org.apache.qpid.server.RequiredDeliveryException; import org.apache.qpid.server.store.MemoryMessageStore; import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.store.StoreContext; import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.txn.NonTransactionalContext; @@ -101,8 +103,8 @@ public class TxAckTest extends TestCase { TransactionalContext txnContext = new NonTransactionalContext(new MemoryMessageStore(), _storeContext, null, - new LinkedList<RequiredDeliveryException>(), - new HashSet<Long>()); + new LinkedList<RequiredDeliveryException>() + ); for (int i = 0; i < messageCount; i++) { long deliveryTag = i + 1; @@ -115,6 +117,11 @@ public class TxAckTest extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -132,7 +139,7 @@ public class TxAckTest extends TestCase }; TestMessage message = new TestMessage(deliveryTag, i, info, txnContext); - _map.add(deliveryTag, new UnacknowledgedMessage(null, message, null, deliveryTag)); + _map.add(deliveryTag, new UnacknowledgedMessage(new QueueEntry(null,message), null, deliveryTag)); } _acked = acked; _unacked = unacked; @@ -149,7 +156,7 @@ public class TxAckTest extends TestCase { UnacknowledgedMessage u = _map.get(tag); assertTrue("Message not found for tag " + tag, u != null); - ((TestMessage) u.message).assertCountEquals(expected); + ((TestMessage) u.getMessage()).assertCountEquals(expected); } } diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java index 4419768416..0968f0c468 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java @@ -27,6 +27,7 @@ import org.apache.qpid.framing.abstraction.MessagePublishInfo; import org.apache.qpid.server.queue.AMQMessage; import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.MemoryMessageStore; @@ -250,9 +251,9 @@ public class AbstractHeadersExchangeTestBase extends TestCase * @param deliverFirst * @throws AMQException */ - public void process(StoreContext context, AMQMessage msg, boolean deliverFirst) throws AMQException + public void process(StoreContext context, QueueEntry msg, boolean deliverFirst) throws AMQException { - messages.add(new HeadersExchangeTest.Message(msg)); + messages.add(new HeadersExchangeTest.Message(msg.getMessage())); } } @@ -267,8 +268,8 @@ public class AbstractHeadersExchangeTestBase extends TestCase private static TransactionalContext _txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, - new LinkedList<RequiredDeliveryException>(), - new HashSet<Long>()); + new LinkedList<RequiredDeliveryException>() + ); Message(String id, String... headers) throws AMQException { diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java index b584c8c80b..3a3b7f92dd 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java @@ -21,8 +21,7 @@ package org.apache.qpid.server.exchange;
import org.apache.qpid.jms.Session;
-
-import uk.co.thebadgerset.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
/**
* MessagingTestConfigProperties defines a set of property names and default values for specifying a messaging topology,
diff --git a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java new file mode 100644 index 0000000000..9b2a3a6e28 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.server.failover; + +import junit.framework.TestCase; +import org.apache.qpid.AMQDisconnectedException; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQConnectionURL; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException; +import org.apache.qpid.url.URLSyntaxException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import java.util.concurrent.CountDownLatch; + +public class FailoverMethodTest extends TestCase implements ExceptionListener +{ + private CountDownLatch _failoverComplete = new CountDownLatch(1); + + public void setUp() throws AMQVMBrokerCreationException + { + TransportConnection.createVMBroker(1); + } + + public void tearDown() throws AMQVMBrokerCreationException + { + TransportConnection.killAllVMBrokers(); + } + + /** + * Test that the round robin method has the correct delays. + * The first connection to vm://:1 will work but the localhost connection should fail but the duration it takes + * to report the failure is what is being tested. + * + * @throws URLSyntaxException + * @throws InterruptedException + * @throws JMSException + */ + public void testFailoverRoundRobinDelay() throws URLSyntaxException, InterruptedException, JMSException + { + String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1;tcp://localhost:5670?connectdelay='2000',retries='3''"; + + AMQConnectionURL url = new AMQConnectionURL(connectionString); + + try + { + long start = System.currentTimeMillis(); + AMQConnection connection = new AMQConnection(url, null); + + connection.setExceptionListener(this); + + TransportConnection.killAllVMBrokers(); + + _failoverComplete.await(); + + long end = System.currentTimeMillis(); + + //Failover should take less that 10 seconds. + // This is calculated by vm://:1 two retries left after initial connection + // localhost get three retries so (6s) so 10s in total for connection dropping + assertTrue("Failover took less than 6 seconds:"+(end - start), (end - start) > 6000); + // The sleep method is not 100% accurate under windows so with 5 sleeps and a 10ms accuracy then there is + // the potential for the tests to finish in 500ms sooner than the predicted 10s. + + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + + public void testFailoverSingleDelay() throws URLSyntaxException, AMQVMBrokerCreationException, InterruptedException, JMSException + { + String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1?connectdelay='2000',retries='3''"; + + AMQConnectionURL url = new AMQConnectionURL(connectionString); + + try + { + long start = System.currentTimeMillis(); + AMQConnection connection = new AMQConnection(url, null); + + connection.setExceptionListener(this); + + TransportConnection.killAllVMBrokers(); + + _failoverComplete.await(); + + long end = System.currentTimeMillis(); + + //Failover should take less that 10 seconds. + // This is calculated by vm://:1 two retries left after initial connection + // so 4s in total for connection dropping + + assertTrue("Failover took less than 3.7 seconds", (end - start) > 3700); + // The sleep method is not 100% accurate under windows so with 3 sleeps and a 10ms accuracy then there is + // the potential for the tests to finish in 300ms sooner than the predicted 4s. + + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + + + public void onException(JMSException e) + { + if (e.getLinkedException() instanceof AMQDisconnectedException) + { + _failoverComplete.countDown(); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java b/java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java new file mode 100644 index 0000000000..0762a7a561 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.server.plugins; + +import java.util.Map; + +import org.apache.qpid.server.exchange.ExchangeType; + +import junit.framework.TestCase; + +public class PluginTest extends TestCase +{ + + private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType"; + private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target"); + + public void testLoadExchanges() throws Exception + { + PluginManager manager = new PluginManager(PLUGIN_DIRECTORY); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertNotNull("No exchanges found in "+PLUGIN_DIRECTORY, exchanges); + assertEquals("Wrong number of exchanges found in "+PLUGIN_DIRECTORY, + 2, exchanges.size()); + assertNotNull("Wrong exchange found in "+PLUGIN_DIRECTORY, + exchanges.get(TEST_EXCHANGE_CLASS)); + } + + public void testNoExchanges() throws Exception + { + PluginManager manager = new PluginManager("/path/to/nowhere"); + Map<String, ExchangeType<?>> exchanges = manager.getExchanges(); + assertNull("Exchanges found", exchanges); + } + +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java b/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java index 66cd1cc7da..e4555e020e 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java @@ -21,6 +21,9 @@ package org.apache.qpid.server.protocol; import junit.framework.TestCase; + +import org.apache.log4j.Logger; + import org.apache.qpid.AMQException; import org.apache.qpid.codec.AMQCodecFactory; import org.apache.qpid.framing.AMQShortString; @@ -28,10 +31,9 @@ import org.apache.qpid.server.AMQChannel; import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; -import org.apache.qpid.server.store.MemoryMessageStore; import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.txn.MemoryTransactionManager; -import org.apache.qpid.server.txn.TransactionManager; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; import javax.management.JMException; @@ -41,8 +43,10 @@ import javax.management.JMException; */ public class AMQProtocolSessionMBeanTest extends TestCase { - private MessageStore _messageStore = new MemoryMessageStore(); - private TransactionManager _txm = new MemoryTransactionManager(); + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(AMQProtocolSessionMBeanTest.class); + + private MessageStore _messageStore = new SkeletonMessageStore(); private AMQMinaProtocolSession _protocolSession; private AMQChannel _channel; private AMQProtocolSessionMBean _mbean; @@ -57,7 +61,8 @@ public class AMQProtocolSessionMBeanTest extends TestCase new AMQShortString("test"), true, _protocolSession.getVirtualHost()); - AMQChannel channel = new AMQChannel(_protocolSession, 2, _txm, _messageStore, null); + AMQChannel channel = new AMQChannel(_protocolSession, 2, _messageStore); + channel.setDefaultQueue(queue); _protocolSession.addChannel(channel); channelCount = _mbean.channels().size(); @@ -68,7 +73,7 @@ public class AMQProtocolSessionMBeanTest extends TestCase assertTrue(_mbean.getMaximumNumberOfChannels() == 1000L); // check APIs - AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _txm, _messageStore, null); + AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _messageStore); channel3.setLocalTransactional(); _protocolSession.addChannel(channel3); _mbean.rollbackTransactions(2); @@ -84,23 +89,23 @@ public class AMQProtocolSessionMBeanTest extends TestCase } catch (JMException ex) { - System.out.println("expected exception is thrown :" + ex.getMessage()); + log.debug("expected exception is thrown :" + ex.getMessage()); } // check if closing of session works - _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _txm, _messageStore, null)); + _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _messageStore)); _mbean.closeConnection(); try { channelCount = _mbean.channels().size(); assertTrue(channelCount == 0); // session is now closed so adding another channel should throw an exception - _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _txm, _messageStore, null)); + _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _messageStore)); fail(); } catch (AMQException ex) { - System.out.println("expected exception is thrown :" + ex.getMessage()); + log.debug("expected exception is thrown :" + ex.getMessage()); } } @@ -110,12 +115,11 @@ public class AMQProtocolSessionMBeanTest extends TestCase super.setUp(); IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); - _protocolSession = new AMQMinaProtocolSession(new MockIoSession(), - appRegistry.getVirtualHostRegistry(), - new AMQCodecFactory(true), - null); + _protocolSession = + new AMQMinaProtocolSession(new MockIoSession(), appRegistry.getVirtualHostRegistry(), new AMQCodecFactory(true), + null); _protocolSession.setVirtualHost(appRegistry.getVirtualHostRegistry().getVirtualHost("test")); - _channel = new AMQChannel(_protocolSession, 1, _txm, _messageStore, null); + _channel = new AMQChannel(_protocolSession, 1, _messageStore); _protocolSession.addChannel(_channel); _mbean = (AMQProtocolSessionMBean) _protocolSession.getManagedObject(); } diff --git a/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java b/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java index c1d7b0f598..62f5e0c6bf 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java @@ -27,6 +27,8 @@ import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.server.AMQChannel; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; /** Test class to test MBean operations for AMQMinaProtocolSession. */ public class MaxChannelsTest extends TestCase @@ -55,7 +57,7 @@ public class MaxChannelsTest extends TestCase { for (long currentChannel = 0L; currentChannel < maxChannels; currentChannel++) { - _protocolSession.addChannel(new AMQChannel(_protocolSession, (int) currentChannel, null, null, null)); + _protocolSession.addChannel(new AMQChannel(_protocolSession, (int) currentChannel, null)); } } catch (AMQException e) diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java index 65ac12463f..2416442b10 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java @@ -34,7 +34,6 @@ import org.apache.qpid.server.ack.UnacknowledgedMessageMap; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.store.StoreContext; import org.apache.qpid.server.store.TestableMemoryMessageStore; -import org.apache.qpid.server.txn.MemoryTransactionManager; import org.apache.qpid.server.txn.NonTransactionalContext; import org.apache.qpid.server.txn.TransactionalContext; import org.apache.qpid.server.util.NullApplicationRegistry; @@ -56,8 +55,6 @@ public class AckTest extends TestCase private TestableMemoryMessageStore _messageStore; - private MemoryTransactionManager _txm; - private StoreContext _storeContext = new StoreContext(); private AMQChannel _channel; @@ -77,9 +74,8 @@ public class AckTest extends TestCase { super.setUp(); _messageStore = new TestableMemoryMessageStore(); - _txm = new MemoryTransactionManager(); _protocolSession = new MockProtocolSession(_messageStore); - _channel = new AMQChannel(_protocolSession, 5, _txm, _messageStore, null/*dont need exchange registry*/); + _channel = new AMQChannel(_protocolSession, 5, _messageStore); _protocolSession.addChannel(_channel); _subscriptionManager = new SubscriptionSet(); @@ -94,8 +90,8 @@ public class AckTest extends TestCase private void publishMessages(int count, boolean persistent) throws AMQException { TransactionalContext txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, - new LinkedList<RequiredDeliveryException>(), - new HashSet<Long>()); + new LinkedList<RequiredDeliveryException>() + ); MessageHandleFactory factory = new MessageHandleFactory(); for (int i = 1; i <= count; i++) { @@ -109,6 +105,11 @@ public class AckTest extends TestCase return new AMQShortString("someExchange"); } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -144,7 +145,7 @@ public class AckTest extends TestCase msg.incrementReference(); msg.routingComplete(_messageStore, _storeContext, factory); // we manually send the message to the subscription - _subscription.send(msg, _queue); + _subscription.send(new QueueEntry(_queue,msg), _queue); } } @@ -172,7 +173,7 @@ public class AckTest extends TestCase assertTrue(deliveryTag == i); i++; UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); } assertTrue(map.size() == msgCount); @@ -219,7 +220,7 @@ public class AckTest extends TestCase { assertTrue(deliveryTag == i); UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); // 5 is the delivery tag of the message that *should* be removed if (++i == 5) { @@ -248,7 +249,7 @@ public class AckTest extends TestCase { assertTrue(deliveryTag == i + 5); UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); ++i; } } @@ -272,7 +273,7 @@ public class AckTest extends TestCase { assertTrue(deliveryTag == i + 5); UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); ++i; } } diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java b/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java index 6a70fea711..4f92cc94b7 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java @@ -42,9 +42,9 @@ public class ConcurrencyTestDisabled extends MessageTestHelper private final List<SubscriptionTestHelper> _subscribers = new ArrayList<SubscriptionTestHelper>(); private final Set<Subscription> _active = new HashSet<Subscription>(); - private final List<AMQMessage> _messages = new ArrayList<AMQMessage>(); + private final List<QueueEntry> _messages = new ArrayList<QueueEntry>(); private int next = 0;//index to next message to send - private final List<AMQMessage> _received = Collections.synchronizedList(new ArrayList<AMQMessage>()); + private final List<QueueEntry> _received = Collections.synchronizedList(new ArrayList<QueueEntry>()); private final Executor _executor = new OnCurrentThreadExecutor(); private final List<Thread> _threads = new ArrayList<Thread>(); @@ -159,7 +159,7 @@ public class ConcurrencyTestDisabled extends MessageTestHelper } } - private AMQMessage nextMessage() + private QueueEntry nextMessage() { synchronized (_messages) { @@ -191,7 +191,7 @@ public class ConcurrencyTestDisabled extends MessageTestHelper { void doRun() throws Throwable { - AMQMessage msg = nextMessage(); + QueueEntry msg = nextMessage(); if (msg != null) { _deliveryMgr.deliver(null, new AMQShortString(toString()), msg, false); diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java index dc5a6d3cf6..b33259cfba 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java @@ -40,7 +40,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper public void testStartInQueueingMode() throws AMQException { - AMQMessage[] messages = new AMQMessage[10]; + QueueEntry[] messages = new QueueEntry[10]; for (int i = 0; i < messages.length; i++) { messages[i] = message(); @@ -85,7 +85,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper public void testStartInDirectMode() throws AMQException { - AMQMessage[] messages = new AMQMessage[10]; + QueueEntry[] messages = new QueueEntry[10]; for (int i = 0; i < messages.length; i++) { messages[i] = message(); @@ -132,7 +132,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper { try { - AMQMessage msg = message(true); + QueueEntry msg = message(true); _mgr.deliver(_storeContext, DEFAULT_QUEUE_NAME, msg, false); msg.checkDeliveredToConsumer(); fail("expected exception did not occur"); @@ -154,7 +154,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper SubscriptionTestHelper s = new SubscriptionTestHelper("A"); _subscriptions.addSubscriber(s); s.setSuspended(true); - AMQMessage msg = message(true); + QueueEntry msg = message(true); _mgr.deliver(_storeContext, DEFAULT_QUEUE_NAME, msg, false); msg.checkDeliveredToConsumer(); fail("expected exception did not occur"); diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java b/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java index 0d4e3e748b..114c8cac32 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java @@ -36,7 +36,6 @@ import org.apache.qpid.AMQException; import junit.framework.TestCase; import java.util.LinkedList; -import java.util.HashSet; class MessageTestHelper extends TestCase { @@ -45,20 +44,20 @@ class MessageTestHelper extends TestCase private final StoreContext _storeContext = new StoreContext(); private final TransactionalContext _txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, - new LinkedList<RequiredDeliveryException>(), - new HashSet<Long>()); + new LinkedList<RequiredDeliveryException>() + ); MessageTestHelper() throws Exception { ApplicationRegistry.initialise(new NullApplicationRegistry()); } - AMQMessage message() throws AMQException + QueueEntry message() throws AMQException { return message(false); } - AMQMessage message(final boolean immediate) throws AMQException + QueueEntry message(final boolean immediate) throws AMQException { MessagePublishInfo publish = new MessagePublishInfo() { @@ -68,6 +67,11 @@ class MessageTestHelper extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return immediate; @@ -84,8 +88,8 @@ class MessageTestHelper extends TestCase } }; - return new AMQMessage(_messageStore.getNewMessageId(), publish, _txnContext, - new ContentHeaderBody()); + return new QueueEntry(null,new AMQMessage(_messageStore.getNewMessageId(), publish, _txnContext, + new ContentHeaderBody())); } } diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java b/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java index 0ad6502755..cf986e7803 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java @@ -21,10 +21,7 @@ package org.apache.qpid.server.queue; import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.VersionSpecificRegistry; +import org.apache.qpid.framing.*; import org.apache.qpid.server.AMQChannel; import org.apache.qpid.server.output.ProtocolOutputConverter; import org.apache.qpid.server.output.ProtocolOutputConverterRegistry; @@ -188,16 +185,53 @@ public class MockProtocolSession implements AMQProtocolSession return null; //To change body of implemented methods use File | Settings | File Templates. } + public MethodRegistry getMethodRegistry() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void methodFrameReceived(int channelId, AMQMethodBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void contentBodyReceived(int channelId, ContentBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public MethodDispatcher getMethodDispatcher() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + public byte getProtocolMajorVersion() { - return 8; //To change body of implemented methods use File | Settings | File Templates. + return getProtocolVersion().getMajorVersion(); } public byte getProtocolMinorVersion() { - return 0; //To change body of implemented methods use File | Settings | File Templates. + return getProtocolVersion().getMinorVersion(); } + + public ProtocolVersion getProtocolVersion() + { + return ProtocolVersion.getLatestSupportedVersion(); //To change body of implemented methods use File | Settings | File Templates. + } + + public VersionSpecificRegistry getRegistry() { return null; //To change body of implemented methods use File | Settings | File Templates. diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java b/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java index fe947ef3bc..1fa70a08d4 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java @@ -28,13 +28,13 @@ import java.util.Queue; public class SubscriptionTestHelper implements Subscription { - private final List<AMQMessage> messages; + private final List<QueueEntry> messages; private final Object key; private boolean isSuspended; public SubscriptionTestHelper(Object key) { - this(key, new ArrayList<AMQMessage>()); + this(key, new ArrayList<QueueEntry>()); } public SubscriptionTestHelper(final Object key, final boolean isSuspended) @@ -43,18 +43,18 @@ public class SubscriptionTestHelper implements Subscription setSuspended(isSuspended); } - SubscriptionTestHelper(Object key, List<AMQMessage> messages) + SubscriptionTestHelper(Object key, List<QueueEntry> messages) { this.key = key; this.messages = messages; } - List<AMQMessage> getMessages() + List<QueueEntry> getMessages() { return messages; } - public void send(AMQMessage msg, AMQQueue queue) + public void send(QueueEntry msg, AMQQueue queue) { messages.add(msg); } @@ -69,12 +69,12 @@ public class SubscriptionTestHelper implements Subscription return isSuspended; } - public boolean wouldSuspend(AMQMessage msg) + public boolean wouldSuspend(QueueEntry msg) { return isSuspended; } - public void addToResendQueue(AMQMessage msg) + public void addToResendQueue(QueueEntry msg) { //no-op } @@ -88,6 +88,11 @@ public class SubscriptionTestHelper implements Subscription { return null; } + + public void start() + { + //no-op + } public void queueDeleted(AMQQueue queue) { @@ -98,36 +103,31 @@ public class SubscriptionTestHelper implements Subscription return false; } - public boolean hasInterest(AMQMessage msg) + public boolean hasInterest(QueueEntry msg) { return true; } - public Queue<AMQMessage> getPreDeliveryQueue() + public Queue<QueueEntry> getPreDeliveryQueue() { return null; } - public Queue<AMQMessage> getResendQueue() + public Queue<QueueEntry> getResendQueue() { return null; } - public Queue<AMQMessage> getNextQueue(Queue<AMQMessage> messages) + public Queue<QueueEntry> getNextQueue(Queue<QueueEntry> messages) { return messages; } - public void enqueueForPreDelivery(AMQMessage msg, boolean deliverFirst) + public void enqueueForPreDelivery(QueueEntry msg, boolean deliverFirst) { //no-op } - public boolean isAutoClose() - { - return false; - } - public void close() { //no-op @@ -157,4 +157,5 @@ public class SubscriptionTestHelper implements Subscription { return key.toString(); } + } diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java index 06956ba52f..a803bf7da5 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java @@ -25,7 +25,11 @@ import junit.framework.TestCase; import junit.framework.Assert; import org.apache.qpid.client.transport.TransportConnection; import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQDestination; import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.AMQException; import org.apache.log4j.Logger; import javax.jms.JMSException; @@ -38,6 +42,7 @@ import javax.jms.Connection; import javax.jms.Message; import javax.naming.spi.InitialContextFactory; import javax.naming.Context; +import javax.naming.NamingException; import java.util.Hashtable; @@ -53,21 +58,37 @@ public class TimeToLiveTest extends TestCase private final long TIME_TO_LIVE = 1000L; - Context _context; - - private Connection _clientConnection, _producerConnection; - - private MessageConsumer _consumer; - MessageProducer _producer; - Session _clientSession, _producerSession; private static final int MSG_COUNT = 50; + private static final long SERVER_TTL_TIMEOUT = 60000L; protected void setUp() throws Exception { - if (BROKER.startsWith("vm://")) + super.setUp(); + + if (usingInVMBroker()) { TransportConnection.createVMBroker(1); } + + + } + + private boolean usingInVMBroker() + { + return BROKER.startsWith("vm://"); + } + + protected void tearDown() throws Exception + { + if (usingInVMBroker()) + { + TransportConnection.killAllVMBrokers(); + } + super.tearDown(); + } + + public void testPassiveTTL() throws JMSException, NamingException + { InitialContextFactory factory = new PropertiesFileInitialContextFactory(); Hashtable<String, String> env = new Hashtable<String, String>(); @@ -75,56 +96,40 @@ public class TimeToLiveTest extends TestCase env.put("connectionfactory.connection", "amqp://guest:guest@TTL_TEST_ID" + VHOST + "?brokerlist='" + BROKER + "'"); env.put("queue.queue", QUEUE); - _context = factory.getInitialContext(env); + Context context = factory.getInitialContext(env); - Queue queue = (Queue) _context.lookup("queue"); + Queue queue = (Queue) context.lookup("queue"); //Create Client 1 - _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + Connection clientConnection = ((ConnectionFactory) context.lookup("connection")).createConnection(); - _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - _consumer = _clientSession.createConsumer(queue); + MessageConsumer consumer = clientSession.createConsumer(queue); //Create Producer - _producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + Connection producerConnection = ((ConnectionFactory) context.lookup("connection")).createConnection(); - _producerConnection.start(); + producerConnection.start(); - _producerSession = _producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - _producer = _producerSession.createProducer(queue); - } + MessageProducer producer = producerSession.createProducer(queue); - protected void tearDown() throws Exception - { - _clientConnection.close(); - - _producerConnection.close(); - super.tearDown(); - - if (BROKER.startsWith("vm://")) - { - TransportConnection.killAllVMBrokers(); - } - } - - public void test() throws JMSException - { //Set TTL int msg = 0; - _producer.send(nextMessage(String.valueOf(msg), true)); + producer.send(nextMessage(String.valueOf(msg), true, producerSession, producer)); - _producer.setTimeToLive(TIME_TO_LIVE); + producer.setTimeToLive(TIME_TO_LIVE); for (; msg < MSG_COUNT - 2; msg++) { - _producer.send(nextMessage(String.valueOf(msg), false)); + producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer)); } //Reset TTL - _producer.setTimeToLive(0L); - _producer.send(nextMessage(String.valueOf(msg), false)); + producer.setTimeToLive(0L); + producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer)); try { @@ -136,31 +141,71 @@ public class TimeToLiveTest extends TestCase } - _clientConnection.start(); + clientConnection.start(); //Receive Message 0 - Message received = _consumer.receive(100); + Message received = consumer.receive(1000); Assert.assertNotNull("First message not received", received); Assert.assertTrue("First message doesn't have first set.", received.getBooleanProperty("first")); Assert.assertEquals("First message has incorrect TTL.", 0L, received.getLongProperty("TTL")); - received = _consumer.receive(100); + received = consumer.receive(1000); Assert.assertNotNull("Final message not received", received); Assert.assertFalse("Final message has first set.", received.getBooleanProperty("first")); Assert.assertEquals("Final message has incorrect TTL.", 0L, received.getLongProperty("TTL")); - received = _consumer.receive(100); + received = consumer.receive(1000); Assert.assertNull("More messages received", received); + + clientConnection.close(); + + producerConnection.close(); } - private Message nextMessage(String msg, boolean first) throws JMSException + private Message nextMessage(String msg, boolean first, Session producerSession, MessageProducer producer) throws JMSException { - Message send = _producerSession.createTextMessage("Message " + msg); + Message send = producerSession.createTextMessage("Message " + msg); send.setBooleanProperty("first", first); - send.setLongProperty("TTL", _producer.getTimeToLive()); + send.setLongProperty("TTL", producer.getTimeToLive()); return send; } + /** + * Tests the expired messages get actively deleted even on queues which have no consumers + */ + public void testActiveTTL() throws URLSyntaxException, AMQException, JMSException, InterruptedException + { + Connection producerConnection = new AMQConnection(BROKER,"guest","guest","activeTTLtest","test"); + AMQSession producerSession = (AMQSession) producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = producerSession.createTemporaryQueue(); + producerSession.declareAndBind((AMQDestination) queue); + MessageProducer producer = producerSession.createProducer(queue); + producer.setTimeToLive(1000L); + + // send Messages + for(int i = 0; i < MSG_COUNT; i++) + { + producer.send(producerSession.createTextMessage("Message: "+i)); + } + long failureTime = System.currentTimeMillis() + 2*SERVER_TTL_TIMEOUT; + + // check Queue depth for up to TIMEOUT seconds + long messageCount; + + do + { + Thread.sleep(100); + messageCount = producerSession.getQueueDepth((AMQDestination) queue); + } + while(messageCount > 0L && System.currentTimeMillis() < failureTime); + + assertEquals("Messages not automatically expired: ", 0L, messageCount); + + producer.close(); + producerSession.close(); + producerConnection.close(); + } + } diff --git a/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java b/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java new file mode 100644 index 0000000000..9ba0f6024c --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java @@ -0,0 +1,607 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.server.security.acl; + +import junit.framework.TestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.client.*; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.url.URLSyntaxException; + +import javax.jms.*; +import javax.jms.IllegalStateException; +import java.io.File; + + +public class SimpleACLTest extends TestCase implements ConnectionListener +{ + private String BROKER = "vm://:1";//"tcp://localhost:5672"; + + public void setUp() throws Exception + { + // Initialise ACLs. + final String QpidExampleHome = System.getProperty("QPID_EXAMPLE_HOME"); + final File defaultaclConfigFile = new File(QpidExampleHome, "etc/acl.config.xml"); + + if (!defaultaclConfigFile.exists()) + { + System.err.println("Configuration file not found:" + defaultaclConfigFile); + fail("Configuration file not found:" + defaultaclConfigFile); + } + + if (System.getProperty("QPID_HOME") == null) + { + fail("QPID_HOME not set"); + } + + ConfigurationFileApplicationRegistry config = new ConfigurationFileApplicationRegistry(defaultaclConfigFile); + + ApplicationRegistry.initialise(config, 1); + + TransportConnection.createVMBroker(1); + } + + public void tearDown() + { + ApplicationRegistry.remove(1); + TransportConnection.killAllVMBrokers(); + } + + public String createConnectionString(String username, String password, String broker) + { + + return "amqp://" + username + ":" + password + "@clientid/test?brokerlist='" + broker + "'"; + } + + public void testAccessAuthorized() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(true, Session.SESSION_TRANSACTED); + + conn.start(); + + //Do something to show connection is active. + sesh.rollback(); + + conn.close(); + } + catch (Exception e) + { + fail("Connection was not created due to:" + e.getMessage()); + } + } + + public void testAccessNoRights() throws URLSyntaxException, JMSException + { + try + { + Connection conn = new AMQConnection(createConnectionString("guest", "guest", BROKER)); + + //Attempt to do do things to test connection. + Session sesh = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + sesh.rollback(); + + conn.close(); + fail("Connection was created."); + } + catch (AMQException amqe) + { + if (amqe.getCause().getClass() == Exception.class) + { + System.err.println("QPID-594 : WARNING RACE CONDITION. Unable to determine cause of Connection Failure."); + return; + } + assertEquals("Linked Exception Incorrect", JMSException.class, amqe.getCause().getClass()); + Exception linked = ((JMSException) amqe.getCause()).getLinkedException(); + assertEquals("Exception was wrong type", AMQAuthenticationException.class, linked.getClass()); + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) linked).getErrorCode().getCode()); + } + } + + public void testClientConsumeFromTempQueueValid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createTemporaryQueue()); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testClientConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + //Prevent Failover + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createQueue("IllegalQueue")); + fail("Test failed as consumer was created."); + //conn will be automatically closed + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + + assertNotNull("There was no liked exception", cause); + assertEquals("Wrong linked exception type", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code received", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testClientCreateTemporaryQueue() throws JMSException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create Temporary Queue - can't use the createTempQueue as QueueName is null. + ((AMQSession) sesh).createQueue(new AMQShortString("doesnt_matter_as_autodelete_means_tmp"), + true, false, false); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testClientCreateNamedQueue() throws JMSException, URLSyntaxException, AMQException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create a Named Queue + ((AMQSession) sesh).createQueue(new AMQShortString("IllegalQueue"), false, false, false); + + fail("Test failed as Queue creation succeded."); + //conn will be automatically closed + } + catch (AMQAuthenticationException amqe) + { + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) amqe).getErrorCode().getCode()); + } + } + + public void testClientPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(true, Session.SESSION_TRANSACTED); + + conn.start(); + + MessageProducer sender = sesh.createProducer(sesh.createQueue("example.RequestQueue")); + + sender.send(sesh.createTextMessage("test")); + + //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker. + sesh.commit(); + + conn.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testClientPublishValidQueueSuccess() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession) sesh).createProducer(null); + + Queue queue = sesh.createQueue("example.RequestQueue"); + + // Send a message that we will wait to be sent, this should give the broker time to process the msg + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, sesh.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + conn.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testClientPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession) session).createProducer(null); + + Queue queue = session.createQueue("Invalid"); + + // Send a message that we will wait to be sent, this should give the broker time to close the connection + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + // Test the connection with a valid consumer + // This may fail as the session may be closed before the queue or the consumer created. + session.createConsumer(session.createTemporaryQueue()).close(); + + //Connection should now be closed and will throw the exception caused by the above send + conn.close(); + + fail("Close is not expected to succeed."); + } + catch (IllegalStateException ise) + { + System.err.println("QPID-826 : WARNING : Unable to determine cause of failure due to closure as we don't " + + "record it for reporting after connection closed asynchronously"); + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + assertEquals("Incorrect exception", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testServerConsumeFromNamedQueueValid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createQueue("example.RequestQueue")); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createQueue("Invalid")); + + fail("Test failed as consumer was created."); + //conn will be automatically closed + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + + assertNotNull("There was no liked exception", cause); + assertEquals("Wrong linked exception type", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code received", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testServerConsumeFromTemporaryQueue() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + //Prevent Failover + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createTemporaryQueue()); + fail("Test failed as consumer was created."); + //conn will be automatically closed + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + + assertNotNull("There was no liked exception", cause); + assertEquals("Wrong linked exception type", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code received", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testServerCreateNamedQueueValid() throws JMSException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create Temporary Queue + ((AMQSession) sesh).createQueue(new AMQShortString("example.RequestQueue"), false, false, false); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerCreateNamedQueueInvalid() throws JMSException, URLSyntaxException, AMQException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create a Named Queue + ((AMQSession) sesh).createQueue(new AMQShortString("IllegalQueue"), false, false, false); + + fail("Test failed as creation succeded."); + //conn will be automatically closed + } + catch (AMQAuthenticationException amqe) + { + assertEquals("Incorrect error code thrown", 403, amqe.getErrorCode().getCode()); + } + } + + public void testServerCreateTemporyQueueInvalid() throws JMSException, URLSyntaxException, AMQException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + ((AMQSession) sesh).createQueue(new AMQShortString("again_ensure_auto_delete_queue_for_temporary"), + true, false, false); + + fail("Test failed as creation succeded."); + //conn will be automatically closed + } + catch (AMQAuthenticationException amqe) + { + assertEquals("Incorrect error code thrown", 403, amqe.getErrorCode().getCode()); + } + } + + /** + * This test uses both the cilent and sender to validate that the Server is able to publish to a temporary queue. + * The reason the client must be in volved is that the Serve is unable to create its own Temporary Queues. + * + * @throws AMQException + * @throws URLSyntaxException + * @throws JMSException + */ + public void testServerPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, JMSException + { + //Set up the Server + Connection serverConnection = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + ((AMQConnection) serverConnection).setConnectionListener(this); + + Session serverSession = serverConnection.createSession(true, Session.SESSION_TRANSACTED); + + Queue requestQueue = serverSession.createQueue("example.RequestQueue"); + + MessageConsumer server = serverSession.createConsumer(requestQueue); + + serverConnection.start(); + + //Set up the consumer + Connection clientConnection = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + //Send a test mesage + Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + Queue responseQueue = clientSession.createTemporaryQueue(); + + MessageConsumer clientResponse = clientSession.createConsumer(responseQueue); + + clientConnection.start(); + + Message request = clientSession.createTextMessage("Request"); + + assertNotNull("Response Queue is null", responseQueue); + + request.setJMSReplyTo(responseQueue); + + clientSession.createProducer(requestQueue).send(request); + + try + { + Message msg = null; + + msg = server.receive(2000); + + while (msg != null && !((TextMessage) msg).getText().equals("Request")) + { + msg = server.receive(2000); + } + + assertNotNull("Message not received", msg); + + assertNotNull("Reply-To is Null", msg.getJMSReplyTo()); + + MessageProducer sender = serverSession.createProducer(msg.getJMSReplyTo()); + + sender.send(serverSession.createTextMessage("Response")); + + //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker. + serverSession.commit(); + + serverConnection.close(); + + //Ensure Response is received. + Message clientResponseMsg = clientResponse.receive(2000); + assertNotNull("Client did not receive response message,", clientResponseMsg); + assertEquals("Incorrect message received", "Response", ((TextMessage) clientResponseMsg).getText()); + + clientConnection.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testServerPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession) session).createProducer(null); + + Queue queue = session.createQueue("Invalid"); + + // Send a message that we will wait to be sent, this should give the broker time to close the connection + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + // Test the connection with a valid consumer + // This may not work as the session may be closed before the queue or consumer creation can occur. + // The correct JMSexception with linked error will only occur when the close method is recevied whilst in + // the failover safe block + session.createConsumer(session.createQueue("example.RequestQueue")).close(); + + //Connection should now be closed and will throw the exception caused by the above send + conn.close(); + + fail("Close is not expected to succeed."); + } + catch (IllegalStateException ise) + { + System.err.println("QPID-826 : WARNING : Unable to determine cause of failure due to closure as we don't " + + "record it for reporting after connection closed asynchronously"); + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + assertEquals("Incorrect exception", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + // Connection Listener Interface - Used here to block failover + + public void bytesSent(long count) + { + } + + public void bytesReceived(long count) + { + } + + public boolean preFailover(boolean redirect) + { + //Prevent failover. + return false; + } + + public boolean preResubscribe() + { + return false; + } + + public void failoverComplete() + { + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java index ab6d9742e4..c7984d5d33 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java +++ b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java @@ -23,7 +23,6 @@ package org.apache.qpid.server.store; import junit.framework.TestCase; import org.apache.qpid.AMQException; import org.apache.qpid.framing.BasicContentHeaderProperties; -import org.apache.qpid.framing.BasicPublishBody; import org.apache.qpid.framing.ContentHeaderBody; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.abstraction.MessagePublishInfo; @@ -61,6 +60,11 @@ public class TestReferenceCounting extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -78,7 +82,7 @@ public class TestReferenceCounting extends TestCase }; AMQMessage message = new AMQMessage(_store.getNewMessageId(), info, - new NonTransactionalContext(_store, _storeContext, null, null, null), + new NonTransactionalContext(_store, _storeContext, null, null), createPersistentContentHeader()); message = message.takeReference(); @@ -109,6 +113,11 @@ public class TestReferenceCounting extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -127,7 +136,7 @@ public class TestReferenceCounting extends TestCase AMQMessage message = new AMQMessage(_store.getNewMessageId(), info, - new NonTransactionalContext(_store, _storeContext, null, null, null), + new NonTransactionalContext(_store, _storeContext, null, null), createPersistentContentHeader()); message = message.takeReference(); diff --git a/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java b/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java index 58ea392306..b34f28b1a8 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java @@ -27,6 +27,7 @@ import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.store.StoreContext; import java.util.LinkedList; +import java.util.NoSuchElementException; public class TxnBufferTest extends TestCase { @@ -78,7 +79,15 @@ public class TxnBufferTest extends TestCase buffer.enlist(new FailedPrepare()); buffer.enlist(new MockOp()); - buffer.commit(null); + try + { + buffer.commit(null); + } + catch (NoSuchElementException e) + { + + } + validateOps(); store.validate(); } diff --git a/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java index bd7ed60d1d..83b4665be6 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java +++ b/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java @@ -23,6 +23,7 @@ package org.apache.qpid.server.util; import org.apache.qpid.server.exchange.ExchangeFactory; import org.apache.qpid.server.exchange.ExchangeRegistry; import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.queue.QueueRegistry; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; @@ -30,8 +31,8 @@ import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager; -import org.apache.qpid.server.security.access.AccessManager; -import org.apache.qpid.server.security.access.AllowAll; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.plugins.AllowAll; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.virtualhost.VirtualHost; @@ -53,7 +54,7 @@ public class TestApplicationRegistry extends ApplicationRegistry private ManagedObjectRegistry _managedObjectRegistry; - private AccessManager _accessManager; + private ACLPlugin _accessManager; private PrincipalDatabaseManager _databaseManager; @@ -136,14 +137,25 @@ public class TestApplicationRegistry extends ApplicationRegistry return null; //To change body of implemented methods use File | Settings | File Templates. } - public AccessManager getAccessManager() + public ACLPlugin getAccessManager() { return _accessManager; } + public void setAccessManager(ACLPlugin newManager) + { + _accessManager = newManager; + } + public MessageStore getMessageStore() { return _messageStore; } + + public PluginManager getPluginManager() + { + return null; + } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java new file mode 100644 index 0000000000..4dd957c121 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test; + +import org.apache.qpid.test.VMTestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.naming.spi.InitialContextFactory; +import javax.naming.NamingException; +import java.util.Hashtable; +import java.util.Map; + +public class FailoverBaseCase extends VMTestCase +{ + private boolean failedOver = true; + + public void setUp() throws Exception + { + // Make Broker 2 the first one so we can kill it and allow VMTestCase to clean up vm://:1 + _brokerlist = "vm://:2;vm://:1"; + _clientID = this.getClass().getName(); + _virtualhost = "/test"; + + _connections.put("connection1", "amqp://guest:guest@" + _clientID + _virtualhost + "?brokerlist='vm://:1'"); + _connections.put("connection2", "amqp://guest:guest@" + _clientID + _virtualhost + "?brokerlist='vm://:2'"); + + try + { + TransportConnection.createVMBroker(2); + } + catch (Exception e) + { + fail("Unable to create broker: " + e); + } + + super.setUp(); + } + + public void tearDown() throws Exception + { + if (!failedOver) + { + TransportConnection.killVMBroker(2); + ApplicationRegistry.remove(2); + } + super.tearDown(); + } + + + public void failBroker() + { + failedOver = true; + TransportConnection.killVMBroker(2); + ApplicationRegistry.remove(2); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java b/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java index 4d8ddeaddd..dcdfb11618 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java @@ -19,24 +19,28 @@ */ package org.apache.qpid.test; -import junit.extensions.TestSetup; -import junit.framework.Test; import junit.framework.TestCase; - +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.AMQException; -import javax.naming.Context; -import javax.naming.spi.InitialContextFactory; -import javax.jms.Queue; +import javax.jms.Connection; import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Queue; import javax.jms.Session; -import javax.jms.Connection; -import javax.jms.MessageProducer; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; +import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; -import java.util.List; import java.util.LinkedList; +import java.util.Iterator; import java.util.Map; import java.util.HashMap; @@ -85,22 +89,22 @@ public class VMTestCase extends TestCase _brokerlist = "vm://:1"; } - env.put("connectionfactory.connection", "amqp://guest:guest@" + - _clientID + _virtualhost + "?brokerlist='" + _brokerlist + "'"); + env.put("connectionfactory.connection", "amqp://guest:guest@" + _clientID + _virtualhost + "?brokerlist='" + + _brokerlist + "'"); for (Map.Entry<String, String> c : _connections.entrySet()) { env.put("connectionfactory." + c.getKey(), c.getValue()); } - env.put("queue.queue", "queue"); + _queues.put("queue", "queue"); for (Map.Entry<String, String> q : _queues.entrySet()) { env.put("queue." + q.getKey(), q.getValue()); } - env.put("topic.topic", "topic"); + _topics.put("topic", "topic"); for (Map.Entry<String, String> t : _topics.entrySet()) { @@ -112,14 +116,50 @@ public class VMTestCase extends TestCase protected void tearDown() throws Exception { + //Disabled +// checkQueuesClean(); + TransportConnection.killVMBroker(1); ApplicationRegistry.remove(1); super.tearDown(); } - public void testDummyinVMTestCase() + private void checkQueuesClean() throws NamingException, JMSException + { + Connection connection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + connection.start(); + + Iterator<String> queueNames = new HashSet<String>(_queues.values()).iterator(); + + assertTrue("QueueNames doesn't have next", queueNames.hasNext()); + + while (queueNames.hasNext()) + { + Queue queue = session.createQueue(queueNames.next()); + + //Validate that the queue are reporting empty. + long queueDepth = 0; + try + { + queueDepth = ((AMQSession) session).getQueueDepth((AMQDestination) queue); + } + catch (AMQException e) + { + //ignore + } + + assertEquals("Session reports Queue depth not as expected", 0, queueDepth); + } + + connection.close(); + } + + public int getMessageCount(String queueName) { - // keep maven happy + return ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(_virtualhost.substring(1)) + .getQueueRegistry().getQueue(new AMQShortString(queueName)).getMessageCount(); } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java new file mode 100644 index 0000000000..2b02f1cbbf --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java @@ -0,0 +1,111 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import org.apache.log4j.Logger; +import org.apache.qpid.test.VMTestCase; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueBrowser; +import javax.jms.Session; +import javax.jms.JMSException; +import javax.naming.NamingException; +import java.util.Enumeration; +public class CancelTest extends VMTestCase +{ + private static final Logger _logger = Logger.getLogger(CancelTest.class); + + private Connection _clientConnection; + private Session _clientSession; + private Queue _queue; + + public void setUp() throws Exception + { + + super.setUp(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } + + /** + * Simply + */ + public void test() throws JMSException, NamingException + { + Connection producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + producerConnection.start(); + + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + MessageProducer producer = producerSession.createProducer(_queue); + producer.send(producerSession.createTextMessage()); + producerConnection.close(); + + + QueueBrowser browser = _clientSession.createBrowser(_queue); + Enumeration e = browser.getEnumeration(); + + + while (e.hasMoreElements()) + { + e.nextElement(); + } + + browser.close(); + + MessageConsumer consumer = _clientSession.createConsumer(_queue); + consumer.receive(); + consumer.close(); + } + + public void loop() + { + try + { + int run = 0; + while (true) + { + System.err.println(run++); + test(); + } + } + catch (Exception e) + { + _logger.error(e, e); + } + } + +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java new file mode 100644 index 0000000000..463946e14a --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java @@ -0,0 +1,160 @@ +package org.apache.qpid.test.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.test.VMTestCase; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.NamingException; +import java.util.concurrent.CountDownLatch;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +public class DupsOkTest extends VMTestCase +{ + + private Queue _queue; + private static final int MSG_COUNT = 9999; + private CountDownLatch _awaitCompletion = new CountDownLatch(1); + + public void setUp() throws Exception + { + super.setUp(); + + _queue = (Queue) _context.lookup("queue"); + + //CreateQueue + ((ConnectionFactory) _context.lookup("connection")).createConnection().createSession(false, Session.AUTO_ACKNOWLEDGE).createConsumer(_queue).close(); + + //Create Producer put some messages on the queue + Connection producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + producerConnection.start(); + + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + MessageProducer producer = producerSession.createProducer(_queue); + + for (int count = 1; count <= MSG_COUNT; count++) + { + Message msg = producerSession.createTextMessage("Message " + count); + msg.setIntProperty("count", count); + producer.send(msg); + } + + producerConnection.close(); + } + + public void testDupsOK() throws NamingException, JMSException, InterruptedException, AMQException + { + //Create Client + Connection clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + clientConnection.start(); + + final Session clientSession = clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE); + + MessageConsumer consumer = clientSession.createConsumer(_queue); + + consumer.setMessageListener(new MessageListener() + { + int _msgCount = 0; + + public void onMessage(Message message) + { + _msgCount++; + if (message == null) + { + fail("Should not get null messages"); + } + + if (message instanceof TextMessage) + { + try + { + /*if (message.getIntProperty("count") == 5000) + { + assertEquals("The queue should have 4999 msgs left", 4999, getMessageCount(_queue.getQueueName())); + }*/ + + if (message.getIntProperty("count") == MSG_COUNT) + { + try + { + long remainingMessages = ((AMQSession) clientSession).getQueueDepth((AMQDestination) _queue); + if(remainingMessages != 0) + { + + assertEquals("The queue should have 0 msgs left, seen " + _msgCount + " messages.", 0, getMessageCount(_queue.getQueueName())); + } + } + catch (AMQException e) + { + assertNull("Got AMQException", e); + } + finally + { + //This is the last message so release test. + _awaitCompletion.countDown(); + } + } + + } + catch (JMSException e) + { + fail("Unable to get int property 'count'"); + } + } + else + { + fail(""); + } + } + }); + + try + { + _awaitCompletion.await(); + } + catch (InterruptedException e) + { + fail("Unable to wait for test completion"); + throw e; + } + +// consumer.close(); + + assertEquals("The queue should have 0 msgs left", 0, ((AMQSession) clientSession).getQueueDepth((AMQDestination) _queue)); + clientConnection.close(); + + } + + +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java new file mode 100644 index 0000000000..9beaa9844a --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java @@ -0,0 +1,510 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.test.FailoverBaseCase; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueBrowser; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.NamingException; +import java.util.Enumeration; +import java.util.Random; + +public class QueueBrowserAutoAckTest extends FailoverBaseCase +{ + private static final Logger _logger = Logger.getLogger(QueueBrowserAutoAckTest.class); + + protected Connection _clientConnection; + protected Session _clientSession; + protected Queue _queue; + protected static final String MESSAGE_ID_PROPERTY = "MessageIDProperty"; + + public void setUp() throws Exception + { + super.setUp(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + //Ensure there are no messages on the queue to start with. + checkQueueDepth(0); + } + + public void tearDown() throws Exception + { + if (_clientConnection != null) + { + _clientConnection.close(); + } + + super.tearDown(); + } + + protected void sendMessages(int num) throws JMSException + { + Connection producerConnection = null; + try + { + producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + } + catch (NamingException e) + { + fail("Unable to lookup connection in JNDI."); + } + + sendMessages(producerConnection, num); + } + + protected void sendMessages(String connection, int num) throws JMSException + { + Connection producerConnection = null; + try + { + producerConnection = ((ConnectionFactory) _context.lookup(connection)).createConnection(); + } + catch (NamingException e) + { + fail("Unable to lookup connection in JNDI."); + } + sendMessages(producerConnection, num); + } + + + protected void sendMessages(Connection producerConnection, int num) throws JMSException + { + producerConnection.start(); + + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + //Ensure _queue is created + producerSession.createConsumer(_queue).close(); + + MessageProducer producer = producerSession.createProducer(_queue); + + for (int messsageID = 0; messsageID < num; messsageID++) + { + TextMessage textMsg = producerSession.createTextMessage("Message " + messsageID); + textMsg.setIntProperty(MESSAGE_ID_PROPERTY, messsageID); + producer.send(textMsg); + } + + producerConnection.close(); + } + + protected void checkQueueDepth(int depth) throws JMSException + { + + // create QueueBrowser + _logger.info("Creating Queue Browser"); + + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + // check for messages + if (_logger.isDebugEnabled()) + { + _logger.debug("Checking for " + depth + " messages with QueueBrowser"); + } + + //Check what the session believes the queue count to be. + long queueDepth = 0; + + try + { + queueDepth = ((AMQSession) _clientSession).getQueueDepth((AMQDestination) _queue); + } + catch (AMQException e) + { + } + + assertEquals("Session reports Queue depth not as expected", depth, queueDepth); + + // Browse the queue to get a second opinion + int msgCount = 0; + Enumeration msgs = queueBrowser.getEnumeration(); + + while (msgs.hasMoreElements()) + { + msgs.nextElement(); + msgCount++; + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Found " + msgCount + " messages total in browser"); + } + + // check to see if all messages found + assertEquals("Browser did not find all messages", depth, msgCount); + + //Close browser + queueBrowser.close(); + } + + protected void closeBrowserBeforeAfterGetNext(int messageCount) throws JMSException + { + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + Enumeration msgs = queueBrowser.getEnumeration(); + + int msgCount = 0; + + while (msgs.hasMoreElements() && msgCount < messageCount) + { + msgs.nextElement(); + msgCount++; + } + + try + { + queueBrowser.close(); + } + catch (JMSException e) + { + fail("Close should happen without error:" + e.getMessage()); + } + } + + + protected void checkMultipleGetEnum(int sentMessages, int browserCount) throws JMSException + { + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + for (int count = 0; count < browserCount; count++) + { + Enumeration msgs = queueBrowser.getEnumeration(); + + int msgCount = 0; + + while (msgs.hasMoreElements()) + { + msgs.nextElement(); + msgCount++; + } + assertEquals(msgCount, sentMessages); + } + + try + { + queueBrowser.close(); + } + catch (JMSException e) + { + fail("Close should happen without error:" + e.getMessage()); + } + } + + protected void checkOverlappingMultipleGetEnum(int browserCount, int expectedMessages) throws JMSException + { + checkOverlappingMultipleGetEnum(browserCount, expectedMessages, null); + } + + protected void checkOverlappingMultipleGetEnum(int browserCount, int expectedMessages, String selector) throws JMSException + { + QueueBrowser queueBrowser = selector == null ? + _clientSession.createBrowser(_queue, selector) : + _clientSession.createBrowser(_queue); + + Enumeration[] msgs = new Enumeration[browserCount]; + int[] msgCount = new int[browserCount]; + + //create Enums + for (int count = 0; count < browserCount; count++) + { + msgs[count] = queueBrowser.getEnumeration(); + } + + //interleave reads + for (int cnt = 0; cnt < expectedMessages; cnt++) + { + for (int count = 0; count < browserCount; count++) + { + if (msgs[count].hasMoreElements()) + { + msgs[count].nextElement(); + msgCount[count]++; + } + } + } + + //validate all browsers get right message count. + for (int count = 0; count < browserCount; count++) + { + assertEquals(msgCount[count], expectedMessages); + } + + try + { + queueBrowser.close(); + } + catch (JMSException e) + { + fail("Close should happen without error:" + e.getMessage()); + } + } + + protected void validate(int messages) throws JMSException + { + //Create a new connection to validate message content + Connection connection = null; + + try + { + connection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + } + catch (NamingException e) + { + fail("Unable to make validation connection"); + } + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + connection.start(); + + MessageConsumer consumer = session.createConsumer(_queue); + + _logger.info("Verify messages are still on the queue"); + + Message tempMsg; + + for (int msgCount = 0; msgCount < messages; msgCount++) + { + tempMsg = (TextMessage) consumer.receive(RECEIVE_TIMEOUT); + if (tempMsg == null) + { + fail("Message " + msgCount + " not retrieved from queue"); + } + } + + //Close this new connection + connection.close(); + + _logger.info("All messages recevied from queue"); + + //ensure no message left. + checkQueueDepth(0); + } + + protected void checkQueueDepthWithSelectors(int clients, int totalMessages) throws JMSException + { + + String selector = MESSAGE_ID_PROPERTY + " % " + clients; + + checkOverlappingMultipleGetEnum(clients, totalMessages / clients, selector); + } + + + /** + * This tests you can browse an empty queue, see QPID-785 + * + * @throws Exception + */ + public void testBrowsingEmptyQueue() throws Exception + { + checkQueueDepth(0); + } + + /* + * Test Messages Remain on Queue + * Create a queu and send messages to it. Browse them and then receive them all to verify they were still there + * + */ + public void testQueueBrowserMsgsRemainOnQueue() throws Exception + { + int messages = 10; + + sendMessages(messages); + + checkQueueDepth(messages); + + validate(messages); + } + + + public void testClosingBrowserMidReceiving() throws NamingException, JMSException + { + int messages = 100; + + sendMessages(messages); + + checkQueueDepth(messages); + + closeBrowserBeforeAfterGetNext(10); + + validate(messages); + + } + + public void testMultipleGetEnum() throws NamingException, JMSException + { + int messages = 100; + + sendMessages(messages); + + checkQueueDepth(messages); + + checkMultipleGetEnum(messages, 5); + + validate(messages); + } + + public void testMultipleOverlappingGetEnum() throws NamingException, JMSException + { + int messages = 25; + + sendMessages(messages); + + checkQueueDepth(messages); + + checkOverlappingMultipleGetEnum(5, messages); + + validate(messages); + } + + + public void testBrowsingWithSelector() throws JMSException + { + int messages = 40; + + sendMessages(messages); + + checkQueueDepth(messages); + + for (int clients = 2; clients <= 10; clients++) + { + checkQueueDepthWithSelectors(clients, messages); + } + + validate(messages); + } + + public void testFailoverWithQueueBrowser() throws JMSException + { + int messages = 50; + + sendMessages("connection1", messages); + sendMessages("connection2", messages); + + + checkQueueDepth(messages); + + + _logger.info("Creating Queue Browser"); + + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + long queueDepth = 0; + + try + { + queueDepth = ((AMQSession) _clientSession).getQueueDepth((AMQDestination) _queue); + } + catch (AMQException e) + { + } + + assertEquals("Session reports Queue depth not as expected", messages, queueDepth); + + + int msgCount = 0; + + int failPoint = 0; + + failPoint = new Random().nextInt(messages) + 1; + + Enumeration msgs = queueBrowser.getEnumeration(); + + while (msgs.hasMoreElements()) + { + msgs.nextElement(); + msgCount++; + + if (msgCount == failPoint) + { + failBroker(); + } + } + + assertTrue("We should get atleast " + messages + " msgs.", msgCount >= messages); + + if (_logger.isDebugEnabled()) + { + _logger.debug("QBAAT Found " + msgCount + " messages total in browser"); + } + + //Close browser + queueBrowser.close(); + + //Validate all messages still on Broker 1 + validate(messages); + } + + public void testFailoverAsQueueBrowserCreated() throws JMSException + { + // The IoServiceListenerSupport seems to get stuck in with a managedSession that isn't closing when requested. + // So it hangs waiting for the session. + int messages = 50; + + sendMessages("connection1", messages); + sendMessages("connection2", messages); + + failBroker(); + + checkQueueDepth(messages); + + //Validate all messages still on Broker 1 + validate(messages); + } + + public void loop() throws JMSException + { + int run = 0; + try + { + while (true) + { + System.err.println(run++ + ":************************************************************************"); + testQueueBrowserMsgsRemainOnQueue(); + } + } + catch (Exception e) + { + _logger.error(e, e); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java new file mode 100644 index 0000000000..0ef0217234 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; +import javax.jms.Session; + +public class QueueBrowserClientAckTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java new file mode 100644 index 0000000000..80d74b1b79 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; +import javax.jms.Session; + +public class QueueBrowserDupsOkTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java new file mode 100644 index 0000000000..1bc5f07b4e --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import org.apache.qpid.client.AMQSession; + +import javax.jms.ConnectionFactory; +import javax.jms.Queue; + +public class QueueBrowserNoAckTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, AMQSession.NO_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java new file mode 100644 index 0000000000..42e13c89e4 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import org.apache.qpid.client.AMQSession; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; + +public class QueueBrowserPreAckTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, AMQSession.PRE_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java deleted file mode 100644 index ec9df8f1b3..0000000000 --- a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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.test.client; - -import org.apache.log4j.Logger; -import org.apache.qpid.test.VMTestCase; - -import javax.jms.Queue; -import javax.jms.ConnectionFactory; -import javax.jms.Session; -import javax.jms.Connection; -import javax.jms.MessageProducer; -import javax.jms.MessageConsumer; -import javax.jms.QueueBrowser; -import javax.jms.TextMessage; -import javax.jms.JMSException; -import javax.jms.QueueReceiver; -import javax.jms.Message; -import java.util.Enumeration; - -import junit.framework.TestCase; - -public class QueueBrowserTest extends VMTestCase -{ - private static final Logger _logger = Logger.getLogger(QueueBrowserTest.class); - - private static final int MSG_COUNT = 10; - - private Connection _clientConnection; - private Session _clientSession; - private Queue _queue; - - public void setUp() throws Exception - { - - super.setUp(); - - _queue = (Queue) _context.lookup("queue"); - - //Create Client - _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); - - _clientConnection.start(); - - _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - //Ensure _queue is created - _clientSession.createConsumer(_queue).close(); - - //Create Producer put some messages on the queue - Connection producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); - - producerConnection.start(); - - Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - MessageProducer producer = producerSession.createProducer(_queue); - - for (int msg = 0; msg < MSG_COUNT; msg++) - { - producer.send(producerSession.createTextMessage("Message " + msg)); - } - - producerConnection.close(); - - } - - /* - * Test Messages Remain on Queue - * Create a queu and send messages to it. Browse them and then receive them all to verify they were still there - * - */ - - public void testQueueBrowserMsgsRemainOnQueue() throws JMSException - { - - // create QueueBrowser - _logger.info("Creating Queue Browser"); - - QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); - - // check for messages - if (_logger.isDebugEnabled()) - { - _logger.debug("Checking for " + MSG_COUNT + " messages with QueueBrowser"); - } - - int msgCount = 0; - Enumeration msgs = queueBrowser.getEnumeration(); - - while (msgs.hasMoreElements()) - { - msgs.nextElement(); - msgCount++; - } - - if (_logger.isDebugEnabled()) - { - _logger.debug("Found " + msgCount + " messages total in browser"); - } - - // check to see if all messages found -// assertEquals("browser did not find all messages", MSG_COUNT, msgCount); - if (msgCount != MSG_COUNT) - { - _logger.warn(msgCount + "/" + MSG_COUNT + " messages received."); - } - - //Close browser - queueBrowser.close(); - - // VERIFY - - // continue and try to receive all messages - MessageConsumer consumer = _clientSession.createConsumer(_queue); - - _logger.info("Verify messages are still on the queue"); - - Message tempMsg; - - for (msgCount = 0; msgCount < MSG_COUNT; msgCount++) - { - tempMsg = (TextMessage) consumer.receive(RECEIVE_TIMEOUT); - if (tempMsg == null) - { - fail("Message " + msgCount + " not retrieved from queue"); - } - } - - _logger.info("All messages recevied from queue"); - } - - -} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java new file mode 100644 index 0000000000..0d63373e61 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java @@ -0,0 +1,51 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; +import javax.jms.Session; + +public class QueueBrowserTransactedTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(true, Session.SESSION_TRANSACTED); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } + + +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java new file mode 100644 index 0000000000..e7d7c7eba6 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java @@ -0,0 +1,257 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.client.failover; + +import junit.framework.TestCase; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQConnectionFactory; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.log4j.Logger; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import java.util.concurrent.CountDownLatch; + +public class FailoverTest extends TestCase implements ConnectionListener +{ + private static final Logger _logger = Logger.getLogger(FailoverTest.class); + + private static final int NUM_BROKERS = 2; + private static final String BROKER = "amqp://guest:guest@/test?brokerlist='vm://:%d;vm://:%d'"; + private static final String QUEUE = "queue"; + private static final int NUM_MESSAGES = 10; + private Connection con; + private AMQConnectionFactory conFactory; + private Session prodSess; + private AMQQueue q; + private MessageProducer prod; + private Session conSess; + private MessageConsumer consumer; + + private static int usedBrokers = 0; + private CountDownLatch failoverComplete; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + // Create two VM brokers + + for (int i = 0; i < NUM_BROKERS; i++) + { + usedBrokers++; + + TransportConnection.createVMBroker(usedBrokers); + } + + conFactory = new AMQConnectionFactory(String.format(BROKER, usedBrokers - 1, usedBrokers)); + _logger.info("Connecting on:" + conFactory.getConnectionURL()); + con = conFactory.createConnection(); + ((AMQConnection) con).setConnectionListener(this); + con.start(); + failoverComplete = new CountDownLatch(1); + } + + private void init(boolean transacted, int mode) throws JMSException + { + prodSess = con.createSession(transacted, mode); + q = new AMQQueue("amq.direct", QUEUE); + prod = prodSess.createProducer(q); + conSess = con.createSession(transacted, mode); + consumer = conSess.createConsumer(q); + } + + @Override + protected void tearDown() throws Exception + { + try + { + con.close(); + } + catch (Exception e) + { + + } + + try + { + TransportConnection.killAllVMBrokers(); + ApplicationRegistry.removeAll(); + } + catch (Exception e) + { + fail("Unable to clean up"); + } + super.tearDown(); + } + + private void consumeMessages(int toConsume) throws JMSException + { + Message msg; + for (int i = 0; i < toConsume; i++) + { + msg = consumer.receive(1000); + assertNotNull("Message " + i + " was null!", msg); + assertEquals("message " + i, ((TextMessage) msg).getText()); + } + } + + private void sendMessages(int totalMessages) throws JMSException + { + for (int i = 0; i < totalMessages; i++) + { + prod.send(prodSess.createTextMessage("message " + i)); + } + +// try +// { +// Thread.sleep(100 * totalMessages); +// } +// catch (InterruptedException e) +// { +// //evil ignoring of IE +// } + } + + public void testP2PFailover() throws Exception + { + testP2PFailover(NUM_MESSAGES, true); + } + + public void testP2PFailoverWithMessagesLeft() throws Exception + { + testP2PFailover(NUM_MESSAGES, false); + } + + private void testP2PFailover(int totalMessages, boolean consumeAll) throws JMSException + { + Message msg = null; + init(false, Session.AUTO_ACKNOWLEDGE); + sendMessages(totalMessages); + + // Consume some messages + int toConsume = totalMessages; + if (!consumeAll) + { + toConsume = totalMessages / 2; + } + + consumeMessages(toConsume); + + _logger.info("Failing over"); + + causeFailure(); + + msg = consumer.receive(500); + //todo: reinstate + assertNull("Should not have received message from new broker!", msg); + // Check that messages still sent / received + sendMessages(totalMessages); + consumeMessages(totalMessages); + } + + private void causeFailure() + { + _logger.info("Failover"); + + TransportConnection.killVMBroker(usedBrokers - 1); + ApplicationRegistry.remove(usedBrokers - 1); + + _logger.info("Awaiting Failover completion"); + try + { + failoverComplete.await(); + } + catch (InterruptedException e) + { + //evil ignore IE. + } + } + + public void testClientAckFailover() throws Exception + { + init(false, Session.CLIENT_ACKNOWLEDGE); + sendMessages(1); + Message msg = consumer.receive(); + assertNotNull("Expected msgs not received", msg); + + + causeFailure(); + + Exception failure = null; + try + { + msg.acknowledge(); + } + catch (Exception e) + { + failure = e; + } + assertNotNull("Exception should be thrown", failure); + } + + // This test disabled so that it doesn't add 4 minnutes to the length of time it takes to run, which would be lame + public void txest4MinuteFailover() throws Exception + { + conFactory = new AMQConnectionFactory("amqp://guest:guest@/test?brokerlist='vm://:"+(usedBrokers-1)+"?connectdelay='60000'&retries='2''"); + _logger.info("Connecting on:" + conFactory.getConnectionURL()); + con = conFactory.createConnection(); + ((AMQConnection) con).setConnectionListener(this); + con.start(); + + long failTime = System.currentTimeMillis() + 60000; + causeFailure(); + assertTrue("Failover did not take long enough", System.currentTimeMillis() > failTime); + } + + public void bytesSent(long count) + { + } + + public void bytesReceived(long count) + { + } + + public boolean preFailover(boolean redirect) + { + return true; + } + + public boolean preResubscribe() + { + return true; + } + + public void failoverComplete() + { + failoverComplete.countDown(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java b/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java new file mode 100644 index 0000000000..706d99ffe2 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java @@ -0,0 +1,54 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * An AMQPPublisher represents the status of the publishing side of a test circuit that exposes AMQP specific features.
+ * Its provides additional assertions not available through the plain JMS {@link Publisher} interface.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide assertion that the publishers received a no consumers error code on every message.
+ * <tr><td> Provide assertion that the publishers received a no route error code on every message.
+ * </table>
+ */
+public interface AMQPPublisher extends Publisher
+{
+ /**
+ * Provides an assertion that the publisher got a no consumers exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no consumers exception on every message.
+ */
+ Assertion noConsumersAssertion(ParsedProperties testProps);
+
+ /**
+ * Provides an assertion that the publisher got a no rout exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no rout exception on every message.
+ */
+ Assertion noRouteAssertion(ParsedProperties testProps);
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java b/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java new file mode 100644 index 0000000000..e8b7da2537 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java @@ -0,0 +1,70 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+/**
+ * BrokerLifecycleAware is an awareness interface implemented by test cases that can run control the life-cycle of
+ * the brokers on which they run. Its purpose is to expose additional instrumentation of brokers during testing, that
+ * enables tests to use an automated failure mechanism to simulate broker failures, and to re-start failed brokers.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Indicate whether or not a test case is using an in-vm broker.
+ * <tr><td> Track which in-vm broker is currently in use.
+ * <tr><td> Accept setting of a failure mechanism. <td> {@link CauseFailure}.
+ * </table>
+ *
+ * @todo Need to think about how to present the brokers through this interface. Thinking numbering the available
+ * brokers from 1 will do. Then can kill 1 and assume failing onto 2. Restart 1 and kill 2 and fail back onto
+ * 1 again?
+ */
+public interface BrokerLifecycleAware
+{
+ public void setInVmBrokers();
+
+ /**
+ * Indicates whether or not a test case is using in-vm brokers.
+ *
+ * @return <tt>true</tt> if the test is using in-vm brokers, <tt>false</tt> otherwise.
+ */
+ public boolean usingInVmBroker();
+
+ /**
+ * Sets the currently live in-vm broker.
+ *
+ * @param i The currently live in-vm broker.
+ */
+ public void setLiveBroker(int i);
+
+ /**
+ * Reports the currently live in-vm broker.
+ *
+ * @return The currently live in-vm broker.
+ */
+ public int getLiveBroker();
+
+ /**
+ * Accepts a failure mechanism.
+ *
+ * @param failureMechanism The failure mechanism.
+ */
+ public void setFailureMechanism(CauseFailure failureMechanism);
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java new file mode 100644 index 0000000000..8a5a9560a0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java @@ -0,0 +1,42 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+/**
+ * CauseFailure provides a method to cause a failure in a messaging broker, usually used in conjunction with fail-over
+ * or other failure mode testing. In some cases failures may be automated, for example by shutting down an in-vm broker,
+ * or by sending a special control signal to a broker over a network connection. In other cases, it may be preferable
+ * to ask a user interactively to cause a failure scenario, in which case an implementation may display a prompt or
+ * dialog box asking for notification once the failure has been caused. The purpose of this interface is to abstract
+ * the exact cause and nature of a failure out of failure test cases.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Cause messaging broker failure.
+ * </table>
+ */
+public interface CauseFailure
+{
+ /**
+ * Causes the active message broker to fail.
+ */
+ void causeFailure();
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java new file mode 100644 index 0000000000..6b96ade674 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java @@ -0,0 +1,65 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+import org.apache.qpid.test.framework.CauseFailure;
+
+import java.io.IOException;
+
+/**
+ * Causes a message broker failure by interactively prompting the user to cause it.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Cause messaging broker failure.
+ * </table>
+ */
+public class CauseFailureUserPrompt implements CauseFailure
+{
+ /**
+ * Causes the active message broker to fail.
+ */
+ public void causeFailure()
+ {
+ waitForUser("Cause a broker failure now, then press Return.");
+ }
+
+ /**
+ * Outputs a prompt to the console and waits for the user to press return.
+ *
+ * @param prompt The prompt to display on the console.
+ */
+ private void waitForUser(String prompt)
+ {
+ System.out.println(prompt);
+
+ try
+ {
+ System.in.read();
+ }
+ catch (IOException e)
+ {
+ // Ignored.
+ }
+
+ System.out.println("Continuing.");
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java index d971aa5385..d5a33514df 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java @@ -51,11 +51,14 @@ public class CircuitEndBase implements CircuitEnd ExceptionMonitor exceptionMonitor; /** - * Creates a circuit end point on the specified producer, consumer and controlSession. + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. * - * @param producer The message producer for the circuit end point. - * @param consumer The message consumer for the circuit end point. - * @param session The controlSession for the circuit end point. + * @param producer The message producer for the circuit end point. + * @param consumer The message consumer for the circuit end point. + * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. */ public CircuitEndBase(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java b/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java index 1ac94b5244..7d06aba1c0 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java @@ -12,6 +12,20 @@ * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 @@ -36,7 +50,7 @@ import java.util.List; * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> Record all exceptions received. <td> {@link ExceptionListener} + * <tr><td> Record all exceptions received. * </table> */ public class ExceptionMonitor implements ExceptionListener @@ -45,14 +59,14 @@ public class ExceptionMonitor implements ExceptionListener private final Logger log = Logger.getLogger(ExceptionMonitor.class); /** Holds the received exceptions. */ - List<JMSException> exceptions = new ArrayList<JMSException>(); + List<Exception> exceptions = new ArrayList<Exception>(); /** * Receives incoming exceptions. * * @param e The exception to record. */ - public void onException(JMSException e) + public synchronized void onException(JMSException e) { log.debug("public void onException(JMSException e): called", e); @@ -64,7 +78,7 @@ public class ExceptionMonitor implements ExceptionListener * * @return <tt>true</tt> if no exceptions have been received, <tt>false</tt> otherwise. */ - public boolean assertNoExceptions() + public synchronized boolean assertNoExceptions() { return exceptions.isEmpty(); } @@ -74,7 +88,7 @@ public class ExceptionMonitor implements ExceptionListener * * @return <tt>true</tt> if exactly one exception been received, <tt>false</tt> otherwise. */ - public boolean assertOneJMSException() + public synchronized boolean assertOneJMSException() { return exceptions.size() == 1; } @@ -82,20 +96,27 @@ public class ExceptionMonitor implements ExceptionListener /** * Checks that exactly one exception, with a linked cause of the specified type, has been received. * + * @param aClass The type of the linked cause. + * * @return <tt>true</tt> if exactly one exception, with a linked cause of the specified type, been received, * <tt>false</tt> otherwise. */ - public boolean assertOneJMSExceptionWithLinkedCause(Class aClass) + public synchronized boolean assertOneJMSExceptionWithLinkedCause(Class aClass) { if (exceptions.size() == 1) { - JMSException e = exceptions.get(0); - - Exception linkedCause = e.getLinkedException(); + Exception e = exceptions.get(0); - if ((linkedCause != null) && aClass.isInstance(linkedCause)) + if (e instanceof JMSException) { - return true; + JMSException jmse = (JMSException) e; + + Exception linkedCause = jmse.getLinkedException(); + + if ((linkedCause != null) && aClass.isInstance(linkedCause)) + { + return true; + } } } @@ -103,11 +124,37 @@ public class ExceptionMonitor implements ExceptionListener } /** + * Checks that at least one exception of the the specified type, has been received. + * + * @param exceptionClass The type of the exception. + * + * @return <tt>true</tt> if at least one exception of the specified type has been received, <tt>false</tt> otherwise. + */ + public synchronized boolean assertExceptionOfType(Class exceptionClass) + { + // Start by assuming that the exception has no been received. + boolean passed = false; + + // Scan all the exceptions for a match. + for (Exception e : exceptions) + { + if (exceptionClass.isInstance(e)) + { + passed = true; + + break; + } + } + + return passed; + } + + /** * Reports the number of exceptions held by this monitor. * * @return The number of exceptions held by this monitor. */ - public int size() + public synchronized int size() { return exceptions.size(); } @@ -115,9 +162,9 @@ public class ExceptionMonitor implements ExceptionListener /** * Clears the record of received exceptions. */ - public void reset() + public synchronized void reset() { - exceptions = new ArrayList(); + exceptions = new ArrayList<Exception>(); } /** @@ -126,11 +173,11 @@ public class ExceptionMonitor implements ExceptionListener * * @return A string containing a dump of the stack traces of all exceptions. */ - public String toString() + public synchronized String toString() { String result = "ExceptionMonitor: holds " + exceptions.size() + " exceptions.\n\n"; - for (JMSException ex : exceptions) + for (Exception ex : exceptions) { result += getStackTrace(ex) + "\n"; } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java index fa70e14d16..51b053d2b2 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java @@ -20,23 +20,20 @@ */ package org.apache.qpid.test.framework; -import junit.framework.TestCase; - import org.apache.log4j.Logger; import org.apache.log4j.NDC; -import org.apache.qpid.client.transport.TransportConnection; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; +import org.apache.qpid.test.framework.BrokerLifecycleAware; import org.apache.qpid.test.framework.sequencers.CircuitFactory; -import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.AsymptoticTestCase; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.AsymptoticTestCase; +import org.apache.qpid.junit.extensions.SetupTaskAware; +import org.apache.qpid.junit.extensions.SetupTaskHandler; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; import java.util.ArrayList; import java.util.List; -import java.util.Properties; /** * FrameworkBaseCase provides a starting point for writing test cases against the test framework. Its main purpose is @@ -50,13 +47,26 @@ import java.util.Properties; * <tr><td> Convert failed assertions to error messages. * </table> */ -public class FrameworkBaseCase extends AsymptoticTestCase +public class FrameworkBaseCase extends AsymptoticTestCase implements FrameworkTestContext, SetupTaskAware, + BrokerLifecycleAware { /** Used for debugging purposes. */ private static final Logger log = Logger.getLogger(FrameworkBaseCase.class); /** Holds the test sequencer to create and run test circuits with. */ - protected CircuitFactory circuitFactory = new DefaultCircuitFactory(); + protected CircuitFactory circuitFactory = new LocalCircuitFactory(); + + /** Used to read the tests configurable properties through. */ + protected ParsedProperties testProps; + + /** A default setup task processor to delegate setup tasks to. */ + protected SetupTaskHandler taskHandler = new SetupTaskHandler(); + + /** Flag used to track whether the test is in-vm or not. */ + protected boolean isUsingInVM; + + /** Holds the failure mechanism. */ + protected CauseFailure failureMechanism = new CauseFailureUserPrompt(); /** * Creates a new test case with the specified name. @@ -92,6 +102,26 @@ public class FrameworkBaseCase extends AsymptoticTestCase } /** + * Reports the current test case name. + * + * @return The current test case name. + */ + public TestCaseVector getTestCaseVector() + { + return new TestCaseVector(this.getName(), 0); + } + + /** + * Reports the current test case parameters. + * + * @return The current test case parameters. + */ + public MessagingTestConfigProperties getTestParameters() + { + return new MessagingTestConfigProperties(testProps); + } + + /** * Creates a list of assertions. * * @param asserts The assertions to compile in a list. @@ -116,7 +146,7 @@ public class FrameworkBaseCase extends AsymptoticTestCase * * @param asserts The list of failed assertions. */ - protected void assertNoFailures(List<Assertion> asserts) + protected static void assertNoFailures(List<Assertion> asserts) { log.debug("protected void assertNoFailures(List<Assertion> asserts = " + asserts + "): called"); @@ -140,7 +170,7 @@ public class FrameworkBaseCase extends AsymptoticTestCase * * @return The error message. */ - protected String assertionsToString(List<Assertion> asserts) + protected static String assertionsToString(List<Assertion> asserts) { String errorMessage = ""; @@ -161,8 +191,10 @@ public class FrameworkBaseCase extends AsymptoticTestCase { NDC.push(getName()); - // Ensure that the in-vm broker is created. - TransportConnection.createVMBroker(1); + testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + // Process all optional setup tasks. This may include in-vm broker creation, if a decorator has added it. + taskHandler.runSetupTasks(); } /** @@ -170,16 +202,30 @@ public class FrameworkBaseCase extends AsymptoticTestCase */ protected void tearDown() { - try - { - // Ensure that the in-vm broker is cleaned up so that the next test starts afresh. - TransportConnection.killVMBroker(1); - ApplicationRegistry.remove(1); - } - finally - { - NDC.pop(); - } + NDC.pop(); + + // Process all optional tear down tasks. This may include in-vm broker clean up, if a decorator has added it. + taskHandler.runTearDownTasks(); + } + + /** + * Adds the specified task to the tests setup. + * + * @param task The task to add to the tests setup. + */ + public void chainSetupTask(Runnable task) + { + taskHandler.chainSetupTask(task); + } + + /** + * Adds the specified task to the tests tear down. + * + * @param task The task to add to the tests tear down. + */ + public void chainTearDownTask(Runnable task) + { + taskHandler.chainTearDownTask(task); } /** @@ -197,84 +243,46 @@ public class FrameworkBaseCase extends AsymptoticTestCase return methodName; } + public void setInVmBrokers() + { + isUsingInVM = true; + } + /** - * DefaultCircuitFactory is a test sequencer that creates test circuits with publishing and receiving ends rooted - * on the same JVM. + * Indicates whether or not a test case is using in-vm brokers. + * + * @return <tt>true</tt> if the test is using in-vm brokers, <tt>false</tt> otherwise. */ - public class DefaultCircuitFactory implements CircuitFactory + public boolean usingInVmBroker() { - /** - * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, - * begining the test and gathering the test reports from the participants. - * - * @param testCircuit The test circuit. - * @param assertions The list of assertions to apply to the test circuit. - * @param testProperties The test case definition. - */ - public void sequenceTest(Circuit testCircuit, List<Assertion> assertions, Properties testProperties) - { - assertNoFailures(testCircuit.test(1, assertions)); - } - - /** - * Creates a test circuit for the test, configered by the test parameters specified. - * - * @param testProperties The test parameters. - * @return A test circuit. - */ - public Circuit createCircuit(ParsedProperties testProperties) - { - return LocalCircuitImpl.createCircuit(testProperties); - } - - /** - * Sets the sender test client to coordinate the test with. - * - * @param sender The contact details of the sending client in the test. - */ - public void setSender(TestClientDetails sender) - { - throw new RuntimeException("Not implemented."); - } - - /** - * Sets the receiving test client to coordinate the test with. - * - * @param receiver The contact details of the sending client in the test. - */ - public void setReceiver(TestClientDetails receiver) - { - throw new RuntimeException("Not implemented."); - } + return isUsingInVM; + } - /** - * Supplies the sending test client. - * - * @return The sending test client. - */ - public TestClientDetails getSender() - { - throw new RuntimeException("Not implemented."); - } + /** + * Sets the currently live in-vm broker. + * + * @param i The currently live in-vm broker. + */ + public void setLiveBroker(int i) + { } - /** - * Supplies the receiving test client. - * - * @return The receiving test client. - */ - public List<TestClientDetails> getReceivers() - { - throw new RuntimeException("Not implemented."); - } + /** + * Reports the currently live in-vm broker. + * + * @return The currently live in-vm broker. + */ + public int getLiveBroker() + { + return 0; + } - /** - * Accepts the conversation factory over which to hold the test coordinating conversation. - * - * @param conversationFactory The conversation factory to coordinate the test over. - */ - public void setConversationFactory(ConversationFactory conversationFactory) - { - throw new RuntimeException("Not implemented."); - } + /** + * Accepts a failure mechanism. + * + * @param failureMechanism The failure mechanism. + */ + public void setFailureMechanism(CauseFailure failureMechanism) + { + this.failureMechanism = failureMechanism; } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java index 7bd65618e3..2322955253 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java @@ -1,3 +1,23 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.test.framework; /** diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java new file mode 100644 index 0000000000..e7268db8eb --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java @@ -0,0 +1,48 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+/**
+ * A FrameworkTestContext provides context information to test code about the current test case being run; its name, its
+ * parameters.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide the name of the current test case.
+ * <tr><td> Provide the test parameters.
+ * </table>
+ */
+public interface FrameworkTestContext
+{
+ /**
+ * Reports the current test case name.
+ *
+ * @return The current test case name.
+ */
+ TestCaseVector getTestCaseVector();
+
+ /**
+ * Reports the current test case parameters.
+ *
+ * @return The current test case parameters.
+ */
+ MessagingTestConfigProperties getTestParameters();
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java new file mode 100644 index 0000000000..d1fcad9cc0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java @@ -0,0 +1,168 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.framework.localcircuit.LocalAMQPPublisherImpl;
+import org.apache.qpid.test.framework.localcircuit.LocalPublisherImpl;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+/**
+ * LocalAMQPCircuitFactory is a test sequencer that creates test circuits with publishing and receiving ends rooted
+ * on the same JVM, allowing AMQP/Qpid specific options to be applied to the circuit.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a standard test procedure over a test circuit.
+ * <tr><td> Construct test circuits appropriate to a tests context.
+ * <tr><td> Construct test circuits the support AMQP specific options.
+ * </table>
+ */
+public class LocalAMQPCircuitFactory extends LocalCircuitFactory
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(LocalAMQPCircuitFactory.class);
+
+ /**
+ * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the publishing side of a test circuit.
+ *
+ * @throws javax.jms.JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageProducer producer =
+ props.getPublisherProducerBind()
+ ? ((props.getImmediate() | props.getMandatory())
+ ? ((AMQSession) session).createProducer(destination, props.getMandatory(), props.getImmediate())
+ : session.createProducer(destination)) : null;
+
+ MessageConsumer consumer =
+ props.getPublisherConsumerBind()
+ ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ ExceptionMonitor exceptionMonitor = new ExceptionMonitor();
+ connection.setExceptionListener(exceptionMonitor);
+
+ if (!props.getPublisherConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the receiving side of a test circuit.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ MessageProducer producer =
+ props.getReceiverProducerBind()
+ ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageConsumer consumer =
+ props.getReceiverConsumerBind()
+ ? ((props.getDurableSubscription() && props.getPubsub())
+ ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination))
+ : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ if (!props.getReceiverConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, null);
+ }
+
+ /**
+ * Creates a local {@link Publisher} from a {@link CircuitEnd}. The publisher implementation provides AMQP
+ * specific assertion methods, for testing beyond JMS.
+ *
+ * @param publisherEnd The publishing circuit end.
+ *
+ * @return A {@link Receiver}.
+ */
+ protected LocalPublisherImpl createPublisherFromCircuitEnd(CircuitEndBase publisherEnd)
+ {
+ return new LocalAMQPPublisherImpl(publisherEnd);
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java new file mode 100644 index 0000000000..38a924a4ee --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java @@ -0,0 +1,316 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl;
+import org.apache.qpid.test.framework.localcircuit.LocalPublisherImpl;
+import org.apache.qpid.test.framework.localcircuit.LocalReceiverImpl;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.util.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * LocalCircuitFactory is a circuit factory that creates test circuits with publishing and receiving ends rooted
+ * on the same JVM. The ends of the circuit are presented as {@link Publisher} and {@link Receiver} interfaces, which
+ * in turn provide methods to apply assertions to the circuit. The creation of the circuit ends, and the presentation
+ * of the ends as publisher/receiver interfaces, are designed to be overriden, so that circuits and assertions that
+ * use messaging features not available in JMS can be written. This provides an extension point for writing tests
+ * against proprietary features of JMS implementations.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a standard test procedure over a test circuit.
+ * <tr><td> Construct test circuits appropriate to a tests context.
+ * </table>
+ */
+public class LocalCircuitFactory implements CircuitFactory
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(LocalCircuitFactory.class);
+
+ /** Used to create unique destination names for each test. */
+ protected static AtomicLong uniqueDestsId = new AtomicLong();
+
+ /**
+ * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles,
+ * begining the test and gathering the test reports from the participants.
+ *
+ * @param testCircuit The test circuit.
+ * @param assertions The list of assertions to apply to the test circuit.
+ * @param testProperties The test case definition.
+ */
+ public void sequenceTest(Circuit testCircuit, List<Assertion> assertions, Properties testProperties)
+ {
+ FrameworkBaseCase.assertNoFailures(testCircuit.test(1, assertions));
+ }
+
+ /**
+ * Creates a test circuit for the test, configered by the test parameters specified.
+ *
+ * @param testProperties The test parameters.
+ *
+ * @return A test circuit.
+ */
+ public Circuit createCircuit(ParsedProperties testProperties)
+ {
+ Circuit result;
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProperties);
+
+ // Create a standard publisher/receivers test client pair on a shared connection, individual sessions.
+ try
+ {
+ // Get a unique offset to append to destination names to make them unique to the connection.
+ long uniqueId = uniqueDestsId.incrementAndGet();
+
+ // Set up the connection.
+ Connection connection = TestUtils.createConnection(testProperties);
+
+ // Add the connection exception listener to assert on exception conditions with.
+ // ExceptionMonitor exceptionMonitor = new ExceptionMonitor();
+ // connection.setExceptionListener(exceptionMonitor);
+
+ // Set up the publisher.
+ CircuitEndBase publisherEnd = createPublisherCircuitEnd(connection, props, uniqueId);
+
+ // Set up the receiver.
+ CircuitEndBase receiverEnd = createReceiverCircuitEnd(connection, props, uniqueId);
+
+ // Start listening for incoming messages.
+ connection.start();
+
+ // Package everything up.
+ LocalPublisherImpl publisher = createPublisherFromCircuitEnd(publisherEnd);
+ LocalReceiverImpl receiver = createReceiverFromCircuitEnd(receiverEnd);
+
+ result = new LocalCircuitImpl(testProperties, publisher, receiver, connection, publisher.getExceptionMonitor());
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Could not create publisher/receivers pair due to a JMSException.", e);
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a local {@link Receiver} from a {@link CircuitEnd}. Sub-classes may override this to provide more
+ * specialized receivers if necessary.
+ *
+ * @param receiverEnd The receiving circuit end.
+ *
+ * @return A {@link Receiver}.
+ */
+ protected LocalReceiverImpl createReceiverFromCircuitEnd(CircuitEndBase receiverEnd)
+ {
+ return new LocalReceiverImpl(receiverEnd);
+ }
+
+ /**
+ * Creates a local {@link Publisher} from a {@link CircuitEnd}. Sub-classes may override this to provide more
+ * specialized receivers if necessary.
+ *
+ * @param publisherEnd The publishing circuit end.
+ *
+ * @return A {@link Receiver}.
+ */
+ protected LocalPublisherImpl createPublisherFromCircuitEnd(CircuitEndBase publisherEnd)
+ {
+ return new LocalPublisherImpl(publisherEnd);
+ }
+
+ /**
+ * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the publishing side of a test circuit.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ // Check that the test properties do not contain AMQP/Qpid specific settings, and fail if they do.
+ if (props.getImmediate() || props.getMandatory())
+ {
+ throw new RuntimeException(
+ "Cannot create a pure JMS circuit as the test properties require AMQP specific options.");
+ }
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageProducer producer = props.getPublisherProducerBind() ? session.createProducer(destination) : null;
+
+ MessageConsumer consumer =
+ props.getPublisherConsumerBind()
+ ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ ExceptionMonitor exceptionMonitor = new ExceptionMonitor();
+ connection.setExceptionListener(exceptionMonitor);
+
+ if (!props.getPublisherConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the receiving side of a test circuit.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ // Check that the test properties do not contain AMQP/Qpid specific settings, and fail if they do.
+ if (props.getImmediate() || props.getMandatory())
+ {
+ throw new RuntimeException(
+ "Cannot create a pure JMS circuit as the test properties require AMQP specific options.");
+ }
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ MessageProducer producer =
+ props.getReceiverProducerBind()
+ ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageConsumer consumer =
+ props.getReceiverConsumerBind()
+ ? ((props.getDurableSubscription() && props.getPubsub())
+ ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination))
+ : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ if (!props.getReceiverConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, null);
+ }
+
+ /**
+ * Sets the sender test client to coordinate the test with.
+ *
+ * @param sender The contact details of the sending client in the test.
+ */
+ public void setSender(TestClientDetails sender)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Sets the receiving test client to coordinate the test with.
+ *
+ * @param receiver The contact details of the sending client in the test.
+ */
+ public void setReceiver(TestClientDetails receiver)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Supplies the sending test client.
+ *
+ * @return The sending test client.
+ */
+ public TestClientDetails getSender()
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Supplies the receiving test client.
+ *
+ * @return The receiving test client.
+ */
+ public List<TestClientDetails> getReceivers()
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Accepts the conversation factory over which to hold the test coordinating conversation.
+ *
+ * @param conversationFactory The conversation factory to coordinate the test over.
+ */
+ public void setConversationFactory(ConversationFactory conversationFactory)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java b/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java new file mode 100644 index 0000000000..b672b9c3ce --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java @@ -0,0 +1,167 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+/**
+ * MessageIdentityVector provides a message identification scheme, that matches individual messages with test cases.
+ * Test messages are being sent by a number of test clients, sending messages over a set of routes, and being received
+ * by another set of test clients. Each test is itself, being run within a test cycle, of which there could be many. It
+ * is the job of the test coordinator to request and receive reports from the available test clients, on what has been
+ * sent, what has been received, and what errors may have occurred, and to reconcile this information against the
+ * assertions being applied by the test case. In order to be able to figure out which messages belong to which test,
+ * there needs to be an identification scheme, that the coordinator can use to correlate messages in senders and
+ * receiver reports. Every message sent in a test can be associated with this information.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Identify a test case, a handling client id, a circuit end within the client, and a test cycle number.
+ * </table>
+ */
+public class MessageIdentityVector
+{
+ /** Holds the test case vector component of the message identity vector. */
+ private TestCaseVector testCaseVector;
+
+ /** The unique client id. */
+ private String clientId;
+
+ /** The unique circuit end number within the client id. */
+ private int circuitEndId;
+
+ /**
+ * Creates a new identity vector for test messages.
+ *
+ * @param testCase The name of the test case generating the messages.
+ * @param clientId The unique id of the client implementing a circuit end that is handling the messages.
+ * @param circuitEndId The unique id number of the circuit end within the client.
+ * @param testCycleNumber The cycle iteration number of the test case.
+ */
+ public MessageIdentityVector(String testCase, String clientId, int circuitEndId, int testCycleNumber)
+ {
+ this.testCaseVector = new TestCaseVector(testCase, testCycleNumber);
+ this.clientId = clientId;
+ this.circuitEndId = circuitEndId;
+ }
+
+ /**
+ * Reports the test case vector component of the message identity vector.
+ *
+ * @return The test case vector component of the message identity vector.
+ */
+ public TestCaseVector getTestCaseVector()
+ {
+ return testCaseVector;
+ }
+
+ /**
+ * Reports the name of the test case.
+ *
+ * @return The name of the test case.
+ */
+ public String getTestCase()
+ {
+ return testCaseVector.getTestCase();
+ }
+
+ /**
+ * Reports the test iteration cycle number within the test case.
+ *
+ * @return The test iteration cycle number within the test case.
+ */
+ public int getTestCycleNumber()
+ {
+ return testCaseVector.getTestCycleNumber();
+ }
+
+ /**
+ * Resports the client id.
+ *
+ * @return The client id.
+ */
+ public String getClientId()
+ {
+ return clientId;
+ }
+
+ /**
+ * Reports the circuit end number within the test client.
+ *
+ * @return The circuit end number within the test client.
+ */
+ public int getCircuitEndId()
+ {
+ return circuitEndId;
+ }
+
+ /**
+ * Compares this identity vector with another for equality. All fields must match.
+ *
+ * @param o The identity vector to compare with.
+ *
+ * @return <tt>true</tt> if the identity vector is identical to this one by all fields, <tt>false</tt> otherwise.
+ */
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if ((o == null) || (getClass() != o.getClass()))
+ {
+ return false;
+ }
+
+ MessageIdentityVector that = (MessageIdentityVector) o;
+
+ if (circuitEndId != that.circuitEndId)
+ {
+ return false;
+ }
+
+ if ((clientId != null) ? (!clientId.equals(that.clientId)) : (that.clientId != null))
+ {
+ return false;
+ }
+
+ if ((testCaseVector != null) ? (!testCaseVector.equals(that.testCaseVector)) : (that.testCaseVector != null))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Computes a hash code for this identity vector based on all fields.
+ *
+ * @return A hash code for this identity vector based on all fields.
+ */
+ public int hashCode()
+ {
+ int result;
+ result = ((testCaseVector != null) ? testCaseVector.hashCode() : 0);
+ result = (31 * result) + ((clientId != null) ? clientId.hashCode() : 0);
+ result = (31 * result) + circuitEndId;
+
+ return result;
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java b/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java index 9e91286683..27f9261d94 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java @@ -20,9 +20,9 @@ */ package org.apache.qpid.test.framework; -import org.apache.qpid.jms.Session; +import org.apache.qpid.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import javax.jms.Session; import java.util.Properties; @@ -181,10 +181,16 @@ public class MessagingTestConfigProperties extends ParsedProperties public static final boolean PERSISTENT_MODE_DEFAULT = false; /** Holds the name of the property to get the test transactional mode from. */ - public static final String TRANSACTED_PROPNAME = "transacted"; + public static final String TRANSACTED_PUBLISHER_PROPNAME = "transactedPublisher"; /** Holds the transactional mode to use for the test. */ - public static final boolean TRANSACTED_DEFAULT = false; + public static final boolean TRANSACTED_PUBLISHER_DEFAULT = false; + + /** Holds the name of the property to get the test transactional mode from. */ + public static final String TRANSACTED_RECEIVER_PROPNAME = "transactedReceiver"; + + /** Holds the transactional mode to use for the test. */ + public static final boolean TRANSACTED_RECEIVER_DEFAULT = false; /** Holds the name of the property to set the no local flag from. */ public static final String NO_LOCAL_PROPNAME = "noLocal"; @@ -204,7 +210,7 @@ public class MessagingTestConfigProperties extends ParsedProperties /** Defines the default value of the durable subscriptions flag. */ public static final boolean DURABLE_SUBSCRIPTION_DEFAULT = false; - // ====================== Qpid Options and Flags ================================ + // ====================== Qpid/AMQP Options and Flags ================================ /** Holds the name of the property to set the exclusive flag from. */ public static final String EXCLUSIVE_PROPNAME = "exclusive"; @@ -230,7 +236,7 @@ public class MessagingTestConfigProperties extends ParsedProperties /** Default value for the durable destinations flag. */ public static final boolean DURABLE_DESTS_DEFAULT = false; - /** Holds the name of the proeprty to set the prefetch size from. */ + /** Holds the name of the property to set the prefetch size from. */ public static final String PREFETCH_PROPNAME = "prefetch"; /** Defines the default prefetch size to use when consuming messages. */ @@ -274,6 +280,26 @@ public class MessagingTestConfigProperties extends ParsedProperties /** Defines the default maximum quantity of pending message data to allow producers to hold. */ public static final int MAX_PENDING_DEFAULT = 0; + /** Holds the name of the property to get the publisher rollback flag from. */ + public static final String ROLLBACK_PUBLISHER_PROPNAME = "rollbackPublisher"; + + /** Holds the default publisher roll back setting. */ + public static final boolean ROLLBACK_PUBLISHER_DEFAULT = false; + + /** Holds the name of the property to get the publisher rollback flag from. */ + public static final String ROLLBACK_RECEIVER_PROPNAME = "rollbackReceiver"; + + /** Holds the default publisher roll back setting. */ + public static final boolean ROLLBACK_RECEIVER_DEFAULT = false; + + // ====================== Options that control the bahviour of the test framework. ========================= + + /** Holds the name of the property to get the behavioural mode of not applicable assertions. */ + public static final String NOT_APPLICABLE_ASSERTION_PROPNAME = "notApplicableAssertion"; + + /** Holds the default behavioral mode of not applicable assertions, which is logging them as a warning. */ + public static final String NOT_APPLICABLE_ASSERTION_DEFAULT = "warn"; + /** Holds the name of the property to get the verbose mode proeprty from. */ public static final String VERBOSE_PROPNAME = "verbose"; @@ -286,8 +312,11 @@ public class MessagingTestConfigProperties extends ParsedProperties static { defaults.setPropertyIfNull(INITIAL_CONTEXT_FACTORY_PROPNAME, INITIAL_CONTEXT_FACTORY_DEFAULT); - // defaults.setPropertyIfNull(CONNECTION_PROPNAME, CONNECTION_DEFAULT); - defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT); + defaults.setPropertyIfNull(BROKER_PROPNAME, BROKER_DEFAULT); + defaults.setPropertyIfNull(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT); + defaults.setPropertyIfNull(USERNAME_PROPNAME, USERNAME_DEFAULT); + defaults.setPropertyIfNull(PASSWORD_PROPNAME, PASSWORD_DEFAULT); + defaults.setPropertyIfNull(PUBLISHER_PRODUCER_BIND_PROPNAME, PUBLISHER_PRODUCER_BIND_DEFAULT); defaults.setPropertyIfNull(PUBLISHER_CONSUMER_BIND_PROPNAME, PUBLISHER_CONSUMER_BIND_DEFAULT); defaults.setPropertyIfNull(RECEIVER_PRODUCER_BIND_PROPNAME, RECEIVER_PRODUCER_BIND_DEFAULT); @@ -296,28 +325,33 @@ public class MessagingTestConfigProperties extends ParsedProperties defaults.setPropertyIfNull(RECEIVER_CONSUMER_ACTIVE_PROPNAME, RECEIVER_CONSUMER_ACTIVE_DEFAULT); defaults.setPropertyIfNull(SEND_DESTINATION_NAME_ROOT_PROPNAME, SEND_DESTINATION_NAME_ROOT_DEFAULT); defaults.setPropertyIfNull(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME, RECEIVE_DESTINATION_NAME_ROOT_DEFAULT); - defaults.setPropertyIfNull(PERSISTENT_MODE_PROPNAME, PERSISTENT_MODE_DEFAULT); - defaults.setPropertyIfNull(TRANSACTED_PROPNAME, TRANSACTED_DEFAULT); - defaults.setPropertyIfNull(BROKER_PROPNAME, BROKER_DEFAULT); - defaults.setPropertyIfNull(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT); - defaults.setPropertyIfNull(RATE_PROPNAME, RATE_DEFAULT); - defaults.setPropertyIfNull(VERBOSE_PROPNAME, VERBOSE_DEFAULT); - defaults.setPropertyIfNull(PUBSUB_PROPNAME, PUBSUB_DEFAULT); - defaults.setPropertyIfNull(USERNAME_PROPNAME, USERNAME_DEFAULT); - defaults.setPropertyIfNull(PASSWORD_PROPNAME, PASSWORD_DEFAULT); - defaults.setPropertyIfNull(SELECTOR_PROPNAME, SELECTOR_DEFAULT); defaults.setPropertyIfNull(DESTINATION_COUNT_PROPNAME, DESTINATION_COUNT_DEFAULT); - defaults.setPropertyIfNull(TIMEOUT_PROPNAME, TIMEOUT_DEFAULT); - defaults.setPropertyIfNull(TX_BATCH_SIZE_PROPNAME, TX_BATCH_SIZE_DEFAULT); - defaults.setPropertyIfNull(DURABLE_DESTS_PROPNAME, DURABLE_DESTS_DEFAULT); + defaults.setPropertyIfNull(PUBSUB_PROPNAME, PUBSUB_DEFAULT); + + defaults.setPropertyIfNull(PERSISTENT_MODE_PROPNAME, PERSISTENT_MODE_DEFAULT); + defaults.setPropertyIfNull(TRANSACTED_PUBLISHER_PROPNAME, TRANSACTED_PUBLISHER_DEFAULT); + defaults.setPropertyIfNull(TRANSACTED_RECEIVER_PROPNAME, TRANSACTED_RECEIVER_DEFAULT); + defaults.setPropertyIfNull(NO_LOCAL_PROPNAME, NO_LOCAL_DEFAULT); defaults.setPropertyIfNull(ACK_MODE_PROPNAME, ACK_MODE_DEFAULT); defaults.setPropertyIfNull(DURABLE_SUBSCRIPTION_PROPNAME, DURABLE_SUBSCRIPTION_DEFAULT); - defaults.setPropertyIfNull(MAX_PENDING_PROPNAME, MAX_PENDING_DEFAULT); - defaults.setPropertyIfNull(PREFETCH_PROPNAME, PREFETCH_DEFAULT); - defaults.setPropertyIfNull(NO_LOCAL_PROPNAME, NO_LOCAL_DEFAULT); + defaults.setPropertyIfNull(EXCLUSIVE_PROPNAME, EXCLUSIVE_DEFAULT); defaults.setPropertyIfNull(IMMEDIATE_PROPNAME, IMMEDIATE_DEFAULT); defaults.setPropertyIfNull(MANDATORY_PROPNAME, MANDATORY_DEFAULT); + defaults.setPropertyIfNull(DURABLE_DESTS_PROPNAME, DURABLE_DESTS_DEFAULT); + defaults.setPropertyIfNull(PREFETCH_PROPNAME, PREFETCH_DEFAULT); + + defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT); + defaults.setPropertyIfNull(RATE_PROPNAME, RATE_DEFAULT); + defaults.setPropertyIfNull(SELECTOR_PROPNAME, SELECTOR_DEFAULT); + defaults.setPropertyIfNull(TIMEOUT_PROPNAME, TIMEOUT_DEFAULT); + defaults.setPropertyIfNull(TX_BATCH_SIZE_PROPNAME, TX_BATCH_SIZE_DEFAULT); + defaults.setPropertyIfNull(MAX_PENDING_PROPNAME, MAX_PENDING_DEFAULT); + defaults.setPropertyIfNull(ROLLBACK_PUBLISHER_PROPNAME, ROLLBACK_PUBLISHER_DEFAULT); + defaults.setPropertyIfNull(ROLLBACK_RECEIVER_PROPNAME, ROLLBACK_RECEIVER_DEFAULT); + + defaults.setPropertyIfNull(NOT_APPLICABLE_ASSERTION_PROPNAME, NOT_APPLICABLE_ASSERTION_DEFAULT); + defaults.setPropertyIfNull(VERBOSE_PROPNAME, VERBOSE_DEFAULT); } /** @@ -338,148 +372,314 @@ public class MessagingTestConfigProperties extends ParsedProperties super(properties); } + /** + * The size of test messages to send. + * + * @return The size of test messages to send. + */ public int getMessageSize() { return getPropertyAsInteger(MESSAGE_SIZE_PROPNAME); } + /** + * Flag to indicate that the publishing producer should be set up to publish to a destination. + * + * @return Flag to indicate that the publishing producer should be set up to publish to a destination. + */ public boolean getPublisherProducerBind() { return getPropertyAsBoolean(PUBLISHER_PRODUCER_BIND_PROPNAME); } + /** + * Flag to indicate that the publishing consumer should be set up to receive from a destination. + * + * @return Flag to indicate that the publishing consumer should be set up to receive from a destination. + */ public boolean getPublisherConsumerBind() { return getPropertyAsBoolean(PUBLISHER_CONSUMER_BIND_PROPNAME); } + /** + * Flag to indicate that the receiving producer should be set up to publish to a destination. + * + * @return Flag to indicate that the receiving producer should be set up to publish to a destination. + */ public boolean getReceiverProducerBind() { return getPropertyAsBoolean(RECEIVER_PRODUCER_BIND_PROPNAME); } + /** + * Flag to indicate that the receiving consumer should be set up to receive from a destination. + * + * @return Flag to indicate that the receiving consumer should be set up to receive from a destination. + */ public boolean getReceiverConsumerBind() { return getPropertyAsBoolean(RECEIVER_CONSUMER_BIND_PROPNAME); } + /** + * Flag to indicate that the publishing consumer should be created and actively listening. + * + * @return Flag to indicate that the publishing consumer should be created. + */ public boolean getPublisherConsumerActive() { return getPropertyAsBoolean(PUBLISHER_CONSUMER_ACTIVE_PROPNAME); } + /** + * Flag to indicate that the receiving consumers should be created and actively listening. + * + * @return Flag to indicate that the receiving consumers should be created and actively listening. + */ public boolean getReceiverConsumerActive() { return getPropertyAsBoolean(RECEIVER_CONSUMER_ACTIVE_PROPNAME); } + /** + * A root to create all test destination names from. + * + * @return A root to create all test destination names from. + */ public String getSendDestinationNameRoot() { return getProperty(SEND_DESTINATION_NAME_ROOT_PROPNAME); } + /** + * A root to create all receiving destination names from. + * + * @return A root to create all receiving destination names from. + */ public String getReceiveDestinationNameRoot() { return getProperty(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME); } + /** + * Flag to indicate that persistent messages should be used. + * + * @return Flag to indicate that persistent messages should be used. + */ public boolean getPersistentMode() { return getPropertyAsBoolean(PERSISTENT_MODE_PROPNAME); } - public boolean getTransacted() + /** + * Flag to indicate that transactional messages should be sent by the publisher. + * + * @return Flag to indicate that transactional messages should be sent by the publisher. + */ + public boolean getPublisherTransacted() { - return getPropertyAsBoolean(TRANSACTED_PROPNAME); + return getPropertyAsBoolean(TRANSACTED_PUBLISHER_PROPNAME); } - public String getBroker() + /** + * Flag to indicate that transactional receives should be used by the receiver. + * + * @return Flag to indicate that transactional receives should be used by the receiver. + */ + public boolean getReceiverTransacted() { - return getProperty(BROKER_PROPNAME); + return getPropertyAsBoolean(TRANSACTED_PUBLISHER_PROPNAME); } + /** + * The name of the virtual host to run all tests over. + * + * @return The name of the virtual host to run all tests over. + */ public String getVirtualHost() { return getProperty(VIRTUAL_HOST_PROPNAME); } + /** + * Limiting rate for each sender in messages per second, or zero for unlimited. + * + * @return Limiting rate for each sender in messages per second, or zero for unlimited. + */ public String getRate() { return getProperty(RATE_PROPNAME); } + /** + * Flag to indicate that test messages should be received publish/subscribe style by all receivers. + * + * @return Flag to indicate that test messages should be received publish/subscribe style by all receivers. + */ public boolean getPubsub() { return getPropertyAsBoolean(PUBSUB_PROPNAME); } + /** + * The username credentials to run tests with. + * + * @return The username credentials to run tests with. + */ public String getUsername() { return getProperty(USERNAME_PROPNAME); } + /** + * The password credentials to run tests with. + * + * @return The password credentials to run tests with. + */ public String getPassword() { return getProperty(PASSWORD_PROPNAME); } - public int getDestinationCount() - { - return getPropertyAsInteger(DESTINATION_COUNT_PROPNAME); - } - + /** + * The timeout duration to fail tests on, should they receive no messages within it. + * + * @return The timeout duration to fail tests on, should they receive no messages within it. + */ public long getTimeout() { return getPropertyAsLong(TIMEOUT_PROPNAME); } + /** + * The number of messages to batch into each transaction in transational tests. + * + * @return The number of messages to batch into each transaction in transational tests. + */ public int getTxBatchSize() { return getPropertyAsInteger(TX_BATCH_SIZE_PROPNAME); } + /** + * Flag to indicate that tests should use durable destinations. + * + * @return Flag to indicate that tests should use durable destinations. + */ public boolean getDurableDests() { return getPropertyAsBoolean(DURABLE_DESTS_PROPNAME); } + /** + * The ack mode for message receivers to use. + * + * @return The ack mode for message receivers to use. + */ public int getAckMode() { return getPropertyAsInteger(ACK_MODE_PROPNAME); } + /** + * Flag to indicate that tests should use durable subscriptions. + * + * @return Flag to indicate that tests should use durable subscriptions. + */ public boolean getDurableSubscription() { return getPropertyAsBoolean(DURABLE_SUBSCRIPTION_PROPNAME); } + /** + * The maximum amount of in-flight data, in bytes, that tests should send at any time. + * + * @return The maximum amount of in-flight data, in bytes, that tests should send at any time. + */ public int getMaxPending() { return getPropertyAsInteger(MAX_PENDING_PROPNAME); } - public int getPrefecth() + /** + * The size of the prefetch queue to use. + * + * @return The size of the prefetch queue to use. + */ + public int getPrefetch() { return getPropertyAsInteger(PREFETCH_PROPNAME); } + /** + * Flag to indicate that subscriptions should be no-local. + * + * @return Flag to indicate that subscriptions should be no-local. + */ public boolean getNoLocal() { return getPropertyAsBoolean(NO_LOCAL_PROPNAME); } + /** + * Flag to indicate that subscriptions should be exclusive. + * + * @return Flag to indicate that subscriptions should be exclusive. + */ public boolean getExclusive() { return getPropertyAsBoolean(EXCLUSIVE_PROPNAME); } + /** + * Flag to indicate that messages must be delivered immediately. + * + * @return Flag to indicate that messages must be delivered immediately. + */ public boolean getImmediate() { return getPropertyAsBoolean(IMMEDIATE_PROPNAME); } + /** + * Flag to indicate that messages must be routable. + * + * @return Flag to indicate that messages must be routable. + */ public boolean getMandatory() { return getPropertyAsBoolean(MANDATORY_PROPNAME); } + + /** + * Gets the value of a flag to indicate that the publisher should rollback all messages sent. + * + * @return A flag to indicate that the publisher should rollback all messages sent. + */ + public boolean getRollbackPublisher() + { + return getPropertyAsBoolean(ROLLBACK_PUBLISHER_PROPNAME); + } + + /** + * Gets the value of a flag to indicate that the receiver should rollback all messages received, then receive them + * again. + * + * @return A flag to indicate that the publisher should rollback all messages received. + */ + public boolean getRollbackReceiver() + { + return getPropertyAsBoolean(ROLLBACK_RECEIVER_PROPNAME); + } + + /** + * Gets the behavioural mode of not applicable assertions. Should be one of 'quiet', 'warn' or 'fail'. + * + * @return The behavioural mode of not applicable assertions. + */ + public String getNotApplicableAssertionMode() + { + return getProperty(NOT_APPLICABLE_ASSERTION_PROPNAME); + } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java b/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java new file mode 100644 index 0000000000..63c7fd61c3 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java @@ -0,0 +1,112 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * NotApplicableAssertion is a messaging assertion that can be used when an assertion requested by a test-case is not
+ * applicable to the testing scenario. For example an assertion may relate to AMQP functionality, but a test case may be
+ * being run over a non-AMQP JMS implementation, in which case the request to create the assertion may return this
+ * instead of the proper assertion. The test framework is configurable to quietly drop these assertions, log them
+ * as warnings to the console, or raise them as test failures.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Quitely pass.
+ * <tr><td> Log a warning.
+ * <tr><td> Raise a test failure.
+ * </table>
+ */
+public class NotApplicableAssertion implements Assertion
+{
+ /** Used for logging to the console. */
+ private static final Logger console = Logger.getLogger("CONSOLE." + NotApplicableAssertion.class.getName());
+
+ /** The possible behavioural modes of this assertion. */
+ private enum Mode
+ {
+ /** Quietly ignore the assertion by passing. */
+ Quiet,
+
+ /** Ignore the assertion by passing but log a warning about it. */
+ Warn,
+
+ /** Fail the assertion. */
+ Fail;
+ }
+
+ /** The behavioural mode of the assertion. */
+ private Mode mode;
+
+ /**
+ * Creates an assertion that is driven by the value of the 'notApplicableAssertion' property of the test
+ * configuration. Its value should match one of 'quiet', 'warn' or 'fail' and if it does not it is automatically
+ * read as 'fail'.
+ *
+ * @param testProperties The test configuration properties.
+ */
+ public NotApplicableAssertion(ParsedProperties testProperties)
+ {
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProperties);
+
+ String modeName = props.getNotApplicableAssertionMode();
+
+ if ("quiet".equals(modeName))
+ {
+ mode = Mode.Quiet;
+ }
+ else if ("warn".equals(modeName))
+ {
+ mode = Mode.Warn;
+ }
+ else
+ {
+ mode = Mode.Fail;
+ }
+ }
+
+ /**
+ * Applies the assertion.
+ *
+ * @return <tt>true</tt> if the assertion passes, <tt>false</tt> if it fails.
+ */
+ public boolean apply()
+ {
+ switch (mode)
+ {
+ case Quiet:
+ return true;
+
+ case Warn:
+ console.warn("Warning: Not applicable assertion being ignored.");
+
+ return true;
+
+ case Fail:
+ default:
+ return false;
+ }
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java b/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java index 8e61deedcf..2c8be4f787 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java @@ -20,37 +20,55 @@ */ package org.apache.qpid.test.framework; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** - * A Publisher is a {@link CircuitEnd} that represents the status of the publishing side of a test circuit. Its main - * purpose is to provide assertions that can be applied to test the behaviour of the publishers. + * A Publisher represents the status of the publishing side of a test circuit. Its main purpose is to provide assertions + * that can be applied to test the behaviour of the publishers. * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities * <tr><td> Provide assertion that the publishers received no exceptions. - * <tr><td> Provide assertion that the publishers received a no consumers error code on every message. - * <tr><td> Provide assertion that the publishers received a no route error code on every message. * </table> + * + * @todo There are mixtures of AMQP and JMS assertions in this interface. Either keep them here, but quietly (or with a + * warning or error) drop them from test cases where they are not relevant, or push them down into sub-classes. + * I am tempted to go with the dropping/warning/error approach, that would imply that it makes sense to pull + * the assertions back from AMQPPublisher to here. */ public interface Publisher { + // Assertions that are meaningfull to AMQP and to JMS. + /** * Provides an assertion that the publisher encountered no exceptions. * + * @param testProps The test configuration properties. + * * @return An assertion that the publisher encountered no exceptions. */ - public Assertion noExceptionsAssertion(); + public Assertion noExceptionsAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to AMQP. /** - * Provides an assertion that the publisher got a no consumers exception on every message. + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. * - * @return An assertion that the publisher got a no consumers exception on every message. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. */ - public Assertion noConsumersAssertion(); + public Assertion channelClosedAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to Java/JMS. /** - * Provides an assertion that the publisher got a no rout exception on every message. + * Provides an assertion that the publisher got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. * - * @return An assertion that the publisher got a no rout exception on every message. + * @return An assertion that the publisher got a given exception during the test. */ - public Assertion noRouteAssertion(); + public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass); } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java b/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java index f1343b9997..19dc4d90e7 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.test.framework; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** * A Receiver is a {@link CircuitEnd} that represents the status of the receiving side of a test circuit. Its main * purpose is to provide assertions that can be applied to check the behaviour of the receivers. @@ -29,20 +31,62 @@ package org.apache.qpid.test.framework; * <tr><td> Provide assertion that the receivers received no exceptions. * <tr><td> Provide assertion that the receivers received all test messages sent to it. * </table> + * + * @todo There are mixtures of AMQP and JMS assertions in this interface. Either keep them here, but quietly (or with a + * warning or error) drop them from test cases where they are not relevant, or push them down into sub-classes. + * I am tempted to go with the dropping/warning/error approach. */ public interface Receiver { + // Assertions that are meaningfull to AMQP and to JMS. + /** * Provides an assertion that the receivers encountered no exceptions. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers encountered no exceptions. */ - public Assertion noExceptionsAssertion(); + public Assertion noExceptionsAssertion(ParsedProperties testProps); /** * Provides an assertion that the receivers got all messages that were sent to it. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers got all messages that were sent to it. */ - public Assertion allMessagesAssertion(); + public Assertion allMessagesReceivedAssertion(ParsedProperties testProps); + + /** + * Provides an assertion that the receivers got none of the messages that were sent to it. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the receivers got none of the messages that were sent to it. + */ + public Assertion noMessagesReceivedAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to AMQP. + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to Java/JMS. + + /** + * Provides an assertion that the receiver got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. + * + * @return An assertion that the receiver got a given exception during the test. + */ + public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass); } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java b/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java new file mode 100644 index 0000000000..0518a827ba --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java @@ -0,0 +1,88 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework;
+
+/**
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ */
+public class TestCaseVector
+{
+ /** The test case name. */
+ private String testCase;
+
+ /** The test cycle number within the test case. */
+ private int testCycleNumber;
+
+ public TestCaseVector(String testCase, int testCycleNumber)
+ {
+ this.testCase = testCase;
+ this.testCycleNumber = testCycleNumber;
+ }
+
+ public String getTestCase()
+ {
+ return testCase;
+ }
+
+ public int getTestCycleNumber()
+ {
+ return testCycleNumber;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if ((o == null) || (getClass() != o.getClass()))
+ {
+ return false;
+ }
+
+ TestCaseVector that = (TestCaseVector) o;
+
+ if (testCycleNumber != that.testCycleNumber)
+ {
+ return false;
+ }
+
+ if ((testCase != null) ? (!testCase.equals(that.testCase)) : (that.testCase != null))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = ((testCase != null) ? testCase.hashCode() : 0);
+ result = (31 * result) + testCycleNumber;
+
+ return result;
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java b/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java index c855ee21fa..d7a6f83527 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java @@ -24,7 +24,7 @@ import org.apache.log4j.Logger; import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.*; import javax.naming.Context; @@ -32,7 +32,6 @@ import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Map; -import java.util.Properties; /** * TestUtils provides static helper methods that are usefull for writing tests against QPid. @@ -40,7 +39,9 @@ import java.util.Properties; * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations * <tr><td> Create connections from test properties. <td> {@link MessagingTestConfigProperties} + * <tr><td> Create test messages. * <tr><td> Inject a short pause in a test. + * <tr><td> Serialize properties into a message. * </table> */ public class TestUtils @@ -48,7 +49,8 @@ public class TestUtils /** Used for debugging. */ private static Logger log = Logger.getLogger(TestUtils.class); - private static byte[] MESSAGE_DATA_BYTES = + /** Some dummy data to stuff all test messages with. */ + private static final byte[] MESSAGE_DATA_BYTES = "Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- " .getBytes(); @@ -118,7 +120,7 @@ public class TestUtils * * @return A bytes message, of the specified size, filled with dummy data. * - * + * @throws JMSException Any underlying JMSExceptions are allowed to fall through. */ public static Message createTestMessageOfSize(Session session, int size) throws JMSException { @@ -187,3 +189,4 @@ public class TestUtils } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java index 51b839d51e..00cc2d8966 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java @@ -21,8 +21,8 @@ package org.apache.qpid.test.framework.clocksynch; /** - * ClockSynchFailureException represents failure of a {@link ClockSynchronizer} to achieve synchronization. This could - * be because a reference signal is not available, or because a desired accurracy cannot be attained, for example. + * ClockSynchFailureException represents failure of a {@link ClockSynchronizer} to achieve synchronization. For example, + * this could be because a reference signal is not available, or because a desired accurracy cannot be attained. * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java index 88f0043067..3d4c4f7d12 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java @@ -22,8 +22,8 @@ package org.apache.qpid.test.framework.clocksynch; import org.apache.log4j.Logger; -import uk.co.thebadgerset.junit.extensions.ShutdownHookable; -import uk.co.thebadgerset.junit.extensions.Throttle; +import org.apache.qpid.junit.extensions.ShutdownHookable; +import org.apache.qpid.junit.extensions.Throttle; /** * ClockSynchThread is a convenient utility for running a thread that periodically synchronizes the clock against @@ -121,3 +121,4 @@ public class ClockSynchThread extends Thread implements ShutdownHookable }); } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java index 96485edbea..a92c551bc2 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java @@ -32,7 +32,7 @@ package org.apache.qpid.test.framework.clocksynch; * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> Trigger a clock synchronziation. + * <tr><td> Trigger a clock synchronization. * <tr><td> Compute a clock delta to apply to the local clock. * <tr><td> Estimate the error in the synchronzation. * </table> diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java index 8b68097158..8bce752f68 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java @@ -20,14 +20,12 @@ */ package org.apache.qpid.test.framework.clocksynch; -import org.apache.log4j.Logger; +import org.apache.qpid.junit.extensions.ShutdownHookable; import java.io.IOException; import java.net.*; import java.nio.ByteBuffer; -import uk.co.thebadgerset.junit.extensions.ShutdownHookable; - /** * UDPClockReference supplies a refernce clock signal (generated from System.nanoTime()). * @@ -49,7 +47,7 @@ public class UDPClockReference implements Runnable, ShutdownHookable private static final int TIMEOUT = 200; /** Defines the port to run the clock reference on. */ - public static final int REFERENCE_PORT = 4445; + public static final int REFERENCE_PORT = 4444; /** Holds the socket to receive clock reference requests on. */ protected DatagramSocket socket = null; @@ -164,3 +162,4 @@ public class UDPClockReference implements Runnable, ShutdownHookable } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java index a5ae0a0db6..c89112eff8 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java @@ -20,10 +20,8 @@ */ package org.apache.qpid.test.framework.clocksynch; -import org.apache.log4j.Logger; - -import uk.co.thebadgerset.junit.extensions.util.CommandLineParser; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.CommandLineParser; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import java.io.IOException; import java.net.*; @@ -462,3 +460,4 @@ public class UDPClockSynchronizer implements ClockSynchronizer } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java index 159f364825..aefeb17d59 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java @@ -25,9 +25,9 @@ import org.apache.log4j.Logger; import org.apache.qpid.test.framework.*; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.TimingController; -import uk.co.thebadgerset.junit.extensions.TimingControllerAware; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.TimingController; +import org.apache.qpid.junit.extensions.TimingControllerAware; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.Destination; import javax.jms.JMSException; @@ -232,7 +232,7 @@ public class DistributedCircuitImpl implements Circuit, TimingControllerAware } /** - * Used by tests cases that can supply a {@link uk.co.thebadgerset.junit.extensions.TimingController} to set the + * Used by tests cases that can supply a {@link org.apache.qpid.junit.extensions.TimingController} to set the * controller on an aware test. * * @param controller The timing controller. diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java index 810e1ae685..c51f710494 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java @@ -23,10 +23,17 @@ package org.apache.qpid.test.framework.distributedcircuit; import org.apache.qpid.test.framework.Assertion; import org.apache.qpid.test.framework.Publisher; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** + * DistributedPublisherImpl represents the status of the publishing side of a test circuit. Its main purpose is to + * provide assertions that can be applied to verify the behaviour of a non-local publisher. + * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> + * <tr><td> Provide assertion that the publishers received no exceptions. + * <tr><td> Provide assertion that the publishers received a no consumers error code on every message. + * <tr><td> Provide assertion that the publishers received a no route error code on every message. * </table> */ public class DistributedPublisherImpl implements Publisher @@ -35,8 +42,9 @@ public class DistributedPublisherImpl implements Publisher * Provides an assertion that the publisher encountered no exceptions. * * @return An assertion that the publisher encountered no exceptions. + * @param testProps */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) { throw new RuntimeException("Not implemented."); } @@ -60,4 +68,28 @@ public class DistributedPublisherImpl implements Publisher { throw new RuntimeException("Not implemented."); } + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the publisher got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. + * @return An assertion that the publisher got a given exception during the test. + */ + public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass) + { + throw new RuntimeException("Not implemented."); + } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java index db85921b85..863921e387 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java @@ -23,10 +23,16 @@ package org.apache.qpid.test.framework.distributedcircuit; import org.apache.qpid.test.framework.Assertion; import org.apache.qpid.test.framework.Receiver; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** + * DistributedReceiverImpl represents the status of the receiving side of a test circuit. Its main purpose is to + * provide assertions that can be applied to verify the behaviour of a non-local receiver. + * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> + * <tr><td> Provide assertion that the receivers received no exceptions. + * <tr><td> Provide assertion that the receivers received all test messages sent to it. * </table> */ public class DistributedReceiverImpl implements Receiver @@ -35,8 +41,9 @@ public class DistributedReceiverImpl implements Receiver * Provides an assertion that the receivers encountered no exceptions. * * @return An assertion that the receivers encountered no exceptions. + * @param testProps */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) { throw new RuntimeException("Not implemented."); } @@ -45,9 +52,44 @@ public class DistributedReceiverImpl implements Receiver * Provides an assertion that the receivers got all messages that were sent to it. * * @return An assertion that the receivers got all messages that were sent to it. + * @param testProps + */ + public Assertion allMessagesReceivedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the receivers got none of the messages that were sent to it. + * + * @return An assertion that the receivers got none of the messages that were sent to it. + * @param testProps + */ + public Assertion noMessagesReceivedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the receiver got a given exception during the test. + * + * @param testProps + *@param exceptionClass The exception class to check for. @return An assertion that the receiver got a given exception during the test. */ - public Assertion allMessagesAssertion() + public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass) { throw new RuntimeException("Not implemented."); } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java index 95f02aa70e..dce2706bc4 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java @@ -24,10 +24,9 @@ import org.apache.log4j.Logger; import org.apache.qpid.test.framework.*; import org.apache.qpid.test.framework.distributedtesting.TestClientControlledTest; -import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; import javax.jms.*; @@ -48,7 +47,15 @@ import javax.jms.*; * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> + * <tr><td> Provide a message producer for sending messages. + * <td> {@link CircuitEnd}, {@link LocalCircuitFactory}, {@link TestUtils} + * <tr><td> Provide a message consumer for receiving messages. + * <td> {@link CircuitEnd}, {@link LocalCircuitFactory}, {@link TestUtils} + * <tr><td> Supply the name of the test case that this implements. + * <tr><td> Accept/Reject invites based on test parameters. <td> {@link MessagingTestConfigProperties} + * <tr><td> Adapt to assigned roles. <td> {@link TestClientControlledTest.Roles} + * <tr><td> Perform test case actions. <td> {@link MessageMonitor} + * <tr><td> Generate test reports. <td> {@link MessageMonitor} * </table> */ public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTest @@ -145,13 +152,15 @@ public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTes connection = TestUtils.createConnection(testProps); // Create a circuit end that matches the assigned role and test parameters. + LocalCircuitFactory circuitFactory = new LocalCircuitFactory(); + switch (role) { // Check if the sender role is being assigned, and set up a message producer if so. case SENDER: // Set up the publisher. - circuitEnd = LocalCircuitImpl.createPublisherCircuitEnd(connection, testProps, 0L); + circuitEnd = circuitFactory.createPublisherCircuitEnd(connection, testProps, 0L); // Create a custom message monitor that will be updated on every message sent. messageMonitor = new MessageMonitor(); @@ -162,7 +171,7 @@ public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTes case RECEIVER: // Set up the receiver. - circuitEnd = LocalCircuitImpl.createReceiverCircuitEnd(connection, testProps, 0L); + circuitEnd = circuitFactory.createReceiverCircuitEnd(connection, testProps, 0L); // Use the message monitor from the consumer for stats. messageMonitor = getMessageMonitor(); diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java index f79cde9dcc..55f05ec6f2 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java @@ -20,33 +20,31 @@ */ package org.apache.qpid.test.framework.distributedtesting; +import java.net.InetAddress; +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; + +import javax.jms.*; + import junit.framework.Test; import junit.framework.TestResult; import junit.framework.TestSuite; import org.apache.log4j.Logger; import org.apache.log4j.NDC; - import org.apache.qpid.test.framework.FrameworkBaseCase; import org.apache.qpid.test.framework.MessagingTestConfigProperties; import org.apache.qpid.test.framework.TestClientDetails; import org.apache.qpid.test.framework.TestUtils; import org.apache.qpid.test.framework.clocksynch.UDPClockReference; import org.apache.qpid.util.ConversationFactory; -import org.apache.qpid.util.PrettyPrintingUtils; - -import uk.co.thebadgerset.junit.extensions.TKTestRunner; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; -import uk.co.thebadgerset.junit.extensions.util.CommandLineParser; -import uk.co.thebadgerset.junit.extensions.util.MathUtils; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; - -import javax.jms.*; -import java.net.InetAddress; -import java.util.*; -import java.util.concurrent.LinkedBlockingQueue; +import org.apache.qpid.junit.extensions.TKTestRunner; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.util.CommandLineParser; +import org.apache.qpid.junit.extensions.util.MathUtils; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; /** * <p/>Implements the coordinator client described in the interop testing specification @@ -56,7 +54,7 @@ import java.util.concurrent.LinkedBlockingQueue; * <p><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations * <tr><td> Find out what test clients are available. <td> {@link ConversationFactory} - * <tr><td> Decorate available tests to run all available clients. <td> {@link DistributedTestDecorator} + * <tr><td> Decorate available tests to run on all available clients. <td> {@link DistributedTestDecorator} * <tr><td> Attach XML test result logger. * <tr><td> Terminate the interop testing framework. * </table> @@ -64,8 +62,6 @@ import java.util.concurrent.LinkedBlockingQueue; * @todo Should accumulate failures over all tests, and return with success or fail code based on all results. May need * to write a special TestResult to do this properly. At the moment only the last one used will be tested for * errors, as the start method creates a fresh one for each test case run. - * - * @todo Remove hard coding of test cases and put on command line instead. */ public class Coordinator extends TKTestRunner { @@ -119,27 +115,28 @@ public class Coordinator extends TKTestRunner /** * Creates an interop test coordinator on the specified broker and virtual host. * - * @param repetitions The number of times to repeat the test, or test batch size. - * @param duration The length of time to run the tests for. -1 means no duration has been set. - * @param threads The concurrency levels to ramp up to. - * @param delay A delay in milliseconds between test runs. - * @param params The sets of 'size' parameters to pass to test. - * @param testCaseName The name of the test case to run. - * @param reportDir The directory to output the test results to. - * @param runName The name of the test run; used to name the output file. - * @param verbose Whether to print comments during test run. - * @param brokerUrl The URL of the broker to connect to. - * @param virtualHost The virtual host to run all tests on. Optional, may be <tt>null</tt>. - * @param engine The distributed test engine type to run the tests with. - * @param terminate <tt>true</tt> if test client nodes should be terminated at the end of the tests. - * @param csv <tt>true</tt> if the CSV results listener should be attached. - * @param xml <tt>true</tt> if the XML results listener should be attached. + * @param repetitions The number of times to repeat the test, or test batch size. + * @param duration The length of time to run the tests for. -1 means no duration has been set. + * @param threads The concurrency levels to ramp up to. + * @param delay A delay in milliseconds between test runs. + * @param params The sets of 'size' parameters to pass to test. + * @param testCaseName The name of the test case to run. + * @param reportDir The directory to output the test results to. + * @param runName The name of the test run; used to name the output file. + * @param verbose Whether to print comments during test run. + * @param brokerUrl The URL of the broker to connect to. + * @param virtualHost The virtual host to run all tests on. Optional, may be <tt>null</tt>. + * @param engine The distributed test engine type to run the tests with. + * @param terminate <tt>true</tt> if test client nodes should be terminated at the end of the tests. + * @param csv <tt>true</tt> if the CSV results listener should be attached. + * @param xml <tt>true</tt> if the XML results listener should be attached. + * @param decoratorFactories List of factories for user specified decorators. */ public Coordinator(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName, String reportDir, String runName, boolean verbose, String brokerUrl, String virtualHost, TestEngine engine, - boolean terminate, boolean csv, boolean xml) + boolean terminate, boolean csv, boolean xml, List<TestDecoratorFactory> decoratorFactories) { - super(repetitions, duration, threads, delay, params, testCaseName, reportDir, runName, csv, xml, verbose); + super(repetitions, duration, threads, delay, params, testCaseName, reportDir, runName, csv, xml, decoratorFactories); log.debug("public Coordinator(Integer repetitions = " + repetitions + " , Long duration = " + duration + ", int[] threads = " + Arrays.toString(threads) + ", int delay = " + delay + ", int[] params = " @@ -221,7 +218,11 @@ public class Coordinator extends TKTestRunner }, { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP }, { "v", "Verbose mode.", null, "false" }, - { "n", "A name for this test run, used to name the output file.", "name", "true" } + { "n", "A name for this test run, used to name the output file.", "name", "true" }, + { + "X:decorators", "A list of additional test decorators to wrap the tests in.", + "\"class.name[:class.name]*\"", "false" + } }), testContextProperties)); // Extract the command line options. @@ -234,13 +235,13 @@ public class Coordinator extends TKTestRunner boolean terminate = options.getPropertyAsBoolean("t"); boolean csvResults = options.getPropertyAsBoolean("-csv"); boolean xmlResults = options.getPropertyAsBoolean("-xml"); - String threadsString = options.getProperty("c"); Integer repetitions = options.getPropertyAsInteger("r"); String durationString = options.getProperty("d"); String paramsString = options.getProperty("s"); boolean verbose = options.getPropertyAsBoolean("v"); String testRunName = options.getProperty("n"); + String decorators = options.getProperty("X:decorators"); int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString); int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString); @@ -253,6 +254,9 @@ public class Coordinator extends TKTestRunner Collection<Class<? extends FrameworkBaseCase>> testCaseClasses = new ArrayList<Class<? extends FrameworkBaseCase>>(); + // Create a list of test decorator factories for use specified decorators to be applied. + List<TestDecoratorFactory> decoratorFactories = parseDecorators(decorators); + // Scan for available test cases using a classpath scanner. // ClasspathScanner.getMatches(DistributedTestCase.class, "^Test.*", true); @@ -306,7 +310,7 @@ public class Coordinator extends TKTestRunner // Create a coordinator and begin its test procedure. Coordinator coordinator = new Coordinator(repetitions, duration, threads, 0, params, null, reportDir, testRunName, verbose, brokerUrl, - virtualHost, engine, terminate, csvResults, xmlResults); + virtualHost, engine, terminate, csvResults, xmlResults, decoratorFactories); TestResult testResult = coordinator.start(testClassNames); @@ -324,6 +328,7 @@ public class Coordinator extends TKTestRunner { log.debug("Top level handler caught execption.", e); console.info(e.getMessage()); + e.printStackTrace(); System.exit(EXCEPTION_EXIT); } } @@ -339,8 +344,7 @@ public class Coordinator extends TKTestRunner */ public TestResult start(String[] testClassNames) throws Exception { - log.debug("public TestResult start(String[] testClassNames = " + PrettyPrintingUtils.printArray(testClassNames) - + ": called"); + log.debug("public TestResult start(String[] testClassNames = " + Arrays.toString(testClassNames) + ": called"); // Connect to the broker. connection = TestUtils.createConnection(TestContextProperties.getInstance()); @@ -474,7 +478,7 @@ public class Coordinator extends TKTestRunner { log.debug("targetTest is a TestSuite"); - TestSuite suite = (TestSuite) test; + TestSuite suite = (TestSuite)test; int numTests = suite.countTestCases(); log.debug("There are " + numTests + " in the suite."); @@ -494,6 +498,9 @@ public class Coordinator extends TKTestRunner log.debug("Wrapped with a WrappedSuiteTestDecorator."); } + // Apply any optional user specified decorators. + targetTest = applyOptionalUserDecorators(targetTest); + // Wrap the tests in a suitable distributed test decorator, to perform the invite/test cycle. targetTest = newTestDecorator(targetTest, enlistedClients, conversationFactory, connection); diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java index d11348cbad..d2f8ca896c 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java @@ -29,7 +29,7 @@ import org.apache.qpid.test.framework.TestClientDetails; import org.apache.qpid.test.framework.sequencers.CircuitFactory; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; import javax.jms.Connection; import javax.jms.Destination; diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java index 6fc3b2937e..d6ae390a4a 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java @@ -32,14 +32,13 @@ import org.apache.qpid.test.framework.sequencers.CircuitFactory; import org.apache.qpid.test.framework.sequencers.FanOutCircuitFactory; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; -import java.util.Collection; import java.util.Iterator; import java.util.Set; diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java index 130173cc96..38ab66d6ae 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java @@ -31,7 +31,7 @@ import org.apache.qpid.test.framework.sequencers.CircuitFactory; import org.apache.qpid.test.framework.sequencers.InteropCircuitFactory; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; import javax.jms.Connection; @@ -50,7 +50,7 @@ import java.util.*; * <tr><td> Broadcast test invitations and collect enlists. <td> {@link org.apache.qpid.util.ConversationFactory}. * <tr><td> Output test failures for clients unwilling to run the test case. <td> {@link Coordinator} * <tr><td> Execute distributed test cases. <td> {@link FrameworkBaseCase} - * <tr><td> Fail non participating pairings. <td> {@link OptOutTestCase} + * <tr><td> Fail non-participating pairings. <td> {@link OptOutTestCase} * </table> */ public class InteropTestDecorator extends DistributedTestDecorator @@ -206,3 +206,4 @@ public class InteropTestDecorator extends DistributedTestDecorator } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java new file mode 100644 index 0000000000..1c138fe575 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java @@ -0,0 +1,497 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework.distributedtesting;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.clocksynch.ClockSynchThread;
+import org.apache.qpid.test.framework.clocksynch.UDPClockSynchronizer;
+import org.apache.qpid.util.ReflectionUtils;
+import org.apache.qpid.util.ReflectionUtilsException;
+
+import org.apache.qpid.junit.extensions.SleepThrottle;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import javax.jms.*;
+
+import java.util.*;
+
+/**
+ * Implements a test client as described in the interop testing spec
+ * (http://cwiki.apache.org/confluence/display/qpid/Interop+Testing+Specification). A test client is an agent that
+ * reacts to control message sequences send by the test {@link Coordinator}.
+ *
+ * <p/><table><caption>Messages Handled by TestClient</caption>
+ * <tr><th> Message <th> Action
+ * <tr><td> Invite(compulsory) <td> Reply with Enlist.
+ * <tr><td> Invite(test case) <td> Reply with Enlist if test case available.
+ * <tr><td> AssignRole(test case) <td> Reply with Accept Role if matches an enlisted test. Keep test parameters.
+ * <tr><td> Start <td> Send test messages defined by test parameters. Send report on messages sent.
+ * <tr><td> Status Request <td> Send report on messages received.
+ * <tr><td> Terminate <td> Terminate the test client.
+ * <tr><td> ClockSynch <td> Synch clock against the supplied UDP address.
+ * </table>
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Handle all incoming control messages. <td> {@link TestClientControlledTest}
+ * <tr><td> Configure and look up test cases by name. <td> {@link TestClientControlledTest}
+ * </table>
+ */
+public class TestClient implements MessageListener
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(TestClient.class);
+
+ /** Used for reporting to the console. */
+ private static final Logger console = Logger.getLogger("CONSOLE");
+
+ /** Holds the default identifying name of the test client. */
+ public static final String CLIENT_NAME = "java";
+
+ /** Holds the URL of the broker to run the tests on. */
+ public static String brokerUrl;
+
+ /** Holds the virtual host to run the tests on. If <tt>null</tt>, then the default virtual host is used. */
+ public static String virtualHost;
+
+ /**
+ * Holds the test context properties that provides the default test parameters, plus command line overrides.
+ * This is initialized with the default test parameters, to which command line overrides may be applied.
+ */
+ public static ParsedProperties testContextProperties =
+ TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** Holds all the test cases loaded from the classpath. */
+ Map<String, TestClientControlledTest> testCases = new HashMap<String, TestClientControlledTest>();
+
+ /** Holds the test case currently being run by this client. */
+ protected TestClientControlledTest currentTestCase;
+
+ /** Holds the connection to the broker that the test is being coordinated on. */
+ protected Connection connection;
+
+ /** Holds the message producer to hold the test coordination over. */
+ protected MessageProducer producer;
+
+ /** Holds the JMS controlSession for the test coordination. */
+ protected Session session;
+
+ /** Holds the name of this client, with a default value. */
+ protected String clientName = CLIENT_NAME;
+
+ /** This flag indicates that the test client should attempt to join the currently running test case on start up. */
+ protected boolean join;
+
+ /** Holds the clock synchronizer for the test node. */
+ ClockSynchThread clockSynchThread;
+
+ /**
+ * Creates a new interop test client, listenting to the specified broker and virtual host, with the specified client
+ * identifying name.
+ *
+ * @param pBrokerUrl The url of the broker to connect to.
+ * @param pVirtualHost The virtual host to conect to.
+ * @param clientName The client name to use.
+ * @param join Flag to indicate that this client should attempt to join running tests.
+ */
+ public TestClient(String pBrokerUrl, String pVirtualHost, String clientName, boolean join)
+ {
+ log.debug("public TestClient(String pBrokerUrl = " + pBrokerUrl + ", String pVirtualHost = " + pVirtualHost
+ + ", String clientName = " + clientName + ", boolean join = " + join + "): called");
+
+ // Retain the connection parameters.
+ brokerUrl = pBrokerUrl;
+ virtualHost = pVirtualHost;
+ this.clientName = clientName;
+ this.join = join;
+ }
+
+ /**
+ * The entry point for the interop test coordinator. This client accepts the following command line arguments:
+ *
+ * <p/><table>
+ * <tr><td> -b <td> The broker URL. <td> Optional.
+ * <tr><td> -h <td> The virtual host. <td> Optional.
+ * <tr><td> -n <td> The test client name. <td> Optional.
+ * <tr><td> name=value <td> Trailing argument define name/value pairs. Added to system properties. <td> Optional.
+ * </table>
+ *
+ * @param args The command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ log.debug("public static void main(String[] args = " + Arrays.toString(args) + "): called");
+ console.info("Qpid Distributed Test Client.");
+
+ // Override the default broker url to be localhost:5672.
+ testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, "tcp://localhost:5672");
+
+ // Use the command line parser to evaluate the command line with standard handling behaviour (print errors
+ // and usage then exist if there are errors).
+ // Any options and trailing name=value pairs are also injected into the test context properties object,
+ // to override any defaults that may have been set up.
+ ParsedProperties options =
+ new ParsedProperties(org.apache.qpid.junit.extensions.util.CommandLineParser.processCommandLine(args,
+ new org.apache.qpid.junit.extensions.util.CommandLineParser(
+ new String[][]
+ {
+ { "b", "The broker URL.", "broker", "false" },
+ { "h", "The virtual host to use.", "virtual host", "false" },
+ { "o", "The name of the directory to output test timings to.", "dir", "false" },
+ { "n", "The name of the test client.", "name", "false" },
+ { "j", "Join this test client to running test.", "false" }
+ }), testContextProperties));
+
+ // Extract the command line options.
+ String brokerUrl = options.getProperty("b");
+ String virtualHost = options.getProperty("h");
+ String clientName = options.getProperty("n");
+ clientName = (clientName == null) ? CLIENT_NAME : clientName;
+ boolean join = options.getPropertyAsBoolean("j");
+
+ // To distinguish logging output set up an NDC on the client name.
+ NDC.push(clientName);
+
+ // Create a test client and start it running.
+ TestClient client = new TestClient(brokerUrl, virtualHost, clientName, join);
+
+ // Use a class path scanner to find all the interop test case implementations.
+ // Hard code the test classes till the classpath scanner is fixed.
+ Collection<Class<? extends TestClientControlledTest>> testCaseClasses =
+ new ArrayList<Class<? extends TestClientControlledTest>>();
+ // ClasspathScanner.getMatches(TestClientControlledTest.class, "^TestCase.*", true);
+ testCaseClasses.addAll(loadTestCases("org.apache.qpid.interop.clienttestcases.TestCase1DummyRun",
+ "org.apache.qpid.interop.clienttestcases.TestCase2BasicP2P",
+ "org.apache.qpid.interop.clienttestcases.TestCase3BasicPubSub",
+ "org.apache.qpid.interop.clienttestcases.TestCase4P2PMessageSize",
+ "org.apache.qpid.interop.clienttestcases.TestCase5PubSubMessageSize",
+ "org.apache.qpid.test.framework.distributedcircuit.TestClientCircuitEnd"));
+
+ try
+ {
+ client.start(testCaseClasses);
+ }
+ catch (Exception e)
+ {
+ log.error("The test client was unable to start.", e);
+ console.info(e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Parses a list of class names, and loads them if they are available on the class path.
+ *
+ * @param classNames The names of the classes to load.
+ *
+ * @return A list of the loaded test case classes.
+ */
+ public static List<Class<? extends TestClientControlledTest>> loadTestCases(String... classNames)
+ {
+ List<Class<? extends TestClientControlledTest>> testCases =
+ new LinkedList<Class<? extends TestClientControlledTest>>();
+
+ for (String className : classNames)
+ {
+ try
+ {
+ Class<?> cls = ReflectionUtils.forName(className);
+ testCases.add((Class<? extends TestClientControlledTest>) cls);
+ }
+ catch (ReflectionUtilsException e)
+ {
+ // Ignore, class could not be found, so test not available.
+ console.warn("Requested class " + className + " cannot be found, ignoring it.");
+ }
+ catch (ClassCastException e)
+ {
+ // Ignore, class was not of correct type to be a test case.
+ console.warn("Requested class " + className + " is not an instance of TestClientControlledTest.");
+ }
+ }
+
+ return testCases;
+ }
+
+ /**
+ * Starts the interop test client running. This causes it to start listening for incoming test invites.
+ *
+ * @param testCaseClasses The classes of the available test cases. The test case names from these are used to
+ * matchin incoming test invites against.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through.
+ */
+ protected void start(Collection<Class<? extends TestClientControlledTest>> testCaseClasses) throws JMSException
+ {
+ log.debug("protected void start(Collection<Class<? extends TestClientControlledTest>> testCaseClasses = "
+ + testCaseClasses + "): called");
+
+ // Create all the test case implementations and index them by the test names.
+ for (Class<? extends TestClientControlledTest> nextClass : testCaseClasses)
+ {
+ try
+ {
+ TestClientControlledTest testCase = nextClass.newInstance();
+ testCases.put(testCase.getName(), testCase);
+ }
+ catch (InstantiationException e)
+ {
+ log.warn("Could not instantiate test case class: " + nextClass.getName(), e);
+ // Ignored.
+ }
+ catch (IllegalAccessException e)
+ {
+ log.warn("Could not instantiate test case class due to illegal access: " + nextClass.getName(), e);
+ // Ignored.
+ }
+ }
+
+ // Open a connection to communicate with the coordinator on.
+ connection = TestUtils.createConnection(testContextProperties);
+ session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Set this up to listen for control messages.
+ Topic privateControlTopic = session.createTopic("iop.control." + clientName);
+ MessageConsumer consumer = session.createConsumer(privateControlTopic);
+ consumer.setMessageListener(this);
+
+ Topic controlTopic = session.createTopic("iop.control");
+ MessageConsumer consumer2 = session.createConsumer(controlTopic);
+ consumer2.setMessageListener(this);
+
+ // Create a producer to send replies with.
+ producer = session.createProducer(null);
+
+ // If the join flag was set, then broadcast a join message to notify the coordinator that a new test client
+ // is available to join the current test case, if it supports it. This message may be ignored, or it may result
+ // in this test client receiving a test invite.
+ if (join)
+ {
+ Message joinMessage = session.createMessage();
+
+ joinMessage.setStringProperty("CONTROL_TYPE", "JOIN");
+ joinMessage.setStringProperty("CLIENT_NAME", clientName);
+ joinMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName);
+ producer.send(controlTopic, joinMessage);
+ }
+
+ // Start listening for incoming control messages.
+ connection.start();
+ }
+
+ /**
+ * Handles all incoming control messages.
+ *
+ * @param message The incoming message.
+ */
+ public void onMessage(Message message)
+ {
+ NDC.push(clientName);
+ log.debug("public void onMessage(Message message = " + message + "): called");
+
+ try
+ {
+ String controlType = message.getStringProperty("CONTROL_TYPE");
+ String testName = message.getStringProperty("TEST_NAME");
+
+ log.debug("Received control of type '" + controlType + "' for the test '" + testName + "'");
+
+ // Check if the message is a test invite.
+ if ("INVITE".equals(controlType))
+ {
+ // Flag used to indicate that an enlist should be sent. Only enlist to compulsory invites or invites
+ // for which test cases exist.
+ boolean enlist = false;
+
+ if (testName != null)
+ {
+ log.debug("Got an invite to test: " + testName);
+
+ // Check if the requested test case is available.
+ TestClientControlledTest testCase = testCases.get(testName);
+
+ if (testCase != null)
+ {
+ log.debug("Found implementing class for test '" + testName + "', enlisting for it.");
+
+ // Check if the test case will accept the invitation.
+ enlist = testCase.acceptInvite(message);
+
+ log.debug("The test case "
+ + (enlist ? " accepted the invite, enlisting for it."
+ : " did not accept the invite, not enlisting."));
+
+ // Make the requested test case the current test case.
+ currentTestCase = testCase;
+ }
+ else
+ {
+ log.debug("Received an invite to the test '" + testName + "' but this test is not known.");
+ }
+ }
+ else
+ {
+ log.debug("Got a compulsory invite, enlisting for it.");
+
+ enlist = true;
+ }
+
+ if (enlist)
+ {
+ // Reply with the client name in an Enlist message.
+ Message enlistMessage = session.createMessage();
+ enlistMessage.setStringProperty("CONTROL_TYPE", "ENLIST");
+ enlistMessage.setStringProperty("CLIENT_NAME", clientName);
+ enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName);
+ enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending enlist message '" + enlistMessage + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), enlistMessage);
+ }
+ else
+ {
+ // Reply with the client name in an Decline message.
+ Message enlistMessage = session.createMessage();
+ enlistMessage.setStringProperty("CONTROL_TYPE", "DECLINE");
+ enlistMessage.setStringProperty("CLIENT_NAME", clientName);
+ enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName);
+ enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending decline message '" + enlistMessage + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), enlistMessage);
+ }
+ }
+ else if ("ASSIGN_ROLE".equals(controlType))
+ {
+ // Assign the role to the current test case.
+ String roleName = message.getStringProperty("ROLE");
+
+ log.debug("Got a role assignment to role: " + roleName);
+
+ TestClientControlledTest.Roles role = Enum.valueOf(TestClientControlledTest.Roles.class, roleName);
+
+ currentTestCase.assignRole(role, message);
+
+ // Reply by accepting the role in an Accept Role message.
+ Message acceptRoleMessage = session.createMessage();
+ acceptRoleMessage.setStringProperty("CLIENT_NAME", clientName);
+ acceptRoleMessage.setStringProperty("CONTROL_TYPE", "ACCEPT_ROLE");
+ acceptRoleMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending accept role message '" + acceptRoleMessage + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), acceptRoleMessage);
+ }
+ else if ("START".equals(controlType) || "STATUS_REQUEST".equals(controlType))
+ {
+ if ("START".equals(controlType))
+ {
+ log.debug("Got a start notification.");
+
+ // Extract the number of test messages to send from the start notification.
+ int numMessages;
+
+ try
+ {
+ numMessages = message.getIntProperty("MESSAGE_COUNT");
+ }
+ catch (NumberFormatException e)
+ {
+ // If the number of messages is not specified, use the default of one.
+ numMessages = 1;
+ }
+
+ // Start the current test case.
+ currentTestCase.start(numMessages);
+ }
+ else
+ {
+ log.debug("Got a status request.");
+ }
+
+ // Generate the report from the test case and reply with it as a Report message.
+ Message reportMessage = currentTestCase.getReport(session);
+ reportMessage.setStringProperty("CLIENT_NAME", clientName);
+ reportMessage.setStringProperty("CONTROL_TYPE", "REPORT");
+ reportMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending report message '" + reportMessage + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), reportMessage);
+ }
+ else if ("TERMINATE".equals(controlType))
+ {
+ console.info("Received termination instruction from coordinator.");
+
+ // Is a cleaner shutdown needed?
+ connection.close();
+ System.exit(0);
+ }
+ else if ("CLOCK_SYNCH".equals(controlType))
+ {
+ log.debug("Received clock synch command.");
+ String address = message.getStringProperty("ADDRESS");
+
+ log.debug("address = " + address);
+
+ // Re-create (if necessary) and start the clock synch thread to synch the clock every ten seconds.
+ if (clockSynchThread != null)
+ {
+ clockSynchThread.terminate();
+ }
+
+ SleepThrottle throttle = new SleepThrottle();
+ throttle.setRate(0.1f);
+
+ clockSynchThread = new ClockSynchThread(new UDPClockSynchronizer(address), throttle);
+ clockSynchThread.start();
+ }
+ else
+ {
+ // Log a warning about this but otherwise ignore it.
+ log.warn("Got an unknown control message, controlType = " + controlType + ", message = " + message);
+ }
+ }
+ catch (JMSException e)
+ {
+ // Log a warning about this, but otherwise ignore it.
+ log.warn("Got JMSException whilst handling message: " + message, e);
+ }
+ // Log any runtimes that fall through this message handler. These are fatal errors for the test client.
+ catch (RuntimeException e)
+ {
+ log.error("The test client message handler got an unhandled exception: ", e);
+ console.info("The message handler got an unhandled exception, terminating the test client.");
+ System.exit(1);
+ }
+ finally
+ {
+ NDC.pop();
+ }
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java b/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java index 3752888a9e..8a82b12832 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java @@ -26,8 +26,8 @@ import junit.framework.TestCase; import org.apache.log4j.Logger; -import uk.co.thebadgerset.junit.extensions.ShutdownHookable; -import uk.co.thebadgerset.junit.extensions.listeners.TKTestListener; +import org.apache.qpid.junit.extensions.ShutdownHookable; +import org.apache.qpid.junit.extensions.listeners.TKTestListener; import java.io.IOException; import java.io.PrintWriter; diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java new file mode 100644 index 0000000000..14ae108da8 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java @@ -0,0 +1,133 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework.localcircuit;
+
+import org.apache.qpid.client.AMQNoConsumersException;
+import org.apache.qpid.client.AMQNoRouteException;
+import org.apache.qpid.test.framework.*;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * LocalAMQPPublisherImpl is an extension of {@link LocalPublisherImpl} that adds AMQP specific features. Specifically
+ * extra assertions for AMQP features not available through generic JMS.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ */
+public class LocalAMQPPublisherImpl extends LocalPublisherImpl implements AMQPPublisher
+{
+ /**
+ * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured
+ * for messages and exceptions received by the circuit end.
+ *
+ * @param producer The message producer for the circuit end point.
+ * @param consumer The message consumer for the circuit end point.
+ * @param session The controlSession for the circuit end point.
+ * @param messageMonitor The monitor to notify of all messages received by the circuit end.
+ * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end.
+ */
+ public LocalAMQPPublisherImpl(MessageProducer producer, MessageConsumer consumer, Session session,
+ MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor)
+ {
+ super(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Creates a circuit end point from the producer, consumer and controlSession in a circuit end base implementation.
+ *
+ * @param end The circuit end base implementation to take producers and consumers from.
+ */
+ public LocalAMQPPublisherImpl(CircuitEndBase end)
+ {
+ super(end);
+ }
+
+ /**
+ * Provides an assertion that the publisher got a no consumers exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no consumers exception on every message.
+ */
+ public Assertion noConsumersAssertion(ParsedProperties testProps)
+ {
+ return new AssertionBase()
+ {
+ public boolean apply()
+ {
+ boolean passed = true;
+ ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor();
+
+ if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoConsumersException.class))
+ {
+ passed = false;
+
+ addError("Was expecting linked exception type " + AMQNoConsumersException.class.getName()
+ + " on the connection.\n");
+ addError((connectionExceptionMonitor.size() > 0)
+ ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor)
+ : "Got no exceptions on the connection.");
+ }
+
+ return passed;
+ }
+ };
+ }
+
+ /**
+ * Provides an assertion that the publisher got a no rout exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no rout exception on every message.
+ */
+ public Assertion noRouteAssertion(ParsedProperties testProps)
+ {
+ return new AssertionBase()
+ {
+ public boolean apply()
+ {
+ boolean passed = true;
+ ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor();
+
+ if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoRouteException.class))
+ {
+ passed = false;
+
+ addError("Was expecting linked exception type " + AMQNoRouteException.class.getName()
+ + " on the connection.\n");
+ addError((connectionExceptionMonitor.size() > 0)
+ ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor)
+ : "Got no exceptions on the connection.");
+ }
+
+ return passed;
+ }
+ };
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java index 3a5ff49fd1..391091266c 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java @@ -22,16 +22,14 @@ package org.apache.qpid.test.framework.localcircuit; import org.apache.log4j.Logger; -import org.apache.qpid.client.AMQSession; import org.apache.qpid.test.framework.*; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.*; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; /** * LocalCircuitImpl provides an implementation of the test circuit. This is a local only circuit implementation that @@ -47,7 +45,7 @@ import java.util.concurrent.atomic.AtomicLong; * <tr><td> Apply assertions against the circuits state. <td> {@link Assertion} * <tr><td> Send test messages over the circuit. * <tr><td> Perform the default test procedure on the circuit. - * <tr><td> Provide access to connection and controlSession exception monitors <td> {@link ExceptionMonitor} + * <tr><td> Provide access to connection and controlSession exception monitors. <td> {@link ExceptionMonitor} * </table> */ public class LocalCircuitImpl implements Circuit @@ -55,9 +53,6 @@ public class LocalCircuitImpl implements Circuit /** Used for debugging. */ private static final Logger log = Logger.getLogger(LocalCircuitImpl.class); - /** Used to create unique destination names for each test. */ - private static AtomicLong uniqueDestsId = new AtomicLong(); - /** Holds the test configuration for the circuit. */ private ParsedProperties testProps; @@ -86,7 +81,7 @@ public class LocalCircuitImpl implements Circuit * @param connection The connection. * @param connectionExceptionMonitor The connection exception monitor. */ - protected LocalCircuitImpl(ParsedProperties testProps, LocalPublisherImpl publisher, LocalReceiverImpl receiver, + public LocalCircuitImpl(ParsedProperties testProps, LocalPublisherImpl publisher, LocalReceiverImpl receiver, Connection connection, ExceptionMonitor connectionExceptionMonitor) { this.testProps = testProps; @@ -102,159 +97,6 @@ public class LocalCircuitImpl implements Circuit } /** - * Creates a local test circuit from the specified test parameters. - * - * @param testProps The test parameters. - * - * @return A connected and ready to start, test circuit. - */ - public static Circuit createCircuit(ParsedProperties testProps) - { - // Create a standard publisher/receivers test client pair on a shared connection, individual sessions. - try - { - // Cast the test properties into a typed interface for convenience. - MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); - - // Get a unique offset to append to destination names to make them unique to the connection. - long uniqueId = uniqueDestsId.incrementAndGet(); - - // Set up the connection. - Connection connection = TestUtils.createConnection(testProps); - - // Add the connection exception listener to assert on exception conditions with. - // ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); - // connection.setExceptionListener(exceptionMonitor); - - // Set up the publisher. - CircuitEndBase publisherEnd = createPublisherCircuitEnd(connection, props, uniqueId); - - // Set up the receiver. - CircuitEndBase receiverEnd = createReceiverCircuitEnd(connection, props, uniqueId); - - // Start listening for incoming messages. - connection.start(); - - // Package everything up. - LocalPublisherImpl publisher = new LocalPublisherImpl(publisherEnd); - LocalReceiverImpl receiver = new LocalReceiverImpl(receiverEnd); - - return new LocalCircuitImpl(testProps, publisher, receiver, connection, publisher.getExceptionMonitor()); - } - catch (JMSException e) - { - throw new RuntimeException("Could not create publisher/receivers pair due to a JMSException.", e); - } - } - - /** - * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters. - * - * @param connection The connection to build the circuit end on. - * @param testProps The test parameters to configure the circuit end construction. - * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. - * - * @return A circuit end suitable for the publishing side of a test circuit. - * - * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. - */ - public static CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) - throws JMSException - { - log.debug( - "public static CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " - + uniqueId + "): called"); - - // Cast the test properties into a typed interface for convenience. - MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); - - Session session = connection.createSession(props.getTransacted(), props.getAckMode()); - - Destination destination = - props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) - : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); - - MessageProducer producer = - props.getPublisherProducerBind() - ? ((props.getImmediate() | props.getMandatory()) - ? ((AMQSession) session).createProducer(destination, props.getMandatory(), props.getImmediate()) - : session.createProducer(destination)) : null; - - MessageConsumer consumer = - props.getPublisherConsumerBind() - ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; - - MessageMonitor messageMonitor = new MessageMonitor(); - - if (consumer != null) - { - consumer.setMessageListener(messageMonitor); - } - - ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); - connection.setExceptionListener(exceptionMonitor); - - if (!props.getPublisherConsumerActive() && (consumer != null)) - { - consumer.close(); - } - - return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor); - } - - /** - * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters. - * - * @param connection The connection to build the circuit end on. - * @param testProps The test parameters to configure the circuit end construction. - * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. - * - * @return A circuit end suitable for the receiving side of a test circuit. - * - * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. - */ - public static CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) - throws JMSException - { - log.debug( - "public static CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " - + uniqueId + "): called"); - - // Cast the test properties into a typed interface for convenience. - MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); - - Session session = connection.createSession(props.getTransacted(), props.getAckMode()); - - MessageProducer producer = - props.getReceiverProducerBind() - ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; - - Destination destination = - props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) - : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); - - MessageConsumer consumer = - props.getReceiverConsumerBind() - ? ((props.getDurableSubscription() && props.getPubsub()) - ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination)) - : null; - - MessageMonitor messageMonitor = new MessageMonitor(); - - if (consumer != null) - { - consumer.setMessageListener(messageMonitor); - } - - if (!props.getReceiverConsumerActive() && (consumer != null)) - { - consumer.close(); - } - - return new CircuitEndBase(producer, consumer, session, messageMonitor, null); - } - - /** * Gets the interface on the publishing end of the circuit. * * @return The publishing end of the circuit. @@ -342,7 +184,7 @@ public class LocalCircuitImpl implements Circuit } catch (JMSException e) { - throw new RuntimeException("Got JMSException during close.", e); + throw new RuntimeException("Got JMSException during close:" + e.getMessage(), e); } } @@ -351,16 +193,24 @@ public class LocalCircuitImpl implements Circuit */ protected void send() { - boolean transactional = testProps.getPropertyAsBoolean(MessagingTestConfigProperties.TRANSACTED_PROPNAME); + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + boolean transactional = props.getPublisherTransacted(); + boolean rollback = props.getRollbackPublisher(); - // Send an immediate message through the publisher and ensure that it results in a JMSException. + // Send a message through the publisher and log any exceptions raised. try { CircuitEnd end = getLocalPublisherCircuitEnd(); end.send(createTestMessage(end)); - if (transactional) + if (rollback) + { + end.getSession().rollback(); + } + else if (transactional) { end.getSession().commit(); } @@ -406,12 +256,12 @@ public class LocalCircuitImpl implements Circuit // Request a status report. check(); - // Apply all of the requested assertions, keeping record of any that fail. - List<Assertion> failures = applyAssertions(assertions); - // Clean up the publisher/receivers/controlSession/connections. close(); + // Apply all of the requested assertions, keeping record of any that fail. + List<Assertion> failures = applyAssertions(assertions); + // Return any failed assertions to the caller. return failures; } @@ -427,7 +277,10 @@ public class LocalCircuitImpl implements Circuit */ private Message createTestMessage(CircuitEnd client) throws JMSException { - return client.getSession().createTextMessage("Hello"); + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + return TestUtils.createTestMessageOfSize(client.getSession(), props.getMessageSize()); } /** @@ -450,3 +303,4 @@ public class LocalCircuitImpl implements Circuit return exceptionMonitor; } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java index 5c5807dcd9..3ec3f62538 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java @@ -20,10 +20,10 @@ */ package org.apache.qpid.test.framework.localcircuit; -import org.apache.qpid.client.AMQNoConsumersException; -import org.apache.qpid.client.AMQNoRouteException; import org.apache.qpid.test.framework.*; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; @@ -31,7 +31,7 @@ import javax.jms.Session; /** * Provides an implementation of the {@link Publisher} interface and wraps a single message producer and consumer on * a single controlSession, as a {@link CircuitEnd}. A local publisher also acts as a circuit end, because for a locally - * located circuit the assertions may be applied directly, there does not need to be any inter process messaging + * located circuit the assertions may be applied directly, there does not need to be any inter-process messaging * between the publisher and its single circuit end, in order to ascertain its status. * * <p/><table id="crc"><caption>CRC Card</caption> @@ -46,14 +46,17 @@ import javax.jms.Session; public class LocalPublisherImpl extends CircuitEndBase implements Publisher { /** Holds a reference to the containing circuit. */ - private LocalCircuitImpl circuit; + protected LocalCircuitImpl circuit; /** - * Creates a circuit end point on the specified producer, consumer and controlSession. + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. * * @param producer The message producer for the circuit end point. * @param consumer The message consumer for the circuit end point. * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. */ public LocalPublisherImpl(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) @@ -74,9 +77,11 @@ public class LocalPublisherImpl extends CircuitEndBase implements Publisher /** * Provides an assertion that the publisher encountered no exceptions. * + * @param testProps + * * @return An assertion that the publisher encountered no exceptions. */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) { return new AssertionBase() { @@ -109,41 +114,26 @@ public class LocalPublisherImpl extends CircuitEndBase implements Publisher } /** - * Provides an assertion that the publisher got a no consumers exception on every message. + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. * - * @return An assertion that the publisher got a no consumers exception on every message. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. */ - public Assertion noConsumersAssertion() + public Assertion channelClosedAssertion(ParsedProperties testProps) { - return new AssertionBase() - { - public boolean apply() - { - boolean passed = true; - ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor(); - - if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoConsumersException.class)) - { - passed = false; - - addError("Was expecting linked exception type " + AMQNoConsumersException.class.getName() - + " on the connection.\n"); - addError((connectionExceptionMonitor.size() > 0) - ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor) - : "Got no exceptions on the connection."); - } - - return passed; - } - }; + return new NotApplicableAssertion(testProps); } /** - * Provides an assertion that the publisher got a no rout exception on every message. + * Provides an assertion that the publisher got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. * - * @return An assertion that the publisher got a no rout exception on every message. + * @return An assertion that the publisher got a given exception during the test. */ - public Assertion noRouteAssertion() + public Assertion exceptionAssertion(ParsedProperties testProps, final Class<? extends Exception> exceptionClass) { return new AssertionBase() { @@ -152,11 +142,11 @@ public class LocalPublisherImpl extends CircuitEndBase implements Publisher boolean passed = true; ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor(); - if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoRouteException.class)) + if (!connectionExceptionMonitor.assertExceptionOfType(exceptionClass)) { passed = false; - addError("Was expecting linked exception type " + AMQNoRouteException.class.getName() + addError("Was expecting linked exception type " + exceptionClass.getName() + " on the connection.\n"); addError((connectionExceptionMonitor.size() > 0) ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor) diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java index 0c5dae096e..74f414c974 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java @@ -22,6 +22,8 @@ package org.apache.qpid.test.framework.localcircuit; import org.apache.qpid.test.framework.*; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; @@ -46,11 +48,14 @@ public class LocalReceiverImpl extends CircuitEndBase implements Receiver private LocalCircuitImpl circuit; /** - * Creates a circuit end point on the specified producer, consumer and controlSession. + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. * * @param producer The message producer for the circuit end point. * @param consumer The message consumer for the circuit end point. * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. */ public LocalReceiverImpl(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) @@ -71,21 +76,60 @@ public class LocalReceiverImpl extends CircuitEndBase implements Receiver /** * Provides an assertion that the receivers encountered no exceptions. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers encountered no exceptions. */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) + { + return new NotApplicableAssertion(testProps); + } + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps) { - return null; + return new NotApplicableAssertion(testProps); } /** * Provides an assertion that the receivers got all messages that were sent to it. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers got all messages that were sent to it. */ - public Assertion allMessagesAssertion() + public Assertion allMessagesReceivedAssertion(ParsedProperties testProps) + { + return new NotApplicableAssertion(testProps); + } + + /** + * Provides an assertion that the receivers got none of the messages that were sent to it. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the receivers got none of the messages that were sent to it. + */ + public Assertion noMessagesReceivedAssertion(ParsedProperties testProps) + { + return new NotApplicableAssertion(testProps); + } + + /** + * Provides an assertion that the receiver got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. @return An assertion that the receiver got a given exception during the test. + */ + public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass) { - return null; + return new NotApplicableAssertion(testProps); } /** diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java new file mode 100644 index 0000000000..4545e3c164 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java @@ -0,0 +1,96 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework.qpid;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.LocalAMQPCircuitFactory;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+/**
+ * AMQPFeatureDecorator applies decorations to {@link FrameworkBaseCase} tests, so that they may use Qpid/AMQP specific
+ * features, not available through JMS. For example, the immediate and mandatory flags. This decorator replaces the
+ * standard test circuit factory on the base class with one that allows these features to be used.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Substitute the circuit factory with an AMQP/Qpid specific one.
+ * </table>
+ *
+ * @todo This wrapper substitutes in a LocalAMQPCircuitFactory, which is fine for local tests. For distributed tests
+ * the Fanout or Interop factories are substituted in by their decorators instead. These actually use
+ * distributed circuit static create methods to build the circuits, which should actually be changed to a factory,
+ * so that static methods do not need to be used. The distributed circuit creater delegates the circuit
+ * construction to remote test nodes. This decorator should not be used with distributed tests, or should be made
+ * aware of them, in which case it might ensure that an AMQP feature (implied already by other properties) flag
+ * is passed out to the remote test nodes, and provide a mechansim for them to decorate their circuit creation
+ * with AMQP features too. Add factory substituion/decoration mechansim for test clients, here or in a seperate
+ * class.
+ */
+public class AMQPFeatureDecorator extends WrappedSuiteTestDecorator
+{
+ /** The test suite to run. */
+ private Test test;
+
+ /**
+ * Creates a wrapped test test decorator from another one.
+ *
+ * @param test The test test.
+ */
+ public AMQPFeatureDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Runs the tests with a LocalAMQPCircuitFactory. Only tests that extend FrameworkBaseCase are decorated.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ for (Test test : getAllUnderlyingTests())
+ {
+ if (test instanceof FrameworkBaseCase)
+ {
+ FrameworkBaseCase frameworkTest = (FrameworkBaseCase) test;
+ frameworkTest.setCircuitFactory(new LocalAMQPCircuitFactory());
+ }
+ }
+
+ // Run the test.
+ test.run(testResult);
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "AMQPFeatureDecorator: [test = \"" + test + "\"]";
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java new file mode 100644 index 0000000000..3a048ac042 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java @@ -0,0 +1,95 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework.qpid;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+import org.apache.qpid.test.framework.CauseFailureUserPrompt;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+/**
+ * CauseFailureDecorator applies decorations to {@link BrokerLifecycleAware} tests, so that they may use different failure
+ * mechanisms. It is capable of detecting when a test case uses in-vm brokers, and setting up an automatic failure
+ * for those tests, so that the current live broker can be shut-down by test cases. For external brokers, automatic
+ * failure could be implemented, for example by having a kill script. At the moment this sets up the failure to prompt
+ * a user interactively to cause a failure, using {@link CauseFailureUserPrompt}.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Setup automatic failures for in-vm brokers. <td> {@link CauseFailureInVM}
+ * <tr><td> Setup user generated failures for external brokers. <td> {@link CauseFailureUserPrompt}.
+ * <tr><td>
+ * </table>
+ *
+ * @todo Slight problem in that CauseFailureInVM is Qpid specific, whereas CauseFailureUserPrompt is not. Would like the
+ * failure decorator to be non-qpid specific so that it can test failure of any JMS implementation too. Either pass
+ * in class name of failure mechanism, set it up in the in-vm decorator instead of here but with prompt user as the
+ * default for when the in-vm decorator is not used?
+ */
+public class CauseFailureDecorator extends WrappedSuiteTestDecorator
+{
+ /** The test suite to run. */
+ private Test test;
+
+ /**
+ * Creates a wrapped test test decorator from another one.
+ *
+ * @param test The test test.
+ */
+ public CauseFailureDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Runs the tests with a LocalAMQPCircuitFactory. Only tests that extend FrameworkBaseCase are decorated.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ for (Test test : getAllUnderlyingTests())
+ {
+ if (test instanceof BrokerLifecycleAware)
+ {
+ BrokerLifecycleAware failureTest = (BrokerLifecycleAware) test;
+ failureTest.setFailureMechanism(new CauseFailureUserPrompt());
+ }
+ }
+
+ // Run the test.
+ test.run(testResult);
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "CauseFailureDecorator: [test = \"" + test + "\"]";
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java new file mode 100644 index 0000000000..b63ac43601 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java @@ -0,0 +1,70 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework.qpid;
+
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.test.framework.CauseFailure;
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+
+/**
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Cause messaging broker failure on the active in-vm broker.
+ * <td> {@link TransportConnection}, {@link ApplicationRegistry}
+ * </table>
+ */
+public class CauseFailureInVM implements CauseFailure
+{
+ /** Holds the in-vm broker instrumented test case to create failures for. */
+ private BrokerLifecycleAware inVMTest;
+
+ /**
+ * Creates an automated failure mechanism for testing against in-vm brokers. The test to create the mechanism
+ * for is specified, and as this failure is for in-vm brokers, the test must be {@link org.apache.qpid.test.framework.BrokerLifecycleAware}. The test
+ * must also report that it is currently being run against an in-vm broker, and it is a runtime error if it is not,
+ * as the creator of this failure mechanism should already have checked that it is.
+ *
+ * @param inVMTest The test case to create an automated failure mechanism for.
+ */
+ public CauseFailureInVM(BrokerLifecycleAware inVMTest)
+ {
+ // Check that the test is really using in-vm brokers.
+ if (!inVMTest.usingInVmBroker())
+ {
+ throw new RuntimeException(
+ "Cannot create in-vm broker failure mechanism for a test that is not using in-vm brokers.");
+ }
+
+ this.inVMTest = inVMTest;
+ }
+
+ /**
+ * Causes the active message broker to fail.
+ */
+ public void causeFailure()
+ {
+ int liveBroker = inVMTest.getLiveBroker();
+
+ TransportConnection.killVMBroker(liveBroker);
+ ApplicationRegistry.remove(liveBroker);
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java new file mode 100644 index 0000000000..bcf052ea06 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java @@ -0,0 +1,135 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.framework.qpid;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+
+import org.apache.qpid.junit.extensions.SetupTaskAware;
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+/**
+ * InVMBrokerDecorator is a test decorator, that is activated when running tests against an in-vm broker only. Its
+ * purpose is to automatically create, and close and delete an in-vm broker, during the set-up and tear-down of
+ * each test case. This decorator may only be used in conjunction with tests that extend {@link FrameworkBaseCase}.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create/Destroy an in-vm broker on every test run.
+ * </table>
+ *
+ * @todo May need to add a more fine grained injection point for the in-vm broker management, as this acts at the
+ * suite level, rather than the individual test level.
+ *
+ * @todo Management of in-vm brokers for failure testing. Failure test setups may need to set their connection url to
+ * use multiple broker (vm://:1;vm://:2), with fail-over between them. There is round-robin fail-over, but also
+ * retry? A test case using an in-vm broker needs to record which one it is using, so that it can be
+ * killed/restarted.
+ */
+public class InVMBrokerDecorator extends WrappedSuiteTestDecorator
+{
+ /** The test suite to run. */
+ private Test test;
+
+ /**
+ * Creates a wrapped test suite decorator from another one.
+ *
+ * @param test The test suite.
+ */
+ public InVMBrokerDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Runs the tests with in-vm broker creation and clean-up added to the tests task stack.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ for (Test test : getAllUnderlyingTests())
+ {
+ // Check that the test to have an in-vm broker setup/teardown task added to it, is actually a framework
+ // test that can handle setup tasks.
+ if ((test instanceof SetupTaskAware))
+ {
+ SetupTaskAware frameworkTest = (SetupTaskAware) test;
+
+ frameworkTest.chainSetupTask(new Runnable()
+ {
+ public void run()
+ {
+ // Ensure that the in-vm broker is created.
+ try
+ {
+ TransportConnection.createVMBroker(1);
+ }
+ catch (AMQVMBrokerCreationException e)
+ {
+ throw new RuntimeException("In-VM broker creation failed: " + e.getMessage(), e);
+ }
+ }
+ });
+
+ frameworkTest.chainTearDownTask(new Runnable()
+ {
+ public void run()
+ {
+ // Ensure that the in-vm broker is cleaned up so that the next test starts afresh.
+ TransportConnection.killVMBroker(1);
+ ApplicationRegistry.remove(1);
+ }
+ });
+
+ // Check if the test is aware whether or not it can control the broker life cycle, and if so provide
+ // additional instrumentation for it to control the in-vm broker through.
+ if (test instanceof BrokerLifecycleAware)
+ {
+ BrokerLifecycleAware inVMTest = (BrokerLifecycleAware) test;
+ inVMTest.setInVmBrokers();
+ inVMTest.setLiveBroker(1);
+ inVMTest.setFailureMechanism(new CauseFailureInVM(inVMTest));
+ }
+ }
+ }
+
+ // Run the test.
+ test.run(testResult);
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "InVMBrokerDecorator: [test = " + test + "]";
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java index 4514fb836c..c7bde1ae03 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java @@ -31,13 +31,20 @@ import java.util.List; import java.util.Properties; /** + * BaseCircuitFactory provides some functionality common to all {@link CircuitFactory}s, such as the details of + * all {@link org.apache.qpid.test.framework.distributedtesting.TestClient}s that make up the end-points of + * the circuits that the factory creates, and an active {@link ConversationFactory} that can be used to generate + * control conversations with those circuit end-points. + * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> + * <tr><td> Hold the details of the sending and receiving end-points to create circuits from. + * <tr><td> Provide a conversation factory to create control conversations with the end-points. * </table> */ public abstract class BaseCircuitFactory implements CircuitFactory { + /** Used for debugging. */ private final Logger log = Logger.getLogger(BaseCircuitFactory.class); @@ -126,3 +133,4 @@ public abstract class BaseCircuitFactory implements CircuitFactory return conversationFactory; } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java index bf7fedcffc..fff617c583 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java @@ -25,17 +25,13 @@ import org.apache.qpid.test.framework.Circuit; import org.apache.qpid.test.framework.TestClientDetails; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; - -import javax.jms.JMSException; -import javax.jms.Message; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import java.util.List; -import java.util.Map; import java.util.Properties; /** - * A TestCaseSequence is responsibile for creating test circuits appropriate to the context that a test case is + * A CircuitFactory is responsibile for creating test circuits appropriate to the context that a test case is * running in, and providing an implementation of a standard test procedure over a test circuit. * * <p/><table id="crc"><caption>CRC Card</caption> @@ -43,12 +39,6 @@ import java.util.Properties; * <tr><td> Provide a standard test procedure over a test circuit. * <tr><td> Construct test circuits appropriate to a tests context. * </table> - * - * @todo The sequence test method is deprecated, in favour of using test circuits instead. This interface might be - * better renamed to somethign like CircuitFactory, also the split between this interface and - * DistributedTestSequencer could be removed and DistributedTestCase functionality merged into FrameworkBaseCase. - * This is so that any test case written on top of the framework base case can be distributed, without having - * to extend a different base test class. */ public interface CircuitFactory { diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java index 2ff6725365..d1c39ff3ff 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java @@ -29,7 +29,7 @@ import org.apache.qpid.test.framework.TestUtils; import org.apache.qpid.test.framework.distributedcircuit.DistributedCircuitImpl; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.Destination; import javax.jms.JMSException; @@ -46,7 +46,7 @@ import java.util.Properties; * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> + * <tr><td> Create distributed circuits from one to many test nodes, for fanout style testing. * </table> * * @todo Adapt this to be an n*m topology circuit factory. Need to add circuit topology definitions to the test @@ -57,7 +57,7 @@ import java.util.Properties; * * @todo The createCircuit methods on this and InteropCircuitFactory are going to be identical. This is because the * partitioning into senders and receivers is already done by the test decorators. Either eliminate these factories - * as unnesesary, or move the partitioning functionaility into the factories, in which case the test decorators + * as unnesesary, or move the partitioning functionality into the factories, in which case the test decorators * can probably be merged or eliminated. There is confusion over the placement of responsibilities between the * factories and the test decorators... although the test decorators may well do more than just circuit creation * in the future. For example, there may have to be a special decorator for test repetition that does one circuit diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java index 75df5c65ea..feb87e7b9c 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java @@ -29,7 +29,7 @@ import org.apache.qpid.test.framework.TestUtils; import org.apache.qpid.test.framework.distributedcircuit.DistributedCircuitImpl; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.Destination; import javax.jms.JMSException; @@ -41,10 +41,20 @@ import java.util.List; import java.util.Properties; /** + * InteropCircuitFactory is a circuit factory that creates distributed test circuits. Given a set of participating + * test client nodes, it assigns one node to the SENDER role and one the RECEIVER role. + * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><td> + * <tr><td> Create distributed circuits from pairs of test nodes, for interop style testing. * </table> + * + * @todo The partitioning of a set of nodes into sender and receiver roles is actually done by the interop test + * decorator. See the todo comment in FanOutCircuitFactory about merging the factories with the decorators, or + * more carefully dividing up responsibilities between them. + * + * @todo The squenceTest code is deprecated, but currently still used by the interop tests. It will be removed once it + * have been fully replaced by the default test procedure. */ public class InteropCircuitFactory extends BaseCircuitFactory { diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java new file mode 100644 index 0000000000..e7d874ffa9 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java @@ -0,0 +1,119 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.testcases;
+
+import org.apache.qpid.test.framework.*;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import javax.jms.JMSException;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * FailoverTest provides testing of fail-over over a local-circuit implementation. The circuit being tested may be
+ * against an in-vm broker or against an external broker, with the failure mechanism abstracted out of the test case.
+ * Automatic failures can be simulated against an in-vm broker. Currently the test must interact with the user to
+ * simulate failures on an external broker.
+ *
+ * Things to test:
+ * In tx, failure duing tx causes tx to error on subsequent sends/receives or commits/rollbacks.
+ * Outside of tx, reconnection allows msg flow to continue but there may be loss.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ *
+ * @todo This test is designed to be run over a local circuit only. For in-vm using automatic failures, for external
+ * brokers by prompting the user (or maybe using a script). Enforce the local-circuit only nature of the tests as
+ * well as thinking about how other local-circuit tests might be implemented. For example, could add a method
+ * to the framework base case for local only tests to call, that allows them access to the local-circuit
+ * implementation and so on.
+ *
+ * @todo More. Need to really expand the set of fail-over tests.
+ */
+public class FailoverTest extends FrameworkBaseCase
+{
+ /* Used for debugging purposes. */
+ // private static final Logger log = Logger.getLogger(FailoverTest.class);
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public FailoverTest(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Checks that all messages sent within a transaction are receieved despite a fail-over occuring outside of
+ * the transaction.
+ *
+ * @throws JMSException Allowed to fall through and fail test.
+ */
+ public void testTxP2PFailover() throws JMSException
+ {
+ // Set up the test properties to match the test cases requirements.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(ACK_MODE_PROPNAME, Session.AUTO_ACKNOWLEDGE);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // MessagingTestConfigProperties props = this.getTestParameters();
+
+ // Create the test circuit from the test configuration parameters.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Create an assertion that all messages are received.
+ Assertion allMessagesReceived = testCircuit.getReceiver().allMessagesReceivedAssertion(testProps);
+
+ // This test case assumes it is using a local circuit.
+ LocalCircuitImpl localCircuit = (LocalCircuitImpl) testCircuit;
+
+ Session producerSession = localCircuit.getLocalPublisherCircuitEnd().getSession();
+ MessageProducer producer = localCircuit.getLocalPublisherCircuitEnd().getProducer();
+ // MessageConsumer consumer = localCircuit.getLocalReceiverCircuitEnd().getConsumer();
+
+ // Send some test messages.
+ for (int i = 0; i < 100; i++)
+ {
+ producer.send(TestUtils.createTestMessageOfSize(producerSession, 10));
+ producerSession.commit();
+
+ // Cause a failover.
+ if (i == 50)
+ {
+ failureMechanism.causeFailure();
+ }
+
+ // Wait for the reconnection to complete.
+ }
+
+ // Check that trying to send within the original transaction fails.
+
+ // Check that all messages sent were received.
+ assertTrue("All messages sent were not received back again.", allMessagesReceived.apply());
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java new file mode 100644 index 0000000000..845c3ed9c8 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java @@ -0,0 +1,303 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.testcases;
+
+import org.apache.qpid.test.framework.AMQPPublisher;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * ImmediateMessageTest tests for the desired behaviour of immediate messages. Immediate messages are a non-JMS
+ * feature. A message may be marked with an immediate delivery flag, which means that a consumer must be connected
+ * to receive the message, through a valid route, when it is sent, or when its transaction is committed in the case
+ * of transactional messaging. If this is not the case, the broker should return the message with a NO_CONSUMERS code.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check that an immediate message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an immediate message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that an immediate message results in no consumers code, not using transactions, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no consumers code, in a transaction, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no route code, not using transactions, when no outgoing route is
+ * connected.
+ * <tr><td> Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is
+ * connected.
+ * <tr><td> Check that an immediate message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an immediate message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that an immediate message results in no consumers code, not using transactions, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no consumers code, in a transaction, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no route code, not using transactions, when no outgoing route is
+ * connected.
+ * <tr><td> Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is
+ * connected.
+ * </table>
+ *
+ * @todo All of these test cases will be generated by a test generator that thoroughly tests all combinations of test
+ * circuits.
+ */
+public class ImmediateMessageTest extends FrameworkBaseCase
+{
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public ImmediateMessageTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkNoTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Send one message with no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedNoTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedTxP2P()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteNoTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteTxP2P()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkNoTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Send one message with no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Send one message with no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedNoTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedTxPubSub()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteNoTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteTxPubSub()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** All these tests should have the immediate flag on. */
+ testProps.setProperty(IMMEDIATE_PROPNAME, true);
+ testProps.setProperty(MANDATORY_PROPNAME, false);
+
+ /** Bind the receivers consumer by default. */
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true);
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true);
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java new file mode 100644 index 0000000000..066b4e24ba --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java @@ -0,0 +1,321 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.testcases;
+
+import org.apache.qpid.test.framework.AMQPPublisher;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * MandatoryMessageTest tests for the desired behaviour of mandatory messages. Mandatory messages are a non-JMS
+ * feature. A message may be marked with a mandatory delivery flag, which means that a valid route for the message
+ * must exist, when it is sent, or when its transaction is committed in the case of transactional messaging. If this
+ * is not the case, the broker should return the message with a NO_CONSUMERS code.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check that an mandatory message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an mandatory message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected
+ * but the route exists.
+ * <tr><td> Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ * <tr><td> Check that an mandatory message results in no route code, not using transactions, when no consumer is
+ * connected.
+ * <tr><td> Check that an mandatory message results in no route code, upon transaction commit, when a consumer is
+ * connected.
+ * <tr><td> Check that an mandatory message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an mandatory message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected
+ * but the route exists.
+ * <tr><td> Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ * <tr><td> Check that an mandatory message results in no route code, not using transactions, when no consumer is
+ * connected.
+ * <tr><td> Check that an mandatory message results in no route code, upon transaction commit, when a consumer is
+ * connected.
+ * </table>
+ *
+ * @todo All of these test cases will be generated by a test generator that thoroughly tests all combinations of test
+ * circuits.
+ */
+public class MandatoryMessageTest extends FrameworkBaseCase
+{
+ /** Used to read the tests configurable properties through. */
+ ParsedProperties testProps;
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public MandatoryMessageTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkNoTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedNoTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedTxP2P()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteNoTxP2P()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteTxP2P()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkNoTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkTxPubSub()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedNoTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedTxPubSub()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteNoTxPubSub()
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteTxPubSub()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** All these tests should have the mandatory flag on. */
+ testProps.setProperty(IMMEDIATE_PROPNAME, false);
+ testProps.setProperty(MANDATORY_PROPNAME, true);
+
+ /** Bind the receivers consumer by default. */
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true);
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true);
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java new file mode 100644 index 0000000000..f39d22bc67 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java @@ -0,0 +1,132 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.testcases;
+
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * RollbackTest tests the rollback ability of transactional messaging.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check messages sent but rolled back are never received.
+ * <tr><td> Check messages received but rolled back are redelivered on subsequent receives.
+ * <tr><td> Attempting to rollback outside of a transaction results in an IllegalStateException.
+ * </table>
+ */
+public class RollbackTest extends FrameworkBaseCase
+{
+ /** Used to read the tests configurable properties through. */
+ ParsedProperties testProps;
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public RollbackTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check messages sent but rolled back are never received. */
+ public void testRolledbackMessageNotDelivered()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(ROLLBACK_PUBLISHER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps),
+ testCircuit.getReceiver().noMessagesReceivedAssertion(testProps))));
+ }
+
+ /** Check messages received but rolled back are redelivered on subsequent receives. */
+ public void testRolledbackMessagesSubsequentlyReceived()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_RECEIVER_PROPNAME, true);
+ testProps.setProperty(ROLLBACK_RECEIVER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps),
+ testCircuit.getReceiver().allMessagesReceivedAssertion(testProps))));
+ }
+
+ /** Attempting to rollback outside of a transaction results in an IllegalStateException. */
+ public void testRollbackUnavailableOutsideTransactionPublisher()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(ROLLBACK_PUBLISHER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().channelClosedAssertion(testProps))));
+ }
+
+ /** Attempting to rollback outside of a transaction results in an IllegalStateException. */
+ public void testRollbackUnavailableOutsideTransactionReceiver()
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_RECEIVER_PROPNAME, false);
+ testProps.setProperty(ROLLBACK_RECEIVER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getReceiver().channelClosedAssertion(testProps))));
+ }
+
+ /**
+ * Sets up all tests to have an active outward route and consumer by default.
+ *
+ * @throws Exception Any exceptions are allowed to fall through and fail the test.
+ */
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** Bind the receivers consumer by default. */
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true);
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true);
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java new file mode 100644 index 0000000000..f752ccba00 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java @@ -0,0 +1,151 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.test.testcases;
+
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.ACK_MODE_PROPNAME;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.PUBSUB_PROPNAME;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import javax.jms.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * TTLTest checks that time-to-live is applied to messages. The test sends messages with a variety of TTL stamps on them
+ * then after a pause attempts to receive those messages. Only messages with a large enough TTL to have survived the pause
+ * should be receiveable. This test case also applies an additional assertion against the broker, that the message store
+ * is empty at the end of the test.
+ *
+ * <p/>This test is designed to run over local circuits only, as it must control a timed pause between sending and receiving
+ * messages to that TTL can be applied to purge some of the messages.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ *
+ * @todo Use an interface or other method to mark this test as local only.
+ *
+ * @todo Implement the message store assertion for in-vm broker. Could also be done for external broker, for example
+ * by using diagnostic exchange.
+ *
+ * @todo Implement and add a queue depth assertion too. This might already be in another test to copy from.
+ *
+ * @todo Create variations on test theme, for different ack mode and tx and message sizes etc.
+ *
+ * @todo Add an allowable margin of error to the test, as ttl may not be precise.
+ */
+public class TTLTest extends FrameworkBaseCase
+{
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public TTLTest(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Checks that all messages sent with a TTL shorter than a pause between sending them and attempting to receive them
+ * will fail to arrive. Once all messages have been purged by TTL or received, check that they no longer exist on
+ * the broker.
+ *
+ * @throws javax.jms.JMSException Allowed to fall through and fail test.
+ */
+ public void testTTLP2P() throws JMSException
+ {
+ String errorMessages = "";
+ Random r = new Random();
+
+ // Used to accumulate correctly received messages in.
+ List<Message> receivedMessages = new LinkedList<Message>();
+
+ // Set up the test properties to match the test case requirements.
+ testProps.setProperty(ACK_MODE_PROPNAME, Session.AUTO_ACKNOWLEDGE);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Create the test circuit from the test configuration parameters.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(testProps);
+
+ // This test case assumes it is using a local circuit.
+ LocalCircuitImpl localCircuit = (LocalCircuitImpl) testCircuit;
+
+ Session producerSession = localCircuit.getLocalPublisherCircuitEnd().getSession();
+ MessageProducer producer = localCircuit.getLocalPublisherCircuitEnd().getProducer();
+ MessageConsumer consumer = localCircuit.getLocalReceiverCircuitEnd().getConsumer();
+
+ // Send some tests messages, with random TTLs, some shorter and some longer than the pause time.
+ for (int i = 0; i < 100; i++)
+ {
+ Message testMessage = TestUtils.createTestMessageOfSize(producerSession, 10);
+
+ // Set the TTL on the message and record its value in the message headers.
+ long ttl = 500 + r.nextInt(1500);
+ producer.setTimeToLive(ttl);
+ testMessage.setLongProperty("testTTL", ttl);
+
+ producer.send(testMessage);
+ // producerSession.commit();
+ }
+
+ // Inject a pause to allow some messages to be purged by TTL.
+ TestUtils.pause(1000);
+
+ // Attempt to receive back all of the messages, confirming by the message time stamps and TTLs that only
+ // those received should have avoided being purged by the TTL.
+ boolean timedOut = false;
+
+ while (!timedOut)
+ {
+ Message testMessage = consumer.receive(1000);
+
+ long ttl = testMessage.getLongProperty("testTTL");
+ long timeStamp = testMessage.getJMSTimestamp();
+ long now = System.currentTimeMillis();
+
+ if ((timeStamp + ttl) < now)
+ {
+ errorMessages +=
+ "Received message [sent: " + timeStamp + ", ttl: " + ttl + ", received: " + now
+ + "] which should have been purged by its TTL.\n";
+ }
+ /*else
+ {
+ receivedMessages.add(testMessage);
+ }*/
+ }
+
+ // Check that the queue and message store on the broker are empty.
+ // assertTrue("Message store is not empty.", messageStoreEmpty.apply());
+ // assertTrue("Queue is not empty.", queueEmpty.apply());
+
+ assertTrue(errorMessages, "".equals(errorMessages));
+ }
+}
diff --git a/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java b/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java new file mode 100644 index 0000000000..8fddf651b4 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java @@ -0,0 +1,174 @@ +package org.apache.qpid.test.unit.ack; + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.apache.log4j.Logger; +import org.apache.qpid.client.AMQConnectionFactory; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.test.VMTestCase; + +public class AcknowledgeTest extends VMTestCase +{ + private static final int NUM_MESSAGES = 50; + private Connection _con; + private Queue _queue; + private MessageProducer _producer; + private Session _producerSession; + private Session _consumerSession; + private MessageConsumer _consumerA; + private MessageConsumer _consumerB; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _queue = (Queue) _context.lookup("queue"); + + //CreateQueue + ((ConnectionFactory) _context.lookup("connection")).createConnection().createSession(false, Session.AUTO_ACKNOWLEDGE).createConsumer(_queue).close(); + + //Create Producer put some messages on the queue + _con = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + _con.start(); + } + + private void init(boolean transacted, int mode) throws JMSException { + _producerSession = _con.createSession(false, Session.AUTO_ACKNOWLEDGE); + _consumerSession = _con.createSession(transacted, mode); + _producer = _producerSession.createProducer(_queue); + _consumerA = _consumerSession.createConsumer(_queue); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + try + { + TransportConnection.killAllVMBrokers(); + ApplicationRegistry.removeAll(); + } + catch (Exception e) + { + fail("Unable to clean up"); + } + + } + + private void consumeMessages(int toConsume, MessageConsumer consumer) throws JMSException + { + Message msg; + for (int i = 0; i < toConsume; i++) + { + msg = consumer.receive(1000); + assertNotNull("Message " + i + " was null!", msg); + assertEquals("message " + i, ((TextMessage) msg).getText()); + } + } + + private void sendMessages(int totalMessages) throws JMSException + { + for (int i = 0; i < totalMessages; i++) + { + _producer.send(_producerSession.createTextMessage("message " + i)); + } + } + + private void testMessageAck(boolean transacted, int mode) throws Exception + { + init(transacted, mode); + sendMessages(NUM_MESSAGES/2); + Thread.sleep(1500); + _consumerB = _consumerSession.createConsumer(_queue); + sendMessages(NUM_MESSAGES/2); + int count = 0; + Message msg = _consumerB.receive(1500); + while (msg != null) + { + if (mode == Session.CLIENT_ACKNOWLEDGE) + { + msg.acknowledge(); + } + count++; + msg = _consumerB.receive(1500); + } + if (transacted) + { + _consumerSession.commit(); + } + _consumerA.close(); + _consumerB.close(); + _consumerSession.close(); + assertEquals("Wrong number of messages on queue", NUM_MESSAGES - count, + ((AMQSession) _producerSession).getQueueDepth((AMQDestination) _queue)); + + // Clean up messages that may be left on the queue + _consumerSession = _con.createSession(transacted, mode); + _consumerA = _consumerSession.createConsumer(_queue); + msg = _consumerA.receive(1500); + while (msg != null) + { + if (mode == Session.CLIENT_ACKNOWLEDGE) + { + msg.acknowledge(); + } + msg = _consumerA.receive(1500); + } + _consumerA.close(); + if (transacted) + { + _consumerSession.commit(); + } + _consumerSession.close(); + super.tearDown(); + } + + public void test2ConsumersAutoAck() throws Exception + { + testMessageAck(false, Session.AUTO_ACKNOWLEDGE); + } + + public void test2ConsumersClientAck() throws Exception + { + testMessageAck(true, Session.CLIENT_ACKNOWLEDGE); + } + + public void test2ConsumersTx() throws Exception + { + testMessageAck(true, Session.AUTO_ACKNOWLEDGE); + } + +} diff --git a/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java b/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java index 352cb80211..00cb458c86 100644 --- a/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java @@ -92,7 +92,7 @@ import java.util.concurrent.atomic.AtomicLong; * * <p/><table id="crc"><caption>CRC Card</caption> * <tr><th> Responsibilities <th> Collaborations - * <tr><th> Associate messages to an ongoing conversation using correlation ids. + * <tr><td> Associate messages to an ongoing conversation using correlation ids. * <tr><td> Auto manage sessions for conversations. * <tr><td> Store messages not in a conversation in dead letter box. * </table> |
