From 913489deb2ee9dbf44455de5f407ddaf4bd8c540 Mon Sep 17 00:00:00 2001 From: "Rafael H. Schloming" Date: Tue, 19 Sep 2006 22:06:50 +0000 Subject: Import of qpid from etp: URL: https://etp.108.redhat.com/svn/etp/trunk/blaze Repository Root: https://etp.108.redhat.com/svn/etp Repository UUID: 06e15bec-b515-0410-bef0-cc27a458cf48 Revision: 608 git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@447994 13f79535-47bb-0310-9956-ffa450edef68 --- java/Developing.txt | 75 + java/ReadMe.txt | 15 + java/broker/README.txt | 40 + java/broker/bin/qpid-server | 18 + java/broker/bin/qpid-server.bat | 53 + java/broker/bin/run.bat | 28 + java/broker/bin/run.sh | 41 + java/broker/bin/runAll | 18 + java/broker/build-module.xml | 22 + java/broker/build-old.xml | 265 + java/broker/etc/config.xml | 90 + java/broker/etc/log4j.xml | 46 + java/broker/etc/passwd | 1 + java/broker/etc/qpid-server.conf | 22 + java/broker/etc/virtualhosts.xml | 25 + java/broker/src/log4j.properties | 6 + .../src/org/apache/qpid/server/AMQChannel.java | 702 + .../qpid/server/ConsumerTagNotUniqueException.java | 22 + java/broker/src/org/apache/qpid/server/Main.java | 612 + .../src/org/apache/qpid/server/ManagedChannel.java | 64 + .../qpid/server/RequiredDeliveryException.java | 109 + .../qpid/server/configuration/Configurator.java | 102 + .../configuration/VirtualHostConfiguration.java | 217 + .../qpid/server/exchange/AbstractExchange.java | 134 + .../server/exchange/DefaultExchangeFactory.java | 63 + .../server/exchange/DefaultExchangeRegistry.java | 92 + .../qpid/server/exchange/DestNameExchange.java | 204 + .../qpid/server/exchange/DestWildExchange.java | 210 + .../org/apache/qpid/server/exchange/Exchange.java | 47 + .../qpid/server/exchange/ExchangeFactory.java | 28 + .../server/exchange/ExchangeInUseException.java | 28 + .../qpid/server/exchange/ExchangeRegistry.java | 38 + .../qpid/server/exchange/HeadersBinding.java | 142 + .../qpid/server/exchange/HeadersExchange.java | 226 + .../src/org/apache/qpid/server/exchange/Index.java | 85 + .../qpid/server/exchange/ManagedExchange.java | 78 + .../apache/qpid/server/exchange/MessageRouter.java | 36 + .../qpid/server/exchange/NoRouteException.java | 39 + .../qpid/server/handler/BasicAckMethodHandler.java | 52 + .../server/handler/BasicCancelMethodHandler.java | 58 + .../server/handler/BasicConsumeMethodHandler.java | 90 + .../server/handler/BasicPublishMethodHandler.java | 77 + .../qpid/server/handler/BasicQosHandler.java | 46 + .../server/handler/BasicRecoverMethodHandler.java | 54 + .../qpid/server/handler/ChannelCloseHandler.java | 58 + .../qpid/server/handler/ChannelCloseOkHandler.java | 51 + .../qpid/server/handler/ChannelFlowHandler.java | 61 + .../qpid/server/handler/ChannelOpenHandler.java | 58 + .../handler/ConnectionCloseMethodHandler.java | 65 + .../handler/ConnectionCloseOkMethodHandler.java | 63 + .../handler/ConnectionOpenMethodHandler.java | 68 + .../handler/ConnectionSecureOkMethodHandler.java | 115 + .../handler/ConnectionStartOkMethodHandler.java | 127 + .../handler/ConnectionTuneOkMethodHandler.java | 54 + .../server/handler/ExchangeDeclareHandler.java | 79 + .../qpid/server/handler/ExchangeDeleteHandler.java | 62 + .../server/handler/OnCurrentThreadExecutor.java | 31 + .../qpid/server/handler/QueueBindHandler.java | 94 + .../qpid/server/handler/QueueDeclareHandler.java | 124 + .../qpid/server/handler/QueueDeleteHandler.java | 84 + .../qpid/server/handler/TxCommitHandler.java | 55 + .../qpid/server/handler/TxRollbackHandler.java | 59 + .../qpid/server/handler/TxSelectHandler.java | 50 + .../org/apache/qpid/server/jms/JmsConsumer.java | 107 + .../qpid/server/management/AMQManagedObject.java | 65 + .../server/management/DefaultManagedObject.java | 126 + .../management/JMXManagedObjectRegistry.java | 66 + .../apache/qpid/server/management/Managable.java | 31 + .../qpid/server/management/ManagedBroker.java | 78 + .../qpid/server/management/ManagedObject.java | 53 + .../server/management/ManagedObjectRegistry.java | 39 + .../server/management/ManagementConfiguration.java | 27 + .../management/NoopManagedObjectRegistry.java | 45 + .../qpid/server/protocol/AMQMethodEvent.java | 62 + .../qpid/server/protocol/AMQMethodListener.java | 52 + .../server/protocol/AMQMinaProtocolSession.java | 603 + .../server/protocol/AMQPFastProtocolHandler.java | 217 + .../qpid/server/protocol/AMQPProtocolProvider.java | 50 + .../qpid/server/protocol/AMQProtocolSession.java | 122 + .../qpid/server/protocol/ExchangeInitialiser.java | 38 + .../qpid/server/protocol/HeartbeatConfig.java | 64 + .../qpid/server/protocol/ManagedConnection.java | 92 + .../qpid/server/protocol/ManagedSession.java | 33 + .../org/apache/qpid/server/queue/AMQMessage.java | 343 + .../src/org/apache/qpid/server/queue/AMQQueue.java | 654 + .../qpid/server/queue/AsyncDeliveryConfig.java | 53 + .../qpid/server/queue/DefaultQueueRegistry.java | 47 + .../apache/qpid/server/queue/DeliveryManager.java | 262 + .../apache/qpid/server/queue/ExchangeBindings.java | 109 + .../org/apache/qpid/server/queue/ManagedQueue.java | 177 + .../qpid/server/queue/NoConsumersException.java | 47 + .../apache/qpid/server/queue/QueueRegistry.java | 30 + .../org/apache/qpid/server/queue/Subscription.java | 29 + .../qpid/server/queue/SubscriptionFactory.java | 37 + .../apache/qpid/server/queue/SubscriptionImpl.java | 172 + .../qpid/server/queue/SubscriptionManager.java | 28 + .../apache/qpid/server/queue/SubscriptionSet.java | 180 + .../server/queue/WeightedSubscriptionManager.java | 23 + .../qpid/server/registry/ApplicationRegistry.java | 108 + .../ConfigurationFileApplicationRegistry.java | 155 + .../qpid/server/registry/IApplicationRegistry.java | 65 + .../security/auth/AuthenticationManager.java | 30 + .../auth/AuthenticationProviderInitialiser.java | 63 + .../server/security/auth/AuthenticationResult.java | 40 + .../server/security/auth/CRAMMD5Initialiser.java | 35 + .../qpid/server/security/auth/JCAProvider.java | 43 + .../auth/PasswordFilePrincipalDatabase.java | 130 + .../server/security/auth/PrincipalDatabase.java | 42 + .../security/auth/SASLAuthenticationManager.java | 224 + .../security/auth/UsernamePasswordInitialiser.java | 99 + .../server/security/auth/UsernamePrincipal.java | 39 + .../auth/amqplain/AmqPlainInitialiser.java | 35 + .../security/auth/amqplain/AmqPlainSaslServer.java | 120 + .../auth/amqplain/AmqPlainSaslServerFactory.java | 56 + .../security/auth/plain/PlainInitialiser.java | 35 + .../security/auth/plain/PlainSaslServer.java | 141 + .../auth/plain/PlainSaslServerFactory.java | 56 + .../src/org/apache/qpid/server/state/AMQState.java | 33 + .../apache/qpid/server/state/AMQStateManager.java | 219 + .../state/IllegalStateTransitionException.java | 45 + .../server/state/StateAwareMethodListener.java | 37 + .../apache/qpid/server/state/StateListener.java | 27 + .../qpid/server/store/MemoryMessageStore.java | 137 + .../org/apache/qpid/server/store/MessageStore.java | 80 + .../server/transport/ConnectorConfiguration.java | 93 + .../qpid/server/transport/ThreadPoolFilter.java | 692 + .../src/org/apache/qpid/server/txn/TxnBuffer.java | 75 + .../src/org/apache/qpid/server/txn/TxnOp.java | 26 + .../apache/qpid/server/util/CircularBuffer.java | 123 + .../org/apache/qpid/server/util/LoggingProxy.java | 102 + java/broker/test/build-module.xml | 32 + java/broker/test/lib/README | 1 + java/broker/test/lib/junit/junit-4.0.jar | Bin 0 -> 105601 bytes java/broker/test/lib/junit/junit.jar | Bin 0 -> 121070 bytes .../test/src/org/apache/qpid/server/UnitTests.java | 39 + .../server/configuration/TestPropertyUtils.java | 50 + .../qpid/server/configuration/UnitTests.java | 32 + .../exchange/AbstractHeadersExchangeTest.java | 212 + .../qpid/server/exchange/HeadersBindingTest.java | 200 + .../exchange/HeadersExchangePerformanceTest.java | 181 + .../qpid/server/exchange/HeadersExchangeTest.java | 81 + .../org/apache/qpid/server/exchange/UnitTests.java | 32 + .../apache/qpid/server/protocol/MockIoSession.java | 288 + .../server/protocol/TestProtocolInitiation.java | 212 + .../org/apache/qpid/server/protocol/UnitTests.java | 32 + .../src/org/apache/qpid/server/queue/AckTest.java | 243 + .../apache/qpid/server/queue/ConcurrencyTest.java | 261 + .../qpid/server/queue/DeliveryManagerTest.java | 159 + .../qpid/server/queue/MessageTestHelper.java | 49 + .../qpid/server/queue/MockProtocolSession.java | 121 + .../qpid/server/queue/QueueConcurrentPerfTest.java | 46 + .../apache/qpid/server/queue/QueuePerfTest.java | 255 + .../org/apache/qpid/server/queue/SendPerfTest.java | 171 + .../qpid/server/queue/SubscriptionManagerTest.java | 105 + .../qpid/server/queue/SubscriptionSetTest.java | 149 + .../apache/qpid/server/queue/TestSubscription.java | 84 + .../org/apache/qpid/server/queue/UnitTests.java | 38 + .../qpid/server/store/SkeletonMessageStore.java | 99 + .../qpid/server/store/TestReferenceCounting.java | 68 + .../server/store/TestableMemoryMessageStore.java | 39 + .../org/apache/qpid/server/store/UnitTests.java | 34 + .../org/apache/qpid/server/util/AveragedRun.java | 63 + .../apache/qpid/server/util/ConcurrentTest.java | 76 + .../apache/qpid/server/util/LoggingProxyTest.java | 89 + .../qpid/server/util/NullApplicationRegistry.java | 103 + .../server/util/NullAuthenticationManager.java | 82 + .../src/org/apache/qpid/server/util/RunStats.java | 54 + .../src/org/apache/qpid/server/util/TimedRun.java | 49 + .../src/org/apache/qpid/server/util/UnitTests.java | 32 + java/build-old.xml | 37 + java/build.xml | 101 + java/client/build-module.xml | 21 + java/client/build-old.xml | 287 + java/client/dist/readme.txt | 2 + java/client/lib/jms/jms.jar | Bin 0 -> 25998 bytes java/client/readme.txt | 31 + java/client/src/log4j.properties | 10 + .../qpid/client/AMQAuthenticationException.java | 29 + .../org/apache/qpid/client/AMQBrokerDetails.java | 301 + .../src/org/apache/qpid/client/AMQConnection.java | 927 + .../apache/qpid/client/AMQConnectionFactory.java | 358 + .../org/apache/qpid/client/AMQConnectionURL.java | 399 + .../src/org/apache/qpid/client/AMQDestination.java | 282 + .../org/apache/qpid/client/AMQHeadersExchange.java | 49 + .../qpid/client/AMQNoConsumersException.java | 34 + .../apache/qpid/client/AMQNoRouteException.java | 34 + .../src/org/apache/qpid/client/AMQQueue.java | 91 + .../src/org/apache/qpid/client/AMQSession.java | 1149 + .../org/apache/qpid/client/AMQTemporaryQueue.java | 44 + .../org/apache/qpid/client/AMQTemporaryTopic.java | 46 + .../src/org/apache/qpid/client/AMQTopic.java | 89 + .../apache/qpid/client/BasicMessageConsumer.java | 499 + .../apache/qpid/client/BasicMessageProducer.java | 480 + .../src/org/apache/qpid/client/Closeable.java | 48 + .../qpid/client/ConnectionTuneParameters.java | 69 + .../apache/qpid/client/TopicSubscriberAdaptor.java | 91 + .../qpid/client/failover/FailoverException.java | 30 + .../qpid/client/failover/FailoverHandler.java | 180 + .../apache/qpid/client/failover/FailoverState.java | 46 + .../qpid/client/failover/FailoverSupport.java | 63 + .../client/handler/BasicDeliverMethodHandler.java | 47 + .../client/handler/BasicReturnMethodHandler.java | 49 + .../client/handler/ChannelCloseMethodHandler.java | 79 + .../handler/ChannelCloseOkMethodHandler.java | 43 + .../client/handler/ChannelFlowOkMethodHandler.java | 46 + .../handler/ConnectionCloseMethodHandler.java | 89 + .../handler/ConnectionOpenOkMethodHandler.java | 52 + .../handler/ConnectionRedirectMethodHandler.java | 65 + .../handler/ConnectionSecureMethodHandler.java | 64 + .../handler/ConnectionStartMethodHandler.java | 184 + .../handler/ConnectionTuneMethodHandler.java | 79 + .../org/apache/qpid/client/message/AMQMessage.java | 68 + .../qpid/client/message/AbstractJMSMessage.java | 677 + .../client/message/AbstractJMSMessageFactory.java | 77 + .../qpid/client/message/JMSBytesMessage.java | 351 + .../client/message/JMSBytesMessageFactory.java | 37 + .../qpid/client/message/JMSObjectMessage.java | 175 + .../client/message/JMSObjectMessageFactory.java | 37 + .../apache/qpid/client/message/JMSTextMessage.java | 159 + .../qpid/client/message/JMSTextMessageFactory.java | 39 + .../apache/qpid/client/message/MessageFactory.java | 35 + .../client/message/MessageFactoryRegistry.java | 105 + .../message/UnexpectedBodyReceivedException.java | 40 + .../qpid/client/message/UnprocessedMessage.java | 61 + .../qpid/client/protocol/AMQMethodEvent.java | 59 + .../qpid/client/protocol/AMQMethodListener.java | 41 + .../qpid/client/protocol/AMQProtocolHandler.java | 530 + .../qpid/client/protocol/AMQProtocolSession.java | 379 + .../protocol/BlockingMethodFrameListener.java | 133 + .../qpid/client/protocol/HeartbeatConfig.java | 57 + .../qpid/client/protocol/HeartbeatDiagnostics.java | 118 + .../protocol/ProtocolBufferMonitorFilter.java | 110 + .../qpid/client/security/AMQCallbackHandler.java | 27 + .../client/security/CallbackHandlerRegistry.java | 154 + .../security/CallbackHandlerRegistry.properties | 2 + .../qpid/client/security/DynamicSaslRegistrar.java | 125 + .../security/DynamicSaslRegistrar.properties | 1 + .../apache/qpid/client/security/JCAProvider.java | 43 + .../security/UsernamePasswordCallbackHandler.java | 53 + .../security/amqplain/AmqPlainSaslClient.java | 101 + .../amqplain/AmqPlainSaslClientFactory.java | 59 + .../src/org/apache/qpid/client/state/AMQState.java | 53 + .../qpid/client/state/AMQStateChangedEvent.java | 45 + .../apache/qpid/client/state/AMQStateListener.java | 23 + .../apache/qpid/client/state/AMQStateManager.java | 224 + .../state/IllegalStateTransitionException.java | 45 + .../client/state/StateAwareMethodListener.java | 31 + .../apache/qpid/client/state/StateListener.java | 27 + .../org/apache/qpid/client/state/StateWaiter.java | 114 + .../listener/SpecificMethodFrameListener.java | 38 + .../client/transport/ITransportConnection.java | 30 + .../transport/SocketTransportConnection.java | 96 + .../qpid/client/transport/TransportConnection.java | 71 + .../client/util/FlowControllingBlockingQueue.java | 87 + .../src/org/apache/qpid/jms/BrokerDetails.java | 59 + .../qpid/jms/ChannelLimitReachedException.java | 43 + .../client/src/org/apache/qpid/jms/Connection.java | 49 + .../org/apache/qpid/jms/ConnectionListener.java | 55 + .../src/org/apache/qpid/jms/ConnectionURL.java | 69 + .../src/org/apache/qpid/jms/FailoverPolicy.java | 306 + .../src/org/apache/qpid/jms/MessageConsumer.java | 24 + .../src/org/apache/qpid/jms/MessageProducer.java | 49 + java/client/src/org/apache/qpid/jms/Session.java | 70 + .../apache/qpid/jms/failover/FailoverMethod.java | 72 + .../jms/failover/FailoverRoundRobinServers.java | 256 + .../qpid/jms/failover/FailoverSingleServer.java | 144 + java/client/test/bin/IBM-JNDI-Setup.bat | 59 + java/client/test/bin/IBM-JNDI-Setup.sh | 23 + java/client/test/bin/IBM-Publisher.bat | 59 + java/client/test/bin/IBM-Publisher.sh | 19 + java/client/test/bin/IBM-PutGet.bat | 59 + java/client/test/bin/IBM-PutGet.sh | 18 + java/client/test/bin/IBM-README.txt | 16 + java/client/test/bin/IBM-Receiver.bat | 59 + java/client/test/bin/IBM-Receiver.sh | 19 + java/client/test/bin/IBM-Sender.bat | 59 + java/client/test/bin/IBM-Sender.sh | 19 + java/client/test/bin/IBM-Subscriber.bat | 59 + java/client/test/bin/IBM-Subscriber.sh | 19 + java/client/test/bin/headersListener.sh | 19 + java/client/test/bin/headersListenerGroup.sh | 22 + java/client/test/bin/headersPublisher.sh | 19 + java/client/test/bin/run_many.sh | 27 + java/client/test/bin/serviceProvidingClient.sh | 21 + java/client/test/bin/serviceRequestingClient.sh | 24 + java/client/test/bin/testService.sh | 19 + java/client/test/bin/topicListener.sh | 20 + java/client/test/bin/topicPublisher.sh | 19 + java/client/test/build-module.xml | 32 + java/client/test/etc/ApacheDS.properties | 6 + java/client/test/example_ build.xml | 101 + .../geronimo-j2ee-management_1.0_spec-1.0.jar | Bin 0 -> 16030 bytes .../test/lib/jakarta-commons/commons-codec-1.3.jar | Bin 0 -> 46725 bytes .../test/lib/jboss/jboss-messaging-client.jar | Bin 0 -> 4813172 bytes java/client/test/lib/jboss/jbossall-client.jar | Bin 0 -> 4284897 bytes java/client/test/lib/jmscts/jmscts-0.5-b2.jar | Bin 0 -> 713048 bytes java/client/test/lib/spring-1.2.8/spring.jar | Bin 0 -> 1932690 bytes .../IBMPerfTest/JNDIBindConnectionFactory.java | 130 + .../org/apache/qpid/IBMPerfTest/JNDIBindQueue.java | 180 + .../org/apache/qpid/IBMPerfTest/JNDIBindTopic.java | 179 + .../src/org/apache/qpid/IBMPerfTest/README.txt | 11 + .../qpid/ack/DisconnectAndRedeliverTest.java | 164 + .../test/src/org/apache/qpid/ack/RecoverTest.java | 97 + .../test/src/org/apache/qpid/ack/UnitTests.java | 32 + .../org/apache/qpid/basic/BytesMessageTest.java | 207 + .../qpid/basic/FieldTableKeyEnumeratorTest.java | 83 + .../apache/qpid/basic/FieldTableMessageTest.java | 156 + .../apache/qpid/basic/MultipleConnectionTest.java | 217 + .../org/apache/qpid/basic/ObjectMessageTest.java | 206 + .../src/org/apache/qpid/basic/ReceiveTest.java | 85 + .../org/apache/qpid/basic/SessionStartTest.java | 109 + .../src/org/apache/qpid/basic/TextMessageTest.java | 177 + .../test/src/org/apache/qpid/basic/UnitTests.java | 41 + .../org/apache/qpid/client/AllClientUnitTests.java | 41 + .../client/channelclose/ChannelCloseOkTest.java | 206 + .../apache/qpid/client/channelclose/UnitTests.java | 34 + .../qpid/client/message/ObjectMessageTest.java | 247 + .../qpid/client/message/TestBytesMessage.java | 92 + .../qpid/client/message/TestTextMessage.java | 51 + .../org/apache/qpid/client/message/UnitTests.java | 32 + .../qpid/client/testutil/VmOrRemoteTestCase.java | 56 + .../test/src/org/apache/qpid/cluster/Client.java | 122 + .../org/apache/qpid/codec/BasicDeliverTest.java | 257 + .../test/src/org/apache/qpid/codec/Client.java | 130 + .../test/src/org/apache/qpid/codec/Server.java | 100 + .../config/AMQConnectionFactoryInitialiser.java | 32 + .../src/org/apache/qpid/config/AbstractConfig.java | 66 + .../qpid/config/ConnectionFactoryInitialiser.java | 26 + .../test/src/org/apache/qpid/config/Connector.java | 37 + .../org/apache/qpid/config/ConnectorConfig.java | 25 + .../config/JBossConnectionFactoryInitialiser.java | 108 + .../org/apache/qpid/connection/ConnectionTest.java | 101 + .../qpid/connection/TestManyConnections.java | 100 + .../qpid/connectionurl/ConnectionURLTest.java | 449 + .../org/apache/qpid/connectionurl/UnitTests.java | 33 + .../test/src/org/apache/qpid/cts/bin/jmscts.sh | 159 + .../test/src/org/apache/qpid/cts/bin/setenv.sh | 38 + .../src/org/apache/qpid/cts/config/jmscts.policy | 5 + .../org/apache/qpid/cts/config/jmscts.properties | 52 + .../src/org/apache/qpid/cts/config/providers.xml | 38 + .../client/test/src/org/apache/qpid/cts/readme.txt | 5 + .../test/src/org/apache/qpid/cts/src/compile.sh | 31 + .../org/exolab/jmscts/amqp/AMQPAdministrator.java | 242 + .../amqp/org/exolab/jmscts/amqp/AMQPProvider.java | 95 + .../qpid/destinationurl/DestinationURLTest.java | 150 + .../org/apache/qpid/destinationurl/UnitTests.java | 33 + .../test/src/org/apache/qpid/example/log4j.xml | 42 + .../example/publisher/FileMessageDispatcher.java | 142 + .../qpid/example/publisher/MessageFactory.java | 109 + .../example/publisher/MessageFactoryException.java | 60 + .../publisher/MonitorMessageDispatcher.java | 116 + .../qpid/example/publisher/MonitorPublisher.java | 64 + .../apache/qpid/example/publisher/Publisher.java | 213 + .../publisher/UndeliveredMessageException.java | 60 + .../qpid/example/shared/ConnectionException.java | 59 + .../org/apache/qpid/example/shared/FileUtils.java | 154 + .../org/apache/qpid/example/shared/Statics.java | 42 + .../example/subscriber/MonitoredSubscriber.java | 120 + .../subscriber/MonitoredSubscriptionWrapper.java | 41 + .../apache/qpid/example/subscriber/Subscriber.java | 210 + .../example/subscriber/SubscriptionWrapper.java | 42 + .../apache/qpid/example/test/TestAMSPubSub.java | 95 + .../qpid/example/test/TestMultSubscribers.java | 105 + .../apache/qpid/example/test/TestPublisher.java | 76 + .../apache/qpid/example/test/TestSubscriber.java | 60 + .../qpid/failover/FailoverMultiMethodTest.java | 258 + .../qpid/failover/FailoverRoundRobinTest.java | 233 + .../qpid/failover/FailoverSingleServerTest.java | 257 + .../src/org/apache/qpid/failover/FailoverTest.java | 249 + .../org/apache/qpid/failover/FailoverTxTest.java | 146 + .../src/org/apache/qpid/flow/ChannelFlowTest.java | 107 + .../src/org/apache/qpid/forwardall/Client.java | 106 + .../src/org/apache/qpid/forwardall/Combined.java | 50 + .../src/org/apache/qpid/forwardall/Service.java | 75 + .../org/apache/qpid/forwardall/ServiceCreator.java | 74 + .../org/apache/qpid/forwardall/SpecialQueue.java | 41 + .../src/org/apache/qpid/forwardall/UnitTests.java | 34 + .../qpid/fragmentation/TestLargePublisher.java | 190 + .../qpid/fragmentation/TestLargeSubscriber.java | 160 + .../org/apache/qpid/framing/FieldTableTest.java | 159 + .../test/src/org/apache/qpid/framing/content.txt | 73585 +++++++++++++++++++ .../test/src/org/apache/qpid/headers/Listener.java | 114 + .../org/apache/qpid/headers/MessageFactory.java | 166 + .../src/org/apache/qpid/headers/Publisher.java | 130 + .../org/apache/qpid/jndi/referenceable/Bind.java | 270 + .../org/apache/qpid/jndi/referenceable/Lookup.java | 176 + .../org/apache/qpid/jndi/referenceable/Unbind.java | 163 + .../apache/qpid/jndi/referenceabletest/Bind.java | 168 + .../referenceabletest/JNDIReferenceableTest.java | 108 + .../apache/qpid/jndi/referenceabletest/Lookup.java | 95 + .../apache/qpid/jndi/referenceabletest/Unbind.java | 124 + .../qpid/jndi/referenceabletest/UnitTests.java | 33 + .../src/org/apache/qpid/latency/LatencyTest.java | 148 + .../src/org/apache/qpid/mina/AcceptorTest.java | 107 + .../org/apache/qpid/mina/BlockingAcceptorTest.java | 91 + .../test/src/org/apache/qpid/mina/WriterTest.java | 272 + .../src/org/apache/qpid/multiconsumer/AMQTest.java | 264 + .../src/org/apache/qpid/ping/TestPingClient.java | 128 + .../src/org/apache/qpid/ping/TestPingProducer.java | 210 + .../org/apache/qpid/ping/TestPingPublisher.java | 177 + .../org/apache/qpid/ping/TestPingSubscriber.java | 129 + .../src/org/apache/qpid/pubsub1/TestPublisher.java | 171 + .../org/apache/qpid/pubsub1/TestSubscriber.java | 117 + .../qpid/requestreply1/ServiceProvidingClient.java | 198 + .../requestreply1/ServiceRequestingClient.java | 299 + .../apache/qpid/requestreply1/VmRequestReply.java | 69 + .../test/src/org/apache/qpid/testutil/Config.java | 195 + .../test/src/org/apache/qpid/topic/Config.java | 240 + .../apache/qpid/topic/DurableSubscriptionTest.java | 135 + .../test/src/org/apache/qpid/topic/Listener.java | 138 + .../src/org/apache/qpid/topic/MessageFactory.java | 150 + .../test/src/org/apache/qpid/topic/Publisher.java | 171 + .../src/org/apache/qpid/transacted/Config.java | 107 + .../test/src/org/apache/qpid/transacted/Ping.java | 40 + .../test/src/org/apache/qpid/transacted/Pong.java | 40 + .../test/src/org/apache/qpid/transacted/Relay.java | 124 + .../test/src/org/apache/qpid/transacted/Start.java | 39 + .../org/apache/qpid/transacted/TransactedTest.java | 145 + .../qpid/transport/VmPipeTransportConnection.java | 59 + .../src/org/apache/qpid/vmbroker/VmPipeBroker.java | 76 + .../org/apache/qpid/weblogic/ServiceProvider.java | 148 + .../qpid/weblogic/ServiceRequestingClient.java | 189 + java/cluster/build-module.xml | 21 + java/cluster/build-old.xml | 144 + java/cluster/doc/design.doc | Bin 0 -> 69120 bytes .../qpid/server/cluster/BlockingHandler.java | 88 + .../qpid/server/cluster/BroadcastPolicy.java | 23 + .../src/org/apache/qpid/server/cluster/Broker.java | 244 + .../apache/qpid/server/cluster/BrokerFactory.java | 23 + .../apache/qpid/server/cluster/BrokerGroup.java | 365 + .../apache/qpid/server/cluster/ClientAdapter.java | 70 + .../qpid/server/cluster/ClientHandlerRegistry.java | 132 + .../apache/qpid/server/cluster/ClusterBuilder.java | 60 + .../qpid/server/cluster/ClusterCapability.java | 55 + .../server/cluster/ClusteredProtocolHandler.java | 190 + .../server/cluster/ClusteredProtocolSession.java | 132 + .../server/cluster/ConnectionStatusMonitor.java | 77 + .../qpid/server/cluster/DefaultGroupManager.java | 366 + .../apache/qpid/server/cluster/GroupManager.java | 69 + .../apache/qpid/server/cluster/GroupRequest.java | 104 + .../qpid/server/cluster/GroupResponseHandler.java | 28 + .../qpid/server/cluster/InductionBuffer.java | 87 + .../org/apache/qpid/server/cluster/JoinState.java | 23 + .../org/apache/qpid/server/cluster/LoadTable.java | 104 + .../src/org/apache/qpid/server/cluster/Main.java | 117 + .../src/org/apache/qpid/server/cluster/Member.java | 28 + .../qpid/server/cluster/MemberFailureListener.java | 23 + .../apache/qpid/server/cluster/MemberHandle.java | 31 + .../server/cluster/MembershipChangeListener.java | 25 + .../apache/qpid/server/cluster/MethodHandler.java | 26 + .../qpid/server/cluster/MethodHandlerFactory.java | 25 + .../qpid/server/cluster/MethodHandlerRegistry.java | 41 + .../qpid/server/cluster/MinaBrokerProxy.java | 269 + .../server/cluster/MinaBrokerProxyFactory.java | 33 + .../qpid/server/cluster/ResponseHandler.java | 27 + .../org/apache/qpid/server/cluster/Sendable.java | 25 + .../qpid/server/cluster/ServerHandlerRegistry.java | 91 + .../qpid/server/cluster/SimpleMemberHandle.java | 156 + .../apache/qpid/server/cluster/SimpleSendable.java | 53 + .../handler/ChainedClusterMethodHandler.java | 69 + .../cluster/handler/ChannelQueueManager.java | 136 + .../cluster/handler/ClusterMethodHandler.java | 46 + .../handler/ClusterMethodHandlerFactory.java | 278 + .../server/cluster/handler/ExtendedHandler.java | 52 + .../qpid/server/cluster/handler/HandlerUtils.java | 22 + .../cluster/handler/LocalQueueDeclareHandler.java | 74 + .../qpid/server/cluster/handler/NullListener.java | 35 + .../qpid/server/cluster/handler/PeerHandler.java | 57 + .../server/cluster/handler/QueueNameGenerator.java | 60 + .../cluster/handler/RemoteCancelHandler.java | 50 + .../cluster/handler/RemoteConsumeHandler.java | 55 + .../cluster/handler/ReplicatingConsumeHandler.java | 81 + .../server/cluster/handler/ReplicatingHandler.java | 127 + .../server/cluster/handler/WrappedListener.java | 53 + .../handler/WrappingMethodHandlerFactory.java | 82 + .../cluster/policy/AsynchBroadcastPolicy.java | 28 + .../policy/MajorityResponseBroadcastPolicy.java | 28 + .../cluster/policy/OneResponseBroadcastPolicy.java | 28 + .../server/cluster/policy/StandardPolicies.java | 26 + .../cluster/policy/SynchBroadcastPolicy.java | 28 + .../cluster/replay/ChainedMethodRecorder.java | 45 + .../qpid/server/cluster/replay/ConsumerCounts.java | 66 + .../qpid/server/cluster/replay/MethodRecorder.java | 29 + .../replay/RecordingMethodHandlerFactory.java | 70 + .../qpid/server/cluster/replay/ReplayManager.java | 34 + .../qpid/server/cluster/replay/ReplayStore.java | 310 + .../apache/qpid/server/cluster/util/Bindings.java | 80 + .../qpid/server/cluster/util/InvokeMultiple.java | 69 + .../qpid/server/cluster/util/LogMessage.java | 50 + .../qpid/server/cluster/util/MultiValuedMap.java | 58 + .../apache/qpid/server/queue/ClusteredQueue.java | 161 + .../server/queue/ClusteredSubscriptionManager.java | 92 + .../server/queue/NestedSubscriptionManager.java | 100 + .../org/apache/qpid/server/queue/PrivateQueue.java | 60 + .../qpid/server/queue/ProxiedQueueCleanup.java | 57 + .../apache/qpid/server/queue/RemoteQueueProxy.java | 106 + .../qpid/server/queue/RemoteSubscriptionImpl.java | 93 + .../qpid/server/queue/SubscriberCleanup.java | 53 + .../qpid/server/cluster/BrokerGroupTest.java | 267 + .../org/apache/qpid/server/cluster/BrokerTest.java | 231 + .../qpid/server/cluster/ClusterCapabilityTest.java | 42 + .../qpid/server/cluster/InductionBufferTest.java | 103 + .../qpid/server/cluster/RecordingBroker.java | 50 + .../server/cluster/RecordingBrokerFactory.java | 26 + .../qpid/server/cluster/SimpleClusterTest.java | 41 + .../server/cluster/SimpleMemberHandleTest.java | 59 + .../org/apache/qpid/server/cluster/TestBroker.java | 67 + .../qpid/server/cluster/TestBrokerFactory.java | 26 + .../qpid/server/cluster/TestReplayManager.java | 44 + .../apache/qpid/server/cluster/TestSession.java | 261 + java/common.xml | 72 + java/common/bin/qpid-run | 176 + java/common/build-module.xml | 129 + java/common/build-old.xml | 98 + java/common/etc/qpid-run.conf | 22 + java/common/etc/qpid-run.conf.dev | 23 + java/common/lib/commons-cli/commons-cli-1.0.jar | Bin 0 -> 30117 bytes .../commons-collections-3.1.jar | Bin 0 -> 559366 bytes .../commons-configuration-1.2.jar | Bin 0 -> 163822 bytes java/common/lib/commons-lang/commons-lang-2.1.jar | Bin 0 -> 207723 bytes .../lib/commons-logging/commons-logging-api.jar | Bin 0 -> 26202 bytes .../common/lib/commons-logging/commons-logging.jar | Bin 0 -> 38015 bytes java/common/lib/junit/junit-4.0.jar | Bin 0 -> 105601 bytes java/common/lib/junit/junit.jar | Bin 0 -> 121070 bytes java/common/lib/logging-log4j/log4j-1.2.13.jar | Bin 0 -> 358180 bytes java/common/lib/mina/mina-core-0.9.5-SNAPSHOT.jar | Bin 0 -> 348936 bytes .../lib/mina/mina-filter-ssl-0.9.5-SNAPSHOT.jar | Bin 0 -> 18413 bytes java/common/lib/saxon/saxon8.jar | Bin 0 -> 3118502 bytes java/common/lib/slf4j/slf4j-simple.jar | Bin 0 -> 12243 bytes java/common/readme.txt | 4 + java/common/resources/ProtocolVersionList.java | 37 + java/common/resources/cluster.asl | 56 + .../common/resources/org/apache/qpid/ssl/qpid.cert | Bin 0 -> 756 bytes java/common/resources/registry.template | 22 + .../org/apache/qpid/AMQChannelClosedException.java | 31 + .../src/org/apache/qpid/AMQChannelException.java | 46 + .../apache/qpid/AMQConnectionClosedException.java | 31 + .../org/apache/qpid/AMQConnectionException.java | 27 + .../org/apache/qpid/AMQDisconnectedException.java | 31 + java/common/src/org/apache/qpid/AMQException.java | 74 + .../org/apache/qpid/AMQUndeliveredException.java | 41 + .../apache/qpid/AMQUnresolvedAddressException.java | 26 + java/common/src/org/apache/qpid/bio/Reader.java | 98 + java/common/src/org/apache/qpid/bio/Sequence.java | 29 + .../org/apache/qpid/bio/SimpleSocketChannel.java | 82 + .../src/org/apache/qpid/bio/SocketAcceptor.java | 277 + .../src/org/apache/qpid/bio/SocketConnector.java | 150 + .../src/org/apache/qpid/bio/SocketFilterChain.java | 62 + .../src/org/apache/qpid/bio/SocketSessionImpl.java | 421 + .../src/org/apache/qpid/codec/AMQCodecFactory.java | 50 + .../src/org/apache/qpid/codec/AMQDecoder.java | 96 + .../src/org/apache/qpid/codec/AMQEncoder.java | 38 + .../org/apache/qpid/configuration/Configured.java | 41 + .../qpid/configuration/PropertyException.java | 62 + .../apache/qpid/configuration/PropertyUtils.java | 153 + .../org/apache/qpid/exchange/ExchangeDefaults.java | 33 + .../src/org/apache/qpid/framing/AMQBody.java | 35 + .../src/org/apache/qpid/framing/AMQDataBlock.java | 40 + .../apache/qpid/framing/AMQDataBlockDecoder.java | 113 + .../apache/qpid/framing/AMQDataBlockEncoder.java | 62 + .../src/org/apache/qpid/framing/AMQFrame.java | 73 + .../qpid/framing/AMQFrameDecodingException.java | 45 + .../src/org/apache/qpid/framing/AMQMethodBody.java | 87 + .../apache/qpid/framing/AMQMethodBodyFactory.java | 43 + .../qpid/framing/AMQProtocolClassException.java | 26 + .../qpid/framing/AMQProtocolHeaderException.java | 28 + .../qpid/framing/AMQProtocolInstanceException.java | 26 + .../qpid/framing/AMQProtocolVersionException.java | 30 + .../qpid/framing/BasicContentHeaderProperties.java | 592 + .../src/org/apache/qpid/framing/BodyFactory.java | 28 + .../apache/qpid/framing/CompositeAMQDataBlock.java | 100 + .../src/org/apache/qpid/framing/ContentBody.java | 64 + .../apache/qpid/framing/ContentBodyFactory.java | 44 + .../org/apache/qpid/framing/ContentHeaderBody.java | 112 + .../qpid/framing/ContentHeaderBodyFactory.java | 47 + .../qpid/framing/ContentHeaderProperties.java | 55 + .../framing/ContentHeaderPropertiesFactory.java | 51 + .../apache/qpid/framing/EncodableAMQDataBlock.java | 32 + .../src/org/apache/qpid/framing/EncodingUtils.java | 519 + .../src/org/apache/qpid/framing/FieldTable.java | 319 + .../qpid/framing/FieldTableKeyEnumeration.java | 44 + .../src/org/apache/qpid/framing/HeartbeatBody.java | 54 + .../apache/qpid/framing/HeartbeatBodyFactory.java | 28 + .../apache/qpid/framing/ProtocolInitiation.java | 176 + .../src/org/apache/qpid/nio/SocketAcceptor.java | 31 + .../apache/qpid/nio/SocketAcceptorDelegate.java | 602 + .../src/org/apache/qpid/nio/SocketConnector.java | 32 + .../apache/qpid/nio/SocketConnectorDelegate.java | 402 + .../src/org/apache/qpid/nio/SocketFilterChain.java | 48 + .../src/org/apache/qpid/nio/SocketIoProcessor.java | 770 + .../src/org/apache/qpid/nio/SocketSessionImpl.java | 404 + java/common/src/org/apache/qpid/pool/Event.java | 111 + java/common/src/org/apache/qpid/pool/Job.java | 110 + .../src/org/apache/qpid/pool/PoolingFilter.java | 183 + .../org/apache/qpid/pool/ReadWriteThreadModel.java | 37 + .../pool/ReferenceCountingExecutorService.java | 95 + .../src/org/apache/qpid/protocol/AMQConstant.java | 105 + .../apache/qpid/ssl/BogusSSLContextFactory.java | 156 + .../apache/qpid/ssl/BogusTrustManagerFactory.java | 79 + .../apache/qpid/ssl/SSLServerSocketFactory.java | 105 + .../src/org/apache/qpid/ssl/SSLSocketFactory.java | 135 + .../src/org/apache/qpid/url/AMQBindingURL.java | 260 + .../common/src/org/apache/qpid/url/BindingURL.java | 65 + java/common/src/org/apache/qpid/url/URLHelper.java | 173 + .../org/apache/qpid/url/URLSyntaxException.java | 94 + java/common/stylesheets/framing.xsl | 61 + java/common/stylesheets/java.xsl | 247 + java/common/stylesheets/prepare1.xsl | 111 + java/common/stylesheets/prepare2.xsl | 66 + java/common/stylesheets/prepare3.xsl | 62 + java/common/stylesheets/readme.txt | 52 + java/common/stylesheets/registry.xsl | 29 + java/common/stylesheets/utils.xsl | 201 + java/doc/AMQBlazeDetailedDesign.vsd | Bin 0 -> 120320 bytes java/doc/FramingClassDiagram.vsd | Bin 0 -> 206848 bytes java/management/cli/bin/stac.bat | 38 + java/management/cli/bin/stac.sh | 39 + java/management/cli/bin/stacDEV.bat | 41 + java/management/cli/build-module.xml | 21 + java/management/cli/build-old.xml | 122 + java/management/cli/lib/jython/jython.jar | Bin 0 -> 719950 bytes .../cli/src/org/apache/qpid/stac/Stac.java | 94 + .../src/org/apache/qpid/stac/StacInterpreter.java | 31 + .../org/apache/qpid/stac/commands/CdCommand.java | 50 + .../apache/qpid/stac/commands/InvokeCommand.java | 31 + .../org/apache/qpid/stac/commands/LsCommand.java | 123 + .../src/org/apache/qpid/stac/jmx/CurrentMBean.java | 180 + .../stac/jmx/MBeanAttributeInfoComparator.java | 29 + .../stac/jmx/MBeanOperationInfoComparator.java | 29 + .../stac/jmx/MBeanServerConnectionContext.java | 202 + .../src/org/apache/qpid/stac/jmx/MBeanUtils.java | 35 + .../qpid/stac/jmx/NotConnectedException.java | 28 + java/management/cli/src/python/stac.py | 190 + .../test/org/apache/qpid/stac/ConnectionTest.java | 35 + java/management/core/build-module.xml | 47 + java/management/core/build-old.xml | 189 + java/management/core/etc/cml-exampleschema.xml | 51 + java/management/core/etc/cml.xsd | 175 + .../lib/jakarta-commons/commons-attributes-api.jar | Bin 0 -> 36342 bytes .../commons-attributes-compiler.jar | Bin 0 -> 29216 bytes .../core/lib/jakarta-commons/commons-beanutils.jar | Bin 0 -> 188671 bytes .../core/lib/jakarta-commons/commons-codec.jar | Bin 0 -> 46725 bytes .../lib/jakarta-commons/commons-collections.jar | Bin 0 -> 559366 bytes .../core/lib/jakarta-commons/commons-dbcp.jar | Bin 0 -> 107631 bytes .../core/lib/jakarta-commons/commons-digester.jar | Bin 0 -> 168446 bytes .../core/lib/jakarta-commons/commons-discovery.jar | Bin 0 -> 74527 bytes .../lib/jakarta-commons/commons-fileupload.jar | Bin 0 -> 22379 bytes .../lib/jakarta-commons/commons-httpclient.jar | Bin 0 -> 279317 bytes .../core/lib/jakarta-commons/commons-lang.jar | Bin 0 -> 207723 bytes .../core/lib/jakarta-commons/commons-logging.jar | Bin 0 -> 38015 bytes .../core/lib/jakarta-commons/commons-pool.jar | Bin 0 -> 42492 bytes .../core/lib/jakarta-commons/commons-validator.jar | Bin 0 -> 84462 bytes java/management/core/lib/log4j/log4j-1.2.9.jar | Bin 0 -> 352291 bytes java/management/core/lib/spring/spring-beans.dtd | 587 + java/management/core/lib/spring/spring.ftl | 316 + java/management/core/lib/spring/spring.tld | 311 + java/management/core/lib/spring/spring.vm | 294 + java/management/core/lib/xmlbeans/jsr173_api.jar | Bin 0 -> 26396 bytes java/management/core/lib/xmlbeans/resolver.jar | Bin 0 -> 60047 bytes java/management/core/lib/xmlbeans/saxon8.jar | Bin 0 -> 2297989 bytes java/management/core/lib/xmlbeans/xbean.jar | Bin 0 -> 2526707 bytes java/management/core/lib/xmlbeans/xbean_xpath.jar | Bin 0 -> 5182 bytes java/management/core/lib/xmlbeans/xmlpublic.jar | Bin 0 -> 425062 bytes java/management/core/src/log4j.properties | 6 + .../qpid/management/ManagementConnection.java | 120 + .../org/apache/qpid/management/jmx/AMQConsole.java | 95 + .../apache/qpid/management/jmx/AMQMBeanInfo.java | 40 + .../org/apache/qpid/management/jmx/CMLMBean.java | 298 + .../apache/qpid/management/jmx/JmxConstants.java | 23 + .../qpid/management/jmx/MBeanInfoRegistry.java | 201 + .../apache/qpid/management/jmx/MBeanRegistrar.java | 141 + .../jmx/UnsupportedCMLTypeException.java | 36 + .../management/messaging/CMLMessageFactory.java | 59 + .../messaging/ManagementDestination.java | 43 + .../qpid/management/harness/SimpleJMXClient.java | 25 + .../qpid/management/schema/TestParseSchema.java | 87 + java/management/mc4j/qpid/BlazeConnections.xml | 84 + java/management/mc4j/qpid/BlazeExchanges.xml | 81 + java/management/mc4j/qpid/BlazeQueues.xml | 83 + java/management/mc4j/qpid/BlazeSingleQueue.xml | 99 + java/management/webapp/META-INF/context.xml | 20 + java/management/webapp/WEB-INF/web.xml | 24 + java/module.xml | 230 + java/tasks/src/org/apache/qpid/tasks/BaseTask.java | 71 + java/tasks/src/org/apache/qpid/tasks/Foreach.java | 81 + java/tasks/src/org/apache/qpid/tasks/Map.java | 91 + java/tasks/src/org/apache/qpid/tasks/Require.java | 62 + 687 files changed, 142267 insertions(+) create mode 100644 java/Developing.txt create mode 100644 java/ReadMe.txt create mode 100644 java/broker/README.txt create mode 100644 java/broker/bin/qpid-server create mode 100644 java/broker/bin/qpid-server.bat create mode 100755 java/broker/bin/run.bat create mode 100755 java/broker/bin/run.sh create mode 100644 java/broker/bin/runAll create mode 100644 java/broker/build-module.xml create mode 100644 java/broker/build-old.xml create mode 100644 java/broker/etc/config.xml create mode 100644 java/broker/etc/log4j.xml create mode 100644 java/broker/etc/passwd create mode 100644 java/broker/etc/qpid-server.conf create mode 100644 java/broker/etc/virtualhosts.xml create mode 100644 java/broker/src/log4j.properties create mode 100644 java/broker/src/org/apache/qpid/server/AMQChannel.java create mode 100644 java/broker/src/org/apache/qpid/server/ConsumerTagNotUniqueException.java create mode 100644 java/broker/src/org/apache/qpid/server/Main.java create mode 100644 java/broker/src/org/apache/qpid/server/ManagedChannel.java create mode 100644 java/broker/src/org/apache/qpid/server/RequiredDeliveryException.java create mode 100644 java/broker/src/org/apache/qpid/server/configuration/Configurator.java create mode 100644 java/broker/src/org/apache/qpid/server/configuration/VirtualHostConfiguration.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/AbstractExchange.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeFactory.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/DestNameExchange.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/DestWildExchange.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/Exchange.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/ExchangeFactory.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/ExchangeInUseException.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/ExchangeRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/HeadersBinding.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/HeadersExchange.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/Index.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/ManagedExchange.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/MessageRouter.java create mode 100644 java/broker/src/org/apache/qpid/server/exchange/NoRouteException.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/BasicAckMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/BasicCancelMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/BasicPublishMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/BasicQosHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ChannelCloseHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ChannelCloseOkHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ChannelFlowHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ChannelOpenHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ExchangeDeclareHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/ExchangeDeleteHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/QueueBindHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/QueueDeclareHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/QueueDeleteHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/TxCommitHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/TxRollbackHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/handler/TxSelectHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/jms/JmsConsumer.java create mode 100644 java/broker/src/org/apache/qpid/server/management/AMQManagedObject.java create mode 100644 java/broker/src/org/apache/qpid/server/management/DefaultManagedObject.java create mode 100644 java/broker/src/org/apache/qpid/server/management/JMXManagedObjectRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/management/Managable.java create mode 100644 java/broker/src/org/apache/qpid/server/management/ManagedBroker.java create mode 100644 java/broker/src/org/apache/qpid/server/management/ManagedObject.java create mode 100644 java/broker/src/org/apache/qpid/server/management/ManagedObjectRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/management/ManagementConfiguration.java create mode 100644 java/broker/src/org/apache/qpid/server/management/NoopManagedObjectRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/AMQMethodEvent.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/AMQMethodListener.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/AMQPProtocolProvider.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/AMQProtocolSession.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/ExchangeInitialiser.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/HeartbeatConfig.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/ManagedConnection.java create mode 100644 java/broker/src/org/apache/qpid/server/protocol/ManagedSession.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/AMQMessage.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/AMQQueue.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/AsyncDeliveryConfig.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/DefaultQueueRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/DeliveryManager.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/ExchangeBindings.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/ManagedQueue.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/NoConsumersException.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/QueueRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/Subscription.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/SubscriptionFactory.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/SubscriptionImpl.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/SubscriptionManager.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/SubscriptionSet.java create mode 100644 java/broker/src/org/apache/qpid/server/queue/WeightedSubscriptionManager.java create mode 100644 java/broker/src/org/apache/qpid/server/registry/ApplicationRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/registry/IApplicationRegistry.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/AuthenticationManager.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/AuthenticationResult.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/JCAProvider.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/PrincipalDatabase.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/UsernamePrincipal.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java create mode 100644 java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java create mode 100644 java/broker/src/org/apache/qpid/server/state/AMQState.java create mode 100644 java/broker/src/org/apache/qpid/server/state/AMQStateManager.java create mode 100644 java/broker/src/org/apache/qpid/server/state/IllegalStateTransitionException.java create mode 100644 java/broker/src/org/apache/qpid/server/state/StateAwareMethodListener.java create mode 100644 java/broker/src/org/apache/qpid/server/state/StateListener.java create mode 100644 java/broker/src/org/apache/qpid/server/store/MemoryMessageStore.java create mode 100644 java/broker/src/org/apache/qpid/server/store/MessageStore.java create mode 100644 java/broker/src/org/apache/qpid/server/transport/ConnectorConfiguration.java create mode 100644 java/broker/src/org/apache/qpid/server/transport/ThreadPoolFilter.java create mode 100644 java/broker/src/org/apache/qpid/server/txn/TxnBuffer.java create mode 100644 java/broker/src/org/apache/qpid/server/txn/TxnOp.java create mode 100644 java/broker/src/org/apache/qpid/server/util/CircularBuffer.java create mode 100644 java/broker/src/org/apache/qpid/server/util/LoggingProxy.java create mode 100644 java/broker/test/build-module.xml create mode 100644 java/broker/test/lib/README create mode 100644 java/broker/test/lib/junit/junit-4.0.jar create mode 100644 java/broker/test/lib/junit/junit.jar create mode 100644 java/broker/test/src/org/apache/qpid/server/UnitTests.java create mode 100644 java/broker/test/src/org/apache/qpid/server/configuration/TestPropertyUtils.java create mode 100644 java/broker/test/src/org/apache/qpid/server/configuration/UnitTests.java create mode 100644 java/broker/test/src/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/exchange/HeadersBindingTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangeTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/exchange/UnitTests.java create mode 100644 java/broker/test/src/org/apache/qpid/server/protocol/MockIoSession.java create mode 100644 java/broker/test/src/org/apache/qpid/server/protocol/TestProtocolInitiation.java create mode 100644 java/broker/test/src/org/apache/qpid/server/protocol/UnitTests.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/AckTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/ConcurrencyTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/DeliveryManagerTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/MessageTestHelper.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/MockProtocolSession.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/QueuePerfTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/SendPerfTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/SubscriptionManagerTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/SubscriptionSetTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/TestSubscription.java create mode 100644 java/broker/test/src/org/apache/qpid/server/queue/UnitTests.java create mode 100644 java/broker/test/src/org/apache/qpid/server/store/SkeletonMessageStore.java create mode 100644 java/broker/test/src/org/apache/qpid/server/store/TestReferenceCounting.java create mode 100644 java/broker/test/src/org/apache/qpid/server/store/TestableMemoryMessageStore.java create mode 100644 java/broker/test/src/org/apache/qpid/server/store/UnitTests.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/AveragedRun.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/ConcurrentTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/LoggingProxyTest.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/NullApplicationRegistry.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/NullAuthenticationManager.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/RunStats.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/TimedRun.java create mode 100644 java/broker/test/src/org/apache/qpid/server/util/UnitTests.java create mode 100644 java/build-old.xml create mode 100644 java/build.xml create mode 100644 java/client/build-module.xml create mode 100644 java/client/build-old.xml create mode 100644 java/client/dist/readme.txt create mode 100644 java/client/lib/jms/jms.jar create mode 100644 java/client/readme.txt create mode 100644 java/client/src/log4j.properties create mode 100644 java/client/src/org/apache/qpid/client/AMQAuthenticationException.java create mode 100644 java/client/src/org/apache/qpid/client/AMQBrokerDetails.java create mode 100644 java/client/src/org/apache/qpid/client/AMQConnection.java create mode 100644 java/client/src/org/apache/qpid/client/AMQConnectionFactory.java create mode 100644 java/client/src/org/apache/qpid/client/AMQConnectionURL.java create mode 100644 java/client/src/org/apache/qpid/client/AMQDestination.java create mode 100644 java/client/src/org/apache/qpid/client/AMQHeadersExchange.java create mode 100644 java/client/src/org/apache/qpid/client/AMQNoConsumersException.java create mode 100644 java/client/src/org/apache/qpid/client/AMQNoRouteException.java create mode 100644 java/client/src/org/apache/qpid/client/AMQQueue.java create mode 100644 java/client/src/org/apache/qpid/client/AMQSession.java create mode 100644 java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java create mode 100644 java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java create mode 100644 java/client/src/org/apache/qpid/client/AMQTopic.java create mode 100644 java/client/src/org/apache/qpid/client/BasicMessageConsumer.java create mode 100644 java/client/src/org/apache/qpid/client/BasicMessageProducer.java create mode 100644 java/client/src/org/apache/qpid/client/Closeable.java create mode 100644 java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java create mode 100644 java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java create mode 100644 java/client/src/org/apache/qpid/client/failover/FailoverException.java create mode 100644 java/client/src/org/apache/qpid/client/failover/FailoverHandler.java create mode 100644 java/client/src/org/apache/qpid/client/failover/FailoverState.java create mode 100644 java/client/src/org/apache/qpid/client/failover/FailoverSupport.java create mode 100644 java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java create mode 100644 java/client/src/org/apache/qpid/client/message/AMQMessage.java create mode 100644 java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java create mode 100644 java/client/src/org/apache/qpid/client/message/AbstractJMSMessageFactory.java create mode 100644 java/client/src/org/apache/qpid/client/message/JMSBytesMessage.java create mode 100644 java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java create mode 100644 java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java create mode 100644 java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java create mode 100644 java/client/src/org/apache/qpid/client/message/JMSTextMessage.java create mode 100644 java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java create mode 100644 java/client/src/org/apache/qpid/client/message/MessageFactory.java create mode 100644 java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java create mode 100644 java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java create mode 100644 java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java create mode 100644 java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java create mode 100644 java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java create mode 100644 java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java create mode 100644 java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties create mode 100644 java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java create mode 100644 java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties create mode 100644 java/client/src/org/apache/qpid/client/security/JCAProvider.java create mode 100644 java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java create mode 100644 java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java create mode 100644 java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java create mode 100644 java/client/src/org/apache/qpid/client/state/AMQState.java create mode 100644 java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java create mode 100644 java/client/src/org/apache/qpid/client/state/AMQStateListener.java create mode 100644 java/client/src/org/apache/qpid/client/state/AMQStateManager.java create mode 100644 java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java create mode 100644 java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java create mode 100644 java/client/src/org/apache/qpid/client/state/StateListener.java create mode 100644 java/client/src/org/apache/qpid/client/state/StateWaiter.java create mode 100644 java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java create mode 100644 java/client/src/org/apache/qpid/client/transport/ITransportConnection.java create mode 100644 java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java create mode 100644 java/client/src/org/apache/qpid/client/transport/TransportConnection.java create mode 100644 java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java create mode 100644 java/client/src/org/apache/qpid/jms/BrokerDetails.java create mode 100644 java/client/src/org/apache/qpid/jms/ChannelLimitReachedException.java create mode 100644 java/client/src/org/apache/qpid/jms/Connection.java create mode 100644 java/client/src/org/apache/qpid/jms/ConnectionListener.java create mode 100644 java/client/src/org/apache/qpid/jms/ConnectionURL.java create mode 100644 java/client/src/org/apache/qpid/jms/FailoverPolicy.java create mode 100644 java/client/src/org/apache/qpid/jms/MessageConsumer.java create mode 100644 java/client/src/org/apache/qpid/jms/MessageProducer.java create mode 100644 java/client/src/org/apache/qpid/jms/Session.java create mode 100644 java/client/src/org/apache/qpid/jms/failover/FailoverMethod.java create mode 100644 java/client/src/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java create mode 100644 java/client/src/org/apache/qpid/jms/failover/FailoverSingleServer.java create mode 100644 java/client/test/bin/IBM-JNDI-Setup.bat create mode 100755 java/client/test/bin/IBM-JNDI-Setup.sh create mode 100644 java/client/test/bin/IBM-Publisher.bat create mode 100755 java/client/test/bin/IBM-Publisher.sh create mode 100644 java/client/test/bin/IBM-PutGet.bat create mode 100755 java/client/test/bin/IBM-PutGet.sh create mode 100644 java/client/test/bin/IBM-README.txt create mode 100644 java/client/test/bin/IBM-Receiver.bat create mode 100755 java/client/test/bin/IBM-Receiver.sh create mode 100644 java/client/test/bin/IBM-Sender.bat create mode 100755 java/client/test/bin/IBM-Sender.sh create mode 100644 java/client/test/bin/IBM-Subscriber.bat create mode 100755 java/client/test/bin/IBM-Subscriber.sh create mode 100755 java/client/test/bin/headersListener.sh create mode 100755 java/client/test/bin/headersListenerGroup.sh create mode 100755 java/client/test/bin/headersPublisher.sh create mode 100755 java/client/test/bin/run_many.sh create mode 100755 java/client/test/bin/serviceProvidingClient.sh create mode 100755 java/client/test/bin/serviceRequestingClient.sh create mode 100755 java/client/test/bin/testService.sh create mode 100755 java/client/test/bin/topicListener.sh create mode 100755 java/client/test/bin/topicPublisher.sh create mode 100644 java/client/test/build-module.xml create mode 100644 java/client/test/etc/ApacheDS.properties create mode 100644 java/client/test/example_ build.xml create mode 100644 java/client/test/lib/activemq/geronimo-j2ee-management_1.0_spec-1.0.jar create mode 100644 java/client/test/lib/jakarta-commons/commons-codec-1.3.jar create mode 100644 java/client/test/lib/jboss/jboss-messaging-client.jar create mode 100644 java/client/test/lib/jboss/jbossall-client.jar create mode 100644 java/client/test/lib/jmscts/jmscts-0.5-b2.jar create mode 100644 java/client/test/lib/spring-1.2.8/spring.jar create mode 100644 java/client/test/src/org/apache/qpid/IBMPerfTest/JNDIBindConnectionFactory.java create mode 100644 java/client/test/src/org/apache/qpid/IBMPerfTest/JNDIBindQueue.java create mode 100644 java/client/test/src/org/apache/qpid/IBMPerfTest/JNDIBindTopic.java create mode 100644 java/client/test/src/org/apache/qpid/IBMPerfTest/README.txt create mode 100644 java/client/test/src/org/apache/qpid/ack/DisconnectAndRedeliverTest.java create mode 100644 java/client/test/src/org/apache/qpid/ack/RecoverTest.java create mode 100644 java/client/test/src/org/apache/qpid/ack/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/basic/BytesMessageTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/FieldTableKeyEnumeratorTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/FieldTableMessageTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/MultipleConnectionTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/ObjectMessageTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/ReceiveTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/SessionStartTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/TextMessageTest.java create mode 100644 java/client/test/src/org/apache/qpid/basic/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/client/AllClientUnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/client/channelclose/ChannelCloseOkTest.java create mode 100644 java/client/test/src/org/apache/qpid/client/channelclose/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/client/message/ObjectMessageTest.java create mode 100644 java/client/test/src/org/apache/qpid/client/message/TestBytesMessage.java create mode 100644 java/client/test/src/org/apache/qpid/client/message/TestTextMessage.java create mode 100644 java/client/test/src/org/apache/qpid/client/message/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/client/testutil/VmOrRemoteTestCase.java create mode 100644 java/client/test/src/org/apache/qpid/cluster/Client.java create mode 100644 java/client/test/src/org/apache/qpid/codec/BasicDeliverTest.java create mode 100644 java/client/test/src/org/apache/qpid/codec/Client.java create mode 100644 java/client/test/src/org/apache/qpid/codec/Server.java create mode 100644 java/client/test/src/org/apache/qpid/config/AMQConnectionFactoryInitialiser.java create mode 100644 java/client/test/src/org/apache/qpid/config/AbstractConfig.java create mode 100644 java/client/test/src/org/apache/qpid/config/ConnectionFactoryInitialiser.java create mode 100644 java/client/test/src/org/apache/qpid/config/Connector.java create mode 100644 java/client/test/src/org/apache/qpid/config/ConnectorConfig.java create mode 100644 java/client/test/src/org/apache/qpid/config/JBossConnectionFactoryInitialiser.java create mode 100644 java/client/test/src/org/apache/qpid/connection/ConnectionTest.java create mode 100644 java/client/test/src/org/apache/qpid/connection/TestManyConnections.java create mode 100644 java/client/test/src/org/apache/qpid/connectionurl/ConnectionURLTest.java create mode 100644 java/client/test/src/org/apache/qpid/connectionurl/UnitTests.java create mode 100755 java/client/test/src/org/apache/qpid/cts/bin/jmscts.sh create mode 100755 java/client/test/src/org/apache/qpid/cts/bin/setenv.sh create mode 100644 java/client/test/src/org/apache/qpid/cts/config/jmscts.policy create mode 100644 java/client/test/src/org/apache/qpid/cts/config/jmscts.properties create mode 100644 java/client/test/src/org/apache/qpid/cts/config/providers.xml create mode 100644 java/client/test/src/org/apache/qpid/cts/readme.txt create mode 100755 java/client/test/src/org/apache/qpid/cts/src/compile.sh create mode 100644 java/client/test/src/org/apache/qpid/cts/src/providers/amqp/org/exolab/jmscts/amqp/AMQPAdministrator.java create mode 100644 java/client/test/src/org/apache/qpid/cts/src/providers/amqp/org/exolab/jmscts/amqp/AMQPProvider.java create mode 100644 java/client/test/src/org/apache/qpid/destinationurl/DestinationURLTest.java create mode 100644 java/client/test/src/org/apache/qpid/destinationurl/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/example/log4j.xml create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/FileMessageDispatcher.java create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/MessageFactory.java create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/MessageFactoryException.java create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/MonitorMessageDispatcher.java create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/MonitorPublisher.java create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/Publisher.java create mode 100644 java/client/test/src/org/apache/qpid/example/publisher/UndeliveredMessageException.java create mode 100644 java/client/test/src/org/apache/qpid/example/shared/ConnectionException.java create mode 100644 java/client/test/src/org/apache/qpid/example/shared/FileUtils.java create mode 100644 java/client/test/src/org/apache/qpid/example/shared/Statics.java create mode 100644 java/client/test/src/org/apache/qpid/example/subscriber/MonitoredSubscriber.java create mode 100644 java/client/test/src/org/apache/qpid/example/subscriber/MonitoredSubscriptionWrapper.java create mode 100644 java/client/test/src/org/apache/qpid/example/subscriber/Subscriber.java create mode 100644 java/client/test/src/org/apache/qpid/example/subscriber/SubscriptionWrapper.java create mode 100644 java/client/test/src/org/apache/qpid/example/test/TestAMSPubSub.java create mode 100644 java/client/test/src/org/apache/qpid/example/test/TestMultSubscribers.java create mode 100644 java/client/test/src/org/apache/qpid/example/test/TestPublisher.java create mode 100644 java/client/test/src/org/apache/qpid/example/test/TestSubscriber.java create mode 100644 java/client/test/src/org/apache/qpid/failover/FailoverMultiMethodTest.java create mode 100644 java/client/test/src/org/apache/qpid/failover/FailoverRoundRobinTest.java create mode 100644 java/client/test/src/org/apache/qpid/failover/FailoverSingleServerTest.java create mode 100644 java/client/test/src/org/apache/qpid/failover/FailoverTest.java create mode 100644 java/client/test/src/org/apache/qpid/failover/FailoverTxTest.java create mode 100644 java/client/test/src/org/apache/qpid/flow/ChannelFlowTest.java create mode 100644 java/client/test/src/org/apache/qpid/forwardall/Client.java create mode 100644 java/client/test/src/org/apache/qpid/forwardall/Combined.java create mode 100644 java/client/test/src/org/apache/qpid/forwardall/Service.java create mode 100644 java/client/test/src/org/apache/qpid/forwardall/ServiceCreator.java create mode 100644 java/client/test/src/org/apache/qpid/forwardall/SpecialQueue.java create mode 100644 java/client/test/src/org/apache/qpid/forwardall/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/fragmentation/TestLargePublisher.java create mode 100644 java/client/test/src/org/apache/qpid/fragmentation/TestLargeSubscriber.java create mode 100644 java/client/test/src/org/apache/qpid/framing/FieldTableTest.java create mode 100644 java/client/test/src/org/apache/qpid/framing/content.txt create mode 100644 java/client/test/src/org/apache/qpid/headers/Listener.java create mode 100644 java/client/test/src/org/apache/qpid/headers/MessageFactory.java create mode 100644 java/client/test/src/org/apache/qpid/headers/Publisher.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceable/Bind.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceable/Lookup.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceable/Unbind.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceabletest/Bind.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceabletest/JNDIReferenceableTest.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceabletest/Lookup.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceabletest/Unbind.java create mode 100644 java/client/test/src/org/apache/qpid/jndi/referenceabletest/UnitTests.java create mode 100644 java/client/test/src/org/apache/qpid/latency/LatencyTest.java create mode 100644 java/client/test/src/org/apache/qpid/mina/AcceptorTest.java create mode 100644 java/client/test/src/org/apache/qpid/mina/BlockingAcceptorTest.java create mode 100644 java/client/test/src/org/apache/qpid/mina/WriterTest.java create mode 100644 java/client/test/src/org/apache/qpid/multiconsumer/AMQTest.java create mode 100644 java/client/test/src/org/apache/qpid/ping/TestPingClient.java create mode 100644 java/client/test/src/org/apache/qpid/ping/TestPingProducer.java create mode 100644 java/client/test/src/org/apache/qpid/ping/TestPingPublisher.java create mode 100644 java/client/test/src/org/apache/qpid/ping/TestPingSubscriber.java create mode 100644 java/client/test/src/org/apache/qpid/pubsub1/TestPublisher.java create mode 100644 java/client/test/src/org/apache/qpid/pubsub1/TestSubscriber.java create mode 100644 java/client/test/src/org/apache/qpid/requestreply1/ServiceProvidingClient.java create mode 100644 java/client/test/src/org/apache/qpid/requestreply1/ServiceRequestingClient.java create mode 100644 java/client/test/src/org/apache/qpid/requestreply1/VmRequestReply.java create mode 100644 java/client/test/src/org/apache/qpid/testutil/Config.java create mode 100644 java/client/test/src/org/apache/qpid/topic/Config.java create mode 100644 java/client/test/src/org/apache/qpid/topic/DurableSubscriptionTest.java create mode 100644 java/client/test/src/org/apache/qpid/topic/Listener.java create mode 100644 java/client/test/src/org/apache/qpid/topic/MessageFactory.java create mode 100644 java/client/test/src/org/apache/qpid/topic/Publisher.java create mode 100644 java/client/test/src/org/apache/qpid/transacted/Config.java create mode 100644 java/client/test/src/org/apache/qpid/transacted/Ping.java create mode 100644 java/client/test/src/org/apache/qpid/transacted/Pong.java create mode 100644 java/client/test/src/org/apache/qpid/transacted/Relay.java create mode 100644 java/client/test/src/org/apache/qpid/transacted/Start.java create mode 100644 java/client/test/src/org/apache/qpid/transacted/TransactedTest.java create mode 100644 java/client/test/src/org/apache/qpid/transport/VmPipeTransportConnection.java create mode 100644 java/client/test/src/org/apache/qpid/vmbroker/VmPipeBroker.java create mode 100644 java/client/test/src/org/apache/qpid/weblogic/ServiceProvider.java create mode 100644 java/client/test/src/org/apache/qpid/weblogic/ServiceRequestingClient.java create mode 100644 java/cluster/build-module.xml create mode 100644 java/cluster/build-old.xml create mode 100644 java/cluster/doc/design.doc create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/BlockingHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/BroadcastPolicy.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/Broker.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/BrokerFactory.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/BrokerGroup.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ClientAdapter.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ClientHandlerRegistry.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ClusterBuilder.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ClusterCapability.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ClusteredProtocolHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ClusteredProtocolSession.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ConnectionStatusMonitor.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/DefaultGroupManager.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/GroupManager.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/GroupRequest.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/GroupResponseHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/InductionBuffer.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/JoinState.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/LoadTable.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/Main.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/Member.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MemberFailureListener.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MemberHandle.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MembershipChangeListener.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MethodHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MethodHandlerFactory.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MethodHandlerRegistry.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MinaBrokerProxy.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/MinaBrokerProxyFactory.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ResponseHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/Sendable.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/ServerHandlerRegistry.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/SimpleMemberHandle.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/SimpleSendable.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ChainedClusterMethodHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ChannelQueueManager.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ClusterMethodHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ClusterMethodHandlerFactory.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ExtendedHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/HandlerUtils.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/LocalQueueDeclareHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/NullListener.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/PeerHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/QueueNameGenerator.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/RemoteCancelHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/RemoteConsumeHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ReplicatingConsumeHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/ReplicatingHandler.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/WrappedListener.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/handler/WrappingMethodHandlerFactory.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/policy/AsynchBroadcastPolicy.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/policy/MajorityResponseBroadcastPolicy.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/policy/OneResponseBroadcastPolicy.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/policy/StandardPolicies.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/policy/SynchBroadcastPolicy.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/replay/ChainedMethodRecorder.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/replay/ConsumerCounts.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/replay/MethodRecorder.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/replay/RecordingMethodHandlerFactory.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/replay/ReplayManager.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/replay/ReplayStore.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/util/Bindings.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/util/InvokeMultiple.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/util/LogMessage.java create mode 100644 java/cluster/src/org/apache/qpid/server/cluster/util/MultiValuedMap.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/ClusteredQueue.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/ClusteredSubscriptionManager.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/NestedSubscriptionManager.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/PrivateQueue.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/ProxiedQueueCleanup.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/RemoteQueueProxy.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/RemoteSubscriptionImpl.java create mode 100644 java/cluster/src/org/apache/qpid/server/queue/SubscriberCleanup.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/BrokerGroupTest.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/BrokerTest.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/ClusterCapabilityTest.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/InductionBufferTest.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/RecordingBroker.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/RecordingBrokerFactory.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/SimpleClusterTest.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/SimpleMemberHandleTest.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/TestBroker.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/TestBrokerFactory.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/TestReplayManager.java create mode 100644 java/cluster/test/org/apache/qpid/server/cluster/TestSession.java create mode 100644 java/common.xml create mode 100644 java/common/bin/qpid-run create mode 100644 java/common/build-module.xml create mode 100644 java/common/build-old.xml create mode 100644 java/common/etc/qpid-run.conf create mode 100644 java/common/etc/qpid-run.conf.dev create mode 100644 java/common/lib/commons-cli/commons-cli-1.0.jar create mode 100644 java/common/lib/commons-collections/commons-collections-3.1.jar create mode 100644 java/common/lib/commons-configuration/commons-configuration-1.2.jar create mode 100644 java/common/lib/commons-lang/commons-lang-2.1.jar create mode 100644 java/common/lib/commons-logging/commons-logging-api.jar create mode 100644 java/common/lib/commons-logging/commons-logging.jar create mode 100644 java/common/lib/junit/junit-4.0.jar create mode 100644 java/common/lib/junit/junit.jar create mode 100644 java/common/lib/logging-log4j/log4j-1.2.13.jar create mode 100644 java/common/lib/mina/mina-core-0.9.5-SNAPSHOT.jar create mode 100644 java/common/lib/mina/mina-filter-ssl-0.9.5-SNAPSHOT.jar create mode 100644 java/common/lib/saxon/saxon8.jar create mode 100644 java/common/lib/slf4j/slf4j-simple.jar create mode 100644 java/common/readme.txt create mode 100644 java/common/resources/ProtocolVersionList.java create mode 100644 java/common/resources/cluster.asl create mode 100644 java/common/resources/org/apache/qpid/ssl/qpid.cert create mode 100644 java/common/resources/registry.template create mode 100644 java/common/src/org/apache/qpid/AMQChannelClosedException.java create mode 100644 java/common/src/org/apache/qpid/AMQChannelException.java create mode 100644 java/common/src/org/apache/qpid/AMQConnectionClosedException.java create mode 100644 java/common/src/org/apache/qpid/AMQConnectionException.java create mode 100644 java/common/src/org/apache/qpid/AMQDisconnectedException.java create mode 100644 java/common/src/org/apache/qpid/AMQException.java create mode 100644 java/common/src/org/apache/qpid/AMQUndeliveredException.java create mode 100644 java/common/src/org/apache/qpid/AMQUnresolvedAddressException.java create mode 100644 java/common/src/org/apache/qpid/bio/Reader.java create mode 100644 java/common/src/org/apache/qpid/bio/Sequence.java create mode 100644 java/common/src/org/apache/qpid/bio/SimpleSocketChannel.java create mode 100644 java/common/src/org/apache/qpid/bio/SocketAcceptor.java create mode 100644 java/common/src/org/apache/qpid/bio/SocketConnector.java create mode 100644 java/common/src/org/apache/qpid/bio/SocketFilterChain.java create mode 100644 java/common/src/org/apache/qpid/bio/SocketSessionImpl.java create mode 100644 java/common/src/org/apache/qpid/codec/AMQCodecFactory.java create mode 100644 java/common/src/org/apache/qpid/codec/AMQDecoder.java create mode 100644 java/common/src/org/apache/qpid/codec/AMQEncoder.java create mode 100644 java/common/src/org/apache/qpid/configuration/Configured.java create mode 100644 java/common/src/org/apache/qpid/configuration/PropertyException.java create mode 100644 java/common/src/org/apache/qpid/configuration/PropertyUtils.java create mode 100644 java/common/src/org/apache/qpid/exchange/ExchangeDefaults.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQBody.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQDataBlock.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQDataBlockDecoder.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQDataBlockEncoder.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQFrame.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQFrameDecodingException.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQMethodBody.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQMethodBodyFactory.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQProtocolClassException.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQProtocolHeaderException.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQProtocolInstanceException.java create mode 100644 java/common/src/org/apache/qpid/framing/AMQProtocolVersionException.java create mode 100644 java/common/src/org/apache/qpid/framing/BasicContentHeaderProperties.java create mode 100644 java/common/src/org/apache/qpid/framing/BodyFactory.java create mode 100644 java/common/src/org/apache/qpid/framing/CompositeAMQDataBlock.java create mode 100644 java/common/src/org/apache/qpid/framing/ContentBody.java create mode 100644 java/common/src/org/apache/qpid/framing/ContentBodyFactory.java create mode 100644 java/common/src/org/apache/qpid/framing/ContentHeaderBody.java create mode 100644 java/common/src/org/apache/qpid/framing/ContentHeaderBodyFactory.java create mode 100644 java/common/src/org/apache/qpid/framing/ContentHeaderProperties.java create mode 100644 java/common/src/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java create mode 100644 java/common/src/org/apache/qpid/framing/EncodableAMQDataBlock.java create mode 100644 java/common/src/org/apache/qpid/framing/EncodingUtils.java create mode 100644 java/common/src/org/apache/qpid/framing/FieldTable.java create mode 100644 java/common/src/org/apache/qpid/framing/FieldTableKeyEnumeration.java create mode 100644 java/common/src/org/apache/qpid/framing/HeartbeatBody.java create mode 100644 java/common/src/org/apache/qpid/framing/HeartbeatBodyFactory.java create mode 100644 java/common/src/org/apache/qpid/framing/ProtocolInitiation.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketAcceptor.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketAcceptorDelegate.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketConnector.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketConnectorDelegate.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketFilterChain.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketIoProcessor.java create mode 100644 java/common/src/org/apache/qpid/nio/SocketSessionImpl.java create mode 100644 java/common/src/org/apache/qpid/pool/Event.java create mode 100644 java/common/src/org/apache/qpid/pool/Job.java create mode 100644 java/common/src/org/apache/qpid/pool/PoolingFilter.java create mode 100644 java/common/src/org/apache/qpid/pool/ReadWriteThreadModel.java create mode 100644 java/common/src/org/apache/qpid/pool/ReferenceCountingExecutorService.java create mode 100644 java/common/src/org/apache/qpid/protocol/AMQConstant.java create mode 100644 java/common/src/org/apache/qpid/ssl/BogusSSLContextFactory.java create mode 100644 java/common/src/org/apache/qpid/ssl/BogusTrustManagerFactory.java create mode 100644 java/common/src/org/apache/qpid/ssl/SSLServerSocketFactory.java create mode 100644 java/common/src/org/apache/qpid/ssl/SSLSocketFactory.java create mode 100644 java/common/src/org/apache/qpid/url/AMQBindingURL.java create mode 100644 java/common/src/org/apache/qpid/url/BindingURL.java create mode 100644 java/common/src/org/apache/qpid/url/URLHelper.java create mode 100644 java/common/src/org/apache/qpid/url/URLSyntaxException.java create mode 100644 java/common/stylesheets/framing.xsl create mode 100644 java/common/stylesheets/java.xsl create mode 100644 java/common/stylesheets/prepare1.xsl create mode 100644 java/common/stylesheets/prepare2.xsl create mode 100644 java/common/stylesheets/prepare3.xsl create mode 100644 java/common/stylesheets/readme.txt create mode 100644 java/common/stylesheets/registry.xsl create mode 100644 java/common/stylesheets/utils.xsl create mode 100644 java/doc/AMQBlazeDetailedDesign.vsd create mode 100644 java/doc/FramingClassDiagram.vsd create mode 100644 java/management/cli/bin/stac.bat create mode 100755 java/management/cli/bin/stac.sh create mode 100644 java/management/cli/bin/stacDEV.bat create mode 100644 java/management/cli/build-module.xml create mode 100644 java/management/cli/build-old.xml create mode 100644 java/management/cli/lib/jython/jython.jar create mode 100644 java/management/cli/src/org/apache/qpid/stac/Stac.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/StacInterpreter.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/commands/CdCommand.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/commands/InvokeCommand.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/commands/LsCommand.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/jmx/CurrentMBean.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/jmx/MBeanAttributeInfoComparator.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/jmx/MBeanOperationInfoComparator.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/jmx/MBeanServerConnectionContext.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/jmx/MBeanUtils.java create mode 100644 java/management/cli/src/org/apache/qpid/stac/jmx/NotConnectedException.java create mode 100644 java/management/cli/src/python/stac.py create mode 100644 java/management/cli/test/org/apache/qpid/stac/ConnectionTest.java create mode 100644 java/management/core/build-module.xml create mode 100644 java/management/core/build-old.xml create mode 100644 java/management/core/etc/cml-exampleschema.xml create mode 100644 java/management/core/etc/cml.xsd create mode 100644 java/management/core/lib/jakarta-commons/commons-attributes-api.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-attributes-compiler.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-beanutils.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-codec.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-collections.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-dbcp.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-digester.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-discovery.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-fileupload.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-httpclient.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-lang.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-logging.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-pool.jar create mode 100644 java/management/core/lib/jakarta-commons/commons-validator.jar create mode 100644 java/management/core/lib/log4j/log4j-1.2.9.jar create mode 100644 java/management/core/lib/spring/spring-beans.dtd create mode 100644 java/management/core/lib/spring/spring.ftl create mode 100644 java/management/core/lib/spring/spring.tld create mode 100644 java/management/core/lib/spring/spring.vm create mode 100644 java/management/core/lib/xmlbeans/jsr173_api.jar create mode 100644 java/management/core/lib/xmlbeans/resolver.jar create mode 100644 java/management/core/lib/xmlbeans/saxon8.jar create mode 100644 java/management/core/lib/xmlbeans/xbean.jar create mode 100644 java/management/core/lib/xmlbeans/xbean_xpath.jar create mode 100644 java/management/core/lib/xmlbeans/xmlpublic.jar create mode 100644 java/management/core/src/log4j.properties create mode 100644 java/management/core/src/org/apache/qpid/management/ManagementConnection.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/AMQConsole.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/AMQMBeanInfo.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/CMLMBean.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/JmxConstants.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/MBeanInfoRegistry.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/MBeanRegistrar.java create mode 100644 java/management/core/src/org/apache/qpid/management/jmx/UnsupportedCMLTypeException.java create mode 100644 java/management/core/src/org/apache/qpid/management/messaging/CMLMessageFactory.java create mode 100644 java/management/core/src/org/apache/qpid/management/messaging/ManagementDestination.java create mode 100644 java/management/core/test/org/apache/qpid/management/harness/SimpleJMXClient.java create mode 100644 java/management/core/test/org/apache/qpid/management/schema/TestParseSchema.java create mode 100644 java/management/mc4j/qpid/BlazeConnections.xml create mode 100644 java/management/mc4j/qpid/BlazeExchanges.xml create mode 100644 java/management/mc4j/qpid/BlazeQueues.xml create mode 100644 java/management/mc4j/qpid/BlazeSingleQueue.xml create mode 100644 java/management/webapp/META-INF/context.xml create mode 100644 java/management/webapp/WEB-INF/web.xml create mode 100644 java/module.xml create mode 100644 java/tasks/src/org/apache/qpid/tasks/BaseTask.java create mode 100644 java/tasks/src/org/apache/qpid/tasks/Foreach.java create mode 100644 java/tasks/src/org/apache/qpid/tasks/Map.java create mode 100644 java/tasks/src/org/apache/qpid/tasks/Require.java (limited to 'java') diff --git a/java/Developing.txt b/java/Developing.txt new file mode 100644 index 0000000000..710e5ecc31 --- /dev/null +++ b/java/Developing.txt @@ -0,0 +1,75 @@ +Developing +---------- + +In order to build Qpid you need Ant 1.6.5. Use ant -p to list the +available targets. The default ant target, build, creates a working +development-mode distribution in the build directory. To run the +scripts in build/bin set QPID_HOME to the build directory and put +${QPID_HOME}/bin on your PATH. The scripts in that directory include +the standard ones in the distribution and a number of testing scripts. + +Running Tests +------------- + +The simplest test to ensure everything is working is the "service +request reply" test. This involves one client that is known as a +"service provider" and it listens on a well-known queue for +requests. Another client, known as the "service requester" creates a +private (temporary) response queue, creates a message with the private +response queue set as the "reply to" field and then publishes the +message to the well known service queue. The test allows you to time +how long it takes to send messages and receive the response back. It +also allows varying of the message size. + +You must start the service provider first: + +serviceProvidingClient.sh nop host:port + +where host:port is the host and port you are running the broker +on. + +To run the service requester: + +serviceRequestingClient.sh nop host:post + +This requests messages, each of size . After +receiving all the messages the client outputs the rate it achieved. + +A more realistic test is the "headers test", which tests the +performance of routing messages based on message headers to a +configurable number of clients (e.g. 50). A publisher sends 10000 +messages to each client and waits to receive a message from each +client when it has received all the messages. + +You run the listener processes first: + +run_many.sh 10 header "headersListener.sh -host 10.0.0.1 -port 5672" + +In this command, the first argument means start 10 processes, the +second is just a name use in the log files generated and the third +argument is the command to run. In this case it runs another shell +script but it could be anything. + +Then run the publisher process: + +headersPublisher.sh -host 10.0.0.1 -port 5672 10000 10 + +The last two arguments are: the number of messages to send to each +client, and the number of clients. + +Note that before starting the publisher you should wait about 30 +seconds to ensure all the clients are registered with the broker (you +can see this from the broker output). Otherwise the numbers will be +slightly skewed. + +A third useful test, which can easily be ported to other JMS +implementations is the "topic test". It does the same as the headers +test but using a standard topic (e.g. pub sub). + +To run the listeners: + +run_many.sh 10 topic "topicListener.sh -host 10.0.0.1 -port 5672" + +and to run the publisher: + +topicPublisher.sh -host 10.0.0.1 -port 5672 -clients 10 -messages 10000 diff --git a/java/ReadMe.txt b/java/ReadMe.txt new file mode 100644 index 0000000000..a389131844 --- /dev/null +++ b/java/ReadMe.txt @@ -0,0 +1,15 @@ +Running the Broker +------------------ + +To run the broker, set the QPID_HOME environment variable to +distribution directory and add $QPID_HOME/bin to your PATH. Then run +the qpid-server shell script or qpid-server.bat batch file to start +the broker. By default, the broker will use $QPID_HOME/etc to find +the configuration files. You can supply a custom configuration using +the -c argument. + +For example: + +qpid-server -c ~/etc/config.xml + +You can get a list of all command line arguments by using the -h argument. diff --git a/java/broker/README.txt b/java/broker/README.txt new file mode 100644 index 0000000000..d6519ab1d1 --- /dev/null +++ b/java/broker/README.txt @@ -0,0 +1,40 @@ +======================================================================== +This software uses berkeley db java edition, licensed as described +below. + +======================================================================== +Copyright (c) 1990-2005 + Sleepycat Software. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Redistributions in any form must be accompanied by information on + how to obtain complete source code for the DB software and any + accompanying software that uses the DB software. The source code + must either be included in the distribution or be available for no + more than the cost of distribution plus a nominal fee, and must be + freely redistributable under reasonable conditions. For an + executable file, complete source code means the source code for all + modules it contains. It does not include source code for modules or + files that typically accompany the major components of the operating + system on which the executable file runs. + +THIS SOFTWARE IS PROVIDED BY SLEEPYCAT SOFTWARE ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL SLEEPYCAT SOFTWARE +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + \ No newline at end of file diff --git a/java/broker/bin/qpid-server b/java/broker/bin/qpid-server new file mode 100644 index 0000000000..353b2a2077 --- /dev/null +++ b/java/broker/bin/qpid-server @@ -0,0 +1,18 @@ +#!/bin/bash +# +# 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. +# + +. qpid-run org.apache.qpid.server.Main "$@" diff --git a/java/broker/bin/qpid-server.bat b/java/broker/bin/qpid-server.bat new file mode 100644 index 0000000000..1e2ca7c1ac --- /dev/null +++ b/java/broker/bin/qpid-server.bat @@ -0,0 +1,53 @@ +@REM +@REM Copyright (c) 2006 The Apache Software Foundation +@REM +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +@REM + +@echo off +REM Script to run the Qpid Java Broker + +rem Guess QPID_HOME if not defined +set CURRENT_DIR=%cd% +if not "%QPID_HOME%" == "" goto gotHome +set QPID_HOME=%CURRENT_DIR% +echo %QPID_HOME% +if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome +cd .. +set QPID_HOME=%cd% +cd %CURRENT_DIR% +:gotHome +if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome +echo The QPID_HOME environment variable is not defined correctly +echo This environment variable is needed to run this program +goto end +:okHome + +if not "%JAVA_HOME%" == "" goto gotJavaHome +echo The JAVA_HOME environment variable is not defined +echo This environment variable is needed to run this program +goto exit +:gotJavaHome +if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome +goto okJavaHome +:noJavaHome +echo The JAVA_HOME environment variable is not defined correctly +echo This environment variable is needed to run this program. +goto exit +:okJavaHome + +set LAUNCH_JAR=%QPID_HOME%\lib\broker-launch.jar +set MODULE_JARS=%QPID_HOME%\lib\bdbstore-launch.jar +"%JAVA_HOME%"\bin\java -server -Xmx1024m -DQPID_HOME="%QPID_HOME%" -cp "%LAUNCH_JAR%;%MODULE_JARS%" org.apache.qpid.server.Main * + +:end diff --git a/java/broker/bin/run.bat b/java/broker/bin/run.bat new file mode 100755 index 0000000000..fde68364fd --- /dev/null +++ b/java/broker/bin/run.bat @@ -0,0 +1,28 @@ +@REM +@REM Copyright (c) 2006 The Apache Software Foundation +@REM +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +@REM + +@echo off +set CORE_CLASSES=..\classes;..\testclasses + +set COMMONDIR=..\..\common + +set COMMONLIB=%COMMONDIR%\lib\slf4j\slf4j-simple.jar;%COMMONDIR%\lib\logging-log4j\log4j-1.2.9.jar;%COMMONDIR%\lib\mina\mina-core-0.9.2.jar + +set DIST=..\lib\bdb\je-3.0.12.jar;..\dist\amqpd.jar;..\..\client\dist\amqp-common.jar + +set CP=%CORE_CLASSES%;%DIST%;%COMMONLIB% + +"%JAVA_HOME%\bin\java" -Xmx512m -Damqj.logging.level=INFO -cp %CP% %* diff --git a/java/broker/bin/run.sh b/java/broker/bin/run.sh new file mode 100755 index 0000000000..06ad56b61c --- /dev/null +++ b/java/broker/bin/run.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# +# 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. +# + + +COMMONDIR=../../common + +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/slf4j/slf4j-simple.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/logging-log4j/log4j-1.2.13.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/mina/mina-core-0.9.5-SNAPSHOT.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/mina/mina-filter-ssl-0.9.5-SNAPSHOT.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-collections/commons-collections-3.1.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-cli/commons-cli-1.0.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-configuration/commons-configuration-1.2.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-logging/commons-logging-api.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-logging/commons-logging.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/commons-lang/commons-lang-2.1.jar +COMMONLIB=$COMMONLIB:$COMMONDIR/lib/junit/junit-4.0.jar + +DIST=../lib/bdb/je-3.0.12.jar:../dist/amqpd-tests.jar:../dist/amqpd.jar:../../client/dist/amqp-common.jar + +CP=../intellijclasses:$DIST:$COMMONLIB + +if [ "$(uname -a | fgrep Cygwin)" != "" ]; then + CP=$(cygpath --mixed --path $CP) +fi + +"$JAVA_HOME/bin/java" -Xmx512m -Dprepopulate=10 -Damqj.logging.level=INFO -cp $CP $* diff --git a/java/broker/bin/runAll b/java/broker/bin/runAll new file mode 100644 index 0000000000..0d6c6068e6 --- /dev/null +++ b/java/broker/bin/runAll @@ -0,0 +1,18 @@ +#!/bin/sh + +doRun() +{ + class=$1 + shift + echo + echo ================================================================================ + echo Running $class + ./run.sh $class "$@" +} + +# parameters are: clients messages iterations +doRun org.apache.qpid.server.queue.SendPerfTest 10 1000 100 + +doRun org.apache.qpid.server.queue.QueuePerfTest + +doRun org.apache.qpid.server.queue.QueueConcurrentPerfTest diff --git a/java/broker/build-module.xml b/java/broker/build-module.xml new file mode 100644 index 0000000000..4c3aa56396 --- /dev/null +++ b/java/broker/build-module.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/java/broker/build-old.xml b/java/broker/build-old.xml new file mode 100644 index 0000000000..0f910610ea --- /dev/null +++ b/java/broker/build-old.xml @@ -0,0 +1,265 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This target can only run inside the NetBeans IDE. + + + + + + + + + + + + + + + + This target can only run inside the NetBeans IDE. + + + + + diff --git a/java/broker/etc/config.xml b/java/broker/etc/config.xml new file mode 100644 index 0000000000..531054dd86 --- /dev/null +++ b/java/broker/etc/config.xml @@ -0,0 +1,90 @@ + + + + ${QPID_HOME} + ${prefix}/work + ${prefix}/etc + + false + true + nio + 5672 + 8672 + 32768 + 32768 + + + true + + + + false + false + 65535 + + + + + passwordfile + org.apache.qpid.server.security.auth.PasswordFilePrincipalDatabase + + + passwordFile + ${conf}/passwd + + + + + + + + + org.apache.qpid.server.security.auth.CRAMMD5Initialiser + passwordfile + + + + + org.apache.qpid.server.security.auth.amqplain.AmqPlainInitialiser + passwordfile + + --> + + + org.apache.qpid.server.security.auth.plain.PlainInitialiser + passwordfile + + + + + + + 0 + 2.0 + + + true + + + + org.apache.qpid.server.store.berkeleydb.BDBMessageStore + ${work}/bdb + + ${conf}/virtualhosts.xml + diff --git a/java/broker/etc/log4j.xml b/java/broker/etc/log4j.xml new file mode 100644 index 0000000000..a4a154cca7 --- /dev/null +++ b/java/broker/etc/log4j.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/broker/etc/passwd b/java/broker/etc/passwd new file mode 100644 index 0000000000..9e06cab884 --- /dev/null +++ b/java/broker/etc/passwd @@ -0,0 +1 @@ +guest:guest diff --git a/java/broker/etc/qpid-server.conf b/java/broker/etc/qpid-server.conf new file mode 100644 index 0000000000..494dae6534 --- /dev/null +++ b/java/broker/etc/qpid-server.conf @@ -0,0 +1,22 @@ +# +# 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. +# + +QPID_LIBS=$QPID_HOME/lib/broker-launch.jar:$QPID_HOME/lib/bdbstore-launch.jar + +export JAVA=java \ + JAVA_VM=-server \ + JAVA_MEM=-Xmx1024m \ + CLASSPATH=$QPID_LIBS diff --git a/java/broker/etc/virtualhosts.xml b/java/broker/etc/virtualhosts.xml new file mode 100644 index 0000000000..1f4f5e4d6b --- /dev/null +++ b/java/broker/etc/virtualhosts.xml @@ -0,0 +1,25 @@ + + + + + /development + direct://amq.direct//queue + direct://amq.direct//ping + + diff --git a/java/broker/src/log4j.properties b/java/broker/src/log4j.properties new file mode 100644 index 0000000000..3ff6f0b581 --- /dev/null +++ b/java/broker/src/log4j.properties @@ -0,0 +1,6 @@ +log4j.rootCategory=${amqj.logging.level}, console + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Threshold=info +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n diff --git a/java/broker/src/org/apache/qpid/server/AMQChannel.java b/java/broker/src/org/apache/qpid/server/AMQChannel.java new file mode 100644 index 0000000000..8dc4626c46 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/AMQChannel.java @@ -0,0 +1,702 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.exchange.MessageRouter; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.TxnBuffer; +import org.apache.qpid.server.txn.TxnOp; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.management.DefaultManagedObject; + +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; +import javax.management.JMException; +import javax.management.MBeanException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicBoolean; + +public class AMQChannel implements Managable +{ + public static final int DEFAULT_PREFETCH = 5000; + + private static final Logger _log = Logger.getLogger(AMQChannel.class); + + private final int _channelId; + + private final String _channelName; + + private boolean _transactional; + + private long _prefetchCount; + + /** + * The delivery tag is unique per channel. This is pre-incremented before putting into the deliver frame so that + * value of this represents the last tag sent out + */ + private long _deliveryTag; + + /** + * A channel has a default queue (the last declared) that is used when no queue name is + * explictily set + */ + private AMQQueue _defaultQueue; + + /** + * This tag is unique per subscription to a queue. The server returns this in response to a + * basic.consume request. + */ + private int _consumerTag = 0; + + /** + * The current message - which may be partial in the sense that not all frames have been received yet - + * which has been received by this channel. As the frames are received the message gets updated and once all + * frames have been received the message can then be routed. + */ + private AMQMessage _currentMessage; + + /** + * Maps from consumer tag to queue instance. Allows us to unsubscribe from a queue. + */ + private final Map _consumerTag2QueueMap = new TreeMap(); + + private final MessageStore _messageStore; + + private final Object _unacknowledgedMessageMapLock = new Object(); + + private Map _unacknowledgedMessageMap = new LinkedHashMap(DEFAULT_PREFETCH); + + private final AtomicBoolean _suspended = new AtomicBoolean(false); + + private final MessageRouter _exchanges; + + private final TxnBuffer _txnBuffer; + + private final AMQChannelMBean _managedObject; + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + /** + * MBean interface for the implementation AMQChannelMBean + */ + public interface AMQChannelMBeanMBean extends ManagedChannel + { + + } + + /** + * AMQChannelMBean. It implements the management interface exposed for + * monitoring and managing the channel. + */ + public final class AMQChannelMBean extends DefaultManagedObject implements AMQChannelMBeanMBean + { + public AMQChannelMBean() + { + super(ManagedChannel.class, ManagedChannel.TYPE); + } + + public String getObjectInstanceName() + { + return _channelName; + } + + public boolean isTransactional() + { + return _transactional; + } + + public int getUnacknowledgedMessageCount() + { + return _unacknowledgedMessageMap.size(); + } + + public void commitTransactions() throws JMException + { + try + { + if (_transactional) + { + _txnBuffer.commit(); + } + } + catch(AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + public void rollbackTransactions() throws JMException + { + if (_transactional) + { + synchronized (_txnBuffer) + { + try + { + _txnBuffer.rollback(); + } + catch(AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + } + } + + } // End of MBean class + + + public static class UnacknowledgedMessage + { + public final AMQMessage message; + public final String consumerTag; + public AMQQueue queue; + + public UnacknowledgedMessage(AMQQueue queue, AMQMessage message, String consumerTag) + { + this.queue = queue; + this.message = message; + this.consumerTag = consumerTag; + } + + private void discard() throws AMQException + { + if (queue != null) + { + message.dequeue(queue); + } + message.decrementReference(); + } + } + + public AMQChannel(int channelId, MessageStore messageStore, MessageRouter exchanges) + throws AMQException + { + _channelId = channelId; + _channelName = _channelId + "-" + this.hashCode(); + _prefetchCount = DEFAULT_PREFETCH; + _messageStore = messageStore; + _exchanges = exchanges; + _txnBuffer = new TxnBuffer(_messageStore); + + _managedObject = new AMQChannelMBean(); + _managedObject.register(); + } + + public int getChannelId() + { + return _channelId; + } + + public boolean isTransactional() + { + return _transactional; + } + + public void setTransactional(boolean transactional) + { + _transactional = transactional; + } + + public long getPrefetchCount() + { + return _prefetchCount; + } + + public void setPrefetchCount(long prefetchCount) + { + _prefetchCount = prefetchCount; + } + + public void setPublishFrame(BasicPublishBody publishBody, AMQProtocolSession publisher) throws AMQException + { + _currentMessage = new AMQMessage(_messageStore, publishBody); + _currentMessage.setPublisher(publisher); + } + + public void publishContentHeader(ContentHeaderBody contentHeaderBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content header without previously receiving a BasicDeliver frame"); + } + else + { + _currentMessage.setContentHeaderBody(contentHeaderBody); + // check and route if header says body length is zero + if (contentHeaderBody.bodySize == 0) + { + routeCurrentMessage(); + } + } + } + + public void publishContentBody(ContentBody contentBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content body without previously receiving a JmsPublishBody"); + } + if (_currentMessage.getContentHeaderBody() == null) + { + throw new AMQException("Received content body without previously receiving a content header"); + } + + _currentMessage.addContentBodyFrame(contentBody); + if (_currentMessage.isAllContentReceived()) + { + routeCurrentMessage(); + } + } + + protected void routeCurrentMessage() throws AMQException + { + if (_transactional) + { + //don't route this until commit + _txnBuffer.enlist(new Publish(_currentMessage)); + _currentMessage = null; + } + else + { + _exchanges.routeContent(_currentMessage); + _currentMessage.decrementReference(); + _currentMessage = null; + } + } + + public long getNextDeliveryTag() + { + return ++_deliveryTag; + } + + public int getNextConsumerTag() + { + return ++_consumerTag; + } + + /** + * Subscribe to a queue. We register all subscriptions in the channel so that + * if the channel is closed we can clean up all subscriptions, even if the + * client does not explicitly unsubscribe from all queues. + * + * @param tag the tag chosen by the client (if null, server will generate one) + * @param queue the queue to subscribe to + * @param session the protocol session of the subscriber + * @return the consumer tag. This is returned to the subscriber and used in + * subsequent unsubscribe requests + * @throws ConsumerTagNotUniqueException if the tag is not unique + * @throws AMQException if something goes wrong + */ + public String subscribeToQueue(String tag, AMQQueue queue, AMQProtocolSession session, boolean acks) throws AMQException, ConsumerTagNotUniqueException + { + if (tag == null) + { + tag = "sgen_" + getNextConsumerTag(); + } + if (_consumerTag2QueueMap.containsKey(tag)) + { + throw new ConsumerTagNotUniqueException(); + } + + queue.registerProtocolSession(session, _channelId, tag, acks); + _consumerTag2QueueMap.put(tag, queue); + return tag; + } + + + public void unsubscribeConsumer(AMQProtocolSession session, String consumerTag) throws AMQException + { + AMQQueue q = _consumerTag2QueueMap.remove(consumerTag); + if (q != null) + { + q.unregisterProtocolSession(session, _channelId, consumerTag); + } + else + { + throw new AMQException(_log, "Consumer tag " + consumerTag + " not known to channel " + + _channelId); + } + } + + /** + * Called from the protocol session to close this channel and clean up. + * + * @throws AMQException if there is an error during closure + */ + public void close(AMQProtocolSession session) throws AMQException + { + if (_transactional) + { + synchronized (_txnBuffer) + { + _txnBuffer.rollback();//releases messages + } + } + unsubscribeAllConsumers(session); + requeue(); + _managedObject.unregister(); + } + + private void unsubscribeAllConsumers(AMQProtocolSession session) throws AMQException + { + _log.info("Unsubscribing all consumers on channel " + toString()); + for (Map.Entry me : _consumerTag2QueueMap.entrySet()) + { + me.getValue().unregisterProtocolSession(session, _channelId, me.getKey()); + } + _consumerTag2QueueMap.clear(); + } + + /** + * Add a message to the channel-based list of unacknowledged messages + * + * @param message + * @param deliveryTag + * @param queue + */ + public void addUnacknowledgedMessage(AMQMessage message, long deliveryTag, String consumerTag, AMQQueue queue) + { + synchronized (_unacknowledgedMessageMapLock) + { + _unacknowledgedMessageMap.put(deliveryTag, new UnacknowledgedMessage(queue, message, consumerTag)); + checkSuspension(); + } + } + + /** + * Called to attempt re-enqueue all outstanding unacknowledged messages on the channel. + * May result in delivery to this same channel or to other subscribers. + */ + public void requeue() throws AMQException + { + // we must create a new map since all the messages will get a new delivery tag when they are redelivered + Map currentList; + synchronized (_unacknowledgedMessageMapLock) + { + currentList = _unacknowledgedMessageMap; + _unacknowledgedMessageMap = new LinkedHashMap(DEFAULT_PREFETCH); + } + + for (UnacknowledgedMessage unacked : currentList.values()) + { + if (unacked.queue != null) + { + unacked.queue.deliver(unacked.message); + } + } + } + + /** + * Called to resend all outstanding unacknowledged messages to this same channel. + */ + public void resend(AMQProtocolSession session) + { + //messages go to this channel + synchronized (_unacknowledgedMessageMapLock) + { + for (Map.Entry entry : _unacknowledgedMessageMap.entrySet()) + { + long deliveryTag = entry.getKey(); + String consumerTag = entry.getValue().consumerTag; + AMQMessage msg = entry.getValue().message; + + session.writeFrame(msg.getDataBlock(_channelId, consumerTag, deliveryTag)); + } + } + } + + /** + * Callback indicating that a queue has been deleted. We must update the structure of unacknowledged + * messages to remove the queue reference and also decrement any message reference counts, without + * actually removing the item sine we may get an ack for a delivery tag that was generated from the + * deleted queue. + * + * @param queue + */ + public void queueDeleted(AMQQueue queue) + { + synchronized (_unacknowledgedMessageMapLock) + { + for (Map.Entry unacked : _unacknowledgedMessageMap.entrySet()) + { + final UnacknowledgedMessage unackedMsg = unacked.getValue(); + // we can compare the reference safely in this case + if (unackedMsg.queue == queue) + { + unackedMsg.queue = null; + try + { + unackedMsg.message.decrementReference(); + } + catch (AMQException e) + { + _log.error("Error decrementing ref count on message " + unackedMsg.message.getMessageId() + ": " + + e, e); + } + } + } + } + } + + /** + * Acknowledge one or more messages. + * + * @param deliveryTag the last delivery tag + * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only + * acknowledges the single message specified by the delivery tag + * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel + */ + public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException + { + if (_transactional) + { + //don't handle this until commit + _txnBuffer.enlist(new Ack(deliveryTag, multiple)); + } + else + { + handleAcknowledgement(deliveryTag, multiple); + } + } + + private void handleAcknowledgement(long deliveryTag, boolean multiple) throws AMQException + { + if (multiple) + { + LinkedList acked = new LinkedList(); + synchronized (_unacknowledgedMessageMapLock) + { + if (deliveryTag == 0) + { + //Spec 2.1.6.11 ... If the multiple field is 1, and the delivery tag is zero, tells the server to acknowledge all outstanding mesages. + _log.info("Multiple ack on delivery tag 0. ACKing all messages. Current count:" + _unacknowledgedMessageMap.size()); + acked = new LinkedList(_unacknowledgedMessageMap.values()); + _unacknowledgedMessageMap.clear(); + } + else + { + if (!_unacknowledgedMessageMap.containsKey(deliveryTag)) + { + throw new AMQException("Multiple ack on delivery tag " + deliveryTag + " not known for channel"); + } + Iterator> i = _unacknowledgedMessageMap.entrySet().iterator(); + while (i.hasNext()) + { + Map.Entry unacked = i.next(); + i.remove(); + acked.add(unacked.getValue()); + if (unacked.getKey() == deliveryTag) + { + break; + } + } + } + } + if (_log.isDebugEnabled()) + { + _log.debug("Received multiple ack for delivery tag " + deliveryTag + ". Removing " + + acked.size() + " items."); + } + + for (UnacknowledgedMessage msg : acked) + { + msg.discard(); + } + + } + else + { + UnacknowledgedMessage msg; + synchronized (_unacknowledgedMessageMapLock) + { + msg = _unacknowledgedMessageMap.remove(deliveryTag); + } + if (msg == null) + { + throw new AMQException("Single ack on delivery tag " + deliveryTag + " not known for channel:" + _channelId); + } + msg.discard(); + if (_log.isDebugEnabled()) + { + _log.debug("Received non-multiple ack for messaging with delivery tag " + deliveryTag); + } + } + + checkSuspension(); + } + + /** + * Used only for testing purposes. + * + * @return the map of unacknowledged messages + */ + public Map getUnacknowledgedMessageMap() + { + return _unacknowledgedMessageMap; + } + + private void checkSuspension() + { + boolean suspend; + //noinspection SynchronizeOnNonFinalField + synchronized (_unacknowledgedMessageMapLock) + { + suspend = _unacknowledgedMessageMap.size() >= _prefetchCount; + } + setSuspended(suspend); + } + + public void setSuspended(boolean suspended) + { + boolean wasSuspended = _suspended.getAndSet(suspended); + if (wasSuspended != suspended) + { + if (wasSuspended) + { + _log.info("Unsuspending channel " + this); + //may need to deliver queued messages + for (AMQQueue q : _consumerTag2QueueMap.values()) + { + q.deliverAsync(); + } + } + else + { + _log.info("Suspending channel " + this); + } + } + } + + public boolean isSuspended() + { + return _suspended.get(); + } + + public void commit() throws AMQException + { + _txnBuffer.commit(); + } + + public void rollback() throws AMQException + { + //need to protect rollback and close from each other... + synchronized (_txnBuffer) + { + _txnBuffer.rollback(); + } + } + + public String toString() + { + StringBuilder sb = new StringBuilder(30); + sb.append("Channel: id ").append(_channelId).append(", transaction mode: ").append(_transactional); + sb.append(", prefetch count: ").append(_prefetchCount); + return sb.toString(); + } + + public ObjectName getObjectName() + throws MalformedObjectNameException + { + StringBuilder sb = new StringBuilder(30); + sb.append("Channel:id=").append(_channelId); + sb.append(",transaction mode=").append(_transactional); + return new ObjectName(sb.toString()); + } + + public void setDefaultQueue(AMQQueue queue) + { + _defaultQueue = queue; + } + + public AMQQueue getDefaultQueue() + { + return _defaultQueue; + } + + private class Ack implements TxnOp + { + private final long _msgId; + private final boolean _multi; + + Ack(long msgId, boolean multi) + { + _msgId = msgId; + _multi = multi; + } + + public void commit() throws AMQException + { + handleAcknowledgement(_msgId, _multi); + } + + public void rollback() + { + } + } + + //TODO: + //implement a scheme whereby messages can be stored on disk + //until commit, then reloaded... + private class Publish implements TxnOp + { + private final AMQMessage _msg; + + Publish(AMQMessage msg) + { + _msg = msg; + } + + public void commit() throws AMQException + { + _exchanges.routeContent(_msg); + _msg.decrementReference(); + } + + public void rollback() + { + try + { + _msg.decrementReference(); + } + catch (AMQException e) + { + _log.error("Error rolling back a publish request: " + e, e); + } + } + } + +} diff --git a/java/broker/src/org/apache/qpid/server/ConsumerTagNotUniqueException.java b/java/broker/src/org/apache/qpid/server/ConsumerTagNotUniqueException.java new file mode 100644 index 0000000000..6a7e54bc45 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/ConsumerTagNotUniqueException.java @@ -0,0 +1,22 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server; + +public class ConsumerTagNotUniqueException extends Exception +{ +} diff --git a/java/broker/src/org/apache/qpid/server/Main.java b/java/broker/src/org/apache/qpid/server/Main.java new file mode 100644 index 0000000000..dee6f76334 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/Main.java @@ -0,0 +1,612 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server; + +import org.apache.qpid.framing.ProtocolVersionList; +import org.apache.qpid.pool.ReadWriteThreadModel; +import org.apache.qpid.server.protocol.AMQPFastProtocolHandler; +import org.apache.qpid.server.protocol.AMQPProtocolProvider; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.transport.ConnectorConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.management.DefaultManagedObject; +import org.apache.qpid.server.management.ManagedBroker; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.AMQException; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.commons.cli.*; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.BasicConfigurator; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.transport.socket.nio.SocketAcceptorConfig; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; + +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; +import javax.management.JMException; +import javax.management.MBeanException; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.StringTokenizer; +import java.util.Collection; +import java.util.List; + +/** + * Main entry point for AMQPD. + * + */ +public class Main implements ProtocolVersionList +{ + private static final Logger _logger = Logger.getLogger(Main.class); + + private static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; + + private static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml"; + + protected static class InitException extends Exception + { + InitException(String msg) + { + super(msg); + } + } + + protected final Options options = new Options(); + protected CommandLine commandLine; + + protected Main(String[] args) + { + setOptions(options); + if (parseCommandline(args)) + { + execute(); + } + } + + protected boolean parseCommandline(String[] args) + { + try + { + commandLine = new PosixParser().parse(options, args); + return true; + } + catch (ParseException e) + { + System.err.println("Error: " + e.getMessage()); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + return false; + } + } + + protected void setOptions(Options options) + { + Option help = new Option("h", "help", false, "print this message"); + Option version = new Option("v", "version", false, "print the version information and exit"); + Option configFile = OptionBuilder.withArgName("file").hasArg().withDescription("use given configuration file"). + withLongOpt("config").create("c"); + Option port = OptionBuilder.withArgName("port").hasArg().withDescription("listen on the specified port. Overrides any value in the config file"). + withLongOpt("port").create("p"); + Option bind = OptionBuilder.withArgName("bind").hasArg().withDescription("bind to the specified address. Overrides any value in the config file"). + withLongOpt("bind").create("b"); + Option logconfig = OptionBuilder.withArgName("logconfig").hasArg().withDescription("use the specified log4j xml configuration file. By " + + "default looks for a file named " + DEFAULT_LOG_CONFIG_FILENAME + " in the same directory as the configuration file"). + withLongOpt("logconfig").create("l"); + Option logwatchconfig = OptionBuilder.withArgName("logwatch").hasArg().withDescription("monitor the log file configuration file for changes. Units are seconds. " + + "Zero means do not check for changes.").withLongOpt("logwatch").create("w"); + + options.addOption(help); + options.addOption(version); + options.addOption(configFile); + options.addOption(logconfig); + options.addOption(logwatchconfig); + options.addOption(port); + options.addOption(bind); + } + + protected void execute() + { + // note this understands either --help or -h. If an option only has a long name you can use that but if + // an option has a short name and a long name you must use the short name here. + if (commandLine.hasOption("h")) + { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + } + else if (commandLine.hasOption("v")) + { + String ver = "Qpid 0.9.0.0"; + String protocol = "AMQP version(s) [major.minor]: "; + for (int i=0; i 0) + protocol += ", "; + protocol += pv[i][PROTOCOL_MAJOR] + "." + pv[i][PROTOCOL_MINOR]; + } + System.out.println(ver + " (" + protocol + ")"); + } + else + { + try + { + startup(); + } + catch (InitException e) + { + System.out.println(e.getMessage()); + } + catch (ConfigurationException e) + { + System.out.println("Error configuring message broker: " + e); + e.printStackTrace(); + } + catch (Exception e) + { + System.out.println("Error intialising message broker: " + e); + e.printStackTrace(); + } + } + } + + + protected void startup() throws InitException, ConfigurationException, Exception + { + final String QpidHome = System.getProperty("QPID_HOME"); + final File defaultConfigFile = new File(QpidHome, DEFAULT_CONFIG_FILE); + final File configFile = new File(commandLine.getOptionValue("c", defaultConfigFile.getPath())); + if (!configFile.exists()) + { + String error = "File " + configFile + " could not be found. Check the file exists and is readable."; + + if (QpidHome == null) + { + error = error + "\nNote: Qpid_HOME is not set."; + } + + throw new InitException(error); + } + else + { + System.out.println("Using configuration file " + configFile.getAbsolutePath()); + } + + String logConfig = commandLine.getOptionValue("l"); + String logWatchConfig = commandLine.getOptionValue("w", "0"); + if (logConfig != null) + { + File logConfigFile = new File(logConfig); + configureLogging(logConfigFile, logWatchConfig); + } + else + { + File configFileDirectory = configFile.getParentFile(); + File logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME); + configureLogging(logConfigFile, logWatchConfig); + } + + ApplicationRegistry.initialise(new ConfigurationFileApplicationRegistry(configFile)); + + _logger.info("Starting Qpid.AMQP broker"); + + ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance(). + getConfiguredObject(ConnectorConfiguration.class); + + ByteBuffer.setUseDirectBuffers(connectorConfig.enableDirectBuffers); + + // the MINA default is currently to use the pooled allocator although this may change in future + // once more testing of the performance of the simple allocator has been done + if (!connectorConfig.enablePooledAllocator) + { + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + } + + int port = connectorConfig.port; + + String portStr = commandLine.getOptionValue("p"); + if (portStr != null) + { + try + { + port = Integer.parseInt(portStr); + } + catch (NumberFormatException e) + { + throw new InitException("Invalid port: " + portStr); + } + } + + String VIRTUAL_HOSTS = "virtualhosts"; + + Object virtualHosts = ApplicationRegistry.getInstance().getConfiguration().getProperty(VIRTUAL_HOSTS); + + if (virtualHosts != null) + { + if (virtualHosts instanceof Collection) + { + int totalVHosts = ((Collection) virtualHosts).size(); + for (int vhost = 0; vhost < totalVHosts; vhost++) + { + setupVirtualHosts(configFile.getParent() , (String)((List)virtualHosts).get(vhost)); + } + } + else + { + setupVirtualHosts(configFile.getParent() , (String)virtualHosts); + } + } + bind(port, connectorConfig); + + createAndRegisterBrokerMBean(); + } + + protected void setupVirtualHosts(String configFileParent, String configFilePath) throws ConfigurationException, AMQException, URLSyntaxException + { + String configVar = "${conf}"; + + if (configFilePath.startsWith(configVar)) + { + configFilePath = configFileParent + configFilePath.substring(configVar.length()); + } + + if (configFilePath.indexOf(".xml") != -1 ) + { + VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath); + vHostConfig.performBindings(); + } + else + { + // the virtualhosts value is a path. Search it for XML files. + + File virtualHostDir = new File(configFilePath); + + String[] fileNames = virtualHostDir.list(); + + for (int each=0; each < fileNames.length; each++) + { + if (fileNames[each].endsWith(".xml")) + { + VirtualHostConfiguration vHostConfig = new VirtualHostConfiguration(configFilePath+"/"+fileNames[each]); + vHostConfig.performBindings(); + } + } + } + } + + protected void bind(int port, ConnectorConfiguration connectorConfig) + { + String bindAddr = commandLine.getOptionValue("b"); + if (bindAddr == null) + { + bindAddr = connectorConfig.bindAddress; + } + + try + { + //IoAcceptor acceptor = new SocketAcceptor(connectorConfig.processors); + IoAcceptor acceptor = connectorConfig.createAcceptor(); + SocketAcceptorConfig sconfig = (SocketAcceptorConfig) acceptor.getDefaultConfig(); + SocketSessionConfig sc = (SocketSessionConfig) sconfig.getSessionConfig(); + + sc.setReceiveBufferSize(connectorConfig.socketReceiveBufferSize); + sc.setSendBufferSize(connectorConfig.socketWriteBuferSize); + sc.setTcpNoDelay(connectorConfig.tcpNoDelay); + + // if we do not use the executor pool threading model we get the default leader follower + // implementation provided by MINA + if (connectorConfig.enableExecutorPool) + { + sconfig.setThreadModel(new ReadWriteThreadModel()); + } + + if (connectorConfig.enableNonSSL) + { + AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler(); + InetSocketAddress bindAddress; + if (bindAddr.equals("wildcard")) + { + bindAddress = new InetSocketAddress(port); + } + else + { + bindAddress = new InetSocketAddress(InetAddress.getByAddress(parseIP(bindAddr)), port); + } + acceptor.bind(bindAddress, handler, sconfig); + _logger.info("Qpid.AMQP listening on non-SSL address " + bindAddress); + } + + if (connectorConfig.enableSSL) + { + AMQPFastProtocolHandler handler = new AMQPProtocolProvider().getHandler(); + handler.setUseSSL(true); + try + { + acceptor.bind(new InetSocketAddress(connectorConfig.sslPort), + handler, sconfig); + _logger.info("Qpid.AMQP listening on SSL port " + connectorConfig.sslPort); + } + catch (IOException e) + { + _logger.error("Unable to listen on SSL port: " + e, e); + } + } + } + catch (Exception e) + { + _logger.error("Unable to bind service to registry: " + e, e); + } + } + + public static void main(String[] args) + { + + new Main(args); + } + + private byte[] parseIP(String address) throws Exception + { + StringTokenizer tokenizer = new StringTokenizer(address, "."); + byte[] ip = new byte[4]; + int index = 0; + while (tokenizer.hasMoreTokens()) + { + String token = tokenizer.nextToken(); + try + { + ip[index++] = Byte.parseByte(token); + } + catch (NumberFormatException e) + { + throw new Exception("Error parsing IP address: " + address, e); + } + } + if (index != 4) + { + throw new Exception("Invalid IP address: " + address); + } + return ip; + } + + private void configureLogging(File logConfigFile, String logWatchConfig) + { + int logWatchTime = 0; + try + { + logWatchTime = Integer.parseInt(logWatchConfig); + } + catch (NumberFormatException e) + { + System.err.println("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " + + "a non-negative integer. Using default of zero (no watching configured"); + } + if (logConfigFile.exists() && logConfigFile.canRead()) + { + System.out.println("Configuring logger using configuration file " + logConfigFile.getAbsolutePath()); + if (logWatchTime > 0) + { + System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " + + logWatchTime + " seconds"); + // log4j expects the watch interval in milliseconds + DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000); + } + else + { + DOMConfigurator.configure(logConfigFile.getAbsolutePath()); + } + } + else + { + System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); + System.err.println("Using basic log4j configuration"); + BasicConfigurator.configure(); + } + } + + private void createAndRegisterBrokerMBean() + throws AMQException + { + new AMQBrokerManager().register(); + } + + /** + * MBean interface for the implementation AMQBrokerManager. + */ + public interface AMQBrokerManagerMBean extends ManagedBroker + { + + } + /** + * AMQPBrokerMBean implements the broker management interface and exposes the + * Broker level management features like creating and deleting exchanges and queue. + */ + private final class AMQBrokerManager extends DefaultManagedObject + implements AMQBrokerManagerMBean + { + private final QueueRegistry _queueRegistry; + private final ExchangeRegistry _exchangeRegistry; + private final ExchangeFactory _exchangeFactory; + private final MessageStore _messageStore; + + protected AMQBrokerManager() + { + super(ManagedBroker.class, ManagedBroker.TYPE); + + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + _queueRegistry = appRegistry.getQueueRegistry(); + _exchangeRegistry = appRegistry.getExchangeRegistry(); + _exchangeFactory = ApplicationRegistry.getInstance().getExchangeFactory(); + _messageStore = ApplicationRegistry.getInstance().getMessageStore(); + } + + public String getObjectInstanceName() + { + return this.getClass().getName(); + } + + /** + * Creates new exchange and registers it with the registry. + * @param exchangeName + * @param type + * @param durable + * @param autoDelete + * @throws JMException + */ + public void createNewExchange(String exchangeName, + String type, + boolean durable, + boolean autoDelete) + throws JMException + { + try + { + synchronized(_exchangeRegistry) + { + Exchange exchange = _exchangeRegistry.getExchange(exchangeName); + + if (exchange == null) + { + exchange = _exchangeFactory.createExchange(exchangeName, + type, //eg direct + durable, + autoDelete, + 0); //ticket no + _exchangeRegistry.registerExchange(exchange); + } + else + { + throw new JMException("The exchange \"" + exchangeName + "\" already exists."); + } + } + } + catch(AMQException ex) + { + _logger.error("Error in creating exchange " + exchangeName, ex); + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Unregisters the exchange from registry. + * @param exchangeName + * @throws JMException + */ + public void unregisterExchange(String exchangeName) + throws JMException + { + boolean inUse = false; + // TODO + // Check if the exchange is in use. + // Check if there are queue-bindings with the exchnage and unregister + // when there are no bindings. + try + { + _exchangeRegistry.unregisterExchange(exchangeName, false); + } + catch(AMQException ex) + { + _logger.error("Error in unregistering exchange " + exchangeName, ex); + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Creates a new queue and registers it with the registry and puts it + * in persistance storage if durable queue. + * @param queueName + * @param durable + * @param owner + * @param autoDelete + * @throws JMException + */ + public void createQueue(String queueName, + boolean durable, + String owner, + boolean autoDelete) + throws JMException + { + AMQQueue queue = _queueRegistry.getQueue(queueName); + if (queue == null) + { + try + { + queue = new AMQQueue(queueName, durable, owner, autoDelete, _queueRegistry); + if (queue.isDurable() && !queue.isAutoDelete()) + { + _messageStore.createQueue(queue); + } + _queueRegistry.registerQueue(queue); + } + catch (AMQException ex) + { + _logger.error("Error in creating queue " + queueName, ex); + throw new MBeanException(ex, ex.toString()); + } + } + else + { + throw new JMException("The queue \"" + queueName + "\" already exists."); + } + } + + /** + * Deletes the queue from queue registry and persistant storage. + * @param queueName + * @throws JMException + */ + public void deleteQueue(String queueName) throws JMException + { + AMQQueue queue = _queueRegistry.getQueue(queueName); + if (queue == null) + { + throw new JMException("The Queue " + queueName + " is not a registerd queue."); + } + + try + { + queue.delete(); + _messageStore.removeQueue(queueName); + + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + public ObjectName getObjectName() throws MalformedObjectNameException + { + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + objectName.append(":type=").append(getType()); + + return new ObjectName(objectName.toString()); + } + } // End of MBean class +} diff --git a/java/broker/src/org/apache/qpid/server/ManagedChannel.java b/java/broker/src/org/apache/qpid/server/ManagedChannel.java new file mode 100644 index 0000000000..815dfdcfbd --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/ManagedChannel.java @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.qpid.server; + +import javax.management.JMException; +import java.io.IOException; + +/** + * The managed interface exposed to allow management of channels. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedChannel +{ + static final String TYPE = "Channel"; + + /** + * Tells whether the channel is transactional. + * @return true if the channel is transactional. + * @throws IOException + */ + boolean isTransactional() throws IOException; + + /** + * Tells the number of unacknowledged messages in this channel. + * @return number of unacknowledged messages. + * @throws IOException + */ + int getUnacknowledgedMessageCount() throws IOException; + + + //********** Operations *****************// + + /** + * Commits the transactions if the channel is transactional. + * @throws IOException + * @throws JMException + */ + void commitTransactions() throws IOException, JMException; + + /** + * Rollsback the transactions if the channel is transactional. + * @throws IOException + * @throws JMException + */ + void rollbackTransactions() throws IOException, JMException; + +} diff --git a/java/broker/src/org/apache/qpid/server/RequiredDeliveryException.java b/java/broker/src/org/apache/qpid/server/RequiredDeliveryException.java new file mode 100644 index 0000000000..6c4fb6b730 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/RequiredDeliveryException.java @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server; + +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.CompositeAMQDataBlock; +import org.apache.qpid.framing.BasicReturnBody; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; + +import java.util.List; + +/** + * Signals that a required delivery could not be made. This could be bacuse of + * the immediate flag being set and the queue having no consumers, or the mandatory + * flag being set and the exchange having no valid bindings. + */ +public abstract class RequiredDeliveryException extends AMQException +{ + private final String _message; + private final BasicPublishBody _publishBody; + private final ContentHeaderBody _contentHeaderBody; + private final List _contentBodies; + + public RequiredDeliveryException(String message, AMQMessage payload) + { + super(message); + _message = message; + _publishBody = payload.getPublishBody(); + _contentHeaderBody = payload.getContentHeaderBody(); + _contentBodies = payload.getContentBodies(); + } + + public RequiredDeliveryException(String message, + BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody, + List contentBodies) + { + super(message); + _message = message; + _publishBody = publishBody; + _contentHeaderBody = contentHeaderBody; + _contentBodies = contentBodies; + } + + public BasicPublishBody getPublishBody() + { + return _publishBody; + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + public List getContentBodies() + { + return _contentBodies; + } + + public CompositeAMQDataBlock getReturnMessage(int channel) + { + BasicReturnBody returnBody = new BasicReturnBody(); + returnBody.exchange = _publishBody.exchange; + returnBody.replyCode = getReplyCode(); + returnBody.replyText = _message; + returnBody.routingKey = _publishBody.routingKey; + + AMQFrame[] allFrames = new AMQFrame[2 + _contentBodies.size()]; + + AMQFrame returnFrame = new AMQFrame(); + returnFrame.bodyFrame = returnBody; + returnFrame.channel = channel; + + allFrames[0] = returnFrame; + allFrames[1] = ContentHeaderBody.createAMQFrame(channel, _contentHeaderBody); + for (int i = 2; i < allFrames.length; i++) + { + allFrames[i] = ContentBody.createAMQFrame(channel, _contentBodies.get(i - 2)); + } + + return new CompositeAMQDataBlock(allFrames); + } + + public int getErrorCode() + { + return getReplyCode(); + } + + public abstract int getReplyCode(); +} diff --git a/java/broker/src/org/apache/qpid/server/configuration/Configurator.java b/java/broker/src/org/apache/qpid/server/configuration/Configurator.java new file mode 100644 index 0000000000..e02b958941 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/configuration/Configurator.java @@ -0,0 +1,102 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.configuration; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.configuration.PropertyUtils; +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.lang.reflect.Field; + +/** + * This class contains utilities for populating classes automatically from values pulled from configuration + * files. + */ +public class Configurator +{ + private static final Logger _logger = Logger.getLogger(Configurator.class); + + /** + * Configure a given instance using the application configuration. Note that superclasses are not + * currently configured but this could easily be added if required. + * @param instance the instance to configure + */ + public static void configure(Object instance) + { + final Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + + for (Field f : instance.getClass().getDeclaredFields()) + { + Configured annotation = f.getAnnotation(Configured.class); + if (annotation != null) + { + setValueInField(f, instance, config, annotation); + } + } + } + + private static void setValueInField(Field f, Object instance, Configuration config, Configured annotation) + { + Class fieldClass = f.getType(); + String configPath = annotation.path(); + try + { + if (fieldClass == String.class) + { + String val = config.getString(configPath, annotation.defaultValue()); + val = PropertyUtils.replaceProperties(val); + f.set(instance, val); + } + else if (fieldClass == int.class) + { + int val = config.getInt(configPath, Integer.parseInt(annotation.defaultValue())); + f.setInt(instance, val); + } + else if (fieldClass == long.class) + { + long val = config.getLong(configPath, Long.parseLong(annotation.defaultValue())); + f.setLong(instance, val); + } + else if (fieldClass == double.class) + { + double val = config.getDouble(configPath, Double.parseDouble(annotation.defaultValue())); + f.setDouble(instance, val); + } + else if (fieldClass == boolean.class) + { + boolean val = config.getBoolean(configPath, Boolean.parseBoolean(annotation.defaultValue())); + f.setBoolean(instance, val); + } + else + { + _logger.error("Unsupported field type " + fieldClass + " for " + f + " IGNORING configured value"); + } + } + catch (PropertyException e) + { + _logger.error("Unable to expand property: " + e + " INGORING field " + f, e); + } + catch (IllegalAccessException e) + { + _logger.error("Unable to access field " + f + " IGNORING configured value"); + } + } +} \ No newline at end of file diff --git a/java/broker/src/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/java/broker/src/org/apache/qpid/server/configuration/VirtualHostConfiguration.java new file mode 100644 index 0000000000..a7ad50917c --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/configuration/VirtualHostConfiguration.java @@ -0,0 +1,217 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.configuration; + +import org.apache.qpid.url.AMQBindingURL; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; + + +import java.util.Collection; + +public class VirtualHostConfiguration +{ + private static final Logger _logger = Logger.getLogger(VirtualHostConfiguration.class); + + XMLConfiguration _config; + + private static final String XML_VIRTUALHOST = "virtualhost"; + private static final String XML_PATH = "path"; + private static final String XML_BIND = "bind"; + private static final String XML_VIRTUALHOST_PATH = "virtualhost.path"; + private static final String XML_VIRTUALHOST_BIND = "virtualhost.bind"; + + + public VirtualHostConfiguration(String configFile) throws ConfigurationException + { + _logger.info("Loading Config file:" + configFile); + + _config = new XMLConfiguration(configFile); + + if (_config.getProperty(XML_VIRTUALHOST_PATH) == null) + { + throw new ConfigurationException( + "Virtualhost Configuration document does not contain a valid virtualhost."); + } + } + + public void performBindings() throws AMQException, ConfigurationException, URLSyntaxException + { + Object prop = _config.getProperty(XML_VIRTUALHOST_PATH); + + if (prop instanceof Collection) + { + _logger.debug("Number of VirtualHosts: " + ((Collection) prop).size()); + + int virtualhosts = ((Collection) prop).size(); + for (int vhost = 0; vhost < virtualhosts; vhost++) + { + loadVirtualHost(vhost); + } + } + else + { + loadVirtualHost(-1); + } + } + + private void loadVirtualHost(int index) throws AMQException, ConfigurationException, URLSyntaxException + { + String path = XML_VIRTUALHOST; + + if (index != -1) + { + path = path + "(" + index + ")"; + } + + Object prop = _config.getProperty(path + "." + XML_PATH); + + if (prop == null) + { + prop = _config.getProperty(path + "." + XML_BIND); + String error = "Virtual Host not defined for binding"; + + if (prop != null) + { + if (prop instanceof Collection) + { + error += "s"; + } + + error += ": " + prop; + } + + throw new ConfigurationException(error); + } + + _logger.info("VirtualHost:'" + prop + "'"); + + prop = _config.getProperty(path + "." + XML_BIND); + if (prop instanceof Collection) + { + int bindings = ((Collection) prop).size(); + _logger.debug("Number of Bindings: " + bindings); + for (int dest = 0; dest < bindings; dest++) + { + loadBinding(path, dest); + } + } + else + { + loadBinding(path, -1); + } + } + + private void loadBinding(String rootpath, int index) throws AMQException, ConfigurationException, URLSyntaxException + { + String path = rootpath + "." + XML_BIND; + if (index != -1) + { + path = path + "(" + index + ")"; + } + + String bindingString = _config.getString(path); + + AMQBindingURL binding = new AMQBindingURL(bindingString); + + _logger.debug("Loaded Binding:" + binding); + + try + { + bind(binding); + } + catch (AMQException amqe) + { + _logger.info("Unable to bind url: " + binding); + throw amqe; + } + } + + private void bind(AMQBindingURL binding) throws AMQException, ConfigurationException + { + + String queueName = binding.getQueueName(); + + // This will occur if the URL is a Topic + if (queueName == null) + { + //todo register valid topic + ///queueName = binding.getDestinationName(); + throw new AMQException("Topics cannot be bound. TODO Register valid topic"); + } + + //Get references to Broker Registries + QueueRegistry queueRegistry = ApplicationRegistry.getInstance().getQueueRegistry(); + MessageStore messageStore = ApplicationRegistry.getInstance().getMessageStore(); + ExchangeRegistry exchangeRegistry = ApplicationRegistry.getInstance().getExchangeRegistry(); + + synchronized (queueRegistry) + { + AMQQueue queue = queueRegistry.getQueue(queueName); + + if (queue == null) + { + _logger.info("Queue '" + binding.getQueueName() + "' does not exists. Creating."); + + queue = new AMQQueue(queueName, + Boolean.parseBoolean(binding.getOption(AMQBindingURL.OPTION_DURABLE)), + null /* These queues will have no owner */, + false /* Therefore autodelete makes no sence */, queueRegistry); + + if (queue.isDurable()) + { + messageStore.createQueue(queue); + } + + queueRegistry.registerQueue(queue); + } + else + { + _logger.info("Queue '" + binding.getQueueName() + "' already exists not creating."); + } + + Exchange defaultExchange = exchangeRegistry.getExchange(binding.getExchangeName()); + synchronized (defaultExchange) + { + if (defaultExchange == null) + { + throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + binding); + } + + defaultExchange.registerQueue(queue.getName(), queue, null); + + if (binding.getRoutingKey() == null || binding.getRoutingKey().equals("")) + { + throw new ConfigurationException("Unknown binding not specified on url:" + binding); + } + + queue.bind(binding.getRoutingKey(), defaultExchange); + } + _logger.info("Queue '" + queue.getName() + "' bound to exchange:" + binding.getExchangeName() + " RK:'" + binding.getRoutingKey() + "'"); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/AbstractExchange.java b/java/broker/src/org/apache/qpid/server/exchange/AbstractExchange.java new file mode 100644 index 0000000000..e11de152d0 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/AbstractExchange.java @@ -0,0 +1,134 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.management.DefaultManagedObject; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; + +public abstract class AbstractExchange implements Exchange, Managable +{ + private String _name; + + protected boolean _durable; + + protected int _ticket; + + protected ExchangeMBean _exchangeMbean; + + /** + * Whether the exchange is automatically deleted once all queues have detached from it + */ + protected boolean _autoDelete; + + /** + * Abstract MBean class. This has some of the methods implemented from + * management intrerface for exchanges. Any implementaion of an + * Exchange MBean should extend this class. + */ + protected abstract class ExchangeMBean extends DefaultManagedObject implements ManagedExchange + { + public ExchangeMBean() + { + super(ManagedExchange.class, ManagedExchange.TYPE); + } + + public String getObjectInstanceName() + { + return _name; + } + + public String getName() + { + return _name; + } + + public int getTicketNo() + { + return _ticket; + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + } // End of MBean class + + public String getName() + { + return _name; + } + + /** + * Concrete exchanges must implement this method in order to create the managed representation. This is + * called during initialisation (template method pattern). + * @return the MBean + */ + protected abstract ExchangeMBean createMBean(); + + public void initialise(String name, boolean durable, int ticket, boolean autoDelete) throws AMQException + { + _name = name; + _durable = durable; + _autoDelete = autoDelete; + _ticket = ticket; + _exchangeMbean = createMBean(); + _exchangeMbean.register(); + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getTicket() + { + return _ticket; + } + + public void close() throws AMQException + { + if (_exchangeMbean != null) + { + _exchangeMbean.unregister(); + } + } + + public String toString() + { + return getClass().getName() + "[" + getName() +"]"; + } + + public ManagedObject getManagedObject() + { + return _exchangeMbean; + } + +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeFactory.java new file mode 100644 index 0000000000..990a868e72 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; + +import java.util.HashMap; +import java.util.Map; + +public class DefaultExchangeFactory implements ExchangeFactory +{ + private static final Logger _logger = Logger.getLogger(DefaultExchangeFactory.class); + + private Map> _exchangeClassMap = new HashMap>(); + + public DefaultExchangeFactory() + { + _exchangeClassMap.put("direct", org.apache.qpid.server.exchange.DestNameExchange.class); + _exchangeClassMap.put("topic", org.apache.qpid.server.exchange.DestWildExchange.class); + _exchangeClassMap.put("headers", org.apache.qpid.server.exchange.HeadersExchange.class); + } + + public Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException + { + Class exchClass = _exchangeClassMap.get(type); + if (exchClass == null) + { + throw new AMQException(_logger, "Unknown exchange type: " + type); + } + try + { + Exchange e = exchClass.newInstance(); + e.initialise(exchange, durable, ticket, autoDelete); + return e; + } + catch (InstantiationException e) + { + throw new AMQException(_logger, "Unable to create exchange: " + e, e); + } + catch (IllegalAccessException e) + { + throw new AMQException(_logger, "Unable to create exchange: " + e, e); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java new file mode 100644 index 0000000000..06a3944367 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java @@ -0,0 +1,92 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.protocol.ExchangeInitialiser; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.log4j.Logger; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultExchangeRegistry implements ExchangeRegistry +{ + private static final Logger _log = Logger.getLogger(DefaultExchangeRegistry.class); + + /** + * Maps from exchange name to exchange instance + */ + private ConcurrentMap _exchangeMap = new ConcurrentHashMap(); + + public DefaultExchangeRegistry(ExchangeFactory exchangeFactory) + { + //create 'standard' exchanges: + try + { + new ExchangeInitialiser().initialise(exchangeFactory, this); + } + catch(AMQException e) + { + _log.error("Failed to initialise exchanges: ", e); + } + } + + public void registerExchange(Exchange exchange) + { + _exchangeMap.put(exchange.getName(), exchange); + } + + public void unregisterExchange(String name, boolean inUse) throws AMQException + { + // TODO: check inUse argument + Exchange e = _exchangeMap.remove(name); + if (e != null) + { + e.close(); + } + else + { + throw new AMQException("Unknown exchange " + name); + } + } + + public Exchange getExchange(String name) + { + return _exchangeMap.get(name); + } + + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param payload + * @throws AMQException if something goes wrong delivering data + */ + public void routeContent(AMQMessage payload) throws AMQException + { + final String exchange = payload.getPublishBody().exchange; + final Exchange exch = _exchangeMap.get(exchange); + // there is a small window of opportunity for the exchange to be deleted in between + // the JmsPublish being received (where the exchange is validated) and the final + // content body being received (which triggers this method) + if (exch == null) + { + throw new AMQException("Exchange '" + exchange + "' does not exist"); + } + exch.route(payload); + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/DestNameExchange.java b/java/broker/src/org/apache/qpid/server/exchange/DestNameExchange.java new file mode 100644 index 0000000000..7f1c7df224 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/DestNameExchange.java @@ -0,0 +1,204 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.log4j.Logger; + +import javax.management.openmbean.*; +import javax.management.MBeanException; +import javax.management.JMException; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; + +public class DestNameExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DestNameExchange.class); + + /** + * Maps from queue name to queue instances + */ + private final Index _index = new Index(); + + /** + * MBean class implementing the management interfaces. + */ + private final class DestNameExchangeMBean extends ExchangeMBean + { + private String[] _bindingItemNames = {"BindingKey", "QueueNames"}; + private String[] _bindingItemDescriptions = {"Binding key", "Queue Names"}; + private String[] _bindingItemIndexNames = {"BindingKey"}; + private OpenType[] _bindingItemTypes = new OpenType[2]; + + private CompositeType _bindingDataType = null; + private TabularType _bindinglistDataType = null; + private TabularDataSupport _bindingList = null; + + public DestNameExchangeMBean() + { + super(); + init(); + } + + /** + * initialises the OpenType objects. + */ + private void init() + { + try + { + _bindingItemTypes[0] = SimpleType.STRING; + //_bindingItemTypes[1] = ArrayType.getArrayType(SimpleType.STRING); + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING); + + _bindingDataType = new CompositeType("QueueBinding", + "Queue and binding keye", + _bindingItemNames, + _bindingItemDescriptions, + _bindingItemTypes); + _bindinglistDataType = new TabularType("Bindings", + "List of queues and binding keys", + _bindingDataType, + _bindingItemIndexNames); + } + catch(OpenDataException ex) + { + //It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public TabularData viewBindings() + throws OpenDataException + { + Map> bindings = _index.getBindingsMap(); + _bindingList = new TabularDataSupport(_bindinglistDataType); + + for (Map.Entry> entry : bindings.entrySet()) + { + String key = entry.getKey(); + List queueList = new ArrayList(); + + List queues = entry.getValue(); + for (AMQQueue q : queues) + { + queueList.add(q.getName()); + } + + Object[] bindingItemValues = {key, queueList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + _bindingItemNames, + bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createBinding(String queueName, String binding) + throws JMException + { + AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName); + + if (queue == null) + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + + try + { + registerQueue(binding, queue, null); + queue.bind(binding, DestNameExchange.this); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + }// End of MBean class + + + protected ExchangeMBean createMBean() + { + return new DestNameExchangeMBean(); + } + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert routingKey != null; + if(!_index.add(routingKey, queue)) + { + _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey); + } + else + { + _logger.debug("Binding queue " + queue + " with routing key " + routingKey + + " to exchange " + this); + } + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + assert queue != null; + assert routingKey != null; + + if (!_index.remove(routingKey, queue)) + { + throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey + ". No queue was registered with that routing key"); + } + } + + public void route(AMQMessage payload) throws AMQException + { + BasicPublishBody publishBody = payload.getPublishBody(); + + final String routingKey = publishBody.routingKey; + final List queues = _index.get(routingKey); + if (queues == null || queues.isEmpty()) + { + String msg = "Routing key " + routingKey + " is not known to " + this; + if (publishBody.mandatory) + { + throw new NoRouteException(msg, payload); + } + else + { + _logger.warn(msg); + } + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + queues); + } + + for(AMQQueue q :queues) + { + q.deliver(payload); + } + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/DestWildExchange.java b/java/broker/src/org/apache/qpid/server/exchange/DestWildExchange.java new file mode 100644 index 0000000000..16b35cf6fa --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/DestWildExchange.java @@ -0,0 +1,210 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.management.openmbean.*; +import javax.management.JMException; +import javax.management.MBeanException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class DestWildExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DestWildExchange.class); + + private ConcurrentHashMap> _routingKey2queues = new ConcurrentHashMap>(); + + /** + * DestWildExchangeMBean class implements the management interface for the + * Topic exchanges. + */ + private final class DestWildExchangeMBean extends ExchangeMBean + { + private String[] _bindingItemNames = {"BindingKey", "QueueNames"}; + private String[] _bindingItemDescriptions = {"Binding key", "Queue Names"}; + private String[] _bindingItemIndexNames = {"BindingKey"}; + private OpenType[] _bindingItemTypes = new OpenType[2]; + + private CompositeType _bindingDataType = null; + private TabularType _bindinglistDataType = null; + private TabularDataSupport _bindingList = null; + + public DestWildExchangeMBean() + { + super(); + init(); + } + + /** + * initialises the OpenType objects. + */ + private void init() + { + try + { + _bindingItemTypes[0] = SimpleType.STRING; + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING); + + _bindingDataType = new CompositeType("QueueBinding", + "Queue and binding keye", + _bindingItemNames, + _bindingItemDescriptions, + _bindingItemTypes); + _bindinglistDataType = new TabularType("Bindings", + "List of queues and binding keys", + _bindingDataType, + _bindingItemIndexNames); + } + catch(OpenDataException ex) + { + //It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public TabularData viewBindings() + throws OpenDataException + { + _bindingList = new TabularDataSupport(_bindinglistDataType); + + for (Map.Entry> entry : _routingKey2queues.entrySet()) + { + String key = entry.getKey(); + List queueList = new ArrayList(); + + List queues = entry.getValue(); + for (AMQQueue q : queues) + { + queueList.add(q.getName()); + } + + Object[] bindingItemValues = {key, queueList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + _bindingItemNames, + bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createBinding(String queueName, String binding) + throws JMException + { + AMQQueue queue = ApplicationRegistry.getInstance().getQueueRegistry().getQueue(queueName); + + if (queue == null) + throw new JMException("Queue \"" + queueName + "\" is not registered with the exchange."); + + try + { + registerQueue(binding, queue, null); + queue.bind(binding, DestWildExchange.this); + } + catch (AMQException ex) + { + throw new MBeanException(ex); + } + } + + } // End of MBean class + + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + assert queue != null; + assert routingKey != null; + // we need to use putIfAbsent, which is an atomic operation, to avoid a race condition + List queueList = _routingKey2queues.putIfAbsent(routingKey, new CopyOnWriteArrayList()); + // if we got null back, no previous value was associated with the specified routing key hence + // we need to read back the new value just put into the map + if (queueList == null) + { + queueList = _routingKey2queues.get(routingKey); + } + if (!queueList.contains(queue)) + { + queueList.add(queue); + } + else if(_logger.isDebugEnabled()) + { + _logger.debug("Queue " + queue + " is already registered with routing key " + routingKey); + } + + } + + public void route(AMQMessage payload) throws AMQException + { + BasicPublishBody publishBody = payload.getPublishBody(); + + final String routingKey = publishBody.routingKey; + List queues = _routingKey2queues.get(routingKey); + // if we have no registered queues we have nothing to do + // TODO: add support for the immediate flag + if (queues == null) + { + //todo Check for valid topic - mritchie + return; + } + + for (AMQQueue q : queues) + { + // TODO: modify code generator to add clone() method then clone the deliver body + // without this addition we have a race condition - we will be modifying the body + // before the encoder has encoded the body for delivery + q.deliver(payload); + } + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + assert queue != null; + assert routingKey != null; + + List queues = _routingKey2queues.get(routingKey); + if (queues == null) + { + throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey + ". No queue was registered with that routing key"); + + } + boolean removedQ = queues.remove(queue); + if (!removedQ) + { + throw new AMQException("Queue " + queue + " was not registered with exchange " + this.getName() + + " with routing key " + routingKey); + } + } + + protected ExchangeMBean createMBean() + { + return new DestWildExchangeMBean(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/Exchange.java b/java/broker/src/org/apache/qpid/server/exchange/Exchange.java new file mode 100644 index 0000000000..cd75825601 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/Exchange.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQMessage; + +public interface Exchange +{ + String getName(); + + void initialise(String name, boolean durable, int ticket, boolean autoDelete) throws AMQException; + + boolean isDurable(); + + /** + * @return true if the exchange will be deleted after all queues have been detached + */ + boolean isAutoDelete(); + + int getTicket(); + + void close() throws AMQException; + + void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException; + + void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException; + + void route(AMQMessage message) throws AMQException; +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/ExchangeFactory.java b/java/broker/src/org/apache/qpid/server/exchange/ExchangeFactory.java new file mode 100644 index 0000000000..36fd159f31 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/ExchangeFactory.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; + + +public interface ExchangeFactory +{ + Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException; +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/ExchangeInUseException.java b/java/broker/src/org/apache/qpid/server/exchange/ExchangeInUseException.java new file mode 100644 index 0000000000..4a6c735bee --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/ExchangeInUseException.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; + +public class ExchangeInUseException extends AMQException +{ + public ExchangeInUseException(String exchangeName) + { + super("Exchange " + exchangeName + " is currently in use"); + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/ExchangeRegistry.java b/java/broker/src/org/apache/qpid/server/exchange/ExchangeRegistry.java new file mode 100644 index 0000000000..5b71cd7b0c --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/ExchangeRegistry.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; + + +public interface ExchangeRegistry extends MessageRouter +{ + void registerExchange(Exchange exchange); + + /** + * Unregister an exchange + * @param name name of the exchange to delete + * @param inUse if true, do NOT delete the exchange if it is in use (has queues bound to it) + * @throws ExchangeInUseException when the exchange cannot be deleted because it is in use + * @throws AMQException + */ + void unregisterExchange(String name, boolean inUse) throws ExchangeInUseException, AMQException; + + Exchange getExchange(String name); +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/HeadersBinding.java b/java/broker/src/org/apache/qpid/server/exchange/HeadersBinding.java new file mode 100644 index 0000000000..d69e956b5f --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/HeadersBinding.java @@ -0,0 +1,142 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Defines binding and matching based on a set of headers. + */ +class HeadersBinding +{ + private static final Logger _logger = Logger.getLogger(HeadersBinding.class); + + private final Map _mappings = new HashMap(); + private final Set required = new HashSet(); + private final Set matches = new HashSet(); + private boolean matchAny; + + /** + * Creates a binding for a set of mappings. Those mappings whose value is + * null or the empty string are assumed only to be required headers, with + * no constraint on the value. Those with a non-null value are assumed to + * define a required match of value. + * @param mappings the defined mappings this binding should use + */ + HeadersBinding(Map mappings) + { + //noinspection unchecked + this(mappings == null ? new HashSet() : mappings.entrySet()); + _mappings.putAll(mappings); + } + + private HeadersBinding(Set entries) + { + for (Map.Entry e : entries) + { + if (isSpecial(e.getKey())) + { + processSpecial((String) e.getKey(), e.getValue()); + } + else if (e.getValue() == null || e.getValue().equals("")) + { + required.add(e.getKey()); + } + else + { + matches.add(e); + } + } + } + + protected Map getMappings() + { + return _mappings; + } + + /** + * Checks whether the supplied headers match the requirements of this binding + * @param headers the headers to check + * @return true if the headers define any required keys and match any required + * values + */ + public boolean matches(Map headers) + { + if(headers == null) + { + return required.isEmpty() && matches.isEmpty(); + } + else + { + return matchAny ? or(headers) : and(headers); + } + } + + private boolean and(Map headers) + { + //need to match all the defined mapping rules: + return headers.keySet().containsAll(required) + && headers.entrySet().containsAll(matches); + } + + private boolean or(Map headers) + { + //only need to match one mapping rule: + return !Collections.disjoint(headers.keySet(), required) + || !Collections.disjoint(headers.entrySet(), matches); + } + + private void processSpecial(String key, Object value) + { + if("X-match".equalsIgnoreCase(key)) + { + matchAny = isAny(value); + } + else + { + _logger.warn("Ignoring special header: " + key); + } + } + + private boolean isAny(Object value) + { + if(value instanceof String) + { + if("any".equalsIgnoreCase((String) value)) return true; + if("all".equalsIgnoreCase((String) value)) return false; + } + _logger.warn("Ignoring unrecognised match type: " + value); + return false;//default to all + } + + static boolean isSpecial(Object key) + { + return key instanceof String && isSpecial((String) key); + } + + static boolean isSpecial(String key) + { + return key.startsWith("X-") || key.startsWith("x-"); + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/HeadersExchange.java b/java/broker/src/org/apache/qpid/server/exchange/HeadersExchange.java new file mode 100644 index 0000000000..83475f87f2 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/HeadersExchange.java @@ -0,0 +1,226 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQMessage; + +import javax.management.openmbean.*; +import javax.management.ServiceNotFoundException; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * An exchange that binds queues based on a set of required headers and header values + * and routes messages to these queues by matching the headers of the message against + * those with which the queues were bound. + *

+ *

+ * The Headers Exchange
+ *
+ *  Routes messages according to the value/presence of fields in the message header table.
+ *  (Basic and JMS content has a content header field called "headers" that is a table of
+ *   message header fields).
+ *
+ *  class = "headers"
+ *  routing key is not used
+ *
+ *  Has the following binding arguments:
+ *
+ *  the X-match field - if "all", does an AND match (used for GRM), if "any", does an OR match.
+ *  other fields prefixed with "X-" are ignored (and generate a console warning message).
+ *  a field with no value or empty value indicates a match on presence only.
+ *  a field with a value indicates match on field presence and specific value.
+ *
+ *  Standard instances:
+ *
+ *  amq.match - pub/sub on field content/value
+ *  
+ */ +public class HeadersExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(HeadersExchange.class); + + private final List _bindings = new CopyOnWriteArrayList(); + + /** + * HeadersExchangeMBean class implements the management interface for the + * Header Exchanges. + */ + private final class HeadersExchangeMBean extends ExchangeMBean + { + private String[] _bindingItemNames = {"Queue", "HeaderBinding"}; + private String[] _bindingItemDescriptions = {"Queue Name", "Header attribute bindings"}; + private String[] _bindingItemIndexNames = {"Queue"}; + private OpenType[] _bindingItemTypes = new OpenType[2]; + + private CompositeType _bindingDataType = null; + private TabularType _bindinglistDataType = null; + private TabularDataSupport _bindingList = null; + + public HeadersExchangeMBean() + { + super(); + init(); + } + /** + * initialises the OpenType objects. + */ + private void init() + { + try + { + _bindingItemTypes[0] = SimpleType.STRING; + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING);; + + _bindingDataType = new CompositeType("QueueAndHeaderAttributesBinding", + "Queue and header attributes binding", + _bindingItemNames, + _bindingItemDescriptions, + _bindingItemTypes); + _bindinglistDataType = new TabularType("HeaderBindings", + "List of queues and related header attribute bindings", + _bindingDataType, + _bindingItemIndexNames); + } + catch(OpenDataException ex) + { + //It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public TabularData viewBindings() + throws OpenDataException + { + _bindingList = new TabularDataSupport(_bindinglistDataType); + for (Iterator itr = _bindings.iterator(); itr.hasNext();) + { + Registration registration = itr.next(); + String queueName = registration.queue.getName(); + + HeadersBinding headers = registration.binding; + Map headerMappings = headers.getMappings(); + List mappingList = new ArrayList(); + + for (Map.Entry en : headerMappings.entrySet()) + { + String key = en.getKey().toString(); + String value = en.getValue().toString(); + + mappingList.add(key + "=" + value); + } + + Object[] bindingItemValues = {queueName, mappingList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + _bindingItemNames, + bindingItemValues); + _bindingList.put(bindingData); + } + + return _bindingList; + } + + public void createBinding(String QueueName, String binding) + throws ServiceNotFoundException + { + throw new ServiceNotFoundException("This service is not supported by \"" + this.getName() + "\""); + } + + } // End of MBean class + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + _logger.debug("Exchange " + getName() + ": Binding " + queue.getName() + " with " + args); + _bindings.add(new Registration(new HeadersBinding(args), queue)); + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + _logger.debug("Exchange " + getName() + ": Unbinding " + queue.getName()); + _bindings.remove(new Registration(null, queue)); + } + + public void route(AMQMessage payload) throws AMQException + { + Map headers = getHeaders(payload.getContentHeaderBody()); + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": routing message with headers " + headers); + } + boolean delivered = false; + for (Registration e : _bindings) + { + if (e.binding.matches(headers)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": delivering message with headers " + + headers + " to " + e.queue.getName()); + } + e.queue.deliver(payload); + delivered = true; + } + } + if (!delivered) + { + _logger.warn("Exchange " + getName() + ": message not routable."); + } + } + + protected Map getHeaders(ContentHeaderBody contentHeaderFrame) + { + //what if the content type is not 'basic'? 'file' and 'stream' content classes also define headers, + //but these are not yet implemented. + return ((BasicContentHeaderProperties) contentHeaderFrame.properties).getHeaders(); + } + + protected ExchangeMBean createMBean() + { + return new HeadersExchangeMBean(); + } + + private static class Registration + { + private final HeadersBinding binding; + private final AMQQueue queue; + + Registration(HeadersBinding binding, AMQQueue queue) + { + this.binding = binding; + this.queue = queue; + } + + public int hashCode() + { + return queue.hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof Registration && ((Registration) o).queue.equals(queue); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/Index.java b/java/broker/src/org/apache/qpid/server/exchange/Index.java new file mode 100644 index 0000000000..9e88b6a68c --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/Index.java @@ -0,0 +1,85 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.server.queue.AMQQueue; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * An index of queues against routing key. Allows multiple queues to be stored + * against the same key. Used in the DestNameExchange. + */ +class Index +{ + private ConcurrentMap> _index + = new ConcurrentHashMap>(); + + boolean add(String key, AMQQueue queue) + { + List queues = _index.get(key); + if(queues == null) + { + queues = new CopyOnWriteArrayList(); + //next call is atomic, so there is no race to create the list + List active = _index.putIfAbsent(key, queues); + if(active != null) + { + //someone added the new one in faster than we did, so use theirs + queues = active; + } + } + if(queues.contains(queue)) + { + return false; + } + else + { + return queues.add(queue); + } + } + + boolean remove(String key, AMQQueue queue) + { + List queues = _index.get(key); + if (queues != null) + { + boolean removed = queues.remove(queue); + if (queues.size() == 0) + { + _index.remove(key); + } + return removed; + } + return false; + } + + List get(String key) + { + return _index.get(key); + } + + Map> getBindingsMap() + { + return _index; + } +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/ManagedExchange.java b/java/broker/src/org/apache/qpid/server/exchange/ManagedExchange.java new file mode 100644 index 0000000000..a434ecb443 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/ManagedExchange.java @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import javax.management.openmbean.TabularData; +import javax.management.JMException; +import java.io.IOException; + +/** + * The management interface exposed to allow management of an Exchange. + * @author Robert J. Greig + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedExchange +{ + static final String TYPE = "Exchange"; + + /** + * Returns the name of the managed exchange. + * @return the name of the exchange. + * @throws IOException + */ + String getName() throws IOException; + + /** + * Tells if the exchange is durable or not. + * @return true if the exchange is durable. + * @throws IOException + */ + boolean isDurable() throws IOException; + + /** + * Tells if the exchange is set for autodelete or not. + * @return true if the exchange is set as autodelete. + * @throws IOException + */ + boolean isAutoDelete() throws IOException; + + int getTicketNo() throws IOException; + + + // Operations + + /** + * Returns all the bindings this exchange has with the queues. + * @return the bindings with the exchange. + * @throws IOException + * @throws JMException + */ + TabularData viewBindings() + throws IOException, JMException; + + /** + * Creates new binding with the given queue and binding. + * @param QueueName + * @param binding + * @throws JMException + */ + void createBinding(String QueueName, String binding) + throws JMException; + +} \ No newline at end of file diff --git a/java/broker/src/org/apache/qpid/server/exchange/MessageRouter.java b/java/broker/src/org/apache/qpid/server/exchange/MessageRouter.java new file mode 100644 index 0000000000..2eef5f0676 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/MessageRouter.java @@ -0,0 +1,36 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.AMQException; + +/** + * Separated out from the ExchangeRegistry interface to allow components + * that use only this part to have a dependency with a reduced footprint. + * + */ +public interface MessageRouter +{ + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param message the message to be routed + * @throws org.apache.qpid.AMQException if something goes wrong delivering data + */ + void routeContent(AMQMessage message) throws AMQException; +} diff --git a/java/broker/src/org/apache/qpid/server/exchange/NoRouteException.java b/java/broker/src/org/apache/qpid/server/exchange/NoRouteException.java new file mode 100644 index 0000000000..6d5511d9c8 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/exchange/NoRouteException.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.protocol.AMQConstant; + +/** + * Thrown by an exchange if there is no way to route a message with the + * mandatory flag set. + */ +public class NoRouteException extends RequiredDeliveryException +{ + public NoRouteException(String msg, AMQMessage message) + { + super(msg, message); + } + + public int getReplyCode() + { + return AMQConstant.NO_ROUTE.getCode(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/BasicAckMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/BasicAckMethodHandler.java new file mode 100644 index 0000000000..5e236f7da9 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/BasicAckMethodHandler.java @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicAckBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class BasicAckMethodHandler implements StateAwareMethodListener +{ + private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler(); + + public static BasicAckMethodHandler getInstance() + { + return _instance; + } + + private BasicAckMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + BasicAckBody body = evt.getMethod(); + final AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + // this method throws an AMQException if the delivery tag is not known + channel.acknowledgeMessage(body.deliveryTag, body.multiple); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/BasicCancelMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/BasicCancelMethodHandler.java new file mode 100644 index 0000000000..52d6d9f0f1 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/BasicCancelMethodHandler.java @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicCancelBody; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.AMQException; + +public class BasicCancelMethodHandler implements StateAwareMethodListener +{ + private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler(); + + public static BasicCancelMethodHandler getInstance() + { + return _instance; + } + + private BasicCancelMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + final AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + final BasicCancelBody body = evt.getMethod(); + channel.unsubscribeConsumer(protocolSession, body.consumerTag); + if(!body.nowait) + { + final AMQFrame responseFrame = BasicCancelOkBody.createAMQFrame(evt.getChannelId(), body.consumerTag); + protocolSession.writeFrame(responseFrame); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java new file mode 100644 index 0000000000..7da5863044 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java @@ -0,0 +1,90 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.BasicConsumeBody; +import org.apache.qpid.framing.BasicConsumeOkBody; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.ConsumerTagNotUniqueException; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class BasicConsumeMethodHandler implements StateAwareMethodListener +{ + private static final Logger _log = Logger.getLogger(BasicConsumeMethodHandler.class); + + private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler(); + + public static BasicConsumeMethodHandler getInstance() + { + return _instance; + } + + private BasicConsumeMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession session, + AMQMethodEvent evt) throws AMQException + { + BasicConsumeBody body = evt.getMethod(); + final int channelId = evt.getChannelId(); + + AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + _log.error("Channel " + channelId + " not found"); + // TODO: either alert or error that the + } + else + { + AMQQueue queue = body.queue == null ? channel.getDefaultQueue() : queueRegistry.getQueue(body.queue); + + if(queue == null) + { + _log.info("No queue for '" + body.queue + "'"); + } + try + { + String consumerTag = channel.subscribeToQueue(body.consumerTag, queue, session, !body.noAck); + if(!body.nowait) + { + session.writeFrame(BasicConsumeOkBody.createAMQFrame(channelId, consumerTag)); + } + + //now allow queue to start async processing of any backlog of messages + queue.deliverAsync(); + } + catch(ConsumerTagNotUniqueException e) + { + String msg = "Non-unique consumer tag, '" + body.consumerTag + "'"; + session.writeFrame(ConnectionCloseBody.createAMQFrame(channelId, AMQConstant.NOT_ALLOWED.getCode(), msg, BasicConsumeBody.CLASS_ID, BasicConsumeBody.METHOD_ID)); + } + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/BasicPublishMethodHandler.java new file mode 100644 index 0000000000..f2f660299d --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/BasicPublishMethodHandler.java @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class BasicPublishMethodHandler implements StateAwareMethodListener +{ + private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler(); + + public static BasicPublishMethodHandler getInstance() + { + return _instance; + } + + private BasicPublishMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + final BasicPublishBody body = evt.getMethod(); + + // TODO: check the delivery tag field details - is it unique across the broker or per subscriber? + if (body.exchange == null) + { + body.exchange = "amq.direct"; + } + Exchange e = exchangeRegistry.getExchange(body.exchange); + // if the exchange does not exist we raise a channel exception + if (e == null) + { + protocolSession.closeChannel(evt.getChannelId()); + // TODO: modify code gen to make getClazz and getMethod public methods rather than protected + // then we can remove the hardcoded 0,0 + AMQFrame cf = ChannelCloseBody.createAMQFrame(evt.getChannelId(), 500, "Unknown exchange name", 0, 0); + protocolSession.writeFrame(cf); + } + else + { + // The partially populated BasicDeliver frame plus the received route body + // is stored in the channel. Once the final body frame has been received + // it is routed to the exchange. + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.setPublishFrame(body, protocolSession); + } + } +} + diff --git a/java/broker/src/org/apache/qpid/server/handler/BasicQosHandler.java b/java/broker/src/org/apache/qpid/server/handler/BasicQosHandler.java new file mode 100644 index 0000000000..0d1c039207 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/BasicQosHandler.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicQosBody; +import org.apache.qpid.framing.BasicQosOkBody; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.AMQException; + +public class BasicQosHandler implements StateAwareMethodListener +{ + private static final BasicQosHandler _instance = new BasicQosHandler(); + + public static BasicQosHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateMgr, QueueRegistry queues, ExchangeRegistry exchanges, + AMQProtocolSession session, AMQMethodEvent evt) throws AMQException + { + session.getChannel(evt.getChannelId()).setPrefetchCount(evt.getMethod().prefetchCount); + session.writeFrame(new AMQFrame(evt.getChannelId(), new BasicQosOkBody())); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java new file mode 100644 index 0000000000..1dce283c9e --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.framing.BasicRecoverBody; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +public class BasicRecoverMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(BasicRecoverMethodHandler.class); + + private static final BasicRecoverMethodHandler _instance = new BasicRecoverMethodHandler(); + + public static BasicRecoverMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + _logger.debug("Recover received on protocol session " + protocolSession + " and channel " + evt.getChannelId()); + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + if (channel == null) + { + throw new AMQException("Unknown channel " + evt.getChannelId()); + } + channel.resend(protocolSession); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ChannelCloseHandler.java b/java/broker/src/org/apache/qpid/server/handler/ChannelCloseHandler.java new file mode 100644 index 0000000000..1b03f15d22 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ChannelCloseHandler.java @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelCloseHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseHandler.class); + + private static ChannelCloseHandler _instance = new ChannelCloseHandler(); + + public static ChannelCloseHandler getInstance() + { + return _instance; + } + + private ChannelCloseHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + ChannelCloseBody body = evt.getMethod(); + _logger.info("Received channel close for id " + evt.getChannelId() + " citing class " + body.classId + + " and method " + body.methodId); + protocolSession.closeChannel(evt.getChannelId()); + AMQFrame response = ChannelCloseOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ChannelCloseOkHandler.java b/java/broker/src/org/apache/qpid/server/handler/ChannelCloseOkHandler.java new file mode 100644 index 0000000000..7731e56d4d --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ChannelCloseOkHandler.java @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class ChannelCloseOkHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseOkHandler.class); + + private static ChannelCloseOkHandler _instance = new ChannelCloseOkHandler(); + + public static ChannelCloseOkHandler getInstance() + { + return _instance; + } + + private ChannelCloseOkHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId()); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ChannelFlowHandler.java b/java/broker/src/org/apache/qpid/server/handler/ChannelFlowHandler.java new file mode 100644 index 0000000000..fb26549fad --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ChannelFlowHandler.java @@ -0,0 +1,61 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelFlowBody; +import org.apache.qpid.framing.ChannelFlowOkBody; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.AMQException; + +public class ChannelFlowHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ChannelFlowHandler.class); + + private static ChannelFlowHandler _instance = new ChannelFlowHandler(); + + public static ChannelFlowHandler getInstance() + { + return _instance; + } + + private ChannelFlowHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + ChannelFlowBody body = evt.getMethod(); + + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.setSuspended(!body.active); + _logger.info("Channel.Flow for channel " + evt.getChannelId() + ", active=" + body.active); + + AMQFrame response = ChannelFlowOkBody.createAMQFrame(evt.getChannelId(), body.active); + protocolSession.writeFrame(response); + }} diff --git a/java/broker/src/org/apache/qpid/server/handler/ChannelOpenHandler.java b/java/broker/src/org/apache/qpid/server/handler/ChannelOpenHandler.java new file mode 100644 index 0000000000..634cd70469 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ChannelOpenHandler.java @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelOpenBody; +import org.apache.qpid.framing.ChannelOpenOkBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelOpenHandler implements StateAwareMethodListener +{ + private static ChannelOpenHandler _instance = new ChannelOpenHandler(); + + public static ChannelOpenHandler getInstance() + { + return _instance; + } + + private ChannelOpenHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + IApplicationRegistry registry = ApplicationRegistry.getInstance(); + final AMQChannel channel = new AMQChannel(evt.getChannelId(), registry.getMessageStore(), + exchangeRegistry); + protocolSession.addChannel(channel); + AMQFrame response = ChannelOpenOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java new file mode 100644 index 0000000000..f78d6f7276 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +public class ConnectionCloseMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class); + + private static ConnectionCloseMethodHandler _instance = new ConnectionCloseMethodHandler(); + + public static ConnectionCloseMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + final ConnectionCloseBody body = evt.getMethod(); + _logger.info("ConnectionClose received with reply code/reply text " + body.replyCode + "/" + + body.replyText + " for " + protocolSession); + try + { + protocolSession.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + final AMQFrame response = ConnectionCloseOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java new file mode 100644 index 0000000000..f918158edb --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQState; +import org.apache.log4j.Logger; + +public class ConnectionCloseOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseOkMethodHandler.class); + + private static ConnectionCloseOkMethodHandler _instance = new ConnectionCloseOkMethodHandler(); + + public static ConnectionCloseOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + //todo should this not do more than just log the method? + _logger.info("Received Connection-close-ok"); + + try + { + stateManager.changeState(AMQState.CONNECTION_CLOSED); + protocolSession.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java new file mode 100644 index 0000000000..7bc28f9eb9 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionOpenBody; +import org.apache.qpid.framing.ConnectionOpenOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionOpenMethodHandler implements StateAwareMethodListener +{ + private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler(); + + public static ConnectionOpenMethodHandler getInstance() + { + return _instance; + } + + private ConnectionOpenMethodHandler() + { + } + + private static String generateClientID() + { + return Long.toString(System.currentTimeMillis()); + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + ConnectionOpenBody body = evt.getMethod(); + String contextKey = body.virtualHost; + + //todo //FIXME The virtual host must be validated by the server for the connection to open-ok + // See Spec (0.8.2). Section 3.1.2 Virtual Hosts + if (contextKey == null) + { + contextKey = generateClientID(); + } + protocolSession.setContextKey(contextKey); + AMQFrame response = ConnectionOpenOkBody.createAMQFrame((short)0, contextKey); + stateManager.changeState(AMQState.CONNECTION_OPEN); + protocolSession.writeFrame(response); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java new file mode 100644 index 0000000000..1c0da4f658 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java @@ -0,0 +1,115 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.HeartbeatConfig; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.log4j.Logger; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; + +public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionSecureOkMethodHandler.class); + + private static ConnectionSecureOkMethodHandler _instance = new ConnectionSecureOkMethodHandler(); + + public static ConnectionSecureOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionSecureOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + ConnectionSecureOkBody body = evt.getMethod(); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + SaslServer ss = protocolSession.getSaslServer(); + if (ss == null) + { + throw new AMQException("No SASL context set up in session"); + } + + AuthenticationResult authResult = authMgr.authenticate(ss, body.response); + switch (authResult.status) + { + case ERROR: + // Can't do this as we violate protocol. Need to send Close + // throw new AMQException(AMQConstant.NOT_ALLOWED.getCode(), AMQConstant.NOT_ALLOWED.getName()); + _logger.info("Authentication failed"); + stateManager.changeState(AMQState.CONNECTION_CLOSING); + AMQFrame close = ConnectionCloseBody.createAMQFrame(0, AMQConstant.NOT_ALLOWED.getCode(), + AMQConstant.NOT_ALLOWED.getName(), + ConnectionCloseBody.CLASS_ID, + ConnectionCloseBody.METHOD_ID); + protocolSession.writeFrame(close); + disposeSaslServer(protocolSession); + break; + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + AMQFrame tune = ConnectionTuneBody.createAMQFrame(0, Integer.MAX_VALUE, + ConnectionStartOkMethodHandler.getConfiguredFrameSize(), + HeartbeatConfig.getInstance().getDelay()); + protocolSession.writeFrame(tune); + disposeSaslServer(protocolSession); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + AMQFrame challenge = ConnectionSecureBody.createAMQFrame(0, authResult.challenge); + protocolSession.writeFrame(challenge); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java new file mode 100644 index 0000000000..5715ce181b --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java @@ -0,0 +1,127 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionStartOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.HeartbeatConfig; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + + +public class ConnectionStartOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionStartOkMethodHandler.class); + + private static ConnectionStartOkMethodHandler _instance = new ConnectionStartOkMethodHandler(); + + private static final int DEFAULT_FRAME_SIZE = 65536; + + public static StateAwareMethodListener getInstance() + { + return _instance; + } + + private ConnectionStartOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + final ConnectionStartOkBody body = evt.getMethod(); + _logger.info("SASL Mechanism selected: " + body.mechanism); + _logger.info("Locale selected: " + body.locale); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + + SaslServer ss = null; + try + { + ss = authMgr.createSaslServer(body.mechanism, protocolSession.getLocalFQDN()); + protocolSession.setSaslServer(ss); + + AuthenticationResult authResult = authMgr.authenticate(ss, body.response); + + switch (authResult.status) + { + case ERROR: + throw new AMQException("Authentication failed"); + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + AMQFrame tune = ConnectionTuneBody.createAMQFrame(0, Integer.MAX_VALUE, getConfiguredFrameSize(), + HeartbeatConfig.getInstance().getDelay()); + protocolSession.writeFrame(tune); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + AMQFrame challenge = ConnectionSecureBody.createAMQFrame(0, authResult.challenge); + protocolSession.writeFrame(challenge); + } + } + catch (SaslException e) + { + disposeSaslServer(protocolSession); + throw new AMQException("SASL error: " + e, e); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } + + static int getConfiguredFrameSize() + { + final Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + final int framesize = config.getInt("advanced.framesize", DEFAULT_FRAME_SIZE); + _logger.info("Framesize set to " + framesize); + return framesize; + } +} + diff --git a/java/broker/src/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java b/java/broker/src/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java new file mode 100644 index 0000000000..05ca10fec5 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionTuneOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionTuneOkMethodHandler.class); + + private static ConnectionTuneOkMethodHandler _instance = new ConnectionTuneOkMethodHandler(); + + public static ConnectionTuneOkMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + ConnectionTuneOkBody body = evt.getMethod(); + if (_logger.isDebugEnabled()) + { + _logger.debug(body); + } + stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); + protocolSession.initHeartbeats(body.heartbeat); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/java/broker/src/org/apache/qpid/server/handler/ExchangeDeclareHandler.java new file mode 100644 index 0000000000..444a54a4f2 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ExchangeDeclareHandler.java @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ExchangeDeclareBody; +import org.apache.qpid.framing.ExchangeDeclareOkBody; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class ExchangeDeclareHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ExchangeDeclareHandler.class); + + private static final ExchangeDeclareHandler _instance = new ExchangeDeclareHandler(); + + public static ExchangeDeclareHandler getInstance() + { + return _instance; + } + + private final ExchangeFactory exchangeFactory; + + private ExchangeDeclareHandler() + { + exchangeFactory = ApplicationRegistry.getInstance().getExchangeFactory(); + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + final ExchangeDeclareBody body = evt.getMethod(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Request to declare exchange of type " + body.type + " with name " + body.exchange); + } + synchronized(exchangeRegistry) + { + Exchange exchange = exchangeRegistry.getExchange(body.exchange); + + if (exchange == null) + { + exchange = exchangeFactory.createExchange(body.exchange, body.type, body.durable, + body.passive, body.ticket); + exchangeRegistry.registerExchange(exchange); + } + } + if(!body.nowait) + { + AMQFrame response = ExchangeDeclareOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/ExchangeDeleteHandler.java b/java/broker/src/org/apache/qpid/server/handler/ExchangeDeleteHandler.java new file mode 100644 index 0000000000..441e991872 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/ExchangeDeleteHandler.java @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ExchangeDeleteBody; +import org.apache.qpid.framing.ExchangeDeleteOkBody; +import org.apache.qpid.server.exchange.ExchangeInUseException; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ExchangeDeleteHandler implements StateAwareMethodListener +{ + private static final ExchangeDeleteHandler _instance = new ExchangeDeleteHandler(); + + public static ExchangeDeleteHandler getInstance() + { + return _instance; + } + + private ExchangeDeleteHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + ExchangeDeleteBody body = evt.getMethod(); + try + { + exchangeRegistry.unregisterExchange(body.exchange, body.ifUnused); + AMQFrame response = ExchangeDeleteOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } + catch (ExchangeInUseException e) + { + // TODO: sort out consistent channel close mechanism that does all clean up etc. + } + + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java b/java/broker/src/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java new file mode 100644 index 0000000000..a689366a2f --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import java.util.concurrent.Executor; + +/** + * An executor that executes the task on the current thread. + */ +public class OnCurrentThreadExecutor implements Executor +{ + public void execute(Runnable command) + { + command.run(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/QueueBindHandler.java b/java/broker/src/org/apache/qpid/server/handler/QueueBindHandler.java new file mode 100644 index 0000000000..98eec37a4a --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/QueueBindHandler.java @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.QueueBindBody; +import org.apache.qpid.framing.QueueBindOkBody; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class QueueBindHandler implements StateAwareMethodListener +{ + private static final Logger _log = Logger.getLogger(QueueBindHandler.class); + + private static final QueueBindHandler _instance = new QueueBindHandler(); + + public static QueueBindHandler getInstance() + { + return _instance; + } + + private QueueBindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + final QueueBindBody body = evt.getMethod(); + final AMQQueue queue; + if (body.queue == null) + { + queue = protocolSession.getChannel(evt.getChannelId()).getDefaultQueue(); + if (queue == null) + { + throw new AMQException("No default queue defined on channel and queue was null"); + } + if (body.routingKey == null) + { + body.routingKey = queue.getName(); + } + } + else + { + queue = queueRegistry.getQueue(body.queue); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND.getCode(), "Queue " + body.queue + " does not exist."); + } + final Exchange exch = exchangeRegistry.getExchange(body.exchange); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND.getCode(), "Exchange " + body.exchange + " does not exist."); + } + exch.registerQueue(body.routingKey, queue, body.arguments); + queue.bind(body.routingKey, exch); + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + body.routingKey); + } + if (!body.nowait) + { + final AMQFrame response = QueueBindOkBody.createAMQFrame(evt.getChannelId()); + protocolSession.writeFrame(response); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/QueueDeclareHandler.java b/java/broker/src/org/apache/qpid/server/handler/QueueDeclareHandler.java new file mode 100644 index 0000000000..d1eb387748 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/QueueDeclareHandler.java @@ -0,0 +1,124 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.QueueDeclareBody; +import org.apache.qpid.framing.QueueDeclareOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.configuration.Configurator; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.text.MessageFormat; +import java.util.concurrent.atomic.AtomicInteger; + +public class QueueDeclareHandler implements StateAwareMethodListener +{ + private static final Logger _log = Logger.getLogger(QueueDeclareHandler.class); + + private static final QueueDeclareHandler _instance = new QueueDeclareHandler(); + + public static QueueDeclareHandler getInstance() + { + return _instance; + } + + @Configured(path = "queue.auto_register", defaultValue = "false") + public boolean autoRegister; + + private final AtomicInteger _counter = new AtomicInteger(); + + private final MessageStore _store; + + protected QueueDeclareHandler() + { + Configurator.configure(this); + _store = ApplicationRegistry.getInstance().getMessageStore(); + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + QueueDeclareBody body = evt.getMethod(); + + // if we aren't given a queue name, we create one which we return to the client + if (body.queue == null) + { + body.queue = createName(); + } + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + synchronized (queueRegistry) + { + AMQQueue queue; + if ((queue = queueRegistry.getQueue(body.queue)) == null) + { + queue = createQueue(body, queueRegistry, protocolSession); + if (queue.isDurable() && !queue.isAutoDelete()) + { + _store.createQueue(queue); + } + queueRegistry.registerQueue(queue); + if (autoRegister) + { + Exchange defaultExchange = exchangeRegistry.getExchange("amq.direct"); + defaultExchange.registerQueue(body.queue, queue, null); + queue.bind(body.queue, defaultExchange); + _log.info("Queue " + body.queue + " bound to default exchange"); + } + } + //set this as the default queue on the channel: + protocolSession.getChannel(evt.getChannelId()).setDefaultQueue(queue); + } + if (!body.nowait) + { + AMQFrame response = QueueDeclareOkBody.createAMQFrame(evt.getChannelId(), body.queue, 0L, 0L); + _log.info("Queue " + body.queue + " declared successfully"); + protocolSession.writeFrame(response); + } + } + + protected String createName() + { + return "tmp_" + pad(_counter.incrementAndGet()); + } + + protected static String pad(int value) + { + return MessageFormat.format("{0,number,0000000000000}", value); + } + + protected AMQQueue createQueue(QueueDeclareBody body, QueueRegistry registry, AMQProtocolSession session) + throws AMQException + { + String owner = body.exclusive ? session.getContextKey() : null; + return new AMQQueue(body.queue, body.durable, owner, body.autoDelete, registry); + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/QueueDeleteHandler.java b/java/broker/src/org/apache/qpid/server/handler/QueueDeleteHandler.java new file mode 100644 index 0000000000..82c1d93065 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/QueueDeleteHandler.java @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.framing.QueueDeleteBody; +import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.AMQException; + +public class QueueDeleteHandler implements StateAwareMethodListener +{ + private static final QueueDeleteHandler _instance = new QueueDeleteHandler(); + + public static QueueDeleteHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + private final MessageStore _store; + + public QueueDeleteHandler() + { + this(true); + } + + public QueueDeleteHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + _store = ApplicationRegistry.getInstance().getMessageStore(); + + } + + public void methodReceived(AMQStateManager stateMgr, QueueRegistry queues, ExchangeRegistry exchanges, AMQProtocolSession session, AMQMethodEvent evt) throws AMQException + { + QueueDeleteBody body = evt.getMethod(); + AMQQueue queue; + if(body.queue == null) + { + queue = session.getChannel(evt.getChannelId()).getDefaultQueue(); + } + else + { + queue = queues.getQueue(body.queue); + } + + if(queue == null) + { + if(_failIfNotFound) + { + throw body.getChannelException(404, "Queue " + body.queue + " does not exist."); + } + } + else + { + int purged = queue.delete(body.ifUnused, body.ifEmpty); + _store.removeQueue(queue.getName()); + session.writeFrame(QueueDeleteOkBody.createAMQFrame(evt.getChannelId(), purged)); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/TxCommitHandler.java b/java/broker/src/org/apache/qpid/server/handler/TxCommitHandler.java new file mode 100644 index 0000000000..ce18c94c2b --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/TxCommitHandler.java @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxCommitBody; +import org.apache.qpid.framing.TxCommitOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxCommitHandler implements StateAwareMethodListener +{ + private static TxCommitHandler _instance = new TxCommitHandler(); + + public static TxCommitHandler getInstance() + { + return _instance; + } + + private TxCommitHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + + try{ + protocolSession.getChannel(evt.getChannelId()).commit(); + protocolSession.writeFrame(TxCommitOkBody.createAMQFrame(evt.getChannelId())); + }catch(AMQException e){ + throw evt.getMethod().getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage()); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/TxRollbackHandler.java b/java/broker/src/org/apache/qpid/server/handler/TxRollbackHandler.java new file mode 100644 index 0000000000..ff2d79fb95 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/TxRollbackHandler.java @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxRollbackBody; +import org.apache.qpid.framing.TxRollbackOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class TxRollbackHandler implements StateAwareMethodListener +{ + private static TxRollbackHandler _instance = new TxRollbackHandler(); + + public static TxRollbackHandler getInstance() + { + return _instance; + } + + private TxRollbackHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + try{ + AMQChannel channel = protocolSession.getChannel(evt.getChannelId()); + channel.rollback(); + protocolSession.writeFrame(TxRollbackOkBody.createAMQFrame(evt.getChannelId())); + //Now resend all the unacknowledged messages back to the original subscribers. + //(Must be done after the TxnRollback-ok response). + channel.resend(protocolSession); + }catch(AMQException e){ + throw evt.getMethod().getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage()); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/handler/TxSelectHandler.java b/java/broker/src/org/apache/qpid/server/handler/TxSelectHandler.java new file mode 100644 index 0000000000..d55930489c --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/handler/TxSelectHandler.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxSelectBody; +import org.apache.qpid.framing.TxSelectOkBody; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxSelectHandler implements StateAwareMethodListener +{ + private static TxSelectHandler _instance = new TxSelectHandler(); + + public static TxSelectHandler getInstance() + { + return _instance; + } + + private TxSelectHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException + { + protocolSession.getChannel(evt.getChannelId()).setTransactional(true); + protocolSession.writeFrame(TxSelectOkBody.createAMQFrame(evt.getChannelId())); + } +} diff --git a/java/broker/src/org/apache/qpid/server/jms/JmsConsumer.java b/java/broker/src/org/apache/qpid/server/jms/JmsConsumer.java new file mode 100644 index 0000000000..da82d2166e --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/jms/JmsConsumer.java @@ -0,0 +1,107 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.jms; + +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +public class JmsConsumer +{ + private int _prefetchValue; + + private PrefetchUnits _prefetchUnits; + + private boolean _noLocal; + + private boolean _autoAck; + + private boolean _exclusive; + + private AMQProtocolSession _protocolSession; + + public enum PrefetchUnits + { + OCTETS, + MESSAGES + } + + public int getPrefetchValue() + { + return _prefetchValue; + } + + public void setPrefetchValue(int prefetchValue) + { + _prefetchValue = prefetchValue; + } + + public PrefetchUnits getPrefetchUnits() + { + return _prefetchUnits; + } + + public void setPrefetchUnits(PrefetchUnits prefetchUnits) + { + _prefetchUnits = prefetchUnits; + } + + public boolean isNoLocal() + { + return _noLocal; + } + + public void setNoLocal(boolean noLocal) + { + _noLocal = noLocal; + } + + public boolean isAutoAck() + { + return _autoAck; + } + + public void setAutoAck(boolean autoAck) + { + _autoAck = autoAck; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public void setExclusive(boolean exclusive) + { + _exclusive = exclusive; + } + + public AMQProtocolSession getProtocolSession() + { + return _protocolSession; + } + + public void setProtocolSession(AMQProtocolSession protocolSession) + { + _protocolSession = protocolSession; + } + + public void deliverMessage() throws AMQException + { + + } +} diff --git a/java/broker/src/org/apache/qpid/server/management/AMQManagedObject.java b/java/broker/src/org/apache/qpid/server/management/AMQManagedObject.java new file mode 100644 index 0000000000..fea955b93b --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/AMQManagedObject.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import javax.management.ListenerNotFoundException; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; + +/** + * This class provides additinal feature of Notification Broadcaster to the + * DefaultManagedObject. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public abstract class AMQManagedObject extends DefaultManagedObject + implements NotificationBroadcaster +{ + /** + * broadcaster support class + */ + protected NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport(); + + /** + * sequence number for notifications + */ + protected long _notificationSequenceNumber = 0; + + protected AMQManagedObject(Class managementInterface, String typeName) + { + super(managementInterface, typeName); + } + + + // notification broadcaster implementation + + public void addNotificationListener(NotificationListener listener, + NotificationFilter filter, + Object handback) + { + _broadcaster.addNotificationListener(listener, filter, handback); + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException + { + _broadcaster.removeNotificationListener(listener); + } +} diff --git a/java/broker/src/org/apache/qpid/server/management/DefaultManagedObject.java b/java/broker/src/org/apache/qpid/server/management/DefaultManagedObject.java new file mode 100644 index 0000000000..bb8603f8b4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/DefaultManagedObject.java @@ -0,0 +1,126 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; + +/** + * Provides implementation of the boilerplate ManagedObject interface. Most managed objects should find it useful + * to extend this class rather than implementing ManagedObject from scratch. + * + */ +public abstract class DefaultManagedObject implements ManagedObject +{ + private Class _managementInterface; + + private String _typeName; + + protected DefaultManagedObject(Class managementInterface, String typeName) + { + _managementInterface = managementInterface; + _typeName = typeName; + } + + public String getType() + { + return _typeName; + } + + public Class getManagementInterface() + { + return _managementInterface; + } + + public void register() throws AMQException + { + try + { + ApplicationRegistry.getInstance().getManagedObjectRegistry().registerObject(this); + } + catch (Exception e) + { + throw new AMQException("Error registering managed object " + this + ": " + e, e); + } + } + + public void unregister() throws AMQException + { + try + { + ApplicationRegistry.getInstance().getManagedObjectRegistry().unregisterObject(this); + } + catch (Exception e) + { + throw new AMQException("Error unregistering managed object: " + this + ": " + e, e); + } + } + + public String toString() + { + return getObjectInstanceName() + "[" + getType() + "]"; + } + + /** + * Created the ObjectName as per the JMX Specs + * @return ObjectName + * @throws MalformedObjectNameException + */ + public ObjectName getObjectName() + throws MalformedObjectNameException + { + String name = jmxEncode(new StringBuffer(getObjectInstanceName()), 0).toString(); + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + objectName.append(":type=").append(getType()); + objectName.append(",name=").append(name); + + return new ObjectName(objectName.toString()); + } + + private static StringBuffer jmxEncode(StringBuffer jmxName, int attrPos) + { + for (int i = attrPos; i < jmxName.length(); i++) + { + if (jmxName.charAt(i) == ',') + { + jmxName.setCharAt(i, ';'); + } + else if (jmxName.charAt(i) == ':') + { + jmxName.setCharAt(i, '-'); + } + else if (jmxName.charAt(i) == '?' || + jmxName.charAt(i) == '*' || + jmxName.charAt(i) == '\\') + { + jmxName.insert(i, '\\'); + i++; + } + else if (jmxName.charAt(i) == '\n') + { + jmxName.insert(i, '\\'); + i++; + jmxName.setCharAt(i, 'n'); + } + } + return jmxName; + } +} \ No newline at end of file diff --git a/java/broker/src/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/java/broker/src/org/apache/qpid/server/management/JMXManagedObjectRegistry.java new file mode 100644 index 0000000000..d556973970 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/JMXManagedObjectRegistry.java @@ -0,0 +1,66 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import org.apache.log4j.Logger; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.NotCompliantMBeanException; +import javax.management.StandardMBean; +import java.lang.management.ManagementFactory; + +public class JMXManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); + + private final MBeanServer _mbeanServer; + + public JMXManagedObjectRegistry() + { + _log.info("Initialising managed object registry using platform MBean server"); + // we use the platform MBean server currently but this must be changed or at least be configuurable + _mbeanServer = ManagementFactory.getPlatformMBeanServer(); + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + try + { + _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); + } + catch(NotCompliantMBeanException ex) + { + // The following is a hack due to a silly change to StandardMBean in JDK 1.6 + // They have added in generics to get compile time safety which reduces the + // flexibility + Object o = managedObject; + Class clazz = (Class) managedObject.getManagementInterface(); + StandardMBean mbean = new StandardMBean(o, clazz); + + _mbeanServer.registerMBean(mbean, managedObject.getObjectName()); + } + + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.unregisterMBean(managedObject.getObjectName()); + } + +} \ No newline at end of file diff --git a/java/broker/src/org/apache/qpid/server/management/Managable.java b/java/broker/src/org/apache/qpid/server/management/Managable.java new file mode 100644 index 0000000000..e62e1c7f87 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/Managable.java @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +/** + * Any object that can return a related MBean should implement this interface. + * + * This enables other classes to get the managed object, which in turn is useful when + * constructing relationships between managed objects without having to maintain + * separate data structures containing MBeans. + * + */ +public interface Managable +{ + ManagedObject getManagedObject(); +} diff --git a/java/broker/src/org/apache/qpid/server/management/ManagedBroker.java b/java/broker/src/org/apache/qpid/server/management/ManagedBroker.java new file mode 100644 index 0000000000..ecf6123c7d --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/ManagedBroker.java @@ -0,0 +1,78 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.qpid.server.management; + +import javax.management.JMException; +import java.io.IOException; + +/** + * The ManagedBroker is the management interface to expose management + * features of the Broker. + * + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedBroker +{ + static final String TYPE = "BrokerManager"; + + /** + * Creates a new Exchange. + * @param name + * @param type + * @param durable + * @param passive + * @throws IOException + * @throws JMException + */ + void createNewExchange(String name, String type, boolean durable, boolean passive) + throws IOException, JMException; + + /** + * unregisters all the channels, queuebindings etc and unregisters + * this exchange from managed objects. + * @param exchange + * @throws IOException + * @throws JMException + */ + void unregisterExchange(String exchange) + throws IOException, JMException; + + /** + * Create a new Queue on the Broker server + * @param queueName + * @param durable + * @param owner + * @param autoDelete + * @throws IOException + * @throws JMException + */ + void createQueue(String queueName, boolean durable, String owner, boolean autoDelete) + throws IOException, JMException; + + /** + * Unregisters the Queue bindings, removes the subscriptions and unregisters + * from the managed objects. + * @param queueName + * @throws IOException + * @throws JMException + */ + void deleteQueue(String queueName) + throws IOException, JMException; +} diff --git a/java/broker/src/org/apache/qpid/server/management/ManagedObject.java b/java/broker/src/org/apache/qpid/server/management/ManagedObject.java new file mode 100644 index 0000000000..0643f84744 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/ManagedObject.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import org.apache.qpid.AMQException; + +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; + +/** + * This should be implemented by all Managable objects. + */ +public interface ManagedObject +{ + static final String DOMAIN = "org.apache.qpid"; + + /** + * @return the name that uniquely identifies this object instance. It must be + * unique only among objects of this type at this level in the hierarchy so + * the uniqueness should not be too difficult to ensure. + */ + String getObjectInstanceName(); + + String getType(); + + Class getManagementInterface(); + + void register() throws AMQException; + + void unregister() throws AMQException; + + /** + * Returns the ObjectName required for the mbeanserver registration. + * @return ObjectName + * @throws MalformedObjectNameException + */ + ObjectName getObjectName() throws MalformedObjectNameException; +} diff --git a/java/broker/src/org/apache/qpid/server/management/ManagedObjectRegistry.java b/java/broker/src/org/apache/qpid/server/management/ManagedObjectRegistry.java new file mode 100644 index 0000000000..7270ec83b4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/ManagedObjectRegistry.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import javax.management.JMException; + +/** + * Handles the registration (and unregistration and so on) of managed objects. + * + * Managed objects are responsible for exposting attributes, operations and notifications. They will expose + * these outside the JVM therefore it is important not to use implementation objects directly as managed objects. + * Instead, creating inner classes and exposing those is an effective way of exposing internal state in a + * controlled way. + * + * Although we do not explictly use them while targetting Java 5, the enhanced MXBean approach in Java 6 will + * be the obvious choice for managed objects. + * + */ +public interface ManagedObjectRegistry +{ + void registerObject(ManagedObject managedObject) throws JMException; + + void unregisterObject(ManagedObject managedObject) throws JMException; +} diff --git a/java/broker/src/org/apache/qpid/server/management/ManagementConfiguration.java b/java/broker/src/org/apache/qpid/server/management/ManagementConfiguration.java new file mode 100644 index 0000000000..ec80009d17 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/ManagementConfiguration.java @@ -0,0 +1,27 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import org.apache.qpid.configuration.Configured; + +public class ManagementConfiguration +{ + @Configured(path = "management.enabled", + defaultValue = "true") + public boolean enabled; +} diff --git a/java/broker/src/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/java/broker/src/org/apache/qpid/server/management/NoopManagedObjectRegistry.java new file mode 100644 index 0000000000..3bf2c8d9ca --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/management/NoopManagedObjectRegistry.java @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.management; + +import org.apache.log4j.Logger; + +import javax.management.JMException; + +/** + * This managed object registry does not actually register MBeans. This can be used in tests when management is + * not required or when management has been disabled. + * + */ +public class NoopManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(NoopManagedObjectRegistry.class); + + public NoopManagedObjectRegistry() + { + _log.info("Management is disabled"); + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/AMQMethodEvent.java b/java/broker/src/org/apache/qpid/server/protocol/AMQMethodEvent.java new file mode 100644 index 0000000000..0c8d049951 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/AMQMethodEvent.java @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.framing.AMQMethodBody; + +/** + * An event that is passed to AMQMethodListeners describing a particular method. + * It supplies the: + *
  • channel id
  • + *
  • protocol method
  • + * to listeners. This means that listeners do not need to be stateful. + * + * In the StateAwareMethodListener, other useful objects such as the protocol session + * are made available. + * + */ +public class AMQMethodEvent +{ + private final M _method; + + private final int _channelId; + + public AMQMethodEvent(int channelId, M method) + { + _channelId = channelId; + _method = method; + } + + public M getMethod() + { + return _method; + } + + public int getChannelId() + { + return _channelId; + } + + public String toString() + { + StringBuilder buf = new StringBuilder("Method event: "); + buf.append("\nChannel id: ").append(_channelId); + buf.append("\nMethod: ").append(_method); + return buf.toString(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/AMQMethodListener.java b/java/broker/src/org/apache/qpid/server/protocol/AMQMethodListener.java new file mode 100644 index 0000000000..e8d973cd91 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/AMQMethodListener.java @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.framing.AMQMethodBody; + +/** + * Interface that allows classes to register for interest in protocol method frames. + * + */ +public interface AMQMethodListener +{ + /** + * Invoked when a method frame has been received + * @param evt the event that contains the method and channel + * @param protocolSession the protocol session associated with the event + * @return true if the handler has processed the method frame, false otherwise. Note + * that this does not prohibit the method event being delivered to subsequent listeners + * but can be used to determine if nobody has dealt with an incoming method frame. + * @throws AMQException if an error has occurred. This exception will be delivered + * to all registered listeners using the error() method (see below) allowing them to + * perform cleanup if necessary. + */ + boolean methodReceived(AMQMethodEvent evt, + AMQProtocolSession protocolSession, + QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry) throws AMQException; + + /** + * Callback when an error has occurred. Allows listeners to clean up. + * @param e + */ + void error(AMQException e); +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java b/java/broker/src/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java new file mode 100644 index 0000000000..80aa4756d2 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/AMQMinaProtocolSession.java @@ -0,0 +1,603 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.log4j.Logger; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.transport.vmpipe.VmPipeAddress; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.codec.AMQDecoder; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.DefaultManagedObject; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; + +import javax.security.sasl.SaslServer; +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; +import javax.management.openmbean.*; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class AMQMinaProtocolSession implements AMQProtocolSession, ProtocolVersionList +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class); + + private final IoSession _minaProtocolSession; + + private String _contextKey; + + private final Map _channelMap = new HashMap(); + + private final CopyOnWriteArraySet _frameListeners = new CopyOnWriteArraySet(); + + private final AMQStateManager _stateManager; + + private final QueueRegistry _queueRegistry; + + private final ExchangeRegistry _exchangeRegistry; + + private AMQCodecFactory _codecFactory; + + private AMQProtocolSessionMBean _managedObject; + + private SaslServer _saslServer; + + private Object _lastReceived; + + private Object _lastSent; + + private boolean _closed; + + private long _maxNoOfChannels; + + /* AMQP Version for this session */ + + private byte _major; + private byte _minor; + + /** + * This class implements the management interface (is an MBean). In order to make more attributes, operations + * and notifications available over JMX simply augment the ManagedConnection interface and add the appropriate + * implementation here. + */ + private final class AMQProtocolSessionMBean extends DefaultManagedObject implements ManagedConnection + { + /** + * Represents the channel attributes sent with channel data. + */ + private String[] _channelAtttibuteNames = { "ChannelId", + "ChannelName", + "Transactional", + "DefaultQueue"}; + private String[] _channelAttributeDescriptions = { "Channel Identifier", + "Channel Name", + "is Channel Transactional?", + "Default Queue Name" }; + private OpenType[] _channelAttributeTypes = { SimpleType.INTEGER, + SimpleType.OBJECTNAME, + SimpleType.BOOLEAN, + SimpleType.STRING }; + /** + * Channels in the list will be indexed according to channelId. + */ + private String[] _indexNames = { "ChannelId" }; + + /** + * represents the data type for channel data. + */ + private CompositeType _channelType = null; + /** + * Datatype for list of channelsType. + */ + private TabularType _channelsType = null; + + private TabularDataSupport _channelsList = null; + + public AMQProtocolSessionMBean() + { + super(ManagedConnection.class, ManagedConnection.TYPE); + init(); + } + + /** + * initialises the CompositeTypes and TabularType attributes. + */ + private void init() + { + try + { + _channelType = new CompositeType("channel", + "a Channel", + _channelAtttibuteNames, + _channelAttributeDescriptions, + _channelAttributeTypes); + + _channelsType = new TabularType("channelsType", + "List of available channelsType", + _channelType, + _indexNames); + } + catch(OpenDataException ex) + { + // It should never occur. + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public Date getLastIoTime() + { + return new Date(_minaProtocolSession.getLastIoTime()); + } + + public String getRemoteAddress() + { + return _minaProtocolSession.getRemoteAddress().toString(); + } + + public long getWrittenBytes() + { + return _minaProtocolSession.getWrittenBytes(); + } + + public long getReadBytes() + { + return _minaProtocolSession.getReadBytes(); + } + + public long getMaximumNumberOfAllowedChannels() + { + return _maxNoOfChannels; + } + + public void setMaximumNumberOfAllowedChannels(long value) + { + _maxNoOfChannels = value; + } + + public String getObjectInstanceName() + { + String remote = getRemoteAddress(); + return "anonymous".equals(remote) ? remote + hashCode() : remote; + } + + /** + * Creates the list of channels in tabular form from the _channelMap. + * @return list of channels in tabular form. + * @throws OpenDataException + */ + private TabularData getChannels() + throws OpenDataException + { + _channelsList = new TabularDataSupport(_channelsType); + + for (Map.Entry entry : _channelMap.entrySet()) + { + AMQChannel channel = entry.getValue(); + ObjectName channelObjectName = null; + + try + { + channelObjectName = channel.getObjectName(); + } + catch (MalformedObjectNameException ex) + { + _logger.error("Unable to create object name: ", ex); + } + + Object[] itemValues = {channel.getChannelId(), + channelObjectName, + channel.isTransactional(), + (channel.getDefaultQueue() != null) ? channel.getDefaultQueue().getName() : null}; + + CompositeData channelData = new CompositeDataSupport(_channelType, + _channelAtttibuteNames, + itemValues); + + _channelsList.put(channelData); + } + + return _channelsList; + } + + public TabularData viewChannels() + throws OpenDataException + { + return getChannels(); + } + + public void closeChannel(int id) + throws Exception + { + try + { + AMQMinaProtocolSession.this.closeChannel(id); + } + catch (AMQException ex) + { + throw new Exception(ex.toString()); + } + } + + public void closeConnection() + throws Exception + { + try + { + AMQMinaProtocolSession.this.closeSession(); + } + catch (AMQException ex) + { + throw new Exception(ex.toString()); + } + } + + } + + public AMQMinaProtocolSession(IoSession session, QueueRegistry queueRegistry, ExchangeRegistry exchangeRegistry, + AMQCodecFactory codecFactory) + throws AMQException + { + this(session, queueRegistry, exchangeRegistry, codecFactory, new AMQStateManager()); + } + + public AMQMinaProtocolSession(IoSession session, QueueRegistry queueRegistry, ExchangeRegistry exchangeRegistry, + AMQCodecFactory codecFactory, AMQStateManager stateManager) + throws AMQException + { + _stateManager = stateManager; + _minaProtocolSession = session; + session.setAttachment(this); + _frameListeners.add(_stateManager); + _queueRegistry = queueRegistry; + _exchangeRegistry = exchangeRegistry; + _codecFactory = codecFactory; + _managedObject = new AMQProtocolSessionMBean(); + _managedObject.register(); + } + + public static AMQProtocolSession getAMQProtocolSession(IoSession minaProtocolSession) + { + return (AMQProtocolSession) minaProtocolSession.getAttachment(); + } + + public void dataBlockReceived(AMQDataBlock message) + throws Exception + { + _lastReceived = message; + if (message instanceof ProtocolInitiation) + { + ProtocolInitiation pi = (ProtocolInitiation) message; + // this ensures the codec never checks for a PI message again + ((AMQDecoder)_codecFactory.getDecoder()).setExpectProtocolInitiation(false); + try { + pi.checkVersion(this); // Fails if not correct + // This sets the protocol version (and hence framing classes) for this session. + _major = pi.protocolMajor; + _minor = pi.protocolMinor; + String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager().getMechanisms(); + String locales = "en_US"; + AMQFrame response = ConnectionStartBody.createAMQFrame((short)0, pi.protocolMajor, pi.protocolMinor, null, + mechanisms.getBytes(), locales.getBytes()); + _minaProtocolSession.write(response); + } catch (AMQException e) { + _logger.error("Received incorrect protocol initiation", e); + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be used + here. */ + int i = pv.length - 1; + _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR])); + // TODO: Close connection (but how to wait until message is sent?) + } + } + else + { + AMQFrame frame = (AMQFrame) message; + + if (frame.bodyFrame instanceof AMQMethodBody) + { + methodFrameReceived(frame); + } + else + { + try + { + contentFrameReceived(frame); + } + catch (RequiredDeliveryException e) + { + //need to return the message: + _logger.info("Returning message to " + this + " channel " + frame.channel + + ": " + e.getMessage()); + writeFrame(e.getReturnMessage(frame.channel)); + } + } + } + } + + private void methodFrameReceived(AMQFrame frame) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Method frame received: " + frame); + } + final AMQMethodEvent evt = new AMQMethodEvent(frame.channel, + (AMQMethodBody)frame.bodyFrame); + try + { + boolean wasAnyoneInterested = false; + for (AMQMethodListener listener : _frameListeners) + { + wasAnyoneInterested = listener.methodReceived(evt, this, _queueRegistry, _exchangeRegistry) || + wasAnyoneInterested; + } + if (!wasAnyoneInterested) + { + throw new AMQException("AMQMethodEvent " + evt + " was not processed by any listener."); + } + } + catch (AMQChannelException e) + { + _logger.error("Closing channel due to: " + e.getMessage()); + writeFrame(e.getCloseFrame(frame.channel)); + } + catch (AMQException e) + { + for (AMQMethodListener listener : _frameListeners) + { + listener.error(e); + } + _minaProtocolSession.close(); + } + } + + private void contentFrameReceived(AMQFrame frame) throws AMQException + { + if (frame.bodyFrame instanceof ContentHeaderBody) + { + contentHeaderReceived(frame); + } + else if (frame.bodyFrame instanceof ContentBody) + { + contentBodyReceived(frame); + } + else if (frame.bodyFrame instanceof HeartbeatBody) + { + _logger.debug("Received heartbeat from client"); + } + else + { + _logger.warn("Unrecognised frame " + frame.getClass().getName()); + } + } + + private void contentHeaderReceived(AMQFrame frame) throws AMQException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Content header frame received: " + frame); + } + getChannel(frame.channel).publishContentHeader((ContentHeaderBody)frame.bodyFrame); + } + + private void contentBodyReceived(AMQFrame frame) throws AMQException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Content body frame received: " + frame); + } + getChannel(frame.channel).publishContentBody((ContentBody)frame.bodyFrame); + } + + /** + * Convenience method that writes a frame to the protocol session. Equivalent + * to calling getProtocolSession().write(). + * + * @param frame the frame to write + */ + public void writeFrame(AMQDataBlock frame) + { + _lastSent = frame; + _minaProtocolSession.write(frame); + } + + public String getContextKey() + { + return _contextKey; + } + + public void setContextKey(String contextKey) + { + _contextKey = contextKey; + } + + public AMQChannel getChannel(int channelId) throws AMQException + { + return _channelMap.get(channelId); + } + + public void addChannel(AMQChannel channel) + { + _channelMap.put(channel.getChannelId(), channel); + } + + /** + * Close a specific channel. This will remove any resources used by the channel, including: + *
    • any queue subscriptions (this may in turn remove queues if they are auto delete
    • + *
    + * @param channelId id of the channel to close + * @throws AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + public void closeChannel(int channelId) throws AMQException + { + final AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Unknown channel id"); + } + else + { + try + { + channel.close(this); + } + finally + { + _channelMap.remove(channelId); + } + } + } + + /** + * In our current implementation this is used by the clustering code. + * @param channelId + */ + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + } + + /** + * Initialise heartbeats on the session. + * @param delay delay in seconds (not ms) + */ + public void initHeartbeats(int delay) + { + if(delay > 0) + { + _minaProtocolSession.setIdleTime(IdleStatus.WRITER_IDLE, delay); + _minaProtocolSession.setIdleTime(IdleStatus.READER_IDLE, HeartbeatConfig.getInstance().getTimeout(delay)); + } + } + + /** + * Closes all channels that were opened by this protocol session. This frees up all resources + * used by the channel. + * @throws AMQException if an error occurs while closing any channel + */ + private void closeAllChannels() throws AMQException + { + for (AMQChannel channel : _channelMap.values()) + { + channel.close(this); + } + } + + /** + * This must be called when the session is _closed in order to free up any resources + * managed by the session. + */ + public void closeSession() throws AMQException + { + if(!_closed) + { + _closed = true; + closeAllChannels(); + if (_managedObject != null) + { + _managedObject.unregister(); + } + } + } + + public String toString() + { + return "AMQProtocolSession(" + _minaProtocolSession.getRemoteAddress() + ")"; + } + + public String dump() + { + return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived; + } + + /** + * @return an object that can be used to identity + */ + public Object getKey() + { + return _minaProtocolSession.getRemoteAddress(); + } + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers + * may be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + public String getLocalFQDN() + { + SocketAddress address = _minaProtocolSession.getLocalAddress(); + // we use the vmpipe address in some tests hence the need for this rather ugly test. The host + // information is used by SASL primary. + if (address instanceof InetSocketAddress) + { + return ((InetSocketAddress)address).getHostName(); + } + else if (address instanceof VmPipeAddress) + { + return "vmpipe:" + ((VmPipeAddress)address).getPort(); + } + else + { + throw new IllegalArgumentException("Unsupported socket address class: " + address); + } + } + + public SaslServer getSaslServer() + { + return _saslServer; + } + + public void setSaslServer(SaslServer saslServer) + { + _saslServer = saslServer; + } + + /** + * Convenience methods for managing AMQP version. + * NOTE: Both major and minor will be set to 0 prior to protocol initiation. + */ + + public byte getAmqpMajor() + { + return _major; + } + + public byte getAmqpMinor() + { + return _minor; + } + + public boolean amqpVersionEquals(byte major, byte minor) + { + return _major == major && _minor == minor; + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java b/java/broker/src/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java new file mode 100644 index 0000000000..a51dbd5d59 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/AMQPFastProtocolHandler.java @@ -0,0 +1,217 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.transport.ConnectorConfiguration; +import org.apache.qpid.ssl.BogusSSLContextFactory; +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.SSLFilter; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.util.SessionUtil; + +import java.io.IOException; + + +/** + * The protocol handler handles "protocol events" for all connections. The state + * associated with an individual connection is accessed through the protocol session. + * + * We delegate all frame (message) processing to the AMQProtocolSession which wraps + * the state for the connection. + * + */ +public class AMQPFastProtocolHandler extends IoHandlerAdapter implements ProtocolVersionList +{ + private static final Logger _logger = Logger.getLogger(AMQPFastProtocolHandler.class); + + /** + * The registry of all queues. This is passed to frame listeners when frame + * events occur. + */ + private final QueueRegistry _queueRegistry; + + /** + * The registry of all exchanges. This is passed to frame listeners when frame + * events occur. + */ + private final ExchangeRegistry _exchangeRegistry; + + private boolean _useSSL; + + public AMQPFastProtocolHandler(QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry) + { + _queueRegistry = queueRegistry; + _exchangeRegistry = exchangeRegistry; + + _logger.debug("AMQPFastProtocolHandler created"); + } + + protected AMQPFastProtocolHandler(AMQPFastProtocolHandler handler) + { + this(handler._queueRegistry, handler._exchangeRegistry); + } + + public void sessionCreated(IoSession protocolSession) throws Exception + { + SessionUtil.initialize(protocolSession); + final AMQCodecFactory codecFactory = new AMQCodecFactory(true); + + createSession(protocolSession, _queueRegistry, _exchangeRegistry, codecFactory); + _logger.info("Protocol session created"); + + final ProtocolCodecFilter pcf = new ProtocolCodecFilter(codecFactory); + + ConnectorConfiguration connectorConfig = ApplicationRegistry.getInstance(). + getConfiguredObject(ConnectorConfiguration.class); + if (connectorConfig.enableExecutorPool) + { + if (_useSSL) + { + protocolSession.getFilterChain().addAfter("AsynchronousReadFilter", "sslFilter", + new SSLFilter(BogusSSLContextFactory.getInstance(true))); + } + protocolSession.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf); + } + else + { + protocolSession.getFilterChain().addLast("protocolFilter", pcf); + } + } + + /** + * Separated into its own, protected, method to allow easier reuse + */ + protected void createSession(IoSession session, QueueRegistry queues, ExchangeRegistry exchanges, AMQCodecFactory codec) throws AMQException + { + new AMQMinaProtocolSession(session, queues, exchanges, codec); + } + + public void sessionOpened(IoSession protocolSession) throws Exception + { + _logger.info("Session opened"); + } + + public void sessionClosed(IoSession protocolSession) throws Exception + { + _logger.info("Protocol Session closed"); + final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + amqProtocolSession.closeSession(); + } + + public void sessionIdle(IoSession session, IdleStatus status) throws Exception + { + _logger.debug("Protocol Session [" + this + "] idle: " + status); + if(IdleStatus.WRITER_IDLE.equals(status)) + { + //write heartbeat frame: + session.write(HeartbeatBody.FRAME); + } + else if(IdleStatus.READER_IDLE.equals(status)) + { + //failover: + throw new IOException("Timed out while waiting for heartbeat from peer."); + } + + } + + public void exceptionCaught(IoSession protocolSession, Throwable throwable) throws Exception + { + AMQProtocolSession session = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + if (throwable instanceof AMQProtocolHeaderException) + { + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be returned + here. */ + int i = pv.length - 1; + protocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR])); + protocolSession.close(); + _logger.error("Error in protocol initiation " + session + ": " + throwable.getMessage(), throwable); + } + else if(throwable instanceof IOException) + { + _logger.error("IOException caught in" + session + ", session closed implictly: " + throwable, throwable); + } + else + { + protocolSession.write(ConnectionCloseBody.createAMQFrame(0, 200, throwable.getMessage(), 0, 0)); + _logger.error("Exception caught in" + session + ", closing session explictly: " + throwable, throwable); + protocolSession.close(); + } + } + + /** + * Invoked when a message is received on a particular protocol session. Note that a + * protocol session is directly tied to a particular physical connection. + * @param protocolSession the protocol session that received the message + * @param message the message itself (i.e. a decoded frame) + * @throws Exception if the message cannot be processed + */ + public void messageReceived(IoSession protocolSession, Object message) throws Exception + { + final AMQProtocolSession amqProtocolSession = AMQMinaProtocolSession.getAMQProtocolSession(protocolSession); + + if (message instanceof AMQDataBlock) + { + amqProtocolSession.dataBlockReceived((AMQDataBlock) message); + } + else if (message instanceof ByteBuffer) + { + throw new IllegalStateException("Handed undecoded ByteBuffer buf = " + message); + } + else + { + throw new IllegalStateException("Handed unhandled message. message.class = " + message.getClass() + " message = " + message); + } + } + + /** + * Called after a message has been sent out on a particular protocol session + * @param protocolSession the protocol session (i.e. connection) on which this + * message was sent + * @param object the message (frame) that was encoded and sent + * @throws Exception if we want to indicate an error + */ + public void messageSent(IoSession protocolSession, Object object) throws Exception + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Message sent: " + object); + } + } + + public boolean isUseSSL() + { + return _useSSL; + } + + public void setUseSSL(boolean useSSL) + { + _useSSL = useSSL; + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/AMQPProtocolProvider.java b/java/broker/src/org/apache/qpid/server/protocol/AMQPProtocolProvider.java new file mode 100644 index 0000000000..0088db08bb --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/AMQPProtocolProvider.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; + +/** + * The protocol provide's role is to encapsulate the initialisation of the protocol handler. + * + * The protocol handler (see AMQPFastProtocolHandler class) handles protocol events + * such as connection closing or a frame being received. It can either do this directly + * or pass off to the protocol session in the cases where state information is required to + * deal with the event. + * + */ +public class AMQPProtocolProvider +{ + /** + * Handler for protocol events + */ + private AMQPFastProtocolHandler _handler; + + public AMQPProtocolProvider() + { + IApplicationRegistry registry = ApplicationRegistry.getInstance(); + _handler = new AMQPFastProtocolHandler(registry.getQueueRegistry(), + registry.getExchangeRegistry()); + } + + public AMQPFastProtocolHandler getHandler() + { + return _handler; + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/AMQProtocolSession.java b/java/broker/src/org/apache/qpid/server/protocol/AMQProtocolSession.java new file mode 100644 index 0000000000..402ebc329d --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/AMQProtocolSession.java @@ -0,0 +1,122 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.AMQException; + +import javax.security.sasl.SaslServer; + + +public interface AMQProtocolSession +{ + /** + * Called when a protocol data block is received + * @param message the data block that has been received + * @throws Exception if processing the datablock fails + */ + void dataBlockReceived(AMQDataBlock message) throws Exception; + + /** + * Write a datablock, encoding where necessary (e.g. into a sequence of bytes) + * @param frame the frame to be encoded and written + */ + void writeFrame(AMQDataBlock frame); + + /** + * Get the context key associated with this session. Context key is described + * in the AMQ protocol specification (RFC 6). + * @return the context key + */ + String getContextKey(); + + /** + * Set the context key associated with this session. Context key is described + * in the AMQ protocol specification (RFC 6). + * @param contextKey the context key + */ + void setContextKey(String contextKey); + + /** + * Get the channel for this session associated with the specified id. A channel + * id is unique per connection (i.e. per session). + * @param channelId the channel id which must be valid + * @return null if no channel exists, the channel otherwise + */ + AMQChannel getChannel(int channelId) throws AMQException; + + /** + * Associate a channel with this session. + * @param channel the channel to associate with this session. It is an error to + * associate the same channel with more than one session but this is not validated. + */ + void addChannel(AMQChannel channel); + + /** + * Close a specific channel. This will remove any resources used by the channel, including: + *
    • any queue subscriptions (this may in turn remove queues if they are auto delete
    • + *
    + * @param channelId id of the channel to close + * @throws org.apache.qpid.AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + void closeChannel(int channelId) throws AMQException; + + /** + * Remove a channel from the session but do not close it. + * @param channelId + */ + void removeChannel(int channelId); + + /** + * Initialise heartbeats on the session. + * @param delay delay in seconds (not ms) + */ + void initHeartbeats(int delay); + + /** + * This must be called when the session is _closed in order to free up any resources + * managed by the session. + */ + void closeSession() throws AMQException; + + /** + * @return a key that uniquely identifies this session + */ + Object getKey(); + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers + * may be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + String getLocalFQDN(); + + /** + * @return the sasl server that can perform authentication for this session. + */ + SaslServer getSaslServer(); + + /** + * Set the sasl server that is to perform authentication for this session. + * @param saslServer + */ + void setSaslServer(SaslServer saslServer); +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/ExchangeInitialiser.java b/java/broker/src/org/apache/qpid/server/protocol/ExchangeInitialiser.java new file mode 100644 index 0000000000..08c31ed3ff --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/ExchangeInitialiser.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; + +public class ExchangeInitialiser +{ + public void initialise(ExchangeFactory factory, ExchangeRegistry registry) throws AMQException{ + define(registry, factory, ExchangeDefaults.DIRECT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS); + define(registry, factory, ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS); + define(registry, factory, ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS); + } + + private void define(ExchangeRegistry r, ExchangeFactory f, + String name, String type) throws AMQException + { + r.registerExchange(f.createExchange(name, type, true, false, 0)); + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/HeartbeatConfig.java b/java/broker/src/org/apache/qpid/server/protocol/HeartbeatConfig.java new file mode 100644 index 0000000000..d7678185d4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/HeartbeatConfig.java @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class HeartbeatConfig +{ + @Configured(path = "heartbeat.delay", defaultValue = "5") + public int delay = 5;//in secs + @Configured(path = "heartbeat.timeoutFactor", defaultValue = "2.0") + public double timeoutFactor = 2; + + public double getTimeoutFactor() + { + return timeoutFactor; + } + + public void setTimeoutFactor(double timeoutFactor) + { + this.timeoutFactor = timeoutFactor; + } + + public int getDelay() + { + return delay; + } + + public void setDelay(int delay) + { + this.delay = delay; + } + + int getTimeout(int writeDelay) + { + return (int) (timeoutFactor * writeDelay); + } + + public static HeartbeatConfig getInstance() + { + return ApplicationRegistry.getInstance().getConfiguredObject(HeartbeatConfig.class); + } + + public String toString() + { + return "HeartBeatConfig{delay = " + delay + " timeoutFactor = " + timeoutFactor + "}"; + } +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/ManagedConnection.java b/java/broker/src/org/apache/qpid/server/protocol/ManagedConnection.java new file mode 100644 index 0000000000..f3fd8bc7e2 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/ManagedConnection.java @@ -0,0 +1,92 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; + +import javax.management.openmbean.TabularData; +import javax.management.openmbean.OpenDataException; +import java.util.Date; + +/** + * The management interface exposed to allow management of Connections. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedConnection +{ + static final String TYPE = "Connection"; + + /** + * Tells the last time, the IO operation was done. + * @return last IO time. + */ + Date getLastIoTime(); + + /** + * Tells the remote address of this connection. + * @return remote address + */ + String getRemoteAddress(); + + /** + * Tells the total number of bytes written till now. + * @return number of bytes written. + */ + long getWrittenBytes(); + + /** + * Tells the total number of bytes read till now. + * @return number of bytes read. + */ + long getReadBytes(); + + /** + * Tells the maximum number of channels that can be opened using + * this connection. This is useful in setting notifications or + * taking required action is there are more channels being created. + * @return maximum number of channels allowed to be created. + */ + long getMaximumNumberOfAllowedChannels(); + + /** + * Sets the maximum number of channels allowed to be created using + * this connection. + * @param value + */ + void setMaximumNumberOfAllowedChannels(long value); + + //********** Operations *****************// + + /** + * Returns channel details of all the channels opened for this connection. + * @return channel details. + */ + TabularData viewChannels() throws OpenDataException; + + /** + * Closes all the channels and unregisters this connection from managed objects. + */ + void closeConnection() throws Exception; + + /** + * Unsubscribes the consumers and unregisters the channel from managed objects. + */ + void closeChannel(int channelId) throws Exception; +} diff --git a/java/broker/src/org/apache/qpid/server/protocol/ManagedSession.java b/java/broker/src/org/apache/qpid/server/protocol/ManagedSession.java new file mode 100644 index 0000000000..2a1a0b62c2 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/protocol/ManagedSession.java @@ -0,0 +1,33 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import java.util.Date; + +public interface ManagedSession +{ + static final String TYPE = "Connection"; + + Date getLastIoTime(); + + String getRemoteAddress(); + + long getWrittenBytes(); + + long getReadBytes(); +} diff --git a/java/broker/src/org/apache/qpid/server/queue/AMQMessage.java b/java/broker/src/org/apache/qpid/server/queue/AMQMessage.java new file mode 100644 index 0000000000..a4ff453720 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/AMQMessage.java @@ -0,0 +1,343 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +import java.util.ArrayList; +import java.util.List; +import java.util.LinkedList; +import java.util.Set; +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Combines the information that make up a deliverable message into a more manageable form. + */ +public class AMQMessage +{ + private final Set _tokens = new HashSet(); + + private AMQProtocolSession _publisher; + + private final BasicPublishBody _publishBody; + + private ContentHeaderBody _contentHeaderBody; + + private List _contentBodies; + + private boolean _redelivered; + + private final long _messageId; + + private final AtomicInteger _referenceCount = new AtomicInteger(1); + + /** + * Keeps a track of how many bytes we have received in body frames + */ + private long _bodyLengthReceived = 0; + + /** + * The message store in which this message is contained. + */ + private transient final MessageStore _store; + + public AMQMessage(MessageStore messageStore, BasicPublishBody publishBody) + { + _messageId = messageStore.getNewMessageId(); + _publishBody = publishBody; + _store = messageStore; + _contentBodies = new LinkedList(); + } + + public AMQMessage(MessageStore store, long messageId, BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody, List contentBodies) + throws AMQException + { + _publishBody = publishBody; + _contentHeaderBody = contentHeaderBody; + _contentBodies = contentBodies; + _messageId = messageId; + _store = store; + storeMessage(); + } + + public AMQMessage(MessageStore store, BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody, List contentBodies) + throws AMQException + { + this(store, store.getNewMessageId(), publishBody, contentHeaderBody, contentBodies); + } + + protected AMQMessage(AMQMessage msg) throws AMQException + { + this(msg._store, msg._messageId, msg._publishBody, msg._contentHeaderBody, msg._contentBodies); + } + + private void storeMessage() throws AMQException + { + if (isPersistent()) + { + _store.put(this); + } + } + + public CompositeAMQDataBlock getDataBlock(ByteBuffer encodedDeliverBody, int channel) + { + AMQFrame[] allFrames = new AMQFrame[1 + _contentBodies.size()]; + + allFrames[0] = ContentHeaderBody.createAMQFrame(channel, _contentHeaderBody); + for (int i = 1; i < allFrames.length; i++) + { + allFrames[i] = ContentBody.createAMQFrame(channel, _contentBodies.get(i - 1)); + } + return new CompositeAMQDataBlock(encodedDeliverBody, allFrames); + } + + public CompositeAMQDataBlock getDataBlock(int channel, String consumerTag, long deliveryTag) + { + AMQFrame[] allFrames = new AMQFrame[2 + _contentBodies.size()]; + + allFrames[0] = BasicDeliverBody.createAMQFrame(channel, consumerTag, deliveryTag, _redelivered, + getExchangeName(), getRoutingKey()); + allFrames[1] = ContentHeaderBody.createAMQFrame(channel, _contentHeaderBody); + for (int i = 2; i < allFrames.length; i++) + { + allFrames[i] = ContentBody.createAMQFrame(channel, _contentBodies.get(i - 2)); + } + return new CompositeAMQDataBlock(allFrames); + } + + public List getPayload() + { + List payload = new ArrayList(2 + _contentBodies.size()); + payload.add(_publishBody); + payload.add(_contentHeaderBody); + payload.addAll(_contentBodies); + return payload; + } + + public BasicPublishBody getPublishBody() + { + return _publishBody; + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) throws AMQException + { + _contentHeaderBody = contentHeaderBody; + if (isAllContentReceived()) + { + storeMessage(); + } + } + + public List getContentBodies() + { + return _contentBodies; + } + + public void setContentBodies(List contentBodies) + { + _contentBodies = contentBodies; + } + + public void addContentBodyFrame(ContentBody contentBody) throws AMQException + { + _contentBodies.add(contentBody); + _bodyLengthReceived += contentBody.getSize(); + if (isAllContentReceived()) + { + storeMessage(); + } + } + + public boolean isAllContentReceived() + { + return _bodyLengthReceived == _contentHeaderBody.bodySize; + } + + public boolean isRedelivered() + { + return _redelivered; + } + + String getExchangeName() + { + return _publishBody.exchange; + } + + String getRoutingKey() + { + return _publishBody.routingKey; + } + + boolean isImmediate() + { + return _publishBody.immediate; + } + + NoConsumersException getNoConsumersException(String queue) + { + return new NoConsumersException(queue, _publishBody, _contentHeaderBody, _contentBodies); + } + + void setRedelivered(boolean redelivered) + { + _redelivered = redelivered; + } + + public long getMessageId() + { + return _messageId; + } + + /** + * Threadsafe. Increment the reference count on the message. + */ + public void incrementReference() + { + _referenceCount.incrementAndGet(); + } + + /** + * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the + * message store. + */ + public void decrementReference() throws AMQException + { + // note that the operation of decrementing the reference count and then removing the message does not + // have to be atomic since the ref count starts at 1 and the exchange itself decrements that after + // the message has been passed to all queues. i.e. we are + // not relying on the all the increments having taken place before the delivery manager decrements. + if (_referenceCount.decrementAndGet() == 0) + { + _store.removeMessage(_messageId); + } + } + + public void setPublisher(AMQProtocolSession publisher) + { + _publisher = publisher; + } + + public AMQProtocolSession getPublisher() + { + return _publisher; + } + + public boolean checkToken(Object token) + { + if(_tokens.contains(token)) + { + return true; + } + else + { + _tokens.add(token); + return false; + } + } + + public void enqueue(AMQQueue queue) throws AMQException + { + //if the message is not persistent or the queue is not durable + //we will not need to recover the association and so do not + //need to record it + if(isPersistent() && queue.isDurable()) + { + _store.enqueueMessage(queue.getName(), _messageId); + } + } + + public void dequeue(AMQQueue queue) throws AMQException + { + //only record associations where both queue and message will survive + //a restart, so only need to remove association if this is the case + if(isPersistent() && queue.isDurable()) + { + _store.dequeueMessage(queue.getName(), _messageId); + } + } + + /** + * Used to requeue a message (on delivery where an acknowledgement is + * expected). This will move it to the end of the queue. + */ + public void requeue(AMQQueue queue) throws AMQException + { + if(isPersistent() && queue.isDurable()) + { + if(!_store.inTran()) + { + //if not already in tran, want to be so this is atomic + _store.beginTran(); + try + { + requeueImpl(queue); + _store.commitTran(); + } + catch(AMQException e) + { + _store.abortTran(); + } + } + else + { + //May already be in tran (e.g. if this is called during delivery + //resulting from a commit). + requeueImpl(queue); + } + } + } + + private void requeueImpl(AMQQueue queue) throws AMQException + { + try + { + _store.dequeueMessage(queue.getName(), _messageId); + _store.enqueueMessage(queue.getName(), _messageId); + } + catch(AMQException e) + { + throw e; + } + catch(Throwable t) + { + throw new AMQException("Failure on requeue of message", t); + } + } + + public boolean isPersistent() throws AMQException + { + if(_contentHeaderBody == null) + { + throw new AMQException("Cannot determine delivery mode of message. Content header not found."); + } + return _contentHeaderBody.properties instanceof BasicContentHeaderProperties + &&((BasicContentHeaderProperties) _contentHeaderBody.properties).getDeliveryMode() == 2; + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/AMQQueue.java b/java/broker/src/org/apache/qpid/server/queue/AMQQueue.java new file mode 100644 index 0000000000..8e744d5960 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/AMQQueue.java @@ -0,0 +1,654 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +import javax.management.openmbean.*; +import javax.management.MBeanNotificationInfo; +import javax.management.AttributeChangeNotification; +import javax.management.Notification; +import javax.management.JMException; +import javax.management.MBeanException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * This is an AMQ Queue, and should not be confused with a JMS queue or any other abstraction like + * that. It is described fully in RFC 006. + */ +public class AMQQueue implements Managable +{ + private static final Logger _logger = Logger.getLogger(AMQQueue.class); + + private final String _name; + + /** + * null means shared + */ + private final String _owner; + + private final boolean _durable; + + /** + * If true, this queue is deleted when the last subscriber is removed + */ + private final boolean _autoDelete; + + /** + * Holds subscribers to the queue. + */ + private final SubscriptionSet _subscribers; + + private final SubscriptionFactory _subscriptionFactory; + + /** + * Manages message delivery. + */ + private final DeliveryManager _deliveryMgr; + + /** + * The queue registry with which this queue is registered. + */ + private final QueueRegistry _queueRegistry; + + /** + * Used to track bindings to exchanges so that on deletion they can easily + * be cancelled. + */ + private final ExchangeBindings _bindings = new ExchangeBindings(this); + + /** + * Executor on which asynchronous delivery will be carriedout where required + */ + private final Executor _asyncDelivery; + + private final AMQQueueMBean _managedObject; + + /** + * max allowed size of a single message. + */ + private long _maxAllowedMessageSize = 0; + + /** + * max allowed number of messages on a queue. + */ + private long _maxAllowedMessageCount = 0; + + /** + * max allowed size in bytes for all the messages combined together in a queue. + */ + private long _queueDepth = 0; + + /** + * total messages received by the queue since startup. + */ + private long _totalMessagesReceived = 0; + + /** + * MBean interface for the implementation AMQQueueMBean. + * This is required for making the implementation a compliant MBean. + */ + public interface AMQQueueMBeanMBean extends ManagedQueue + { + + } + /** + * MBean class for AMQQueue. It implements all the management features exposed + * for an AMQQueue. + */ + private final class AMQQueueMBean extends AMQManagedObject implements AMQQueueMBeanMBean + { + // AMQ message attribute names exposed. + private String[] _msgAttributeNames = { "MessageId", + "Redelivered", + "Content's size", + "Contents" }; + // AMQ Message attribute descriptions. + private String[] _msgAttributeDescriptions = { "Message Id", + "Redelivered", + "Message content's size in bytes", + "Message content bodies" }; + // AMQ message attribute types. + private OpenType[] _msgAttributeTypes = new OpenType[4]; + // Messages will be indexed according to the messageId. + private String[] _msgAttributeIndex = { "MessageId"}; + // Composite type for representing AMQ Message data. + private CompositeType _messageDataType = null; + // Datatype for representing AMQ messages list. + private TabularType _messagelistDataType = null; + + private String[] _contentNames = {"SerialNumber", "ContentBody"}; + private String[] _contentDesc = {"SerialNumber", "Message Content"}; + private String[] _contentIndex = {"SerialNumber"}; + private OpenType[] _contentType = new OpenType[2]; + private CompositeType _contentBodyType = null; + private TabularType _contentBodyListType = null; + + public AMQQueueMBean() + { + super(ManagedQueue.class, ManagedQueue.TYPE); + init(); + } + + private void init() + { + try + { + _contentType[0] = SimpleType.INTEGER; + _contentType[1] = new ArrayType(1, SimpleType.BYTE); + _contentBodyType = new CompositeType("Content", + "Message body content", + _contentNames, + _contentDesc, + _contentType); + _contentBodyListType = new TabularType("MessageContents", + "MessageContent", + _contentBodyType, + _contentIndex); + + _msgAttributeTypes[0] = SimpleType.LONG; + _msgAttributeTypes[1] = SimpleType.BOOLEAN; + _msgAttributeTypes[2] = SimpleType.LONG; + _msgAttributeTypes[3] = _contentBodyListType; + + _messageDataType = new CompositeType("Message", + "AMQ Message", + _msgAttributeNames, + _msgAttributeDescriptions, + _msgAttributeTypes); + _messagelistDataType = new TabularType("Messages", + "List of messages", + _messageDataType, + _msgAttributeIndex); + } + catch (OpenDataException ex) + { + _logger.error("OpenDataTypes could not be created.", ex); + throw new RuntimeException(ex); + } + } + + public String getObjectInstanceName() + { + return _name; + } + + public String getName() + { + return _name; + } + + public boolean isDurable() + { + return _durable; + } + + public String getOwner() + { + return _owner; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getMessageCount() + { + return _deliveryMgr.getQueueMessageCount(); + } + + public long getMaximumMessageSize() + { + return _maxAllowedMessageSize; + } + + public void setMaximumMessageSize(long value) + { + _maxAllowedMessageSize = value; + } + + public int getConsumerCount() + { + return _subscribers.size(); + } + + public int getActiveConsumerCount() + { + return _subscribers.getWeight(); + } + + public long getReceivedMessageCount() + { + return _totalMessagesReceived; + } + + public long getMaximumMessageCount() + { + return _maxAllowedMessageCount; + } + + public void setMaximumMessageCount( long value) + { + _maxAllowedMessageCount = value; + } + + public long getQueueDepth() + { + return _queueDepth; + } + + public void setQueueDepth(long value) + { + _queueDepth = value; + } + + // Operations + + private void checkForNotification() + { + if (getMessageCount() >= getMaximumMessageCount()) + { + Notification n = new Notification( + "Warning", + this, + ++_notificationSequenceNumber, + System.currentTimeMillis(), + "Queue has reached its size limit and is now full."); + + _broadcaster.sendNotification(n); + } + } + + public void deleteMessageFromTop() throws JMException + { + try + { + _deliveryMgr.removeAMessageFromTop(); + } + catch(AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + public void clearQueue() throws JMException + { + try + { + _deliveryMgr.clearAllMessages(); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + + /** + * Returns the messages stored in this queue in tabular form. + * @param beginIndex + * @param endIndex + * @return AMQ messages in tabular form. + * @throws JMException + */ + public TabularData viewMessages(int beginIndex, int endIndex) throws JMException + { + if ((beginIndex > endIndex) || (beginIndex < 1)) + { + throw new JMException("FromIndex = " + beginIndex + ", ToIndex = " + endIndex + + "\nFromIndex should be greater than 0 and less than ToIndex"); + } + + List list = _deliveryMgr.getMessages(); + + if (beginIndex > list.size()) + { + throw new JMException("FromIndex = " + beginIndex + ". There are only " + list.size() + " messages in the queue"); + } + + endIndex = endIndex < list.size() ? endIndex : list.size(); + TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType); + + for (int i = beginIndex; i <= endIndex; i++) + { + AMQMessage msg = list.get(i - 1); + long msgId = msg.getMessageId(); + + List cBodies = msg.getContentBodies(); + + TabularDataSupport _contentList = new TabularDataSupport(_contentBodyListType); + int contentSerialNo = 1; + long size = 0; + + for (ContentBody body : cBodies) + { + if (body.getSize() != 0) + { + Byte[] byteArray = getByteArray(body.payload.slice().array()); + size = size + byteArray.length; + + Object[] contentValues = {contentSerialNo, byteArray}; + CompositeData contentData = new CompositeDataSupport(_contentBodyType, + _contentNames, + contentValues); + + _contentList.put(contentData); + } + } + + Object[] itemValues = {msgId, true, size, _contentList}; + CompositeData messageData = new CompositeDataSupport(_messageDataType, + _msgAttributeNames, + itemValues); + _messageList.put(messageData); + } + + return _messageList; + } + + /** + * A utility to convert byte[] to Byte[]. Required to create composite + * type for message contents. + * @param byteArray message content as byte[] + * @return Byte[] + */ + private Byte[] getByteArray(byte[] byteArray) + { + int size = byteArray.length; + List list = new ArrayList(); + + for (int i = 0; i < size; i++) + { + list.add(byteArray[i]); + } + + return list.toArray(new Byte[0]); + } + + /** + * Creates all the notifications this MBean can send. + * @return Notifications broadcasted by this MBean. + */ + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] + {AttributeChangeNotification.ATTRIBUTE_CHANGE}; + String name = AttributeChangeNotification.class.getName(); + String description = "An attribute of this MBean has changed"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, + name, + description); + + return new MBeanNotificationInfo[] {info1}; + } + + } // End of AMQMBean class + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), new SubscriptionImpl.Factory()); + } + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, SubscriptionFactory subscriptionFactory) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscriptionFactory); + } + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, Executor asyncDelivery, + SubscriptionFactory subscriptionFactory) + throws AMQException + { + + this(name, durable, owner, autoDelete, queueRegistry, asyncDelivery, new SubscriptionSet(), subscriptionFactory); + } + + public AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, Executor asyncDelivery) + throws AMQException + { + + this(name, durable, owner, autoDelete, queueRegistry, asyncDelivery, new SubscriptionSet(), + new SubscriptionImpl.Factory()); + } + + protected AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, + SubscriptionSet subscribers, SubscriptionFactory subscriptionFactory) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers, subscriptionFactory); + } + + protected AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, + SubscriptionSet subscribers) + throws AMQException + { + this(name, durable, owner, autoDelete, queueRegistry, + AsyncDeliveryConfig.getAsyncDeliveryExecutor(), subscribers, new SubscriptionImpl.Factory()); + } + + protected AMQQueue(String name, boolean durable, String owner, + boolean autoDelete, QueueRegistry queueRegistry, + Executor asyncDelivery, SubscriptionSet subscribers, SubscriptionFactory subscriptionFactory) + throws AMQException + { + if (name == null) + { + throw new IllegalArgumentException("Queue name must not be null"); + } + if (queueRegistry == null) + { + throw new IllegalArgumentException("Queue registry must not be null"); + } + _name = name; + _durable = durable; + _owner = owner; + _autoDelete = autoDelete; + _queueRegistry = queueRegistry; + _asyncDelivery = asyncDelivery; + _managedObject = new AMQQueueMBean(); + _managedObject.register(); + + _subscribers = subscribers; + _subscriptionFactory = subscriptionFactory; + _deliveryMgr = new DeliveryManager(_subscribers, this); + } + + public String getName() + { + return _name; + } + + public boolean isShared() + { + return _owner == null; + } + + public boolean isDurable() + { + return _durable; + } + + public String getOwner() + { + return _owner; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getMessageCount() + { + return _deliveryMgr.getQueueMessageCount(); + } + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + public void bind(String routingKey, Exchange exchange) + { + _bindings.addBinding(routingKey, exchange); + } + + public void registerProtocolSession(AMQProtocolSession ps, int channel, String consumerTag, boolean acks) + throws AMQException + { + debug("Registering protocol session {0} with channel {1} and consumer tag {2} with {3}", ps, channel, consumerTag, this); + + Subscription subscription = _subscriptionFactory.createSubscription(channel, ps, consumerTag, acks); + _subscribers.addSubscriber(subscription); + } + + public void unregisterProtocolSession(AMQProtocolSession ps, int channel, String consumerTag) throws AMQException + { + debug("Unregistering protocol session {0} with channel {1} and consumer tag {2} from {3}", ps, channel, consumerTag, + this); + + Subscription removedSubscription; + if ((removedSubscription = _subscribers.removeSubscriber(_subscriptionFactory.createSubscription(channel, + ps, + consumerTag))) + == null) + { + throw new AMQException("Protocol session with channel " + channel + " and consumer tag " + consumerTag + + " and protocol session key " + ps.getKey() + " not registered with queue " + this); + } + + // if we are eligible for auto deletion, unregister from the queue registry + if (_autoDelete && _subscribers.isEmpty()) + { + autodelete(); + // we need to manually fire the event to the removed subscription (which was the last one left for this + // queue. This is because the delete method uses the subscription set which has just been cleared + removedSubscription.queueDeleted(this); + } + } + + public int delete(boolean checkUnused, boolean checkEmpty) throws AMQException + { + if(checkUnused && !_subscribers.isEmpty()) + { + _logger.info("Will not delete " + this + " as it is in use."); + return 0; + } + else if(checkEmpty && _deliveryMgr.getQueueMessageCount() > 0) + { + _logger.info("Will not delete " + this + " as it is not empty."); + return 0; + } + else + { + delete(); + return _deliveryMgr.getQueueMessageCount(); + } + } + + public void delete() throws AMQException + { + _subscribers.queueDeleted(this); + _bindings.deregister(); + _queueRegistry.unregisterQueue(_name); + _managedObject.unregister(); + } + + protected void autodelete() throws AMQException + { + debug("autodeleting {0}", this); + delete(); + } + + public void deliver(AMQMessage msg) throws AMQException + { + msg.enqueue(this); + _deliveryMgr.deliver(getName(), msg); + updateReceivedMessageCount(); + } + + public void deliverAsync() + { + _deliveryMgr.processAsync(_asyncDelivery); + } + + protected SubscriptionManager getSubscribers() + { + return _subscribers; + } + + protected void updateReceivedMessageCount() + { + _totalMessagesReceived++; + _managedObject.checkForNotification(); + } + + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + final AMQQueue amqQueue = (AMQQueue) o; + + return (_name.equals(amqQueue._name)); + } + + public int hashCode() + { + return _name.hashCode(); + } + + public String toString() + { + return "Queue(" + _name + ")@" + System.identityHashCode(this); + } + + private void debug(String msg, Object... args) + { + if(_logger.isDebugEnabled()) + { + _logger.debug(MessageFormat.format(msg, args)); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/AsyncDeliveryConfig.java b/java/broker/src/org/apache/qpid/server/queue/AsyncDeliveryConfig.java new file mode 100644 index 0000000000..60788c1ccb --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/AsyncDeliveryConfig.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.configuration.Configured; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class AsyncDeliveryConfig +{ + private Executor _executor; + + @Configured(path = "delivery.poolsize", defaultValue = "0") + public int poolSize; + + public Executor getExecutor() + { + if (_executor == null) + { + if (poolSize > 0) + { + _executor = Executors.newFixedThreadPool(poolSize); + } + else + { + _executor = Executors.newCachedThreadPool(); + } + } + return _executor; + } + + public static Executor getAsyncDeliveryExecutor() + { + return ApplicationRegistry.getInstance().getConfiguredObject(AsyncDeliveryConfig.class).getExecutor(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/java/broker/src/org/apache/qpid/server/queue/DefaultQueueRegistry.java new file mode 100644 index 0000000000..a7dc98ec22 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/DefaultQueueRegistry.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; + +public class DefaultQueueRegistry implements QueueRegistry +{ + private ConcurrentMap _queueMap = new ConcurrentHashMap(); + + public DefaultQueueRegistry() + { + } + + public void registerQueue(AMQQueue queue) throws AMQException + { + _queueMap.put(queue.getName(), queue); + } + + public void unregisterQueue(String name) throws AMQException + { + _queueMap.remove(name); + } + + public AMQQueue getQueue(String name) + { + return _queueMap.get(name); + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/DeliveryManager.java b/java/broker/src/org/apache/qpid/server/queue/DeliveryManager.java new file mode 100644 index 0000000000..fbd952073e --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/DeliveryManager.java @@ -0,0 +1,262 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Manages delivery of messages on behalf of a queue + * + */ +class DeliveryManager +{ + private static final Logger _log = Logger.getLogger(DeliveryManager.class); + + /** + * Holds any queued messages + */ + private final Queue _messages = new LinkedList(); + /** + * Ensures that only one asynchronous task is running for this manager at + * any time. + */ + private final AtomicBoolean _processing = new AtomicBoolean(); + /** + * The subscriptions on the queue to whom messages are delivered + */ + private final SubscriptionManager _subscriptions; + + /** + * An indication of the mode we are in. If this is true then messages are + * being queued up in _messages for asynchronous delivery. If it is false + * then messages can be delivered directly as they come in. + */ + private boolean _queueing; + + /** + * A reference to the queue we are delivering messages for. We need this to be able + * to pass the code that handles acknowledgements a handle on the queue. + */ + private final AMQQueue _queue; + + DeliveryManager(SubscriptionManager subscriptions, AMQQueue queue) + { + _subscriptions = subscriptions; + _queue = queue; + } + + private synchronized boolean enqueue(AMQMessage msg) + { + if (_queueing) + { + _messages.offer(msg); + return true; + } + else + { + return false; + } + } + + private synchronized void startQueueing(AMQMessage msg) + { + _queueing = true; + enqueue(msg); + } + + /** + * Determines whether there are queued messages. Sets _queueing to false if + * there are no queued messages. This needs to be atomic. + * + * @return true if there are queued messages + */ + private synchronized boolean hasQueuedMessages() + { + boolean empty = _messages.isEmpty(); + if (empty) + { + _queueing = false; + } + return !empty; + } + + public synchronized int getQueueMessageCount() + { + return _messages.size(); + } + + protected synchronized List getMessages() + { + return new ArrayList(_messages); + } + + protected synchronized void removeAMessageFromTop() throws AMQException + { + AMQMessage msg = poll(); + if (msg != null) + { + msg.dequeue(_queue); + } + } + + protected synchronized void clearAllMessages() throws AMQException + { + AMQMessage msg = poll(); + while (msg != null) + { + msg.dequeue(_queue); + msg = poll(); + } + } + + /** + * Only one thread should ever execute this method concurrently, but + * it can do so while other threads invoke deliver(). + */ + private void processQueue() + { + try + { + boolean hasSubscribers = _subscriptions.hasActiveSubscribers(); + while (hasQueuedMessages() && hasSubscribers) + { + Subscription next = _subscriptions.nextSubscriber(peek()); + //We don't synchronize access to subscribers so need to re-check + if (next != null) + { + try + { + next.send(poll(), _queue); + } + catch (AMQException e) + { + _log.error("Unable to deliver message: " + e, e); + } + } + else + { + hasSubscribers = false; + } + } + } + finally + { + _processing.set(false); + } + } + + private synchronized AMQMessage peek() + { + return _messages.peek(); + } + + private synchronized AMQMessage poll() + { + return _messages.poll(); + } + + /** + * Requests that the delivery manager start processing the queue asynchronously + * if there is work that can be done (i.e. there are messages queued up and + * subscribers that can receive them. + *

    + * This should be called when subscribers are added, but only after the consume-ok + * message has been returned as message delivery may start immediately. It should also + * be called after unsuspending a client. + *

    + * + * @param executor the executor on which the delivery should take place + */ + void processAsync(Executor executor) + { + if (hasQueuedMessages() && _subscriptions.hasActiveSubscribers()) + { + //are we already running? if so, don't re-run + if (_processing.compareAndSet(false, true)) + { + executor.execute(new Runner()); + } + } + } + + /** + * Handles message delivery. The delivery manager is always in one of two modes; + * it is either queueing messages for asynchronous delivery or delivering + * directly. + * + * @param name the name of the entity on whose behalf we are delivering the message + * @param msg the message to deliver + * @throws NoConsumersException if there are no active subscribers to deliver + * the message to + */ + void deliver(String name, AMQMessage msg) throws AMQException + { + msg.incrementReference(); + // first check whether we are queueing, and enqueue if we are + if (!enqueue(msg)) + { + // not queueing so deliver message to 'next' subscriber + Subscription s = _subscriptions.nextSubscriber(msg); + if (s == null) + { + if (msg.isImmediate()) + { + throw msg.getNoConsumersException(name); + } + else + { + // no subscribers yet so enter 'queueing' mode and queue this message + startQueueing(msg); + } + } + else + { + s.send(msg, _queue); + } + } + + else + { + if (msg.isImmediate()) + { + //todo check with spec to see if enqueing for immediate client delivery is ok. + Subscription s = _subscriptions.nextSubscriber(msg); + if (s == null) + { + throw msg.getNoConsumersException(name); + } + } + } + } + + private class Runner implements Runnable + { + public void run() + { + processQueue(); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/ExchangeBindings.java b/java/broker/src/org/apache/qpid/server/queue/ExchangeBindings.java new file mode 100644 index 0000000000..424330bd11 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/ExchangeBindings.java @@ -0,0 +1,109 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.AMQException; + +import java.util.List; +import java.util.HashSet; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * When a queue is deleted, it should be deregistered from any + * exchange it has been bound to. This class assists in this task, + * by keeping track of all bindings for a given queue. + */ +class ExchangeBindings +{ + static class ExchangeBinding + { + private final Exchange exchange; + private final String routingKey; + + ExchangeBinding(String routingKey, Exchange exchange) + { + this.routingKey = routingKey; + this.exchange = exchange; + } + + void unbind(AMQQueue queue) throws AMQException + { + exchange.deregisterQueue(routingKey, queue); + } + + public Exchange getExchange() + { + return exchange; + } + + public String getRoutingKey() + { + return routingKey; + } + + public int hashCode() + { + return exchange.hashCode() + routingKey.hashCode(); + } + + public boolean equals(Object o) + { + if (!(o instanceof ExchangeBinding)) return false; + ExchangeBinding eb = (ExchangeBinding) o; + return exchange.equals(eb.exchange) && routingKey.equals(eb.routingKey); + } + } + + private final List _bindings = new CopyOnWriteArrayList(); + private final AMQQueue _queue; + + ExchangeBindings(AMQQueue queue) + { + _queue = queue; + } + + /** + * Adds the specified binding to those being tracked. + * @param routingKey the routing key with which the queue whose bindings + * are being tracked by the instance has been bound to the exchange + * @param exchange the exchange bound to + */ + void addBinding(String routingKey, Exchange exchange) + { + _bindings.add(new ExchangeBinding(routingKey, exchange)); + } + + /** + * Deregisters this queue from any exchange it has been bound to + */ + void deregister() throws AMQException + { + //remove duplicates at this point + HashSet copy = new HashSet(_bindings); + for (ExchangeBinding b : copy) + { + b.unbind(_queue); + } + } + + List getExchangeBindings() + { + return _bindings; + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/ManagedQueue.java b/java/broker/src/org/apache/qpid/server/queue/ManagedQueue.java new file mode 100644 index 0000000000..03a0a10337 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/ManagedQueue.java @@ -0,0 +1,177 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import javax.management.openmbean.TabularData; +import javax.management.JMException; +import java.io.IOException; + +/** + * The management interface exposed to allow management of a queue. + * @author Robert J. Greig + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public interface ManagedQueue +{ + static final String TYPE = "Queue"; + + /** + * Returns the Name of the ManagedQueue. + * @return the name of the managedQueue. + * @throws IOException + */ + String getName() throws IOException; + + /** + * Tells whether this ManagedQueue is durable or not. + * @return true if this ManagedQueue is a durable queue. + * @throws IOException + */ + boolean isDurable() throws IOException; + + /** + * Tells the Owner of the ManagedQueue. + * @return the owner's name. + * @throws IOException + */ + String getOwner() throws IOException; + + /** + * Tells if the ManagedQueue is set to AutoDelete. + * @return true if the ManagedQueue is set to AutoDelete. + * @throws IOException + */ + boolean isAutoDelete() throws IOException; + + /** + * Gets the total number of messages on the queue, which are yet to be + * delivered to the consumer(s). + * @return number of undelivered message in the Queue. + * @throws IOException + */ + int getMessageCount() throws IOException; + + /** + * Returns the maximum size of a message (in bytes) allowed to be accepted by the + * ManagedQueue. This is useful in setting notifications or taking + * appropriate action, if the size of the message received is more than + * the allowed size. + * @return the maximum size of a message allowed to be aceepted by the + * ManagedQueue. + * @throws IOException + */ + long getMaximumMessageSize() throws IOException; + + /** + * Sets the maximum size of the message (in bytes) that is allowed to be + * accepted by the Queue. + * @param bytes maximum size of message. + * @throws IOException + */ + void setMaximumMessageSize(long bytes) throws IOException; + + /** + * Returns the total number of subscribers to the queue. + * @return the number of subscribers. + * @throws IOException + */ + int getConsumerCount() throws IOException; + + /** + * Returns the total number of active subscribers to the queue. + * @return the number of active subscribers + * @throws IOException + */ + int getActiveConsumerCount() throws IOException; + + /** + * Tells the total number of messages receieved by the queue since startup. + * @return total number of messages received. + * @throws IOException + */ + long getReceivedMessageCount() throws IOException; + + /** + * Tells the maximum number of messages that can be stored in the queue. + * This is useful in setting the notifications or taking required + * action is the number of message increase this limit. + * @return maximum muber of message allowed to be stored in the queue. + * @throws IOException + */ + long getMaximumMessageCount() throws IOException; + + /** + * Sets the maximum number of messages allowed to be stored in the queue. + * @param value the maximum number of messages allowed to be stored in the queue. + * @throws IOException + */ + void setMaximumMessageCount(long value) throws IOException; + + /** + * Tells the maximum size of all the messages combined together, + * that can be stored in the queue. This is useful for setting notifications + * or taking required action if the size of messages stored in the queue + * increases over this limit. + * @return maximum size of the all the messages allowed for the queue. + * @throws IOException + */ + long getQueueDepth() throws IOException; + + /** + * Sets the maximum size of all the messages together, that can be stored + * in the queue. + * @param value + * @throws IOException + */ + void setQueueDepth(long value) throws IOException; + + + + //********** Operations *****************// + + + /** + * Returns a subset of all the messages stored in the queue. The messages + * are returned based on the given index numbers. + * @param fromIndex + * @param toIndex + * @return + * @throws IOException + * @throws JMException + */ + TabularData viewMessages(int fromIndex, int toIndex) + throws IOException, JMException; + + /** + * Deletes the first message from top. + * @throws IOException + * @throws JMException + */ + void deleteMessageFromTop() + throws IOException, JMException; + + /** + * Clears the queue by deleting all the messages from the queue. + * @throws IOException + * @throws JMException + */ + void clearQueue() + throws IOException, JMException; + +} diff --git a/java/broker/src/org/apache/qpid/server/queue/NoConsumersException.java b/java/broker/src/org/apache/qpid/server/queue/NoConsumersException.java new file mode 100644 index 0000000000..0616f75633 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/NoConsumersException.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.RequiredDeliveryException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.protocol.AMQConstant; + +import java.util.List; + +/** + * Signals that no consumers exist for a message at a given point in time. + * Used if a message has immediate=true and there are no consumers registered + * with the queue. + */ +public class NoConsumersException extends RequiredDeliveryException +{ + public NoConsumersException(String queue, + BasicPublishBody publishBody, + ContentHeaderBody contentHeaderBody, + List contentBodies) + { + super("Immediate delivery to " + queue + " is not possible.", publishBody, contentHeaderBody, contentBodies); + } + + public int getReplyCode() + { + return AMQConstant.NO_CONSUMERS.getCode(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/QueueRegistry.java b/java/broker/src/org/apache/qpid/server/queue/QueueRegistry.java new file mode 100644 index 0000000000..bc5599fb20 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/QueueRegistry.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + + +public interface QueueRegistry +{ + void registerQueue(AMQQueue queue) throws AMQException; + + void unregisterQueue(String name) throws AMQException; + + AMQQueue getQueue(String name); +} diff --git a/java/broker/src/org/apache/qpid/server/queue/Subscription.java b/java/broker/src/org/apache/qpid/server/queue/Subscription.java new file mode 100644 index 0000000000..d76e9ec105 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/Subscription.java @@ -0,0 +1,29 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +public interface Subscription +{ + void send(AMQMessage msg, AMQQueue queue) throws AMQException; + + boolean isSuspended(); + + void queueDeleted(AMQQueue queue); +} diff --git a/java/broker/src/org/apache/qpid/server/queue/SubscriptionFactory.java b/java/broker/src/org/apache/qpid/server/queue/SubscriptionFactory.java new file mode 100644 index 0000000000..127b19b0e4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/SubscriptionFactory.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +/** + * Allows the customisation of the creation of a subscription. This is typically done within an AMQQueue. This + * factory primarily assists testing although in future more sophisticated subscribers may need a different + * subscription implementation. + * + * @see org.apache.qpid.server.queue.AMQQueue + */ +public interface SubscriptionFactory +{ + Subscription createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks) + throws AMQException; + + Subscription createSubscription(int channel, AMQProtocolSession protocolSession,String consumerTag) + throws AMQException; +} diff --git a/java/broker/src/org/apache/qpid/server/queue/SubscriptionImpl.java b/java/broker/src/org/apache/qpid/server/queue/SubscriptionImpl.java new file mode 100644 index 0000000000..a3c2fab4f4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/SubscriptionImpl.java @@ -0,0 +1,172 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicDeliverBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; + +/** + * Encapsulation of a supscription to a queue. + *

    + * Ties together the protocol session of a subscriber, the consumer tag that + * was given out by the broker and the channel id. + *

    + */ +public class SubscriptionImpl implements Subscription +{ + private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class); + + public final AMQChannel channel; + + public final AMQProtocolSession protocolSession; + + public final String consumerTag; + + private final Object sessionKey; + + /** + * True if messages need to be acknowledged + */ + private final boolean _acks; + + public static class Factory implements SubscriptionFactory + { + public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag, boolean acks) + throws AMQException + { + return new SubscriptionImpl(channel, protocolSession, consumerTag, acks); + } + + public SubscriptionImpl createSubscription(int channel, AMQProtocolSession protocolSession, String consumerTag) + throws AMQException + { + return new SubscriptionImpl(channel, protocolSession, consumerTag); + } + } + + public SubscriptionImpl(int channelId, AMQProtocolSession protocolSession, + String consumerTag, boolean acks) + throws AMQException + { + AMQChannel channel = protocolSession.getChannel(channelId); + if (channel == null) { + throw new NullPointerException("channel not found in protocol session"); + } + + this.channel = channel; + this.protocolSession = protocolSession; + this.consumerTag = consumerTag; + sessionKey = protocolSession.getKey(); + _acks = acks; + } + + public SubscriptionImpl(int channel, AMQProtocolSession protocolSession, + String consumerTag) + throws AMQException + { + this(channel, protocolSession, consumerTag, false); + } + + public boolean equals(Object o) + { + return (o instanceof SubscriptionImpl) && equals((SubscriptionImpl) o); + } + + /** + * Equality holds if the session matches and the channel and consumer tag are the same. + */ + private boolean equals(SubscriptionImpl psc) + { + return sessionKey.equals(psc.sessionKey) + && psc.channel == channel + && psc.consumerTag.equals(consumerTag); + } + + public int hashCode() + { + return sessionKey.hashCode(); + } + + public String toString() + { + return "[channel=" + channel + ", consumerTag=" + consumerTag + ", session=" + protocolSession.getKey() + "]"; + } + + public void send(AMQMessage msg, AMQQueue queue) throws AMQException + { + if (msg != null) + { + final long deliveryTag = channel.getNextDeliveryTag(); + ByteBuffer deliver = createEncodedDeliverFrame(deliveryTag, msg.getRoutingKey(), msg.getExchangeName()); + AMQDataBlock frame = msg.getDataBlock(deliver, channel.getChannelId()); + if (_acks) + { + channel.addUnacknowledgedMessage(msg, deliveryTag, consumerTag, queue); + } + protocolSession.writeFrame(frame); + // if we do not need to wait for client acknowledgements we can decrement + // the reference count immediately + if (!_acks) + { + msg.decrementReference(); + msg.dequeue(queue); + } + else + { + //move the msg to the back of the persistently recorded queue while + //witing for ack + msg.requeue(queue); + } + } + else + { + _logger.error("Attempt to send Null message", new NullPointerException()); + } + } + + public boolean isSuspended() + { + return channel.isSuspended(); + } + + /** + * Callback indicating that a queue has been deleted. + * @param queue + */ + public void queueDeleted(AMQQueue queue) + { + channel.queueDeleted(queue); + } + + private ByteBuffer createEncodedDeliverFrame(long deliveryTag, String routingKey, String exchange) + { + AMQFrame deliverFrame = BasicDeliverBody.createAMQFrame(channel.getChannelId(), consumerTag, + deliveryTag, false, exchange, + routingKey); + ByteBuffer buf = ByteBuffer.allocate((int) deliverFrame.getSize()); // XXX: Could cast be a problem? + deliverFrame.writePayload(buf); + buf.flip(); + return buf; + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/SubscriptionManager.java b/java/broker/src/org/apache/qpid/server/queue/SubscriptionManager.java new file mode 100644 index 0000000000..185b0e4268 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/SubscriptionManager.java @@ -0,0 +1,28 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +/** + * Abstraction of actor that will determine the subscriber to whom + * a message will be sent. + */ +public interface SubscriptionManager +{ + public boolean hasActiveSubscribers(); + public Subscription nextSubscriber(AMQMessage msg); +} diff --git a/java/broker/src/org/apache/qpid/server/queue/SubscriptionSet.java b/java/broker/src/org/apache/qpid/server/queue/SubscriptionSet.java new file mode 100644 index 0000000000..3b53792234 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/SubscriptionSet.java @@ -0,0 +1,180 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Holds a set of subscriptions for a queue and manages the round + * robin-ing of deliver etc. + */ +class SubscriptionSet implements WeightedSubscriptionManager +{ + private static final Logger _log = Logger.getLogger(SubscriptionSet.class); + + /** + * List of registered subscribers + */ + private List _subscriptions = new CopyOnWriteArrayList(); + + /** + * Used to control the round robin delivery of content + */ + private int _currentSubscriber; + + /** + * Accessor for unit tests. + */ + int getCurrentSubscriber() + { + return _currentSubscriber; + } + + public void addSubscriber(Subscription subscription) + { + _subscriptions.add(subscription); + } + + /** + * Remove the subscription, returning it if it was found + * @param subscription + * @return null if no match was found + */ + public Subscription removeSubscriber(Subscription subscription) + { + boolean isRemoved = _subscriptions.remove(subscription); // TODO: possibly need O(1) operation here. + if (isRemoved) + { + return subscription; + } + else + { + debugDumpSubscription(subscription); + return null; + } + } + + private void debugDumpSubscription(Subscription subscription) + { + if (_log.isDebugEnabled()) + { + _log.debug("Subscription " + subscription + " not found. Dumping subscriptions:"); + for (Subscription s : _subscriptions) + { + _log.debug("Subscription: " + s); + } + _log.debug("Subscription dump complete"); + } + } + + /** + * Return the next unsuspended subscription or null if not found. + * + * Performance note: + * This method can scan all items twice when looking for a subscription that is not + * suspended. The worst case occcurs when all subscriptions are suspended. However, it is does this + * without synchronisation and subscriptions may be added and removed concurrently. Also note that because of + * race conditions and when subscriptions are removed between calls to nextSubscriber, the + * IndexOutOfBoundsException also causes the scan to start at the beginning. + */ + public Subscription nextSubscriber(AMQMessage msg) + { + if (_subscriptions.isEmpty()) + { + return null; + } + + try { + final Subscription result = nextSubscriber(); + if (result == null) { + _currentSubscriber = 0; + return nextSubscriber(); + } else { + return result; + } + } catch (IndexOutOfBoundsException e) { + _currentSubscriber = 0; + return nextSubscriber(); + } + } + + private Subscription nextSubscriber() + { + final ListIterator iterator = _subscriptions.listIterator(_currentSubscriber); + while (iterator.hasNext()) { + Subscription subscription = iterator.next(); + ++_currentSubscriber; + subscriberScanned(); + if (!subscription.isSuspended()) { + return subscription; + } + } + return null; + } + + /** + * Overridden in test classes. + */ + protected void subscriberScanned() + { + } + + public boolean isEmpty() + { + return _subscriptions.isEmpty(); + } + + public boolean hasActiveSubscribers() + { + for (Subscription s : _subscriptions) + { + if (!s.isSuspended()) return true; + } + return false; + } + + public int getWeight() + { + int count = 0; + for (Subscription s : _subscriptions) + { + if (!s.isSuspended()) count++; + } + return count; + } + + /** + * Notification that a queue has been deleted. This is called so that the subscription can inform the + * channel, which in turn can update its list of unacknowledged messages. + * @param queue + */ + public void queueDeleted(AMQQueue queue) + { + for (Subscription s : _subscriptions) + { + s.queueDeleted(queue); + } + } + + int size() { + return _subscriptions.size(); + } +} diff --git a/java/broker/src/org/apache/qpid/server/queue/WeightedSubscriptionManager.java b/java/broker/src/org/apache/qpid/server/queue/WeightedSubscriptionManager.java new file mode 100644 index 0000000000..adf6aefdfb --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/queue/WeightedSubscriptionManager.java @@ -0,0 +1,23 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +public interface WeightedSubscriptionManager extends SubscriptionManager +{ + public int getWeight(); +} diff --git a/java/broker/src/org/apache/qpid/server/registry/ApplicationRegistry.java b/java/broker/src/org/apache/qpid/server/registry/ApplicationRegistry.java new file mode 100644 index 0000000000..4d1b0dbcfe --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.registry; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.Configurator; + +import java.util.HashMap; +import java.util.Map; + +/** + * An abstract application registry that provides access to configuration information and handles the + * construction and caching of configurable objects. + * + * Subclasses should handle the construction of the "registered objects" such as the exchange registry. + * + */ +public abstract class ApplicationRegistry implements IApplicationRegistry +{ + private static final Logger _logger = Logger.getLogger(ApplicationRegistry.class); + + private static IApplicationRegistry _instance; + + private final Map, Object> _configuredObjects = new HashMap, Object>(); + + protected final Configuration _configuration; + + private static class ShutdownService implements Runnable + { + public void run() + { + _logger.info("Shutting down application registry..."); + try + { + _instance.getMessageStore().close(); + } + catch (Exception e) + { + _logger.error("Error shutting down message store: " + e, e); + } + } + } + + public static void initialise(IApplicationRegistry instance) throws Exception + { + _instance = instance; + instance.initialise(); + Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService())); + } + + protected ApplicationRegistry(Configuration configuration) + { + _configuration = configuration; + } + + public static IApplicationRegistry getInstance() + { + if (_instance == null) + { + throw new RuntimeException("Application registry not initialised"); + } + else + { + return _instance; + } + } + + public Configuration getConfiguration() + { + return _configuration; + } + + public T getConfiguredObject(Class instanceType) + { + T instance = (T) _configuredObjects.get(instanceType); + if (instance == null) + { + try + { + instance = instanceType.newInstance(); + } + catch (Exception e) + { + _logger.error("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor"); + throw new IllegalArgumentException("Unable to instantiate configuration class " + instanceType + " - ensure it has a public default constructor"); + } + Configurator.configure(instance); + _configuredObjects.put(instanceType, instance); + } + return instance; + } +} diff --git a/java/broker/src/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/java/broker/src/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java new file mode 100644 index 0000000000..db79ae8876 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java @@ -0,0 +1,155 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.registry; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.management.JMXManagedObjectRegistry; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.management.ManagementConfiguration; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.SASLAuthenticationManager; +import org.apache.qpid.server.store.MessageStore; + +import java.io.File; + +public class ConfigurationFileApplicationRegistry extends ApplicationRegistry +{ + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private ManagedObjectRegistry _managedObjectRegistry; + + private AuthenticationManager _authenticationManager; + + private MessageStore _messageStore; + + public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException + { + super(config(configurationURL)); + } + + // Our configuration class needs to make the interpolate method + // public so it can be called below from the config method. + private static class MyConfiguration extends CompositeConfiguration { + public String interpolate(String obj) { + return super.interpolate(obj); + } + } + + private static final Configuration config(File url) throws ConfigurationException { + // We have to override the interpolate methods so that + // interpolation takes place accross the entirety of the + // composite configuration. Without doing this each + // configuration object only interpolates variables defined + // inside itself. + final MyConfiguration conf = new MyConfiguration(); + conf.addConfiguration(new SystemConfiguration() { + protected String interpolate(String o) { + return conf.interpolate(o); + } + }); + conf.addConfiguration(new XMLConfiguration(url) { + protected String interpolate(String o) { + return conf.interpolate(o); + } + }); + return conf; + } + + public void initialise() throws Exception + { + initialiseManagedObjectRegistry(); + _queueRegistry = new DefaultQueueRegistry(); + _exchangeFactory = new DefaultExchangeFactory(); + _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory); + _authenticationManager = new SASLAuthenticationManager(); + initialiseMessageStore(); + } + + private void initialiseManagedObjectRegistry() + { + ManagementConfiguration config = getConfiguredObject(ManagementConfiguration.class); + if (config.enabled) + { + _managedObjectRegistry = new JMXManagedObjectRegistry(); + } + else + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + } + } + + private void initialiseMessageStore() throws Exception + { + String messageStoreClass = _configuration.getString("store.class"); + Class clazz = Class.forName(messageStoreClass); + Object o = clazz.newInstance(); + + if (!(o instanceof MessageStore)) + { + throw new Exception("Message store class must implement " + MessageStore.class + ". Class " + clazz + + " does not."); + } + _messageStore = (MessageStore) o; + _messageStore.configure(getQueueRegistry(), "store", _configuration); + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } +} diff --git a/java/broker/src/org/apache/qpid/server/registry/IApplicationRegistry.java b/java/broker/src/org/apache/qpid/server/registry/IApplicationRegistry.java new file mode 100644 index 0000000000..0102f78424 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/registry/IApplicationRegistry.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.registry; + +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.commons.configuration.Configuration; + +public interface IApplicationRegistry +{ + /** + * Initialise the application registry. All initialisation must be done in this method so that any components + * that need access to the application registry itself for initialisation are able to use it. Attempting to + * initialise in the constructor will lead to failures since the registry reference will not have been set. + */ + void initialise() throws Exception; + + /** + * This gets access to a "configured object". A configured object has fields populated from a the configuration + * object (Commons Configuration) automatically, where it has the appropriate attributes defined on fields. + * Application registry implementations can choose the refresh strategy or caching approach. + * @param instanceType the type of object you want initialised. This must be unique - i.e. you can only + * have a single object of this type in the system. + * @return the configured object + */ + T getConfiguredObject(Class instanceType); + + /** + * Get the low level configuration. For use cases where the configured object approach is not required + * you can get the complete configuration information. + * @return a Commons Configuration instance + */ + Configuration getConfiguration(); + + QueueRegistry getQueueRegistry(); + + ExchangeRegistry getExchangeRegistry(); + + ExchangeFactory getExchangeFactory(); + + ManagedObjectRegistry getManagedObjectRegistry(); + + AuthenticationManager getAuthenticationManager(); + + MessageStore getMessageStore(); +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationManager.java b/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationManager.java new file mode 100644 index 0000000000..0adb7b98e2 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationManager.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public interface AuthenticationManager +{ + String getMechanisms(); + + SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException; + + AuthenticationResult authenticate(SaslServer server, byte[] response); +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java b/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java new file mode 100644 index 0000000000..71e3c81ae4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationProviderInitialiser.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import org.apache.commons.configuration.Configuration; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public interface AuthenticationProviderInitialiser +{ + /** + * @return the mechanism's name. This will be used in the list of mechanism's advertised to the + * client. + */ + String getMechanismName(); + + /** + * Initialise the authentication provider. + * @param baseConfigPath the path in the config file that points to any config options for this provider. Each + * provider can have its own set of configuration options + * @param configuration the Apache Commons Configuration instance used to configure this provider + * @param principalDatabases the set of principal databases that are available + */ + void initialise(String baseConfigPath, Configuration configuration, + Map principalDatabases) throws Exception; + + /** + * @return the callback handler that should be used to process authentication requests for this mechanism. This will + * be called after initialise and will be stored by the authentication manager. The callback handler must be + * fully threadsafe. + */ + CallbackHandler getCallbackHandler(); + + /** + * Get the properties that must be passed in to the Sasl.createSaslServer method. + * @return the properties, which may be null + */ + Map getProperties(); + + /** + * Get the class that is the server factory. This is used for the JCA registration. + * @return null if no JCA registration is required, otherwise return the class + * that will be used in JCA registration + */ + Class getServerFactoryClassForJCARegistration(); +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationResult.java b/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationResult.java new file mode 100644 index 0000000000..d7dcf2c973 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/AuthenticationResult.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +public class AuthenticationResult +{ + public enum AuthenticationStatus + { + SUCCESS, CONTINUE, ERROR + } + + public AuthenticationStatus status; + public byte[] challenge; + + public AuthenticationResult(byte[] challenge, AuthenticationStatus status) + { + this.status = status; + this.challenge = challenge; + } + + public AuthenticationResult(AuthenticationStatus status) + { + this.status = status; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java b/java/broker/src/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java new file mode 100644 index 0000000000..bfd2ac1b9f --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/CRAMMD5Initialiser.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5Initialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "CRAM-MD5"; + } + + public Class getServerFactoryClassForJCARegistration() + { + // since the CRAM-MD5 provider is registered as part of the JDK, we do not + // return the factory class here since we do not need to register it ourselves. + return null; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/JCAProvider.java b/java/broker/src/org/apache/qpid/server/security/auth/JCAProvider.java new file mode 100644 index 0000000000..1477b33ebe --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/JCAProvider.java @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import javax.security.sasl.SaslServerFactory; +import java.security.Provider; +import java.security.Security; +import java.util.Map; + +public final class JCAProvider extends Provider +{ + public JCAProvider(Map> providerMap) + { + super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + Security.addProvider(this); + } + + private void register(Map> providerMap) + { + for (Map.Entry> me : + providerMap.entrySet()) + { + put("SaslServerFactory." + me.getKey(), me.getValue().getName()); + } + } +} \ No newline at end of file diff --git a/java/broker/src/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java b/java/broker/src/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..fb11b89367 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/PasswordFilePrincipalDatabase.java @@ -0,0 +1,130 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import org.apache.log4j.Logger; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.security.Principal; +import java.io.*; +import java.util.regex.Pattern; + +/** + * Represents a user database where the account information is stored in a simple flat file. + * + * The file is expected to be in the form: + * username:password + * username1:password1 + * ... + * usernamen:passwordn + * + * where a carriage return separates each username/password pair. Passwords are assumed to be in + * plain text. + * + */ +public class PasswordFilePrincipalDatabase implements PrincipalDatabase +{ + private static final Logger _logger = Logger.getLogger(PasswordFilePrincipalDatabase.class); + + private File _passwordFile; + + private Pattern _regexp = Pattern.compile(":"); + + public PasswordFilePrincipalDatabase() + { + } + + public void setPasswordFile(String passwordFile) throws FileNotFoundException + { + File f = new File(passwordFile); + _logger.info("PasswordFilePrincipalDatabase using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, + AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + char[] pwd = lookupPassword(principal.getName()); + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + /** + * Looks up the password for a specified user in the password file. + * Note this code is not secure since it creates strings of passwords. It should be modified + * to create only char arrays which get nulled out. + * @param name + * @return + * @throws IOException + */ + private char[] lookupPassword(String name) throws IOException + { + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2) + { + continue; + } + + if (name.equals(result[0])) + { + return result[1].toCharArray(); + } + } + return null; + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/PrincipalDatabase.java b/java/broker/src/org/apache/qpid/server/security/auth/PrincipalDatabase.java new file mode 100644 index 0000000000..8add5455ee --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/PrincipalDatabase.java @@ -0,0 +1,42 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.security.Principal; +import java.io.IOException; + +/** + * Represents a "user database" which is really a way of storing principals (i.e. usernames) and + * passwords. + */ +public interface PrincipalDatabase +{ + /** + * Set the password for a given principal in the specified callback. This is used for certain + * SASL providers. The user database implementation should look up the password in any way it + * chooses and set it in the callback by calling its setPassword method. + * @param principal the principal + * @param callback the password callback that wants to receive the password + * @throws AccountNotFoundException if the account for specified principal could not be found + * @throws IOException if there was an error looking up the principal + */ + void setPassword(Principal principal, PasswordCallback callback) + throws IOException, AccountNotFoundException; +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java b/java/broker/src/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java new file mode 100644 index 0000000000..7d0b60d95e --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/SASLAuthenticationManager.java @@ -0,0 +1,224 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.configuration.PropertyUtils; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.security.Security; + +public class SASLAuthenticationManager implements AuthenticationManager +{ + private static final Logger _log = Logger.getLogger(SASLAuthenticationManager.class); + + /** + * The list of mechanisms, in the order in which they are configured (i.e. preferred order) + */ + private String _mechanisms; + + /** + * Maps from the mechanism to the callback handler to use for handling those requests + */ + private Map _callbackHandlerMap = new HashMap(); + + /** + * Maps from the mechanism to the properties used to initialise the server. See the method + * Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation + * of each provider. + */ + private Map> _serverCreationProperties = new HashMap>(); + + public SASLAuthenticationManager() throws Exception + { + _log.info("Initialising SASL authentication manager"); + Map databases = initialisePrincipalDatabases(); + initialiseAuthenticationMechanisms(databases); + } + + private Map initialisePrincipalDatabases() throws Exception + { + Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + List databaseNames = config.getList("security.principal-databases.principal-database.name"); + List databaseClasses = config.getList("security.principal-databases.principal-database.class"); + Map databases = new HashMap(); + for (int i = 0; i < databaseNames.size(); i++) + { + Object o; + try + { + o = Class.forName(databaseClasses.get(i)).newInstance(); + } + catch (Exception e) + { + throw new Exception("Error initialising principal database: " + e, e); + } + + if (!(o instanceof PrincipalDatabase)) + { + throw new Exception("Principal databases must implement the PrincipalDatabase interface"); + } + + initialisePrincipalDatabase((PrincipalDatabase) o, config, i); + + String name = databaseNames.get(i); + if (name == null || name.length() == 0) + { + throw new Exception("Principal database names must have length greater than or equal to one character"); + } + PrincipalDatabase pd = databases.get(name); + if (pd != null) + { + throw new Exception("Duplicate principal database name provided"); + } + _log.info("Initialised principal database " + name + " successfully"); + databases.put(name, (PrincipalDatabase) o); + } + return databases; + } + + private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, Configuration config, int index) + throws Exception + { + String baseName = "security.principal-databases.principal-database(" + index + ").attributes.attribute."; + List argumentNames = config.getList(baseName + "name"); + List argumentValues = config.getList(baseName + "value"); + for (int i = 0; i < argumentNames.size(); i++) + { + String argName = argumentNames.get(i); + if (argName == null || argName.length() == 0) + { + throw new Exception("Argument names must have length >= 1 character"); + } + if (Character.isLowerCase(argName.charAt(0))) + { + argName = Character.toUpperCase(argName.charAt(0)) + argName.substring(1); + } + String methodName = "set" + argName; + Method method = principalDatabase.getClass().getMethod(methodName, String.class); + if (method == null) + { + throw new Exception("No method " + methodName + " found in class " + principalDatabase.getClass() + + " hence unable to configure principal database. The method must be public and " + + "have a single String argument with a void return type"); + } + method.invoke(principalDatabase, PropertyUtils.replaceProperties(argumentValues.get(i))); + } + } + + private void initialiseAuthenticationMechanisms(Map databases) throws Exception + { + Configuration config = ApplicationRegistry.getInstance().getConfiguration(); + List mechanisms = config.getList("security.sasl.mechanisms.mechanism.initialiser.class"); + + // Maps from the mechanism to the properties used to initialise the server. See the method + // Sasl.createSaslServer for details of the use of these properties. This map is populated during initialisation + // of each provider. + Map> providerMap = new TreeMap>(); + + for (int i = 0; i < mechanisms.size(); i++) + { + String baseName = "security.sasl.mechanisms.mechanism(" + i + ").initialiser"; + String clazz = config.getString(baseName + ".class"); + initialiseAuthenticationMechanism(baseName, clazz, databases, config, providerMap); + } + if (providerMap.size() > 0) + { + Security.addProvider(new JCAProvider(providerMap)); + } + } + + private void initialiseAuthenticationMechanism(String baseName, String clazz, + Map databases, + Configuration configuration, + Map> providerMap) + throws Exception + { + Class initialiserClazz = Class.forName(clazz); + Object o = initialiserClazz.newInstance(); + if (!(o instanceof AuthenticationProviderInitialiser)) + { + throw new Exception("The class " + clazz + " must be an instance of " + + AuthenticationProviderInitialiser.class); + } + AuthenticationProviderInitialiser initialiser = (AuthenticationProviderInitialiser) o; + initialiser.initialise(baseName, configuration, databases); + String mechanism = initialiser.getMechanismName(); + if (_mechanisms == null) + { + _mechanisms = mechanism; + } + else + { + // simple append should be fine since the number of mechanisms is small and this is a one time initialisation + _mechanisms = _mechanisms + " " + mechanism; + } + _callbackHandlerMap.put(mechanism, initialiser.getCallbackHandler()); + _serverCreationProperties.put(mechanism, initialiser.getProperties()); + Class factory = initialiser.getServerFactoryClassForJCARegistration(); + if (factory != null) + { + providerMap.put(mechanism, factory); + } + _log.info("Initialised " + mechanism + " SASL provider successfully"); + } + + public String getMechanisms() + { + return _mechanisms; + } + + public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException + { + return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism), + _callbackHandlerMap.get(mechanism)); + } + + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.SUCCESS); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java b/java/broker/src/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java new file mode 100644 index 0000000000..02a953f47c --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/UsernamePasswordInitialiser.java @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import org.apache.commons.configuration.Configuration; + +import javax.security.auth.callback.*; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.AuthorizeCallback; +import java.util.Map; +import java.io.IOException; +import java.security.Principal; + +public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser +{ + private ServerCallbackHandler _callbackHandler; + + private class ServerCallbackHandler implements CallbackHandler + { + private final PrincipalDatabase _principalDatabase; + + protected ServerCallbackHandler(PrincipalDatabase database) + { + _principalDatabase = database; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + Principal username = null; + for (Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + username = new UsernamePrincipal(((NameCallback)callback).getDefaultName()); + } + else if (callback instanceof PasswordCallback) + { + try + { + _principalDatabase.setPassword(username, (PasswordCallback) callback); + } + catch (AccountNotFoundException e) + { + // very annoyingly the callback handler does not throw anything more appropriate than + // IOException + throw new IOException("Error looking up user " + e); + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback)callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } + + public void initialise(String baseConfigPath, Configuration configuration, + Map principalDatabases) throws Exception + { + String principalDatabaseName = configuration.getString(baseConfigPath + ".principal-database"); + PrincipalDatabase db = principalDatabases.get(principalDatabaseName); + if (db == null) + { + throw new Exception("Principal database " + principalDatabaseName + " not found. Ensure the name matches " + + "an entry in the configuration file"); + } + _callbackHandler = new ServerCallbackHandler(db); + } + + public CallbackHandler getCallbackHandler() + { + return _callbackHandler; + } + + public Map getProperties() + { + // there are no properties required for the CRAM-MD5 implementation + return null; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/UsernamePrincipal.java b/java/broker/src/org/apache/qpid/server/security/auth/UsernamePrincipal.java new file mode 100644 index 0000000000..1d425b8399 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/UsernamePrincipal.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import java.security.Principal; + +/** + * A principal that is just a wrapper for a simple username. + * + */ +public class UsernamePrincipal implements Principal +{ + private String _name; + + public UsernamePrincipal(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java b/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java new file mode 100644 index 0000000000..94406237a5 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainInitialiser.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth.amqplain; + +import org.apache.qpid.server.security.auth.UsernamePasswordInitialiser; + +import javax.security.sasl.SaslServerFactory; + +public class AmqPlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "AMQPLAIN"; + } + + public Class getServerFactoryClassForJCARegistration() + { + return AmqPlainSaslServerFactory.class; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java b/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java new file mode 100644 index 0000000000..0f7981abe1 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServer.java @@ -0,0 +1,120 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.mina.common.ByteBuffer; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.auth.callback.*; +import java.io.IOException; + +public class AmqPlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public AmqPlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + final FieldTable ft = new FieldTable(ByteBuffer.wrap(response), response.length); + String username = (String) ft.get("LOGIN"); + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", username); + // we do not care about the prompt but it throws if null + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + String pwd = (String) ft.get("PASSWORD"); + passwordCb.setPassword(pwd.toCharArray()); + AuthorizeCallback authzCb = new AuthorizeCallback(username, username); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + _complete = true; + if (authzCb.isAuthorized()) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (AMQFrameDecodingException e) + { + throw new SaslException("Unable to decode response: " + e, e); + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java b/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java new file mode 100644 index 0000000000..c4e904f923 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/amqplain/AmqPlainSaslServerFactory.java @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth.amqplain; + +import javax.security.sasl.SaslServerFactory; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.auth.callback.CallbackHandler; +import java.util.Map; + +public class AmqPlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (AmqPlainSaslServer.MECHANISM.equals(mechanism)) + { + return new AmqPlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AmqPlainSaslServer.MECHANISM}; + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java b/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java new file mode 100644 index 0000000000..7b055a4f58 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainInitialiser.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth.plain; + +import org.apache.qpid.server.security.auth.UsernamePasswordInitialiser; + +import javax.security.sasl.SaslServerFactory; + +public class PlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "PLAIN"; + } + + public Class getServerFactoryClassForJCARegistration() + { + return PlainSaslServerFactory.class; + } +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java b/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java new file mode 100644 index 0000000000..5a69ed02ba --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServer.java @@ -0,0 +1,141 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth.plain; + +import javax.security.auth.callback.*; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import java.io.IOException; + +public class PlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "PLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public PlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + int authzidNullPosition = findNullPosition(response, 0); + if (authzidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authzid null terminator not found"); + } + int authcidNullPosition = findNullPosition(response, authzidNullPosition + 1); + if (authcidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authcid null terminator not found"); + } + + // we do not currently support authcid in any meaningful way + String authcid = new String(response, 0, authzidNullPosition, "utf8"); + String authzid = new String(response, authzidNullPosition + 1, authcidNullPosition - 1, "utf8"); + + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", authzid); + // we do not care about the prompt but it throws if null + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + int passwordLen = response.length - authcidNullPosition - 1; + String pwd = new String(response, authcidNullPosition + 1, passwordLen, "utf8"); + passwordCb.setPassword(pwd.toCharArray()); + AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + _complete = true; + if (authzCb.isAuthorized()) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + private int findNullPosition(byte[] response, int startPosition) + { + int position = startPosition; + while (position < response.length) + { + if (response[position] == (byte) 0) + { + return position; + } + position++; + } + return -1; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } + +} diff --git a/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java b/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java new file mode 100644 index 0000000000..754ecbde78 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/security/auth/plain/PlainSaslServerFactory.java @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.security.auth.plain; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class PlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{PlainSaslServer.MECHANISM}; + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/state/AMQState.java b/java/broker/src/org/apache/qpid/server/state/AMQState.java new file mode 100644 index 0000000000..46d46eb4c0 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/state/AMQState.java @@ -0,0 +1,33 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.state; + +/** + * States used in the AMQ protocol. Used by the finite state machine to determine + * valid responses. + */ +public enum AMQState +{ + CONNECTION_NOT_STARTED, + CONNECTION_NOT_AUTH, + CONNECTION_NOT_TUNED, + CONNECTION_NOT_OPENED, + CONNECTION_OPEN, + CONNECTION_CLOSING, + CONNECTION_CLOSED +} diff --git a/java/broker/src/org/apache/qpid/server/state/AMQStateManager.java b/java/broker/src/org/apache/qpid/server/state/AMQStateManager.java new file mode 100644 index 0000000000..54c8bcd9d5 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/state/AMQStateManager.java @@ -0,0 +1,219 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.handler.*; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQMethodListener; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * The state manager is responsible for managing the state of the protocol session. + *

    + * For each AMQProtocolHandler there is a separate state manager. + * + */ +public class AMQStateManager implements AMQMethodListener +{ + private static final Logger _logger = Logger.getLogger(AMQStateManager.class); + + /** + * The current state + */ + private AMQState _currentState; + + /** + * Maps from an AMQState instance to a Map from Class to StateTransitionHandler. + * The class must be a subclass of AMQFrame. + */ + private final Map, StateAwareMethodListener>> _state2HandlersMap = + new HashMap, StateAwareMethodListener>>(); + + private CopyOnWriteArraySet _stateListeners = new CopyOnWriteArraySet(); + + public AMQStateManager() + { + this(AMQState.CONNECTION_NOT_STARTED, true); + } + + protected AMQStateManager(AMQState initial, boolean register) + { + _currentState = initial; + if (register) + { + registerListeners(); + } + } + + protected void registerListeners() + { + Map, StateAwareMethodListener> frame2handlerMap = + new HashMap, StateAwareMethodListener>(); + + // 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, StateAwareMethodListener>(); + frame2handlerMap.put(ConnectionStartOkBody.class, ConnectionStartOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); + + frame2handlerMap = new HashMap, StateAwareMethodListener>(); + frame2handlerMap.put(ConnectionSecureOkBody.class, ConnectionSecureOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_AUTH, frame2handlerMap); + + frame2handlerMap = new HashMap, StateAwareMethodListener>(); + frame2handlerMap.put(ConnectionTuneOkBody.class, ConnectionTuneOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); + + frame2handlerMap = new HashMap, StateAwareMethodListener>(); + frame2handlerMap.put(ConnectionOpenBody.class, ConnectionOpenMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); + + // + // ConnectionOpen handlers + // + frame2handlerMap = new HashMap, StateAwareMethodListener>(); + frame2handlerMap.put(ChannelOpenBody.class, ChannelOpenHandler.getInstance()); + frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseHandler.getInstance()); + frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkHandler.getInstance()); + frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); + frame2handlerMap.put(ExchangeDeclareBody.class, ExchangeDeclareHandler.getInstance()); + frame2handlerMap.put(ExchangeDeleteBody.class, ExchangeDeleteHandler.getInstance()); + frame2handlerMap.put(BasicAckBody.class, BasicAckMethodHandler.getInstance()); + frame2handlerMap.put(BasicRecoverBody.class, BasicRecoverMethodHandler.getInstance()); + frame2handlerMap.put(BasicConsumeBody.class, BasicConsumeMethodHandler.getInstance()); + frame2handlerMap.put(BasicCancelBody.class, BasicCancelMethodHandler.getInstance()); + frame2handlerMap.put(BasicPublishBody.class, BasicPublishMethodHandler.getInstance()); + frame2handlerMap.put(BasicQosBody.class, BasicQosHandler.getInstance()); + frame2handlerMap.put(QueueBindBody.class, QueueBindHandler.getInstance()); + frame2handlerMap.put(QueueDeclareBody.class, QueueDeclareHandler.getInstance()); + frame2handlerMap.put(QueueDeleteBody.class, QueueDeleteHandler.getInstance()); + frame2handlerMap.put(ChannelFlowBody.class, ChannelFlowHandler.getInstance()); + frame2handlerMap.put(TxSelectBody.class, TxSelectHandler.getInstance()); + frame2handlerMap.put(TxCommitBody.class, TxCommitHandler.getInstance()); + frame2handlerMap.put(TxRollbackBody.class, TxRollbackHandler.getInstance()); + + _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); + + frame2handlerMap = new HashMap, StateAwareMethodListener>(); + frame2handlerMap.put(ConnectionCloseOkBody.class, ConnectionCloseOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_CLOSING, frame2handlerMap); + + } + + public AMQState getCurrentState() + { + return _currentState; + } + + public void changeState(AMQState newState) throws AMQException + { + _logger.debug("State changing to " + newState + " from old state " + _currentState); + final AMQState oldState = _currentState; + _currentState = newState; + + for (StateListener l : _stateListeners) + { + l.stateChanged(oldState, newState); + } + } + + public void error(AMQException e) + { + _logger.error("State manager received error notification: " + e, e); + for (StateListener l : _stateListeners) + { + l.error(e); + } + } + + public boolean methodReceived(AMQMethodEvent evt, + AMQProtocolSession protocolSession, + QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry) throws AMQException + { + StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod()); + if (handler != null) + { + handler.methodReceived(this, queueRegistry, exchangeRegistry, protocolSession, evt); + return true; + } + return false; + } + + protected StateAwareMethodListener findStateTransitionHandler(AMQState currentState, + B frame) + throws IllegalStateTransitionException + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Looking for state transition handler for frame " + frame.getClass()); + } + final Map, StateAwareMethodListener> + classToHandlerMap = _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(frame.getClass()); + if (handler == null) + { + if (currentState == null) + { + _logger.debug("No state transition handler defined for receiving frame " + frame); + return null; + } + else + { + // if no specialised per state handler is registered look for a + // handler registered for "all" states + return findStateTransitionHandler(null, frame); + } + } + else + { + return handler; + } + } + + public void addStateListener(StateListener listener) + { + _logger.debug("Adding state listener"); + _stateListeners.add(listener); + } + + public void removeStateListener(StateListener listener) + { + _stateListeners.remove(listener); + } +} diff --git a/java/broker/src/org/apache/qpid/server/state/IllegalStateTransitionException.java b/java/broker/src/org/apache/qpid/server/state/IllegalStateTransitionException.java new file mode 100644 index 0000000000..c77cb4f833 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/state/IllegalStateTransitionException.java @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +public class IllegalStateTransitionException extends AMQException +{ + private AMQState _originalState; + + private Class _frame; + + public IllegalStateTransitionException(AMQState originalState, Class frame) + { + super("No valid state transition defined for receiving frame " + frame + + " from state " + originalState); + _originalState = originalState; + _frame = frame; + } + + public AMQState getOriginalState() + { + return _originalState; + } + + public Class getFrameClass() + { + return _frame; + } +} diff --git a/java/broker/src/org/apache/qpid/server/state/StateAwareMethodListener.java b/java/broker/src/org/apache/qpid/server/state/StateAwareMethodListener.java new file mode 100644 index 0000000000..9776151c28 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/state/StateAwareMethodListener.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.framing.AMQMethodBody; + +/** + * A frame listener that is informed of the protocol state when invoked and has + * the opportunity to update state. + * + */ +public interface StateAwareMethodListener +{ + void methodReceived(AMQStateManager stateManager, QueueRegistry queueRegistry, + ExchangeRegistry exchangeRegistry, AMQProtocolSession protocolSession, + AMQMethodEvent evt) throws AMQException; +} diff --git a/java/broker/src/org/apache/qpid/server/state/StateListener.java b/java/broker/src/org/apache/qpid/server/state/StateListener.java new file mode 100644 index 0000000000..d31e786ef1 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/state/StateListener.java @@ -0,0 +1,27 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +public interface StateListener +{ + void stateChanged(AMQState oldState, AMQState newState) throws AMQException; + + void error(Throwable t); +} diff --git a/java/broker/src/org/apache/qpid/server/store/MemoryMessageStore.java b/java/broker/src/org/apache/qpid/server/store/MemoryMessageStore.java new file mode 100644 index 0000000000..d15a035259 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/store/MemoryMessageStore.java @@ -0,0 +1,137 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; +import org.apache.commons.configuration.Configuration; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.List; + +/** + * A simple message store that stores the messages in a threadsafe structure in memory. + * + */ +public class MemoryMessageStore implements MessageStore +{ + private static final Logger _log = Logger.getLogger(MemoryMessageStore.class); + + private static final int DEFAULT_HASHTABLE_CAPACITY = 50000; + + private static final String HASHTABLE_CAPACITY_CONFIG = "hashtable-capacity"; + + protected ConcurrentMap _messageMap; + + private final AtomicLong _messageId = new AtomicLong(1); + + public void configure(String base, Configuration config) + { + int hashtableCapacity = config.getInt(base + "." + HASHTABLE_CAPACITY_CONFIG, DEFAULT_HASHTABLE_CAPACITY); + _log.info("Using capacity " + hashtableCapacity + " for hash table"); + _messageMap = new ConcurrentHashMap(hashtableCapacity); + } + + public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception + { + configure(base, config); + } + + public void close() throws Exception + { + if(_messageMap != null) + { + _messageMap.clear(); + _messageMap = null; + } + } + + public void put(AMQMessage msg) + { + _messageMap.put(msg.getMessageId(), msg); + } + + public void removeMessage(long messageId) + { + if (_log.isDebugEnabled()) + { + _log.debug("Removing message with id " + messageId); + } + _messageMap.remove(messageId); + } + + public void createQueue(AMQQueue queue) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeQueue(String name) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void enqueueMessage(String name, long messageId) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void dequeueMessage(String name, long messageId) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void beginTran() throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void commitTran() throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void abortTran() throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean inTran() + { + return false; + } + + public List createQueues() throws AMQException + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public AMQMessage getMessage(long messageId) + { + return _messageMap.get(messageId); + } +} diff --git a/java/broker/src/org/apache/qpid/server/store/MessageStore.java b/java/broker/src/org/apache/qpid/server/store/MessageStore.java new file mode 100644 index 0000000000..7655b26221 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/store/MessageStore.java @@ -0,0 +1,80 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; + +import java.util.List; + +public interface MessageStore +{ + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * @param queueRegistry the registry of queues to be used by this store + * @param base the base element identifier from which all configuration items are relative. For example, if the base + * element is "store", the all elements used by concrete classes will be "store.foo" etc. + * @param config the apache commons configuration object + */ + void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception; + + /** + * Called to close and cleanup any resources used by the message store. + * @throws Exception + */ + void close() throws Exception; + + void put(AMQMessage msg) throws AMQException; + + void removeMessage(long messageId) throws AMQException; + + void createQueue(AMQQueue queue) throws AMQException; + + void removeQueue(String name) throws AMQException; + + void enqueueMessage(String name, long messageId) throws AMQException; + + void dequeueMessage(String name, long messageId) throws AMQException; + + void beginTran() throws AMQException; + + void commitTran() throws AMQException; + + void abortTran() throws AMQException; + + boolean inTran(); + + /** + * Recreate all queues that were persisted, including re-enqueuing of existing messages + * @return + * @throws AMQException + */ + List createQueues() throws AMQException; + + /** + * Return a valid, currently unused message id. + * @return a message id + */ + long getNewMessageId(); +} + + diff --git a/java/broker/src/org/apache/qpid/server/transport/ConnectorConfiguration.java b/java/broker/src/org/apache/qpid/server/transport/ConnectorConfiguration.java new file mode 100644 index 0000000000..e9e9c8aca4 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/transport/ConnectorConfiguration.java @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.transport; + +import org.apache.qpid.configuration.Configured; +import org.apache.mina.common.IoAcceptor; + +public class ConnectorConfiguration +{ + public static final String DEFAULT_PORT = "5672"; + + public static final String SSL_PORT = "8672"; + + @Configured(path = "connector.processors", + defaultValue = "4") + public int processors; + + @Configured(path = "connector.port", + defaultValue = DEFAULT_PORT) + public int port; + + @Configured(path = "connector.bind", + defaultValue = "wildcard") + public String bindAddress; + + @Configured(path = "connector.sslport", + defaultValue = SSL_PORT) + public int sslPort; + + @Configured(path = "connector.socketReceiveBuffer", + defaultValue = "32767") + public int socketReceiveBufferSize; + + @Configured(path = "connector.socketWriteBuffer", + defaultValue = "32767") + public int socketWriteBuferSize; + + @Configured(path = "connector.tcpNoDelay", + defaultValue = "true") + public boolean tcpNoDelay; + + @Configured(path = "advanced.filterchain[@enableExecutorPool]", + defaultValue = "false") + public boolean enableExecutorPool; + + @Configured(path = "advanced.enablePooledAllocator", + defaultValue = "false") + public boolean enablePooledAllocator; + + @Configured(path = "advanced.enableDirectBuffers", + defaultValue = "false") + public boolean enableDirectBuffers; + + @Configured(path = "connector.ssl", + defaultValue = "false") + public boolean enableSSL; + + @Configured(path = "connector.nonssl", + defaultValue = "true") + public boolean enableNonSSL; + + @Configured(path = "advanced.useBlockingIo", + defaultValue = "false") + public boolean useBlockingIo; + + public IoAcceptor createAcceptor() + { + if(useBlockingIo) + { + System.out.println("Using blocking io"); + return new org.apache.qpid.bio.SocketAcceptor(); + } + else + { + return new org.apache.mina.transport.socket.nio.SocketAcceptor(processors); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/transport/ThreadPoolFilter.java b/java/broker/src/org/apache/qpid/server/transport/ThreadPoolFilter.java new file mode 100644 index 0000000000..e3d87f808c --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/transport/ThreadPoolFilter.java @@ -0,0 +1,692 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.transport; + +import org.apache.mina.common.*; +import org.apache.mina.util.*; +import org.apache.mina.util.Queue; +import org.apache.mina.util.Stack; + +import java.util.*; + +/** + * A Thread-pooling filter. This filter forwards {@link IoHandler} events + * to its thread pool. + *

    + * This is an implementation of + * Leader/Followers + * thread pool by Douglas C. Schmidt et al. + */ +public class ThreadPoolFilter extends IoFilterAdapter +{ + /** + * Default maximum size of thread pool (2G). + */ + public static final int DEFAULT_MAXIMUM_POOL_SIZE = Integer.MAX_VALUE; + + /** + * Default keep-alive time of thread pool (1 min). + */ + public static final int DEFAULT_KEEP_ALIVE_TIME = 60 * 1000; + + /** + * A queue which contains {@link Integer}s which represents reusable + * thread IDs. {@link Worker} first checks this queue and then + * uses {@link #threadId} when no reusable thread ID is available. + */ + private static final Queue threadIdReuseQueue = new Queue(); + private static int threadId = 0; + + private static int acquireThreadId() + { + synchronized (threadIdReuseQueue) + { + Integer id = (Integer) threadIdReuseQueue.pop(); + if (id == null) + { + return ++ threadId; + } + else + { + return id.intValue(); + } + } + } + + private static void releaseThreadId(int id) + { + synchronized (threadIdReuseQueue) + { + threadIdReuseQueue.push(new Integer(id)); + } + } + + private final String threadNamePrefix; + private final Map buffers = new IdentityHashMap(); + private final BlockingQueue unfetchedSessionBuffers = new BlockingQueue(); + private final Set allSessionBuffers = new IdentityHashSet(); + + private Worker leader; + private final Stack followers = new Stack(); + private final Set allWorkers = new IdentityHashSet(); + + private int maximumPoolSize = DEFAULT_MAXIMUM_POOL_SIZE; + private int keepAliveTime = DEFAULT_KEEP_ALIVE_TIME; + + private boolean shuttingDown; + + private int poolSize; + private final Object poolSizeLock = new Object(); + + /** + * Creates a new instance of this filter with default thread pool settings. + */ + public ThreadPoolFilter() + { + this("IoThreadPool"); + } + + /** + * Creates a new instance of this filter with the specified thread name prefix + * and other default settings. + * + * @param threadNamePrefix the prefix of the thread names this pool will create. + */ + public ThreadPoolFilter(String threadNamePrefix) + { + if (threadNamePrefix == null) + { + throw new NullPointerException("threadNamePrefix"); + } + threadNamePrefix = threadNamePrefix.trim(); + if (threadNamePrefix.length() == 0) + { + throw new IllegalArgumentException("threadNamePrefix is empty."); + } + this.threadNamePrefix = threadNamePrefix; + } + + public String getThreadNamePrefix() + { + return threadNamePrefix; + } + + public int getPoolSize() + { + synchronized (poolSizeLock) + { + return poolSize; + } + } + + public int getMaximumPoolSize() + { + return maximumPoolSize; + } + + public int getKeepAliveTime() + { + return keepAliveTime; + } + + public void setMaximumPoolSize(int maximumPoolSize) + { + if (maximumPoolSize <= 0) + { + throw new IllegalArgumentException(); + } + this.maximumPoolSize = maximumPoolSize; + } + + public void setKeepAliveTime(int keepAliveTime) + { + this.keepAliveTime = keepAliveTime; + } + + public void init() + { + shuttingDown = false; + leader = new Worker(); + leader.start(); + leader.lead(); + } + + public void destroy() + { + shuttingDown = true; + int expectedPoolSize = 0; + while (getPoolSize() != expectedPoolSize) + { + List allWorkers; + synchronized (poolSizeLock) + { + allWorkers = new ArrayList(this.allWorkers); + } + + // You may not interrupt the current thread. + if (allWorkers.remove(Thread.currentThread())) + { + expectedPoolSize = 1; + } + + for (Iterator i = allWorkers.iterator(); i.hasNext();) + { + Worker worker = (Worker) i.next(); + while (worker.isAlive()) + { + worker.interrupt(); + try + { + // This timeout will help us from + // infinite lock-up and interrupt workers again. + worker.join(100); + } + catch (InterruptedException e) + { + } + } + } + } + + this.allSessionBuffers.clear(); + this.unfetchedSessionBuffers.clear(); + this.buffers.clear(); + this.followers.clear(); + this.leader = null; + } + + private void increasePoolSize(Worker worker) + { + synchronized (poolSizeLock) + { + poolSize++; + allWorkers.add(worker); + } + } + + private void decreasePoolSize(Worker worker) + { + synchronized (poolSizeLock) + { + poolSize--; + allWorkers.remove(worker); + } + } + + private void fireEvent(NextFilter nextFilter, IoSession session, + EventType type, Object data) + { + final BlockingQueue unfetchedSessionBuffers = this.unfetchedSessionBuffers; + final Set allSessionBuffers = this.allSessionBuffers; + final Event event = new Event(type, nextFilter, data); + + synchronized (unfetchedSessionBuffers) + { + final SessionBuffer buf = getSessionBuffer(session); + final Queue eventQueue = buf.eventQueue; + + synchronized (buf) + { + eventQueue.push(event); + } + + if (!allSessionBuffers.contains(buf)) + { + allSessionBuffers.add(buf); + unfetchedSessionBuffers.push(buf); + } + } + } + + /** + * Implement this method to fetch (or pop) a {@link SessionBuffer} from + * the given unfetchedSessionBuffers. The default implementation + * simply pops the buffer from it. You could prioritize the fetch order. + * + * @return A non-null {@link SessionBuffer} + */ + protected SessionBuffer fetchSessionBuffer(Queue unfetchedSessionBuffers) + { + return (SessionBuffer) unfetchedSessionBuffers.pop(); + } + + private SessionBuffer getSessionBuffer(IoSession session) + { + final Map buffers = this.buffers; + SessionBuffer buf = (SessionBuffer) buffers.get(session); + if (buf == null) + { + synchronized (buffers) + { + buf = (SessionBuffer) buffers.get(session); + if (buf == null) + { + buf = new SessionBuffer(session); + buffers.put(session, buf); + } + } + } + return buf; + } + + private void removeSessionBuffer(SessionBuffer buf) + { + final Map buffers = this.buffers; + final IoSession session = buf.session; + synchronized (buffers) + { + buffers.remove(session); + } + } + + protected static class SessionBuffer + { + private final IoSession session; + + private final Queue eventQueue = new Queue(); + + private SessionBuffer(IoSession session) + { + this.session = session; + } + + public IoSession getSession() + { + return session; + } + + public Queue getEventQueue() + { + return eventQueue; + } + } + + private class Worker extends Thread + { + private final int id; + private final Object promotionLock = new Object(); + private boolean dead; + + private Worker() + { + int id = acquireThreadId(); + this.id = id; + this.setName(threadNamePrefix + '-' + id); + increasePoolSize(this); + } + + public boolean lead() + { + final Object promotionLock = this.promotionLock; + synchronized (promotionLock) + { + if (dead) + { + return false; + } + + leader = this; + promotionLock.notify(); + } + + return true; + } + + public void run() + { + for (; ;) + { + if (!waitForPromotion()) + { + break; + } + + SessionBuffer buf = fetchBuffer(); + giveUpLead(); + if (buf == null) + { + break; + } + + processEvents(buf); + follow(); + releaseBuffer(buf); + } + + decreasePoolSize(this); + releaseThreadId(id); + } + + private SessionBuffer fetchBuffer() + { + BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers; + synchronized (unfetchedSessionBuffers) + { + while (!shuttingDown) + { + try + { + unfetchedSessionBuffers.waitForNewItem(); + } + catch (InterruptedException e) + { + continue; + } + + return ThreadPoolFilter.this.fetchSessionBuffer(unfetchedSessionBuffers); + } + } + + return null; + } + + private void processEvents(SessionBuffer buf) + { + final IoSession session = buf.session; + final Queue eventQueue = buf.eventQueue; + for (; ;) + { + Event event; + synchronized (buf) + { + event = (Event) eventQueue.pop(); + if (event == null) + { + break; + } + } + processEvent(event.getNextFilter(), session, + event.getType(), event.getData()); + } + } + + private void follow() + { + final Object promotionLock = this.promotionLock; + final Stack followers = ThreadPoolFilter.this.followers; + synchronized (promotionLock) + { + if (this != leader) + { + synchronized (followers) + { + followers.push(this); + } + } + } + } + + private void releaseBuffer(SessionBuffer buf) + { + final BlockingQueue unfetchedSessionBuffers = ThreadPoolFilter.this.unfetchedSessionBuffers; + final Set allSessionBuffers = ThreadPoolFilter.this.allSessionBuffers; + final Queue eventQueue = buf.eventQueue; + + synchronized (unfetchedSessionBuffers) + { + if (eventQueue.isEmpty()) + { + allSessionBuffers.remove(buf); + removeSessionBuffer(buf); + } + else + { + unfetchedSessionBuffers.push(buf); + } + } + } + + private boolean waitForPromotion() + { + final Object promotionLock = this.promotionLock; + + long startTime = System.currentTimeMillis(); + long currentTime = System.currentTimeMillis(); + + synchronized (promotionLock) + { + while (this != leader && !shuttingDown) + { + // Calculate remaining keep-alive time + int keepAliveTime = getKeepAliveTime(); + if (keepAliveTime > 0) + { + keepAliveTime -= (currentTime - startTime); + } + else + { + keepAliveTime = Integer.MAX_VALUE; + } + + // Break the loop if there's no remaining keep-alive time. + if (keepAliveTime <= 0) + { + break; + } + + // Wait for promotion + try + { + promotionLock.wait(keepAliveTime); + } + catch (InterruptedException e) + { + } + + // Update currentTime for the next iteration + currentTime = System.currentTimeMillis(); + } + + boolean timeToLead = this == leader && !shuttingDown; + + if (!timeToLead) + { + // time to die + synchronized (followers) + { + followers.remove(this); + } + + // Mark as dead explicitly when we've got promotionLock. + dead = true; + } + + return timeToLead; + } + } + + private void giveUpLead() + { + final Stack followers = ThreadPoolFilter.this.followers; + Worker worker; + do + { + synchronized (followers) + { + worker = (Worker) followers.pop(); + } + + if (worker == null) + { + // Increase the number of threads if we + // are not shutting down and we can increase the number. + if (!shuttingDown + && getPoolSize() < getMaximumPoolSize()) + { + worker = new Worker(); + worker.lead(); + worker.start(); + } + + // This loop should end because: + // 1) lead() is called already, + // 2) or it is shutting down and there's no more threads left. + break; + } + } + while (!worker.lead()); + } + } + + protected static class EventType + { + public static final EventType OPENED = new EventType("OPENED"); + + public static final EventType CLOSED = new EventType("CLOSED"); + + public static final EventType READ = new EventType("READ"); + + public static final EventType WRITTEN = new EventType("WRITTEN"); + + public static final EventType RECEIVED = new EventType("RECEIVED"); + + public static final EventType SENT = new EventType("SENT"); + + public static final EventType IDLE = new EventType("IDLE"); + + public static final EventType EXCEPTION = new EventType("EXCEPTION"); + + private final String value; + + private EventType(String value) + { + this.value = value; + } + + public String toString() + { + return value; + } + } + + protected static class Event + { + private final EventType type; + private final NextFilter nextFilter; + private final Object data; + + public Event(EventType type, NextFilter nextFilter, Object data) + { + this.type = type; + this.nextFilter = nextFilter; + this.data = data; + } + + public Object getData() + { + return data; + } + + + public NextFilter getNextFilter() + { + return nextFilter; + } + + + public EventType getType() + { + return type; + } + } + + public void sessionCreated(NextFilter nextFilter, IoSession session) + { + nextFilter.sessionCreated(session); + } + + public void sessionOpened(NextFilter nextFilter, + IoSession session) + { + fireEvent(nextFilter, session, EventType.OPENED, null); + } + + public void sessionClosed(NextFilter nextFilter, + IoSession session) + { + fireEvent(nextFilter, session, EventType.CLOSED, null); + } + + public void sessionIdle(NextFilter nextFilter, + IoSession session, IdleStatus status) + { + fireEvent(nextFilter, session, EventType.IDLE, status); + } + + public void exceptionCaught(NextFilter nextFilter, + IoSession session, Throwable cause) + { + fireEvent(nextFilter, session, EventType.EXCEPTION, cause); + } + + public void messageReceived(NextFilter nextFilter, + IoSession session, Object message) + { + ByteBufferUtil.acquireIfPossible(message); + fireEvent(nextFilter, session, EventType.RECEIVED, message); + } + + public void messageSent(NextFilter nextFilter, + IoSession session, Object message) + { + ByteBufferUtil.acquireIfPossible(message); + fireEvent(nextFilter, session, EventType.SENT, message); + } + + protected void processEvent(NextFilter nextFilter, + IoSession session, EventType type, + Object data) + { + if (type == EventType.RECEIVED) + { + nextFilter.messageReceived(session, data); + ByteBufferUtil.releaseIfPossible(data); + } + else if (type == EventType.SENT) + { + nextFilter.messageSent(session, data); + ByteBufferUtil.releaseIfPossible(data); + } + else if (type == EventType.EXCEPTION) + { + nextFilter.exceptionCaught(session, (Throwable) data); + } + else if (type == EventType.IDLE) + { + nextFilter.sessionIdle(session, (IdleStatus) data); + } + else if (type == EventType.OPENED) + { + nextFilter.sessionOpened(session); + } + else if (type == EventType.CLOSED) + { + nextFilter.sessionClosed(session); + } + } + + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) + { + nextFilter.filterWrite(session, writeRequest); + } + + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception + { + nextFilter.filterClose(session); + } +} \ No newline at end of file diff --git a/java/broker/src/org/apache/qpid/server/txn/TxnBuffer.java b/java/broker/src/org/apache/qpid/server/txn/TxnBuffer.java new file mode 100644 index 0000000000..9a46afafe5 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/txn/TxnBuffer.java @@ -0,0 +1,75 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.txn; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.store.MessageStore; + +import java.util.ArrayList; +import java.util.List; + +public class TxnBuffer +{ + private final MessageStore _store; + private final List _ops = new ArrayList(); + + public TxnBuffer(MessageStore store) + { + _store = store; + } + + public void commit() throws AMQException + { + _store.beginTran(); + boolean failed = true; + try + { + for(TxnOp op : _ops) + { + op.commit(); + } + _ops.clear(); + failed = false; + } + finally + { + if(failed) + { + _store.abortTran(); + } + else + { + _store.commitTran(); + } + } + } + + public void rollback() throws AMQException + { + for(TxnOp op : _ops) + { + op.rollback(); + } + _ops.clear(); + } + + public void enlist(TxnOp op) + { + _ops.add(op); + } +} diff --git a/java/broker/src/org/apache/qpid/server/txn/TxnOp.java b/java/broker/src/org/apache/qpid/server/txn/TxnOp.java new file mode 100644 index 0000000000..402e75c64b --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/txn/TxnOp.java @@ -0,0 +1,26 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.txn; + +import org.apache.qpid.AMQException; + +public interface TxnOp +{ + public void commit() throws AMQException; + public void rollback(); +} diff --git a/java/broker/src/org/apache/qpid/server/util/CircularBuffer.java b/java/broker/src/org/apache/qpid/server/util/CircularBuffer.java new file mode 100644 index 0000000000..d2c10cb4d1 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/util/CircularBuffer.java @@ -0,0 +1,123 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import java.util.Iterator; + +public class CircularBuffer implements Iterable +{ + private final Object[] _log; + private int _size; + private int _index; + + public CircularBuffer(int size) + { + _log = new Object[size]; + } + + public void add(Object o) + { + _log[_index++] = o; + _size = Math.min(_size+1, _log.length); + if(_index >= _log.length) + { + _index = 0; + } + } + + public Object get(int i) + { + if(i >= _log.length) + { + throw new ArrayIndexOutOfBoundsException(i); + } + return _log[index(i)]; + } + + public int size() { + return _size; + } + + public Iterator iterator() + { + return new Iterator() + { + private int i = 0; + + public boolean hasNext() + { + return i < _size; + } + + public Object next() + { + return get(i++); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + public String toString() + { + StringBuilder s = new StringBuilder(); + boolean first = true; + for(Object o : this) + { + if(!first) + { + s.append(", "); + } + else + { + first = false; + } + s.append(o); + } + return s.toString(); + } + + public void dump() + { + for(Object o : this) + { + System.out.println(o); + } + } + + int index(int i) + { + return _size == _log.length ? (_index + i) % _log.length : i; + } + + public static void main(String[] artgv) + { + String[] items = new String[]{ + "A","B","C","D","E","F","G","H","I","J","K" + }; + CircularBuffer buffer = new CircularBuffer(5); + for(String s : items) + { + buffer.add(s); + System.out.println(buffer); + } + } +} diff --git a/java/broker/src/org/apache/qpid/server/util/LoggingProxy.java b/java/broker/src/org/apache/qpid/server/util/LoggingProxy.java new file mode 100644 index 0000000000..03c4896422 --- /dev/null +++ b/java/broker/src/org/apache/qpid/server/util/LoggingProxy.java @@ -0,0 +1,102 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + +/** + * Dynamic proxy that records invocations in a fixed size circular buffer, + * dumping details on hitting an exception. + *

    + * Useful in debugging. + *

    + */ +public class LoggingProxy implements InvocationHandler +{ + private final Object _target; + private final CircularBuffer _log; + + public LoggingProxy(Object target, int size) + { + _target = target; + _log = new CircularBuffer(size); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + try + { + entered(method, args); + Object result = method.invoke(_target, args); + returned(method, result); + return result; + } + catch(InvocationTargetException e) + { + dump(); + throw e.getTargetException(); + } + } + + void dump() + { + _log.dump(); + } + + CircularBuffer getBuffer() + { + return _log; + } + + private synchronized void entered(Method method, Object[] args) + { + if (args == null) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() entered"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "(" + Arrays.toString(args) + ") entered"); + } + } + + private synchronized void returned(Method method, Object result) + { + if (method.getReturnType() == Void.TYPE) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned " + result); + } + } + + public Object getProxy(Class... c) + { + return Proxy.newProxyInstance(_target.getClass().getClassLoader(), c, this); + } + + public int getBufferSize() { + return _log.size(); + } +} diff --git a/java/broker/test/build-module.xml b/java/broker/test/build-module.xml new file mode 100644 index 0000000000..3128b77249 --- /dev/null +++ b/java/broker/test/build-module.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/java/broker/test/lib/README b/java/broker/test/lib/README new file mode 100644 index 0000000000..a75673935e --- /dev/null +++ b/java/broker/test/lib/README @@ -0,0 +1 @@ +Junit copied here momentarily. diff --git a/java/broker/test/lib/junit/junit-4.0.jar b/java/broker/test/lib/junit/junit-4.0.jar new file mode 100644 index 0000000000..b20406924a Binary files /dev/null and b/java/broker/test/lib/junit/junit-4.0.jar differ diff --git a/java/broker/test/lib/junit/junit.jar b/java/broker/test/lib/junit/junit.jar new file mode 100644 index 0000000000..674d71e89e Binary files /dev/null and b/java/broker/test/lib/junit/junit.jar differ diff --git a/java/broker/test/src/org/apache/qpid/server/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/UnitTests.java new file mode 100644 index 0000000000..377b1fb64e --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/UnitTests.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + org.apache.qpid.server.configuration.UnitTests.class, + org.apache.qpid.server.exchange.UnitTests.class, + org.apache.qpid.server.protocol.UnitTests.class, + org.apache.qpid.server.queue.UnitTests.class, + org.apache.qpid.server.store.UnitTests.class, + org.apache.qpid.server.util.UnitTests.class + }) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/configuration/TestPropertyUtils.java b/java/broker/test/src/org/apache/qpid/server/configuration/TestPropertyUtils.java new file mode 100644 index 0000000000..bd78d1c786 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/configuration/TestPropertyUtils.java @@ -0,0 +1,50 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.configuration; + +import junit.framework.JUnit4TestAdapter; +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.configuration.PropertyUtils; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +// TODO: This belongs in the "common" module. +public class TestPropertyUtils +{ + @Test + public void testSimpleExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}"); + assertEquals(expandedProperty, "fruity"); + } + + @Test + public void testDualExpansion() throws PropertyException + { + System.setProperty("banana", "fruity"); + System.setProperty("concrete", "horrible"); + String expandedProperty = PropertyUtils.replaceProperties("${banana}xyz${concrete}"); + assertEquals(expandedProperty, "fruityxyzhorrible"); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TestPropertyUtils.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/configuration/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/configuration/UnitTests.java new file mode 100644 index 0000000000..4c70d7c4da --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/configuration/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.configuration; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({TestPropertyUtils.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java b/java/broker/test/src/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java new file mode 100644 index 0000000000..ac04c51e46 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/exchange/AbstractHeadersExchangeTest.java @@ -0,0 +1,212 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.NoConsumersException; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.AMQException; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Set; +import java.util.HashSet; + +public class AbstractHeadersExchangeTest +{ + private final HeadersExchange exchange = new HeadersExchange(); + protected final Set queues = new HashSet(); + private int count; + + protected TestQueue bindDefault(String... bindings) throws AMQException + { + return bind("Queue" + (++count), bindings); + } + + protected TestQueue bind(String queueName, String... bindings) throws AMQException + { + return bind(queueName, getHeaders(bindings)); + } + + protected TestQueue bind(String queue, FieldTable bindings) throws AMQException + { + return bind(new TestQueue(queue), bindings); + } + + protected TestQueue bind(TestQueue queue, String... bindings) throws AMQException + { + return bind(queue, getHeaders(bindings)); + } + + protected TestQueue bind(TestQueue queue, FieldTable bindings) throws AMQException + { + queues.add(queue); + exchange.registerQueue(null, queue, bindings); + return queue; + } + + + protected void route(Message m) throws AMQException + { + m.route(exchange); + } + + protected void routeAndTest(Message m, TestQueue... expected) throws AMQException + { + routeAndTest(m, Arrays.asList(expected)); + } + + protected void routeAndTest(Message m, List expected) throws AMQException + { + route(m); + for (TestQueue q : queues) + { + if (expected.contains(q)) + { + assertTrue("Expected " + m + " to be delivered to " + q, m.isInQueue(q)); + //assert m.isInQueue(q) : "Expected " + m + " to be delivered to " + q; + } + else + { + assertFalse("Did not expect " + m + " to be delivered to " + q, m.isInQueue(q)); + //assert !m.isInQueue(q) : "Did not expect " + m + " to be delivered to " + q; + } + } + } + + static FieldTable getHeaders(String... entries) + { + FieldTable headers = new FieldTable(); + for (String s : entries) + { + String[] parts = s.split("=", 2); + headers.put(parts[0], parts.length > 1 ? parts[1] : ""); + } + return headers; + } + + static BasicPublishBody getPublishRequest(String id) + { + BasicPublishBody request = new BasicPublishBody(); + request.routingKey = id; + return request; + } + + static ContentHeaderBody getContentHeader(FieldTable headers) + { + ContentHeaderBody header = new ContentHeaderBody(); + header.properties = getProperties(headers); + return header; + } + + static BasicContentHeaderProperties getProperties(FieldTable headers) + { + BasicContentHeaderProperties properties = new BasicContentHeaderProperties(); + properties.setHeaders(headers); + return properties; + } + + static class TestQueue extends AMQQueue + { + final List messages = new ArrayList(); + + public TestQueue(String name) throws AMQException + { + super(name, false, "test", true, ApplicationRegistry.getInstance().getQueueRegistry()); + } + + public void deliver(AMQMessage msg) throws AMQException + { + messages.add(new HeadersExchangeTest.Message(msg)); + } + } + + /** + * Just add some extra utility methods to AMQMessage to aid testing. + */ + static class Message extends AMQMessage + { + private static MessageStore _messageStore = new SkeletonMessageStore(); + + Message(String id, String... headers) throws AMQException + { + this(id, getHeaders(headers)); + } + + Message(String id, FieldTable headers) throws AMQException + { + this(getPublishRequest(id), getContentHeader(headers), null); + } + + private Message(BasicPublishBody publish, ContentHeaderBody header, List bodies) throws AMQException + { + super(_messageStore, publish, header, bodies); + } + + private Message(AMQMessage msg) throws AMQException + { + super(msg); + } + + void route(Exchange exchange) throws AMQException + { + exchange.route(this); + } + + boolean isInQueue(TestQueue queue) + { + return queue.messages.contains(this); + } + + public int hashCode() + { + return getKey().hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof HeadersExchangeTest.Message && equals((HeadersExchangeTest.Message) o); + } + + private boolean equals(HeadersExchangeTest.Message m) + { + return getKey().equals(m.getKey()); + } + + public String toString() + { + return getKey().toString(); + } + + private Object getKey() + { + return getPublishBody().routingKey; + } + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/exchange/HeadersBindingTest.java b/java/broker/test/src/org/apache/qpid/server/exchange/HeadersBindingTest.java new file mode 100644 index 0000000000..7e33b1d711 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/exchange/HeadersBindingTest.java @@ -0,0 +1,200 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + + +import java.util.Map; +import java.util.HashMap; + +import junit.framework.JUnit4TestAdapter; + +/** + */ +public class HeadersBindingTest +{ + private Map bindHeaders = new HashMap(); + private Map matchHeaders = new HashMap(); + + @Test public void default_1() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void default_2() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void default_3() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Altered value of A"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_1() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_2() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_3() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_4() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + matchHeaders.put("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void all_5() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Altered value of B"); + matchHeaders.put("C", "Value of C"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_1() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_2() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_3() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_4() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Value of B"); + matchHeaders.put("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_5() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Value of A"); + matchHeaders.put("B", "Altered value of B"); + matchHeaders.put("C", "Value of C"); + + assertTrue(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + + @Test public void any_6() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.put("A", "Altered value of A"); + matchHeaders.put("B", "Altered value of B"); + matchHeaders.put("C", "Value of C"); + + assertFalse(new HeadersBinding(bindHeaders).matches(matchHeaders)); + } + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(HeadersBindingTest.class); + } + +} diff --git a/java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java b/java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java new file mode 100644 index 0000000000..74cb082db7 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangePerformanceTest.java @@ -0,0 +1,181 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.NoConsumersException; +import org.apache.qpid.server.util.TimedRun; +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ContentBody; + +import java.util.List; + +/** + * Want to vary the number of regsitrations, messages and matches and measure + * the corresponding variance in execution time. + *

    + * Each registration will contain the 'All' header, even registrations will + * contain the 'Even' header and odd headers will contain the 'Odd' header. + * In additions each regsitration will have a unique value for the 'Specific' + * header as well. + *

    + * Messages can then be routed to all registrations, to even- or odd- registrations + * or to a specific registration. + * + */ +public class HeadersExchangePerformanceTest extends AbstractHeadersExchangeTest +{ + private static enum Mode {ALL, ODD_OR_EVEN, SPECIFIC} + + private final TestQueue[] queues; + private final Mode mode; + + public HeadersExchangePerformanceTest(Mode mode, int registrations) throws AMQException + { + this.mode = mode; + queues = new TestQueue[registrations]; + for (int i = 0; i < queues.length; i++) + { + switch(mode) + { + case ALL: + queues[i] = bind(new FastQueue("Queue" + i), "All"); + break; + case ODD_OR_EVEN: + queues[i] = bind(new FastQueue("Queue" + i), "All", oddOrEven(i)); + break; + case SPECIFIC: + queues[i] = bind(new FastQueue("Queue" + i), "All", oddOrEven(i), "Specific"+ i); + break; + } + } + } + + void sendToAll(int count) throws AMQException + { + send(count, "All=True"); + } + + void sendToOdd(int count) throws AMQException + { + send(count, "All=True", "Odd=True"); + } + + void sendToEven(int count) throws AMQException + { + send(count, "All=True", "Even=True"); + } + + void sendToAllSpecifically(int count) throws AMQException + { + for (int i = 0; i < queues.length; i++) + { + sendToSpecific(count, i); + } + } + + void sendToSpecific(int count, int index) throws AMQException + { + send(count, "All=True", oddOrEven(index) + "=True", "Specific=" + index); + } + + private void send(int count, String... headers) throws AMQException + { + for (int i = 0; i < count; i++) + { + route(new Message("Message" + i, headers)); + } + } + + private static String oddOrEven(int i) + { + return (i % 2 == 0 ? "Even" : "Odd"); + } + + static class FastQueue extends TestQueue + { + + public FastQueue(String name) throws AMQException + { + super(name); + } + + public void deliver(BasicPublishBody publishBody, ContentHeaderBody contentHeaderBody, List contentBodies) throws NoConsumersException + { + //just discard as we are not testing routing functionality here + } + } + + static class Test extends TimedRun + { + private final Mode mode; + private final int registrations; + private final int count; + private HeadersExchangePerformanceTest test; + + Test(Mode mode, int registrations, int count) + { + super(mode + ", registrations=" + registrations + ", count=" + count); + this.mode = mode; + this.registrations = registrations; + this.count = count; + } + + protected void setup() throws Exception + { + test = new HeadersExchangePerformanceTest(mode, registrations); + run(100); //do a warm up run before times start + } + + protected void teardown() throws Exception + { + test = null; + System.gc(); + } + + protected void run() throws Exception + { + run(count); + } + + private void run(int count) throws Exception + { + switch(mode) + { + case ALL: + test.sendToAll(count); + break; + default: + System.out.println("Test for " + mode + " not yet implemented."); + } + } + } + + public static void main(String[] argv) throws Exception + { + int registrations = Integer.parseInt(argv[0]); + int messages = Integer.parseInt(argv[1]); + int iterations = Integer.parseInt(argv[2]); + TimedRun test = new Test(Mode.ALL, registrations, messages); + AveragedRun tests = new AveragedRun(test, iterations); + System.out.println(tests.call()); + } +} + diff --git a/java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangeTest.java new file mode 100644 index 0000000000..86414ffae2 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/exchange/HeadersExchangeTest.java @@ -0,0 +1,81 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.junit.Test; +import org.junit.Before; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.NullApplicationRegistry; +import junit.framework.JUnit4TestAdapter; + +public class HeadersExchangeTest extends AbstractHeadersExchangeTest +{ + @Before + public void init() throws Exception + { + ApplicationRegistry.initialise(new NullApplicationRegistry()); + } + + @Test + public void simple() throws AMQException + { + TestQueue q1 = bindDefault("F0000"); + TestQueue q2 = bindDefault("F0000=Aardvark"); + TestQueue q3 = bindDefault("F0001"); + TestQueue q4 = bindDefault("F0001=Bear"); + TestQueue q5 = bindDefault("F0000", "F0001"); + TestQueue q6 = bindDefault("F0000=Aardvark", "F0001=Bear"); + TestQueue q7 = bindDefault("F0000", "F0001=Bear"); + TestQueue q8 = bindDefault("F0000=Aardvark", "F0001"); + TestQueue q9 = bindDefault("F0000=Apple", "F0001=Banana"); + TestQueue q10 = bindDefault("F0000=Apple", "F0001"); + + routeAndTest(new Message("Message1", "F0000"), q1); + routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2); + routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q5, q8); + routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q3, q4, q5, q7); + routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), + q1, q2, q3, q4, q5, q6, q7, q8); + routeAndTest(new Message("Message6", "F0002")); + } + + @Test + public void any() throws AMQException + { + TestQueue q1 = bindDefault("F0000", "F0001", "X-match=any"); + TestQueue q2 = bindDefault("F0000=Aardvark", "F0001=Bear", "X-match=any"); + TestQueue q3 = bindDefault("F0000", "F0001=Bear", "X-match=any"); + TestQueue q4 = bindDefault("F0000=Aardvark", "F0001", "X-match=any"); + TestQueue q5 = bindDefault("F0000=Apple", "F0001=Banana", "X-match=any"); + TestQueue q6 = bindDefault("F0000=Apple", "F0001", "X-match=any"); + + routeAndTest(new Message("Message1", "F0000"), q1, q3); + routeAndTest(new Message("Message2", "F0000=Aardvark"), q1, q2, q3, q4); + routeAndTest(new Message("Message3", "F0000=Aardvark", "F0001"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message4", "F0000", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message5", "F0000=Aardvark", "F0001=Bear"), q1, q2, q3, q4, q6); + routeAndTest(new Message("Message6", "F0002")); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(HeadersExchangeTest.class); + } + +} diff --git a/java/broker/test/src/org/apache/qpid/server/exchange/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/exchange/UnitTests.java new file mode 100644 index 0000000000..a3c6439b67 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/exchange/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.exchange; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({HeadersBindingTest.class, HeadersExchangeTest.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/protocol/MockIoSession.java b/java/broker/test/src/org/apache/qpid/server/protocol/MockIoSession.java new file mode 100644 index 0000000000..101ba7dd36 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/protocol/MockIoSession.java @@ -0,0 +1,288 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import org.apache.mina.common.*; +import org.apache.mina.common.support.DefaultCloseFuture; +import org.apache.mina.common.support.DefaultWriteFuture; + +import java.net.SocketAddress; +import java.util.Set; + +public class MockIoSession implements IoSession +{ + private AMQProtocolSession _protocolSession; + + /** + * Stores the last response written + */ + private Object _lastWrittenObject; + + private boolean _closing; + + public MockIoSession() + { + } + + public Object getLastWrittenObject() + { + return _lastWrittenObject; + } + + public IoService getService() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public IoHandler getHandler() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public IoSessionConfig getConfig() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public IoFilterChain getFilterChain() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public WriteFuture write(Object message) + { + WriteFuture wf = new DefaultWriteFuture(null); + _lastWrittenObject = message; + return wf; + } + + public CloseFuture close() + { + _closing = true; + CloseFuture cf = new DefaultCloseFuture(null); + cf.setClosed(); + return cf; + } + + public Object getAttachment() + { + return _protocolSession; + } + + public Object setAttachment(Object attachment) + { + Object current = _protocolSession; + _protocolSession = (AMQProtocolSession) attachment; + return current; + } + + public Object getAttribute(String key) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object setAttribute(String key, Object value) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object setAttribute(String key) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object removeAttribute(String key) + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean containsAttribute(String key) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public Set getAttributeKeys() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public TransportType getTransportType() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isConnected() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isClosing() + { + return _closing; + } + + public CloseFuture getCloseFuture() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public SocketAddress getRemoteAddress() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public SocketAddress getLocalAddress() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public SocketAddress getServiceAddress() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getIdleTime(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getIdleTimeInMillis(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setIdleTime(IdleStatus status, int idleTime) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public int getWriteTimeout() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getWriteTimeoutInMillis() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setWriteTimeout(int writeTimeout) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public TrafficMask getTrafficMask() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setTrafficMask(TrafficMask trafficMask) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void suspendRead() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void suspendWrite() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void resumeRead() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void resumeWrite() + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public long getReadBytes() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getWrittenBytes() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getReadMessages() + { + return 0L; + } + + public long getWrittenMessages() + { + return 0L; + } + + public long getWrittenWriteRequests() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getScheduledWriteRequests() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getScheduledWriteBytes() + { + return 0; //TODO + } + + public long getCreationTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastIoTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastReadTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastWriteTime() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isIdle(IdleStatus status) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public int getIdleCount(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + public long getLastIdleTime(IdleStatus status) + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/protocol/TestProtocolInitiation.java b/java/broker/test/src/org/apache/qpid/server/protocol/TestProtocolInitiation.java new file mode 100644 index 0000000000..34e1709a2d --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/protocol/TestProtocolInitiation.java @@ -0,0 +1,212 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import junit.framework.Assert; +import junit.framework.JUnit4TestAdapter; +import org.apache.qpid.codec.AMQDecoder; +import org.apache.qpid.codec.AMQEncoder; +import org.apache.qpid.framing.*; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.WriteFuture; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.filter.codec.support.SimpleProtocolDecoderOutput; +import org.junit.Before; +import org.junit.Test; + +/** + * This test suite tests the handling of protocol initiation frames and related issues. + */ +public class TestProtocolInitiation implements ProtocolVersionList +{ + private AMQPFastProtocolHandler _protocolHandler; + + private MockIoSession _mockIoSession; + + /** + * We need to use the object encoder mechanism so to allow us to retrieve the + * output (a bytebuffer) we define our own encoder output class. The encoder + * writes the encoded data to this class, from where we can retrieve it during + * the test run. + */ + private class TestProtocolEncoderOutput implements ProtocolEncoderOutput + { + public ByteBuffer result; + + public void write(ByteBuffer buf) + { + result = buf; + } + + public void mergeAll() + { + throw new UnsupportedOperationException(); + } + + public WriteFuture flush() + { + throw new UnsupportedOperationException(); + } + } + + private class TestProtocolDecoderOutput implements ProtocolDecoderOutput + { + public Object result; + + public void write(Object buf) + { + result = buf; + } + + public void flush() + { + throw new UnsupportedOperationException(); + } + } + + @Before + public void createCommonObjects() + { + _mockIoSession = new MockIoSession(); + _protocolHandler = new AMQPFastProtocolHandler(null, null); + } + + + /** + * Tests that the AMQDecoder handles invalid protocol classes + * @throws Exception + */ + @Test(expected = AMQProtocolClassException.class) + public void testDecoderValidateProtocolClass() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolClass = 2; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder handles invalid protocol instance numbers + * @throws Exception + */ + @Test(expected = AMQProtocolInstanceException.class) + public void testDecoderValidatesProtocolInstance() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolInstance = 2; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder handles invalid protocol major + * @throws Exception + */ + @Test(expected = AMQProtocolVersionException.class) + public void testDecoderValidatesProtocolMajor() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolMajor = 2; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder handles invalid protocol minor + * @throws Exception + */ + @Test(expected = AMQProtocolVersionException.class) + public void testDecoderValidatesProtocolMinor() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.protocolMinor = 99; + decodePI(pi); + } + + /** + * Tests that the AMQDecoder accepts a valid PI + * @throws Exception + */ + @Test(expected = AMQProtocolHeaderException.class) + public void testDecoderValidatesHeader() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + pi.header = new char[] {'P', 'Q', 'M', 'A' }; + decodePI(pi); + } + + /** + * Test that a valid header is passed by the decoder. + * @throws Exception + */ + @Test + public void testDecoderAcceptsValidHeader() throws Exception + { + ProtocolInitiation pi = createValidProtocolInitiation(); + decodePI(pi); + } + + /** + * This test checks that an invalid protocol header results in the + * connection being closed. + */ + @Test + public void testInvalidProtocolHeaderClosesConnection() throws Exception + { + AMQProtocolHeaderException pe = new AMQProtocolHeaderException("Test"); + _protocolHandler.exceptionCaught(_mockIoSession, pe); + Assert.assertNotNull(_mockIoSession.getLastWrittenObject()); + Object piResponse = _mockIoSession.getLastWrittenObject(); + Assert.assertEquals(piResponse.getClass(), ProtocolInitiation.class); + ProtocolInitiation pi = (ProtocolInitiation) piResponse; + Assert.assertEquals("Protocol Initiation sent out was not the broker's expected header", pi, + createValidProtocolInitiation()); + Assert.assertTrue("Session has not been closed", _mockIoSession.isClosing()); + } + + private ProtocolInitiation createValidProtocolInitiation() + { + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be used + here. */ + int i = pv.length - 1; + return new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR]); + } + + /** + * Helper that encodes a protocol initiation and attempts to decode it + * @param pi + * @throws Exception + */ + private void decodePI(ProtocolInitiation pi) throws Exception + { + // we need to do this test at the level of the decoder since we initially only expect PI frames + // so the protocol handler is not set up to know whether it should be expecting a PI frame or + // a different type of frame + AMQDecoder decoder = new AMQDecoder(true); + AMQEncoder encoder = new AMQEncoder(); + TestProtocolEncoderOutput peo = new TestProtocolEncoderOutput(); + encoder.encode(_mockIoSession, pi, peo); + TestProtocolDecoderOutput pdo = new TestProtocolDecoderOutput(); + decoder.decode(_mockIoSession, peo.result, pdo); + ((ProtocolInitiation) pdo.result).checkVersion(this); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TestProtocolInitiation.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/protocol/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/protocol/UnitTests.java new file mode 100644 index 0000000000..09dc76d310 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/protocol/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.protocol; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({TestProtocolInitiation.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/AckTest.java b/java/broker/test/src/org/apache/qpid/server/queue/AckTest.java new file mode 100644 index 0000000000..904665949c --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/AckTest.java @@ -0,0 +1,243 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.junit.Ignore; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.util.NullApplicationRegistry; +import org.apache.log4j.Logger; + +import java.util.Iterator; +import java.util.Map; + +/** + * Tests that acknowledgements are handled correctly. + */ +public class AckTest +{ + private static final Logger _log = Logger.getLogger(AckTest.class); + + private SubscriptionImpl _subscription; + + private MockProtocolSession _protocolSession; + + private TestableMemoryMessageStore _messageStore; + + private AMQChannel _channel; + + private SubscriptionSet _subscriptionManager; + + private AMQQueue _queue; + + public AckTest() throws Exception + { + ApplicationRegistry.initialise(new NullApplicationRegistry()); + } + + @Before + public void setup() throws Exception + { + _messageStore = new TestableMemoryMessageStore(); + _channel = new AMQChannel(5, _messageStore, null/*dont need exchange registry*/); + _protocolSession = new MockProtocolSession(_messageStore); + _protocolSession.addChannel(_channel); + _subscriptionManager = new SubscriptionSet(); + _queue = new AMQQueue("myQ", false, "guest", true, new DefaultQueueRegistry(), _subscriptionManager); + } + + private void publishMessages(int count) throws AMQException + { + for (int i = 1; i <= count; i++) + { + BasicPublishBody publishBody = new BasicPublishBody(); + publishBody.routingKey = "rk"; + publishBody.exchange = "someExchange"; + AMQMessage msg = new AMQMessage(_messageStore, publishBody); + msg.setContentHeaderBody(new ContentHeaderBody()); + _subscription.send(msg, _queue); + } + } + + /** + * Tests that the acknowledgements are correctly associated with a channel and + * order is preserved when acks are enabled + */ + @Test @Ignore /* FIXME: broken at the moment */ + public void ackChannelAssociationTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + Map map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount); + + Iterator> it = map.entrySet().iterator(); + for (int i = 1; i <= map.size(); i++) + { + Map.Entry entry = it.next(); + assertTrue(entry.getKey() == i); + AMQChannel.UnacknowledgedMessage unackedMsg = entry.getValue(); + assertTrue(unackedMsg.queue == _queue); + } + assertTrue(_messageStore.getMessageMap().size() == msgCount); + } + + /** + * Tests that in no-ack mode no messages are retained + */ + @Test + public void testNoAckMode() throws AMQException + { + // false arg means no acks expected + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", false); + final int msgCount = 10; + publishMessages(msgCount); + + Map map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + assertTrue(_messageStore.getMessageMap().size() == 0); + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + @Test + public void singleAckReceivedTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, false); + Map map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == msgCount - 1); + + Iterator> it = map.entrySet().iterator(); + int i = 1; + while (i <= map.size()) + { + Map.Entry entry = it.next(); + assertTrue(entry.getKey() == i); + AMQChannel.UnacknowledgedMessage unackedMsg = entry.getValue(); + assertTrue(unackedMsg.queue == _queue); + // 5 is the delivery tag of the message that *should* be removed + if (++i == 5) + { + ++i; + } + } + } + + /** + * Tests that a single acknowledgement is handled correctly (i.e multiple flag not + * set case) + */ + @Test + public void multiAckReceivedTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(5, true); + Map map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + + Iterator> it = map.entrySet().iterator(); + int i = 1; + while (i <= map.size()) + { + Map.Entry entry = it.next(); + assertTrue(entry.getKey() == i + 5); + AMQChannel.UnacknowledgedMessage unackedMsg = entry.getValue(); + assertTrue(unackedMsg.queue == _queue); + ++i; + } + } + + /** + * Tests that a multiple acknowledgement is handled correctly. When ack'ing all pending msgs. + * + */ + @Test + public void multiAckAllReceivedTest() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + final int msgCount = 10; + publishMessages(msgCount); + + _channel.acknowledgeMessage(0, true); + Map map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 0); + + Iterator> it = map.entrySet().iterator(); + int i = 1; + while (i <= map.size()) + { + Map.Entry entry = it.next(); + assertTrue(entry.getKey() == i + 5); + AMQChannel.UnacknowledgedMessage unackedMsg = entry.getValue(); + assertTrue(unackedMsg.queue == _queue); + ++i; + } + } + + + + @Test + public void testPrefetch() throws AMQException + { + _subscription = new SubscriptionImpl(5, _protocolSession, "conTag", true); + _channel.setPrefetchCount(5); + final int msgCount = 5; + publishMessages(msgCount); + + // at this point we should have sent out only 5 messages with a further 5 queued + // up in the channel which should be suspended + assertTrue(_subscription.isSuspended()); + Map map = _channel.getUnacknowledgedMessageMap(); + assertTrue(map.size() == 5); + _channel.acknowledgeMessage(5, true); + assertTrue(!_subscription.isSuspended()); + try + { + Thread.sleep(3000); + } + catch (InterruptedException e) + { + _log.error("Error: " + e, e); + } + assertTrue(map.size() == 0); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(AckTest.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/ConcurrencyTest.java b/java/broker/test/src/org/apache/qpid/server/queue/ConcurrencyTest.java new file mode 100644 index 0000000000..1cf11933fa --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/ConcurrencyTest.java @@ -0,0 +1,261 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import org.junit.Test; +import org.junit.Assert; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.handler.OnCurrentThreadExecutor; + +import java.util.*; +import java.util.concurrent.Executor; + +/** + * Tests delivery in the face of concurrent incoming _messages, subscription alterations + * and attempts to asynchronously process queued _messages. + */ +public class ConcurrencyTest extends MessageTestHelper +{ + private final Random random = new Random(); + + private final int numMessages = 1000; + + private final List _subscribers = new ArrayList(); + private final Set _active = new HashSet(); + private final List _messages = new ArrayList(); + private int next = 0;//index to next message to send + private final List _received = Collections.synchronizedList(new ArrayList()); + private final Executor _executor = new OnCurrentThreadExecutor(); + private final List _threads = new ArrayList(); + + private final SubscriptionSet _subscriptionMgr = new SubscriptionSet(); + private final DeliveryManager _deliveryMgr; + + private boolean isComplete; + private boolean failed; + + public ConcurrencyTest() throws Exception + { + _deliveryMgr = new DeliveryManager(_subscriptionMgr, new AMQQueue("myQ", false, "guest", false, + new DefaultQueueRegistry())); + } + + @Test + public void concurrent1() throws InterruptedException, AMQException + { + initSubscriptions(10); + initMessages(numMessages); + initThreads(1, 4, 4, 4); + run(); + check(); + } + + @Test + public void concurrent2() throws InterruptedException, AMQException + { + initSubscriptions(10); + initMessages(numMessages); + initThreads(4, 2, 2, 2); + run(); + check(); + } + + void check() + { + assertFalse("Failed", failed); + + _deliveryMgr.processAsync(_executor); + + assertEquals("Did not recieve the correct number of messages", _messages.size(), _received.size()); + for(int i = 0; i < _messages.size(); i++) + { + assertEquals("Wrong message at " + i, _messages.get(i), _received.get(i)); + } + } + + void initSubscriptions(int subscriptions) + { + for(int i = 0; i < subscriptions; i++) + { + _subscribers.add(new TestSubscription("Subscriber" + i, _received)); + } + } + + void initMessages(int messages) throws AMQException + { + for(int i = 0; i < messages; i++) + { + _messages.add(message()); + } + } + + void initThreads(int senders, int subscribers, int suspenders, int processors) + { + addThreads(senders, senders == 1 ? new Sender() : new OrderedSender()); + addThreads(subscribers, new Subscriber()); + addThreads(suspenders, new Suspender()); + addThreads(processors, new Processor()); + } + + void addThreads(int count, Runnable runner) + { + for(int i = 0; i < count; i++) + { + _threads.add(new Thread(runner, runner.toString())); + } + } + + void run() throws InterruptedException + { + for(Thread t : _threads) + { + t.start(); + } + + for(Thread t : _threads) + { + t.join(); + } + } + + private void toggle(Subscription s) + { + synchronized (_active) + { + if (_active.contains(s)) + { + _active.remove(s); + Subscription result = _subscriptionMgr.removeSubscriber(s); + Assert.assertTrue("Removed subscription " + result + " but trying to remove subscription " + s, + result != null && result.equals(s)); + } + else + { + _active.add(s); + _subscriptionMgr.addSubscriber(s); + } + } + } + + private AMQMessage nextMessage() + { + synchronized (_messages) + { + if (next < _messages.size()) + { + return _messages.get(next++); + } + else + { + if (_deliveryMgr.getQueueMessageCount() == 0) { + isComplete = true; + } + return null; + } + } + } + + private boolean randomBoolean() + { + return random.nextBoolean(); + } + + private TestSubscription randomSubscriber() + { + return _subscribers.get(random.nextInt(_subscribers.size())); + } + + private class Sender extends Runner + { + void doRun() throws Throwable + { + AMQMessage msg = nextMessage(); + if (msg != null) + { + _deliveryMgr.deliver(toString(), msg); + } + } + } + + private class OrderedSender extends Sender + { + synchronized void doRun() throws Throwable + { + super.doRun(); + } + } + + private class Suspender extends Runner + { + void doRun() throws Throwable + { + randomSubscriber().setSuspended(randomBoolean()); + } + } + + private class Subscriber extends Runner + { + void doRun() throws Throwable + { + toggle(randomSubscriber()); + } + } + + private class Processor extends Runner + { + void doRun() throws Throwable + { + _deliveryMgr.processAsync(_executor); + } + } + + private abstract class Runner implements Runnable + { + public void run() + { + try + { + while (!stop()) + { + doRun(); + } + } + catch (Throwable t) + { + failed = true; + t.printStackTrace(); + } + } + + abstract void doRun() throws Throwable; + + boolean stop() + { + return isComplete || failed; + } + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(ConcurrencyTest.class); + } + +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/DeliveryManagerTest.java b/java/broker/test/src/org/apache/qpid/server/queue/DeliveryManagerTest.java new file mode 100644 index 0000000000..d00cd55fa1 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/DeliveryManagerTest.java @@ -0,0 +1,159 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import org.apache.qpid.server.handler.OnCurrentThreadExecutor; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.NullApplicationRegistry; +import org.apache.qpid.AMQException; +import junit.framework.JUnit4TestAdapter; + +public class DeliveryManagerTest extends MessageTestHelper +{ + private final SubscriptionSet _subscriptions = new SubscriptionSet(); + private final DeliveryManager _mgr; + + public DeliveryManagerTest() throws Exception + { + try + { + _mgr = new DeliveryManager(_subscriptions, new AMQQueue("myQ", false, "guest", false, + new DefaultQueueRegistry())); + } + catch(Throwable t) + { + t.printStackTrace(); + throw new AMQException("Could not initialise delivery manager", t); + } + } + + @Test + public void startInQueueingMode() throws AMQException + { + AMQMessage[] messages = new AMQMessage[10]; + for(int i = 0; i < messages.length; i++) + { + messages[i] = message(); + } + int batch = messages.length / 2; + + for(int i = 0; i < batch; i++) + { + _mgr.deliver("Me", messages[i]); + } + + TestSubscription s1 = new TestSubscription("1"); + TestSubscription s2 = new TestSubscription("2"); + _subscriptions.addSubscriber(s1); + _subscriptions.addSubscriber(s2); + + for(int i = batch; i < messages.length; i++) + { + _mgr.deliver("Me", messages[i]); + } + + assertTrue(s1.getMessages().isEmpty()); + assertTrue(s2.getMessages().isEmpty()); + + _mgr.processAsync(new OnCurrentThreadExecutor()); + + assertEquals(messages.length / 2, s1.getMessages().size()); + assertEquals(messages.length / 2, s2.getMessages().size()); + + for(int i = 0; i < messages.length; i++) + { + if(i % 2 == 0) + { + assertTrue(s1.getMessages().get(i / 2) == messages[i]); + } + else + { + assertTrue(s2.getMessages().get(i / 2) == messages[i]); + } + } + } + + @Test + public void startInDirectMode() throws AMQException + { + AMQMessage[] messages = new AMQMessage[10]; + for(int i = 0; i < messages.length; i++) + { + messages[i] = message(); + } + int batch = messages.length / 2; + + TestSubscription s1 = new TestSubscription("1"); + _subscriptions.addSubscriber(s1); + + for(int i = 0; i < batch; i++) + { + _mgr.deliver("Me", messages[i]); + } + + assertEquals(batch, s1.getMessages().size()); + for(int i = 0; i < batch; i++) + { + assertTrue(messages[i] == s1.getMessages().get(i)); + } + s1.getMessages().clear(); + assertEquals(0, s1.getMessages().size()); + + s1.setSuspended(true); + for(int i = batch; i < messages.length; i++) + { + _mgr.deliver("Me", messages[i]); + } + + _mgr.processAsync(new OnCurrentThreadExecutor()); + assertEquals(0, s1.getMessages().size()); + s1.setSuspended(false); + + _mgr.processAsync(new OnCurrentThreadExecutor()); + assertEquals(messages.length - batch, s1.getMessages().size()); + + for(int i = batch; i < messages.length; i++) + { + assertTrue(messages[i] == s1.getMessages().get(i - batch)); + } + + } + + @Test (expected=NoConsumersException.class) + public void noConsumers() throws AMQException + { + _mgr.deliver("Me", message(true)); + } + + @Test (expected=NoConsumersException.class) + public void noActiveConsumers() throws AMQException + { + TestSubscription s = new TestSubscription("A"); + _subscriptions.addSubscriber(s); + s.setSuspended(true); + _mgr.deliver("Me", message(true)); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(DeliveryManagerTest.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/MessageTestHelper.java b/java/broker/test/src/org/apache/qpid/server/queue/MessageTestHelper.java new file mode 100644 index 0000000000..f9baa77b65 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/MessageTestHelper.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.util.NullApplicationRegistry; +import org.apache.qpid.AMQException; + +class MessageTestHelper +{ + private final MessageStore _messageStore = new SkeletonMessageStore(); + + MessageTestHelper() throws Exception + { + ApplicationRegistry.initialise(new NullApplicationRegistry()); + } + + AMQMessage message() throws AMQException + { + return message(false); + } + + AMQMessage message(boolean immediate) throws AMQException + { + BasicPublishBody publish = new BasicPublishBody(); + publish.immediate = immediate; + return new AMQMessage(_messageStore, publish, new ContentHeaderBody(), null); + } + +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/MockProtocolSession.java b/java/broker/test/src/org/apache/qpid/server/queue/MockProtocolSession.java new file mode 100644 index 0000000000..f26d6d64b3 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/MockProtocolSession.java @@ -0,0 +1,121 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.store.MessageStore; + +import javax.security.sasl.SaslServer; +import java.util.HashMap; +import java.util.Map; + +/** + * A protocol session that can be used for testing purposes. + */ +public class MockProtocolSession implements AMQProtocolSession +{ + private MessageStore _messageStore; + + private Map _channelMap = new HashMap(); + + public MockProtocolSession(MessageStore messageStore) + { + _messageStore = messageStore; + } + + public void dataBlockReceived(AMQDataBlock message) throws Exception + { + } + + public void writeFrame(AMQDataBlock frame) + { + } + + public String getContextKey() + { + return null; + } + + public void setContextKey(String contextKey) + { + } + + public AMQChannel getChannel(int channelId) + { + AMQChannel channel = _channelMap.get(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Invalid channel id: " + channelId); + } + else + { + return channel; + } + } + + public void addChannel(AMQChannel channel) + { + if (channel == null) + { + throw new IllegalArgumentException("Channel must not be null"); + } + else + { + _channelMap.put(channel.getChannelId(), channel); + } + } + + public void closeChannel(int channelId) throws AMQException + { + } + + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + } + + public void initHeartbeats(int delay) + { + } + + public void closeSession() throws AMQException + { + } + + public Object getKey() + { + return null; + } + + public String getLocalFQDN() + { + return null; + } + + public SaslServer getSaslServer() + { + return null; + } + + public void setSaslServer(SaslServer saslServer) + { + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java b/java/broker/test/src/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java new file mode 100644 index 0000000000..8ae8ebae79 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/QueueConcurrentPerfTest.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.server.util.ConcurrentTest; + +public class QueueConcurrentPerfTest extends QueuePerfTest +{ + QueueConcurrentPerfTest(Factory factory, int queueCount, int messages) + { + super(factory, queueCount, messages); + } + + public static void main(String[] argv) throws Exception + { + Factory[] factories = new Factory[]{SYNCHRONIZED, CONCURRENT}; + int iterations = 5; + String label = argv.length > 0 ? argv[0]: null; + System.out.println((label == null ? "" : "Label, ") + "Queue Type, No. of Queues, No. of Operations, Avg Time, Min Time, Max Time"); + //vary number of queues: + for(Factory f : factories) + { + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 100, 10000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 10000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 10000, 10000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 1000), iterations), 5)); + run(label, new AveragedRun(new ConcurrentTest(new QueuePerfTest(f, 1000, 100000), iterations), 5)); + } + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/QueuePerfTest.java b/java/broker/test/src/org/apache/qpid/server/queue/QueuePerfTest.java new file mode 100644 index 0000000000..36e4e90f35 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/QueuePerfTest.java @@ -0,0 +1,255 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.server.util.TimedRun; +import org.apache.qpid.server.util.RunStats; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class QueuePerfTest extends TimedRun +{ + private final Factory _factory; + private final int _queueCount; + private final int _messages; + private final String _msg = ""; + private List> _queues; + + QueuePerfTest(Factory factory, int queueCount, int messages) + { + super(factory + ", " + queueCount + ", " + messages); + _factory = factory; + _queueCount = queueCount; + _messages = messages; + } + + protected void setup() throws Exception + { + //init + int count = Integer.getInteger("prepopulate", 0); +// System.err.println("Prepopulating with " + count + " items"); + _queues = new ArrayList>(_queueCount); + for (int i = 0; i < _queueCount; i++) + { + Queue q = _factory.create(); + for(int j = 0; j < count; ++j) + { + q.add("Item"+ j); + } + _queues.add(q); + } + System.gc(); + } + + protected void teardown() throws Exception + { + System.gc(); + } + + protected void run() throws Exception + { + //dispatch + for (int i = 0; i < _messages; i++) + { + for (Queue q : _queues) + { + q.offer(_msg); + q.poll(); + } + } + } + + static interface Factory + { + Queue create(); + } + + static Factory CONCURRENT = new Factory() + { + public Queue create() + { + return new ConcurrentLinkedQueue(); + } + + public String toString() + { + return "ConcurrentLinkedQueue"; + } + + }; + + static Factory SYNCHRONIZED = new Factory() + { + public Queue create() + { + return new SynchronizedQueue(new LinkedList()); + } + + + public String toString() + { + return "Synchronized LinkedList"; + } + }; + + static Factory PLAIN = new Factory() + { + public Queue create() + { + return new LinkedList(); + } + + public String toString() + { + return "Plain LinkedList"; + } + }; + + static class SynchronizedQueue implements Queue + { + private final Queue queue; + + SynchronizedQueue(Queue queue) + { + this.queue = queue; + } + + public synchronized E element() + { + return queue.element(); + } + + public synchronized boolean offer(E o) + { + return queue.offer(o); + } + + public synchronized E peek() + { + return queue.peek(); + } + + public synchronized E poll() + { + return queue.poll(); + } + + public synchronized E remove() + { + return queue.remove(); + } + + public synchronized int size() + { + return queue.size(); + } + + public synchronized boolean isEmpty() + { + return queue.isEmpty(); + } + + public synchronized boolean contains(Object o) + { + return queue.contains(o); + } + + public synchronized Iterator iterator() + { + return queue.iterator(); + } + + public synchronized Object[] toArray() + { + return queue.toArray(); + } + + public synchronized T[] toArray(T[] a) + { + return queue.toArray(a); + } + + public synchronized boolean add(E o) + { + return queue.add(o); + } + + public synchronized boolean remove(Object o) + { + return queue.remove(o); + } + + public synchronized boolean containsAll(Collection c) + { + return queue.containsAll(c); + } + + public synchronized boolean addAll(Collection c) + { + return queue.addAll(c); + } + + public synchronized boolean removeAll(Collection c) + { + return queue.removeAll(c); + } + + public synchronized boolean retainAll(Collection c) + { + return queue.retainAll(c); + } + + public synchronized void clear() + { + queue.clear(); + } + } + + static void run(String label, AveragedRun test) throws Exception + { + RunStats stats = test.call(); + System.out.println((label == null ? "" : label + ", ") + test + + ", " + stats.getAverage() + ", " + stats.getMax() + ", " + stats.getMin()); + } + + public static void main(String[] argv) throws Exception + { + Factory[] factories = new Factory[]{PLAIN, SYNCHRONIZED, CONCURRENT}; + int iterations = 5; + String label = argv.length > 0 ? argv[0]: null; + System.out.println((label == null ? "" : "Label, ") + "Queue Type, No. of Queues, No. of Operations, Avg Time, Min Time, Max Time"); + //vary number of queues: + + for(Factory f : factories) + { + run(label, new AveragedRun(new QueuePerfTest(f, 100, 10000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 1000, 10000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 10000, 10000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 1000, 1000), iterations)); + run(label, new AveragedRun(new QueuePerfTest(f, 1000, 100000), iterations)); + } + } + +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/SendPerfTest.java b/java/broker/test/src/org/apache/qpid/server/queue/SendPerfTest.java new file mode 100644 index 0000000000..eff65a9350 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/SendPerfTest.java @@ -0,0 +1,171 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.AbstractExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.handler.OnCurrentThreadExecutor; +import org.apache.qpid.server.protocol.AMQMinaProtocolSession; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.MockIoSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.util.AveragedRun; +import org.apache.qpid.server.util.NullApplicationRegistry; +import org.apache.qpid.server.util.TimedRun; + +import java.util.ArrayList; +import java.util.List; + +public class SendPerfTest extends TimedRun +{ + private int _messages = 1000; + private int _clients = 10; + private List _queues; + + public SendPerfTest(int clients, int messages) + { + super("SendPerfTest, msgs=" + messages + ", clients=" + clients); + _messages = messages; + _clients = clients; + } + + protected void setup() throws Exception + { + _queues = initQueues(_clients); + System.gc(); + } + + protected void teardown() throws Exception + { + System.gc(); + } + + protected void run() throws Exception + { + deliver(_messages, _queues); + } + + //have a dummy AMQProtocolSession that does nothing on the writeFrame() + //set up x number of queues + //create necessary bits and pieces to deliver a message + //deliver y messages to each queue + + public static void main(String[] argv) throws Exception + { + ApplicationRegistry.initialise(new NullApplicationRegistry()); + int clients = Integer.parseInt(argv[0]); + int messages = Integer.parseInt(argv[1]); + int iterations = Integer.parseInt(argv[2]); + AveragedRun test = new AveragedRun(new SendPerfTest(clients, messages), iterations); + test.run(); + } + + /** + * Delivers messages to a number of queues. + * @param count the number of messages to deliver + * @param queues the list of queues + * @throws NoConsumersException + */ + static void deliver(int count, List queues) throws AMQException + { + BasicPublishBody publish = new BasicPublishBody(); + publish.exchange = new NullExchange().getName(); + ContentHeaderBody header = new ContentHeaderBody(); + List body = new ArrayList(); + MessageStore messageStore = new SkeletonMessageStore(); + body.add(new ContentBody()); + for (int i = 0; i < count; i++) + { + for (AMQQueue q : queues) + { + q.deliver(new AMQMessage(messageStore, i, publish, header, body)); + } + } + } + + static List initQueues(int number) throws AMQException + { + Exchange exchange = new NullExchange(); + List queues = new ArrayList(number); + for (int i = 0; i < number; i++) + { + AMQQueue q = createQueue("Queue" + (i + 1)); + q.bind("routingKey", exchange); + try + { + q.registerProtocolSession(createSession(), 1, "1", false); + } + catch (Exception e) + { + throw new AMQException("Error creating protocol session: " + e, e); + } + queues.add(q); + } + return queues; + } + + static AMQQueue createQueue(String name) throws AMQException + { + return new AMQQueue(name, false, null, false, ApplicationRegistry.getInstance().getQueueRegistry(), + new OnCurrentThreadExecutor()); + } + + static AMQProtocolSession createSession() throws Exception + { + IApplicationRegistry reg = ApplicationRegistry.getInstance(); + AMQCodecFactory codecFactory = new AMQCodecFactory(true); + AMQMinaProtocolSession result = new AMQMinaProtocolSession(new MockIoSession(), reg.getQueueRegistry(), reg.getExchangeRegistry(), codecFactory); + result.addChannel(new AMQChannel(1, null, null)); + return result; + } + + static class NullExchange extends AbstractExchange + { + public String getName() + { + return "NullExchange"; + } + + protected ExchangeMBean createMBean() + { + return null; + } + + public void registerQueue(String routingKey, AMQQueue queue, FieldTable args) throws AMQException + { + } + + public void deregisterQueue(String routingKey, AMQQueue queue) throws AMQException + { + } + + public void route(AMQMessage payload) throws AMQException + { + } + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/SubscriptionManagerTest.java b/java/broker/test/src/org/apache/qpid/server/queue/SubscriptionManagerTest.java new file mode 100644 index 0000000000..7743db5078 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/SubscriptionManagerTest.java @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +import junit.framework.JUnit4TestAdapter; + +public class SubscriptionManagerTest +{ + private final SubscriptionSet mgr = new SubscriptionSet(); + + @Test + public void basicSubscriptionManagement() + { + assertTrue(mgr.isEmpty()); + assertFalse(mgr.hasActiveSubscribers()); + TestSubscription s1 = new TestSubscription("S1"); + mgr.addSubscriber(s1); + assertFalse(mgr.isEmpty()); + assertTrue(mgr.hasActiveSubscribers()); + + TestSubscription s2 = new TestSubscription("S2"); + mgr.addSubscriber(s2); + + s2.setSuspended(true); + assertFalse(mgr.isEmpty()); + assertTrue(mgr.hasActiveSubscribers()); + assertTrue(s2.isSuspended()); + assertFalse(s1.isSuspended()); + + s1.setSuspended(true); + assertFalse(mgr.hasActiveSubscribers()); + + mgr.removeSubscriber(new TestSubscription("S1")); + assertFalse(mgr.isEmpty()); + mgr.removeSubscriber(new TestSubscription("S2")); + assertTrue(mgr.isEmpty()); + } + + @Test + public void roundRobin() + { + TestSubscription a = new TestSubscription("A"); + TestSubscription b = new TestSubscription("B"); + TestSubscription c = new TestSubscription("C"); + TestSubscription d = new TestSubscription("D"); + mgr.addSubscriber(a); + mgr.addSubscriber(b); + mgr.addSubscriber(c); + mgr.addSubscriber(d); + + for (int i = 0; i < 3; i++) + { + assertEquals(a, mgr.nextSubscriber(null)); + assertEquals(b, mgr.nextSubscriber(null)); + assertEquals(c, mgr.nextSubscriber(null)); + assertEquals(d, mgr.nextSubscriber(null)); + } + + c.setSuspended(true); + + for (int i = 0; i < 3; i++) + { + assertEquals(a, mgr.nextSubscriber(null)); + assertEquals(b, mgr.nextSubscriber(null)); + assertEquals(d, mgr.nextSubscriber(null)); + } + + mgr.removeSubscriber(a); + d.setSuspended(true); + c.setSuspended(false); + Subscription e = new TestSubscription("D"); + mgr.addSubscriber(e); + + for (int i = 0; i < 3; i++) + { + assertEquals(b, mgr.nextSubscriber(null)); + assertEquals(c, mgr.nextSubscriber(null)); + assertEquals(e, mgr.nextSubscriber(null)); + } + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(SubscriptionManagerTest.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/SubscriptionSetTest.java b/java/broker/test/src/org/apache/qpid/server/queue/SubscriptionSetTest.java new file mode 100644 index 0000000000..b6e8f8b44d --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/SubscriptionSetTest.java @@ -0,0 +1,149 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Test; + +public class SubscriptionSetTest +{ + /** + * A SubscriptionSet that counts the number of items scanned. + */ + static class TestSubscriptionSet extends SubscriptionSet + { + private int scanned = 0; + + void resetScanned() + { + scanned = 0; + } + + protected void subscriberScanned() + { + ++scanned; + } + + int getScanned() + { + return scanned; + } + } + + final TestSubscription sub1 = new TestSubscription("1"); + final TestSubscription sub2 = new TestSubscription("2"); + final TestSubscription sub3 = new TestSubscription("3"); + + final TestSubscription suspendedSub1 = new TestSubscription("sus1", true); + final TestSubscription suspendedSub2 = new TestSubscription("sus2", true); + final TestSubscription suspendedSub3 = new TestSubscription("sus3", true); + + @Test + public void nextMessage() + { + SubscriptionSet ss = new SubscriptionSet(); + assertNull(ss.nextSubscriber(null)); + assertEquals(0, ss.getCurrentSubscriber()); + + ss.addSubscriber(sub1); + assertEquals(sub1, ss.nextSubscriber(null)); + assertEquals(1, ss.getCurrentSubscriber()); + assertEquals(sub1, ss.nextSubscriber(null)); + assertEquals(1, ss.getCurrentSubscriber()); + + ss.addSubscriber(sub2); + ss.addSubscriber(sub3); + + assertEquals(sub2, ss.nextSubscriber(null)); + assertEquals(2, ss.getCurrentSubscriber()); + + assertEquals(sub3, ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + } + + @Test + public void nextMessageWhenAllSuspended() + { + SubscriptionSet ss = createAllSuspendedSubscriptionSet(); + assertNull(ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + + assertNull(ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + } + + private TestSubscriptionSet createAllSuspendedSubscriptionSet() + { + TestSubscriptionSet ss = new TestSubscriptionSet(); + ss.addSubscriber(suspendedSub1); + ss.addSubscriber(suspendedSub2); + ss.addSubscriber(suspendedSub3); + return ss; + } + + @Test + public void nextMessageAfterRemove() + { + SubscriptionSet ss = new SubscriptionSet(); + ss.addSubscriber(suspendedSub1); + ss.addSubscriber(suspendedSub2); + ss.addSubscriber(sub3); + assertEquals(sub3, ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + + assertEquals(suspendedSub1, ss.removeSubscriber(suspendedSub1)); + + assertEquals(sub3, ss.nextSubscriber(null)); // Current implementation handles OutOfBoundsException here. + assertEquals(2, ss.getCurrentSubscriber()); + } + + @Test + public void nextMessageOverScanning() + { + TestSubscriptionSet ss = new TestSubscriptionSet(); + TestSubscription sub = new TestSubscription("test"); + ss.addSubscriber(suspendedSub1); + ss.addSubscriber(sub); + ss.addSubscriber(suspendedSub3); + assertEquals(sub, ss.nextSubscriber(null)); + assertEquals(2, ss.getCurrentSubscriber()); + assertEquals(2, ss.getScanned()); + + ss.resetScanned(); + sub.setSuspended(true); + assertNull(ss.nextSubscriber(null)); + assertEquals(3, ss.getCurrentSubscriber()); + // Current implementation overscans by one item here. + assertEquals(ss.size() + 1, ss.getScanned()); + } + + @Test + public void nextMessageOverscanWorstCase() { + TestSubscriptionSet ss = createAllSuspendedSubscriptionSet(); + ss.nextSubscriber(null); + // Scans the subscriptions twice. + assertEquals(ss.size() * 2, ss.getScanned()); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(SubscriptionSetTest.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/TestSubscription.java b/java/broker/test/src/org/apache/qpid/server/queue/TestSubscription.java new file mode 100644 index 0000000000..093d7e60f4 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/TestSubscription.java @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import java.util.ArrayList; +import java.util.List; + +public class TestSubscription implements Subscription +{ + private final List messages; + private final Object key; + private boolean isSuspended; + + public TestSubscription(Object key) + { + this(key, new ArrayList()); + } + + public TestSubscription(final Object key, final boolean isSuspended) + { + this(key); + setSuspended(isSuspended); + } + + TestSubscription(Object key, List messages) + { + this.key = key; + this.messages = messages; + } + + List getMessages() + { + return messages; + } + + public void send(AMQMessage msg, AMQQueue queue) + { + messages.add(msg); + } + + public void setSuspended(boolean suspended) + { + isSuspended = suspended; + } + + public boolean isSuspended() + { + return isSuspended; + } + + public void queueDeleted(AMQQueue queue) + { + } + + public int hashCode() + { + return key.hashCode(); + } + + public boolean equals(Object o) + { + return o instanceof TestSubscription && ((TestSubscription) o).key.equals(key); + } + + public String toString() + { + return key.toString(); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/queue/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/queue/UnitTests.java new file mode 100644 index 0000000000..3a86773a15 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/queue/UnitTests.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.queue; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + AckTest.class, + DeliveryManagerTest.class, + SubscriptionManagerTest.class, + SubscriptionSetTest.class, + ConcurrencyTest.class} +) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/store/SkeletonMessageStore.java b/java/broker/test/src/org/apache/qpid/server/store/SkeletonMessageStore.java new file mode 100644 index 0000000000..82292d2503 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/store/SkeletonMessageStore.java @@ -0,0 +1,99 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.AMQException; +import org.apache.commons.configuration.Configuration; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A message store that does nothing. Designed to be used in tests that do not want to use any message store + * functionality. + */ +public class SkeletonMessageStore implements MessageStore +{ + private final AtomicLong _messageId = new AtomicLong(1); + + public void configure(String base, Configuration config) throws Exception + { + } + + public void configure(QueueRegistry queueRegistry, String base, Configuration config) throws Exception + { + } + + public void close() throws Exception + { + } + + public void put(AMQMessage msg) + { + } + + public void removeMessage(long messageId) + { + } + + public void createQueue(AMQQueue queue) throws AMQException + { + } + + public void removeQueue(String name) throws AMQException + { + } + + public void enqueueMessage(String name, long messageId) throws AMQException + { + } + + public void dequeueMessage(String name, long messageId) throws AMQException + { + } + + public void beginTran() throws AMQException + { + } + + public boolean inTran() + { + return false; + } + + public void commitTran() throws AMQException + { + } + + public void abortTran() throws AMQException + { + } + + public List createQueues() throws AMQException + { + return null; + } + + public long getNewMessageId() + { + return _messageId.getAndIncrement(); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/store/TestReferenceCounting.java b/java/broker/test/src/org/apache/qpid/server/store/TestReferenceCounting.java new file mode 100644 index 0000000000..d2e3ca6478 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/store/TestReferenceCounting.java @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.store; + +import junit.framework.JUnit4TestAdapter; +import org.junit.Test; +import org.junit.Assert; +import org.junit.Before; +import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.AMQException; + +/** + * Tests that reference counting works correctly with AMQMessage and the message store + */ +public class TestReferenceCounting +{ + private TestableMemoryMessageStore _store; + + @Before + public void createCommonObjects() + { + _store = new TestableMemoryMessageStore(); + } + + /** + * Check that when the reference count is decremented the message removes itself from the store + */ + @Test + public void testMessageGetsRemoved() throws AMQException + { + AMQMessage message = new AMQMessage(_store, null); + _store.put(message); + Assert.assertTrue(_store.getMessageMap().size() == 1); + message.decrementReference(); + Assert.assertTrue(_store.getMessageMap().size() == 0); + } + + @Test + public void testMessageRemains() throws AMQException + { + AMQMessage message = new AMQMessage(_store, null); + _store.put(message); + Assert.assertTrue(_store.getMessageMap().size() == 1); + message.incrementReference(); + message.decrementReference(); + Assert.assertTrue(_store.getMessageMap().size() == 1); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(TestReferenceCounting.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/java/broker/test/src/org/apache/qpid/server/store/TestableMemoryMessageStore.java new file mode 100644 index 0000000000..1214cd2825 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/store/TestableMemoryMessageStore.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.qpid.server.queue.AMQMessage; + +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestableMemoryMessageStore extends MemoryMessageStore +{ + public TestableMemoryMessageStore() + { + _messageMap = new ConcurrentHashMap(); + } + + public ConcurrentMap getMessageMap() + { + return _messageMap; + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/store/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/store/UnitTests.java new file mode 100644 index 0000000000..a917d736a2 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/store/UnitTests.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.store; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + TestReferenceCounting.class +}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/AveragedRun.java b/java/broker/test/src/org/apache/qpid/server/util/AveragedRun.java new file mode 100644 index 0000000000..3e4a1edac4 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/AveragedRun.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import org.apache.qpid.server.util.TimedRun; + +import java.util.concurrent.Callable; +import java.util.Collection; + +public class AveragedRun implements Callable +{ + private final RunStats stats = new RunStats(); + private final TimedRun test; + private final int iterations; + + public AveragedRun(TimedRun test, int iterations) + { + this.test = test; + this.iterations = iterations; + } + + public RunStats call() throws Exception + { + for (int i = 0; i < iterations; i++) + { + stats.record(test.call()); + } + return stats; + } + + public void run() throws Exception + { + System.out.println(test + ": " + call()); + } + + public String toString() + { + return test.toString(); + } + + static void run(Collection tests) throws Exception + { + for(AveragedRun test : tests) + { + test.run(); + } + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/ConcurrentTest.java b/java/broker/test/src/org/apache/qpid/server/util/ConcurrentTest.java new file mode 100644 index 0000000000..6c5185e254 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/ConcurrentTest.java @@ -0,0 +1,76 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +public class ConcurrentTest extends TimedRun +{ + private final TimedRun _test; + private final Thread[] _threads; + + public ConcurrentTest(TimedRun test, int threads) + { + super(test.toString()); + _test = test; + _threads = new Thread[threads]; + } + + protected void setup() throws Exception + { + _test.setup(); + for(int i = 0; i < _threads.length; i++) + { + _threads[i] = new Thread(new Runner()); + } + } + + protected void teardown() throws Exception + { + _test.teardown(); + } + + protected void run() throws Exception + { + for(Thread t : _threads) + { + t.start(); + } + for(Thread t : _threads) + { + t.join(); + } + } + + private class Runner implements Runnable + { + private Exception error; + + public void run() + { + try + { + _test.run(); + } + catch(Exception e) + { + error = e; + e.printStackTrace(); + } + } + } + +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/LoggingProxyTest.java b/java/broker/test/src/org/apache/qpid/server/util/LoggingProxyTest.java new file mode 100644 index 0000000000..15c9e1a59a --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/LoggingProxyTest.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import junit.framework.JUnit4TestAdapter; +import org.junit.Assert; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class LoggingProxyTest +{ + static interface IFoo { + void foo(); + void foo(int i, Collection c); + String bar(); + String bar(String s, List l); + } + + static class Foo implements IFoo { + public void foo() + { + } + + public void foo(int i, Collection c) + { + } + + public String bar() + { + return null; + } + + public String bar(String s, List l) + { + return "ha"; + } + } + + @Test + public void simple() { + LoggingProxy proxy = new LoggingProxy(new Foo(), 20); + IFoo foo = (IFoo)proxy.getProxy(IFoo.class); + foo.foo(); + assertEquals(2, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(0).toString().matches(".*: foo\\(\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(1).toString().matches(".*: foo\\(\\) returned$")); + + foo.foo(3, Arrays.asList(0, 1, 2)); + assertEquals(4, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(2).toString().matches(".*: foo\\(\\[3, \\[0, 1, 2\\]\\]\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(3).toString().matches(".*: foo\\(\\) returned$")); + + foo.bar(); + assertEquals(6, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(4).toString().matches(".*: bar\\(\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(5).toString().matches(".*: bar\\(\\) returned null$")); + + foo.bar("hello", Arrays.asList(1, 2, 3)); + assertEquals(8, proxy.getBufferSize()); + Assert.assertTrue(proxy.getBuffer().get(6).toString().matches(".*: bar\\(\\[hello, \\[1, 2, 3\\]\\]\\) entered$")); + Assert.assertTrue(proxy.getBuffer().get(7).toString().matches(".*: bar\\(\\) returned ha$")); + + proxy.dump(); + } + + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(LoggingProxyTest.class); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/NullApplicationRegistry.java b/java/broker/test/src/org/apache/qpid/server/util/NullApplicationRegistry.java new file mode 100644 index 0000000000..3daf143561 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/NullApplicationRegistry.java @@ -0,0 +1,103 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +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.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.MapConfiguration; + +import java.util.HashMap; + +public class NullApplicationRegistry extends ApplicationRegistry +{ + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private ManagedObjectRegistry _managedObjectRegistry; + + private AuthenticationManager _authenticationManager; + + private MessageStore _messageStore; + + public NullApplicationRegistry() + { + super(new MapConfiguration(new HashMap())); + } + + public void initialise() throws Exception + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + _queueRegistry = new DefaultQueueRegistry(); + _exchangeFactory = new DefaultExchangeFactory(); + _exchangeRegistry = new DefaultExchangeRegistry(_exchangeFactory); + _authenticationManager = new NullAuthenticationManager(); + _messageStore = new TestableMemoryMessageStore(); + + _configuration.addProperty("heartbeat.delay", 10 * 60); // 10 minutes + } + + public Configuration getConfiguration() + { + return _configuration; + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } +} + diff --git a/java/broker/test/src/org/apache/qpid/server/util/NullAuthenticationManager.java b/java/broker/test/src/org/apache/qpid/server/util/NullAuthenticationManager.java new file mode 100644 index 0000000000..cdde505451 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/NullAuthenticationManager.java @@ -0,0 +1,82 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import org.apache.qpid.server.security.auth.AuthenticationManager; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public class NullAuthenticationManager implements AuthenticationManager +{ + public String getMechanisms() + { + return "PLAIN"; + } + + public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException + { + return new SaslServer() + { + public String getMechanismName() + { + return "PLAIN"; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + return new byte[0]; + } + + public boolean isComplete() + { + return true; + } + + public String getAuthorizationID() + { + return "guest"; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return new byte[0]; + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return new byte[0]; + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + } + }; + } + + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.SUCCESS); + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/RunStats.java b/java/broker/test/src/org/apache/qpid/server/util/RunStats.java new file mode 100644 index 0000000000..248622836d --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/RunStats.java @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +public class RunStats +{ + private long min = Long.MAX_VALUE; + private long max; + private long total; + private int count; + + public void record(long time) + { + max = Math.max(time, max); + min = Math.min(time, min); + total += time; + count++; + } + + public long getMin() + { + return min; + } + + public long getMax() + { + return max; + } + + public long getAverage() + { + return total / count; + } + + public String toString() + { + return "avg=" + getAverage() + ", min=" + min + ", max=" + max; + } +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/TimedRun.java b/java/broker/test/src/org/apache/qpid/server/util/TimedRun.java new file mode 100644 index 0000000000..f779b7fbb6 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/TimedRun.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import java.util.concurrent.Callable; + +public abstract class TimedRun implements Callable +{ + private final String description; + + public TimedRun(String description) + { + this.description = description; + } + + public Long call() throws Exception + { + setup(); + long start = System.currentTimeMillis(); + run(); + long stop = System.currentTimeMillis(); + teardown(); + return stop - start; + } + + public String toString() + { + return description; + } + + protected void setup() throws Exception{} + protected void teardown() throws Exception{} + protected abstract void run() throws Exception; +} diff --git a/java/broker/test/src/org/apache/qpid/server/util/UnitTests.java b/java/broker/test/src/org/apache/qpid/server/util/UnitTests.java new file mode 100644 index 0000000000..d6cc471413 --- /dev/null +++ b/java/broker/test/src/org/apache/qpid/server/util/UnitTests.java @@ -0,0 +1,32 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.server.util; + +import junit.framework.JUnit4TestAdapter; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({LoggingProxyTest.class}) +public class UnitTests +{ + public static junit.framework.Test suite() + { + return new JUnit4TestAdapter(UnitTests.class); + } +} diff --git a/java/build-old.xml b/java/build-old.xml new file mode 100644 index 0000000000..d518b2e08f --- /dev/null +++ b/java/build-old.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/java/build.xml b/java/build.xml new file mode 100644 index 0000000000..91b39881a4 --- /dev/null +++ b/java/build.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/client/build-module.xml b/java/client/build-module.xml new file mode 100644 index 0000000000..637806e14b --- /dev/null +++ b/java/client/build-module.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/java/client/build-old.xml b/java/client/build-old.xml new file mode 100644 index 0000000000..d0fcd090d7 --- /dev/null +++ b/java/client/build-old.xml @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This target can only run inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + This target can only run inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/client/dist/readme.txt b/java/client/dist/readme.txt new file mode 100644 index 0000000000..b5f7b107b6 --- /dev/null +++ b/java/client/dist/readme.txt @@ -0,0 +1,2 @@ +This directory is deliberately empty. +AMQP JAR files will be built here during the build process. diff --git a/java/client/lib/jms/jms.jar b/java/client/lib/jms/jms.jar new file mode 100644 index 0000000000..dad227f694 Binary files /dev/null and b/java/client/lib/jms/jms.jar differ diff --git a/java/client/readme.txt b/java/client/readme.txt new file mode 100644 index 0000000000..ef8fa01717 --- /dev/null +++ b/java/client/readme.txt @@ -0,0 +1,31 @@ +AMQP JMS API + +To build this you will need ant. The build.xml file requires that the amq.home +property be set to point to the location of the root directory under which the +base2 and amqp moduels reside (these contain protocol definitions used in +the code generation). + +You can avoid setting it if you have the following structure: + + root/ + base/ + base2/ + foreign/ + gsl/ + amqp/ + blaze/ + java/ + client + build.xml + Readme.txt [this file] + common + + +Otherwise you can either pass it in on the command line or add it to a file +named build.properties in the same directory as this Readme.txt file. + +E.g.: + +ant -Damq.home=c:\AMQP\ + + diff --git a/java/client/src/log4j.properties b/java/client/src/log4j.properties new file mode 100644 index 0000000000..d3135ff574 --- /dev/null +++ b/java/client/src/log4j.properties @@ -0,0 +1,10 @@ +log4j.rootLogger=${root.logging.level} + + +log4j.logger.org.apache.qpid=${amqj.logging.level}, console +log4j.additivity.org.apache.qpid=false + +log4j.appender.console=org.apache.log4j.ConsoleAppender +log4j.appender.console.Threshold=info +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern=%t %d %p [%c{4}] %m%n diff --git a/java/client/src/org/apache/qpid/client/AMQAuthenticationException.java b/java/client/src/org/apache/qpid/client/AMQAuthenticationException.java new file mode 100644 index 0000000000..93b5cb5b8e --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQAuthenticationException.java @@ -0,0 +1,29 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +public class AMQAuthenticationException extends AMQException +{ + public AMQAuthenticationException(int error, String msg) + { + super(error,msg); + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQBrokerDetails.java b/java/client/src/org/apache/qpid/client/AMQBrokerDetails.java new file mode 100644 index 0000000000..52de858b13 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQBrokerDetails.java @@ -0,0 +1,301 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.jms.BrokerDetails; +import org.apache.qpid.url.URLHelper; +import org.apache.qpid.url.URLSyntaxException; + +import java.util.HashMap; +import java.net.URISyntaxException; +import java.net.URI; + +public class AMQBrokerDetails implements BrokerDetails +{ + private String _host; + private int _port; + private String _transport; + + private HashMap _options; + + public AMQBrokerDetails() + { + _options = new HashMap(); + } + + public AMQBrokerDetails(String url) throws URLSyntaxException + { + this(); + // URL should be of format tcp://host:port?option='value',option='value' + try + { + URI connection = new URI(url); + + String transport = connection.getScheme(); + + // Handles some defaults to minimise changes to existing broker URLS e.g. localhost + if (transport != null) + { + //todo this list of valid transports should be enumerated somewhere + if ((!(transport.equalsIgnoreCase("vm") || + transport.equalsIgnoreCase("tcp")))) + { + if (transport.equalsIgnoreCase("localhost")) + { + connection = new URI(DEFAULT_TRANSPORT + "://" + url); + transport = connection.getScheme(); + } + else + { + if (url.charAt(transport.length()) == ':' && url.charAt(transport.length()+1) != '/' ) + { + //Then most likely we have a host:port value + connection = new URI(DEFAULT_TRANSPORT + "://" + url); + transport = connection.getScheme(); + } + else + { + URLHelper.parseError(0, transport.length(), "Unknown transport", url); + } + } + } + } + else + { + //Default the transport + connection = new URI(DEFAULT_TRANSPORT + "://" + url); + transport = connection.getScheme(); + } + + if (transport == null) + { + URLHelper.parseError(-1, "Unknown transport:'" + transport + "'" + + " In broker URL:'" + url + "' Format: " + URL_FORMAT_EXAMPLE, ""); + } + + setTransport(transport); + + String host = connection.getHost(); + + // Fix for Java 1.5 + if (host == null) + { + host = ""; + } + + setHost(host); + + int port = connection.getPort(); + + if (port == -1) + { + // Another fix for Java 1.5 URI handling + String auth = connection.getAuthority(); + + if (auth != null && auth.startsWith(":")) + { + setPort(Integer.parseInt(auth.substring(1))); + } + else + { + setPort(DEFAULT_PORT); + } + } + else + { + setPort(port); + } + + String queryString = connection.getQuery(); + + URLHelper.parseOptions(_options, queryString); + + //Fragment is #string (not used) + } + catch (URISyntaxException uris) + { + if (uris instanceof URLSyntaxException) + { + throw (URLSyntaxException) uris; + } + + URLHelper.parseError(uris.getIndex(), uris.getReason(), uris.getInput()); + } + } + + public AMQBrokerDetails(String host, int port, boolean useSSL) + { + _host = host; + _port = port; + + if (useSSL) + { + setOption(OPTIONS_SSL, "true"); + } + } + + public String getHost() + { + return _host; + } + + public void setHost(String _host) + { + this._host = _host; + } + + public int getPort() + { + return _port; + } + + public void setPort(int _port) + { + this._port = _port; + } + + public String getTransport() + { + return _transport; + } + + public void setTransport(String _transport) + { + this._transport = _transport; + } + + + public String getOption(String key) + { + return _options.get(key); + } + + public void setOption(String key, String value) + { + _options.put(key, value); + } + + public long getTimeout() + { + if (_options.containsKey(OPTIONS_CONNECT_TIMEOUT)) + { + try + { + return Long.parseLong(_options.get(OPTIONS_CONNECT_TIMEOUT)); + } + catch (NumberFormatException nfe) + { + //Do nothing as we will use the default below. + } + } + + return BrokerDetails.DEFAULT_CONNECT_TIMEOUT; + } + + public void setTimeout(long timeout) + { + setOption(OPTIONS_CONNECT_TIMEOUT, Long.toString(timeout)); + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(_transport); + sb.append("://"); + + if (!(_transport.equalsIgnoreCase("vm"))) + { + sb.append(_host); + } + + sb.append(':'); + sb.append(_port); + + sb.append(printOptionsURL()); + + return sb.toString(); + } + + public boolean equals(Object o) + { + if (!(o instanceof BrokerDetails)) + { + return false; + } + + BrokerDetails bd = (BrokerDetails) o; + + return _host.equalsIgnoreCase(bd.getHost()) && + (_port == bd.getPort()) && + _transport.equalsIgnoreCase(bd.getTransport()) && + (useSSL() == bd.useSSL()); + + //todo do we need to compare all the options as well? + } + + private String printOptionsURL() + { + StringBuffer optionsURL = new StringBuffer(); + + optionsURL.append('?'); + + if (!(_options.isEmpty())) + { + + for (String key : _options.keySet()) + { + optionsURL.append(key); + + optionsURL.append("='"); + + optionsURL.append(_options.get(key)); + + optionsURL.append("'"); + + optionsURL.append(URLHelper.DEFAULT_OPTION_SEPERATOR); + } + } + + //remove the extra DEFAULT_OPTION_SEPERATOR or the '?' if there are no options + optionsURL.deleteCharAt(optionsURL.length() - 1); + + return optionsURL.toString(); + } + + public boolean useSSL() + { + // To be friendly to users we should be case insensitive. + // or simply force users to conform to OPTIONS_SSL + // todo make case insensitive by trying ssl Ssl sSl ssL SSl SsL sSL SSL + + if (_options.containsKey(OPTIONS_SSL)) + { + return _options.get(OPTIONS_SSL).equalsIgnoreCase("true"); + } + + return false; + } + + public void useSSL(boolean ssl) + { + setOption(OPTIONS_SSL, Boolean.toString(ssl)); + } + + +} diff --git a/java/client/src/org/apache/qpid/client/AMQConnection.java b/java/client/src/org/apache/qpid/client/AMQConnection.java new file mode 100644 index 0000000000..f11d4d9307 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQConnection.java @@ -0,0 +1,927 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUndeliveredException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQUnresolvedAddressException; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.failover.FailoverSupport; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.framing.*; +import org.apache.qpid.jms.*; +import org.apache.qpid.jms.Connection; + +import org.apache.log4j.Logger; + +import javax.jms.*; +import javax.jms.Queue; +import javax.jms.Session; +import javax.naming.Reference; +import javax.naming.NamingException; +import javax.naming.StringRefAddr; +import javax.naming.Referenceable; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.net.ConnectException; +import java.nio.channels.UnresolvedAddressException; +import java.text.MessageFormat; + +public class AMQConnection extends Closeable implements Connection, QueueConnection, TopicConnection, Referenceable +{ + private static final Logger _logger = Logger.getLogger(AMQConnection.class); + + private AtomicInteger _idFactory = new AtomicInteger(0); + + /** + * This is the "root" mutex that must be held when doing anything that could be impacted by failover. + * This must be held by any child objects of this connection such as the session, producers and consumers. + */ + private final Object _failoverMutex = new Object(); + + /** + * A channel is roughly analogous to a session. The server can negotiate the maximum number of channels + * per session and we must prevent the client from opening too many. Zero means unlimited. + */ + private long _maximumChannelCount; + + /** + * The maximum size of frame supported by the server + */ + private long _maximumFrameSize; + + /** + * The protocol handler dispatches protocol events for this connection. For example, when the connection is dropped + * the handler deals with this. It also deals with the initial dispatch of any protocol frames to their appropriate + * handler. + */ + private AMQProtocolHandler _protocolHandler; + + /** + * Maps from session id (Integer) to AMQSession instance + */ + private final Map _sessions = new LinkedHashMap(); //fixme this is map is replicated in amqprotocolsession as _channelId2SessionMap + + private String _clientName; + + /** + * The user name to use for authentication + */ + private String _username; + + /** + * The password to use for authentication + */ + private String _password; + + /** + * The virtual path to connect to on the AMQ server + */ + private String _virtualHost; + + private ExceptionListener _exceptionListener; + + private ConnectionListener _connectionListener; + + private ConnectionURL _connectionURL; + + /** + * Whether this connection is started, i.e. whether messages are flowing to consumers. It has no meaning for + * message publication. + */ + private boolean _started; + + /** + * Policy dictating how to failover + */ + private FailoverPolicy _failoverPolicy; + + /* + * _Connected should be refactored with a suitable wait object. + */ + private boolean _connected; + + /* + * The last error code that occured on the connection. Used to return the correct exception to the client + */ + private AMQException _lastAMQException = null; + + public AMQConnection(String broker, String username, String password, + String clientName, String virtualHost) throws AMQException, URLSyntaxException + { + this(new AMQConnectionURL(ConnectionURL.AMQ_PROTOCOL + "://" + + username + ":" + password + "@" + clientName + + virtualHost + "?brokerlist='" + broker + "'")); + } + + public AMQConnection(String host, int port, String username, String password, + String clientName, String virtualHost) throws AMQException, URLSyntaxException + { + this(host, port, false, username, password, clientName, virtualHost); + } + + public AMQConnection(String host, int port, boolean useSSL, String username, String password, + String clientName, String virtualHost) throws AMQException, URLSyntaxException + { + this(new AMQConnectionURL(useSSL ? + ConnectionURL.AMQ_PROTOCOL + "://" + + username + ":" + password + "@" + clientName + + virtualHost + "?brokerlist='tcp://" + host + ":" + port + "'" + + "," + ConnectionURL.OPTIONS_SSL + "='true'" : + ConnectionURL.AMQ_PROTOCOL + "://" + + username + ":" + password + "@" + clientName + + virtualHost + "?brokerlist='tcp://" + host + ":" + port + "'" + + "," + ConnectionURL.OPTIONS_SSL + "='false'" + )); + } + + public AMQConnection(String connection) throws AMQException, URLSyntaxException + { + this(new AMQConnectionURL(connection)); + } + + public AMQConnection(ConnectionURL connectionURL) throws AMQException + { + _logger.info("Connection:" + connectionURL); + + if (connectionURL == null) + { + throw new IllegalArgumentException("Connection must be specified"); + } + + _connectionURL = connectionURL; + + _clientName = connectionURL.getClientName(); + _username = connectionURL.getUsername(); + _password = connectionURL.getPassword(); + _virtualHost = connectionURL.getVirtualHost(); + + _failoverPolicy = new FailoverPolicy(connectionURL); + + _protocolHandler = new AMQProtocolHandler(this); + + // We are not currently connected + _connected = false; + + + Exception lastException = new Exception(); + lastException.initCause(new ConnectException()); + + while (lastException != null && lastException.getCause() instanceof ConnectException && _failoverPolicy.failoverAllowed()) + { + try + { + makeBrokerConnection(_failoverPolicy.getNextBrokerDetails()); + lastException = null; + } + catch (Exception e) + { + lastException = e; + + _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), e.getCause()); + _logger.info(e); + _logger.info(e.getCause()); + } + } + + _logger.debug("Are we connected:" + _connected); + + // Then the Failover Thread will handle conneciton + if (_failoverPolicy.failoverAllowed()) + { + //TODO this needs to be redone so that we are not spinning. + // A suitable object should be set that is then waited on + // and only notified when a connection is made or when + // the AMQConnection gets closed. + while (!_connected && !_closed.get()) + { + try + { + _logger.debug("Sleeping."); + Thread.sleep(100); + } + catch (InterruptedException ie) + { + _logger.debug("Woken up."); + } + } + if (!_failoverPolicy.failoverAllowed() || _failoverPolicy.getCurrentBrokerDetails() == null) + { + if (_lastAMQException != null) + { + throw _lastAMQException; + } + } + } + else + { + String message = null; + + if (lastException != null) + { + if (lastException.getCause() != null) + { + message = lastException.getCause().getMessage(); + } + else + { + message = lastException.getMessage(); + } + } + + if (message == null || message.equals("")) + { + message = "Unable to Connect"; + } + + AMQException e = new AMQConnectionException(message); + + if (lastException != null) + { + if (lastException instanceof UnresolvedAddressException) + { + e = new AMQUnresolvedAddressException(message); + } + e.initCause(lastException); + } + + throw e; + } + } + + protected AMQConnection(String username, String password, String clientName, String virtualHost) + { + _clientName = clientName; + _username = username; + _password = password; + _virtualHost = virtualHost; + } + + private void makeBrokerConnection(BrokerDetails brokerDetail) throws IOException, AMQException + { + try + { + TransportConnection.getInstance().connect(_protocolHandler, brokerDetail); + // this blocks until the connection has been set up or when an error + // has prevented the connection being set up + _protocolHandler.attainState(AMQState.CONNECTION_OPEN); + _failoverPolicy.attainedConnection(); + + //Again this should be changed to a suitable notify + _connected = true; + } + catch (AMQException e) + { + _lastAMQException = e; + throw e; + } + } + + public boolean attemptReconnection(String host, int port, boolean useSSL) + { + BrokerDetails bd = new AMQBrokerDetails(host, port, useSSL); + + _failoverPolicy.setBroker(bd); + + try + { + makeBrokerConnection(bd); + return true; + } + catch (Exception e) + { + _logger.info("Unable to connect to broker at " + bd); + attemptReconnection(); + } + return false; + } + + public boolean attemptReconnection() + { + while (_failoverPolicy.failoverAllowed()) + { + try + { + makeBrokerConnection(_failoverPolicy.getNextBrokerDetails()); + return true; + } + catch (Exception e) + { + if (!(e instanceof AMQException)) + { + _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), e); + } + else + { + _logger.info(e.getMessage() + ":Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails()); + } + } + } + + //connection unsuccessful + return false; + } + + /** + * Get the details of the currently active broker + * + * @return null if no broker is active (i.e. no successful connection has been made, or + * the BrokerDetail instance otherwise + */ + public BrokerDetails getActiveBrokerDetails() + { + return _failoverPolicy.getCurrentBrokerDetails(); + } + + public boolean failoverAllowed() + { + return _failoverPolicy.failoverAllowed(); + } + + public Session createSession(final boolean transacted, final int acknowledgeMode) throws JMSException + { + return createSession(transacted, acknowledgeMode, AMQSession.DEFAULT_PREFETCH); + } + + public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode, + final int prefetch) throws JMSException + { + checkNotClosed(); + if (channelLimitReached()) + { + throw new ChannelLimitReachedException(_maximumChannelCount); + } + else + { + return (org.apache.qpid.jms.Session) new FailoverSupport() + { + public Object operation() throws JMSException + { + int channelId = _idFactory.incrementAndGet(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Write channel open frame for channel id " + channelId); + } + + // We must create the session and register it before actually sending the frame to the server to + // open it, so that there is no window where we could receive data on the channel and not be set + // up to handle it appropriately. + AMQSession session = new AMQSession(AMQConnection.this, channelId, transacted, acknowledgeMode, + prefetch); + _protocolHandler.addSessionByChannel(channelId, session); + registerSession(channelId, session); + + boolean success = false; + try + { + createChannelOverWire(channelId, prefetch, transacted); + success = true; + } + catch (AMQException e) + { + JMSException jmse = new JMSException("Error creating session: " + e); + jmse.setLinkedException(e); + throw jmse; + } + finally + { + if (!success) { + _protocolHandler.removeSessionByChannel(channelId); + deregisterSession(channelId); + } + } + + if (_started) + { + session.start(); + } + return session; + } + }.execute(this); + } + } + + private void createChannelOverWire(int channelId, int prefetch, boolean transacted) + throws AMQException + { + _protocolHandler.syncWrite( + ChannelOpenBody.createAMQFrame(channelId, null), ChannelOpenOkBody.class); + _protocolHandler.syncWrite( + BasicQosBody.createAMQFrame(channelId, 0, prefetch, false), + BasicQosOkBody.class); + + if (transacted) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Issuing TxSelect for " + channelId); + } + _protocolHandler.syncWrite(TxSelectBody.createAMQFrame(channelId), TxSelectOkBody.class); + } + } + + private void reopenChannel(int channelId, int prefetch, boolean transacted) throws AMQException + { + try + { + createChannelOverWire(channelId, prefetch, transacted); + } + catch (AMQException e) + { + _protocolHandler.removeSessionByChannel(channelId); + deregisterSession(channelId); + throw new AMQException("Error reopening channel " + channelId + " after failover: " + e); + } + } + + + public void setFailoverPolicy(FailoverPolicy policy) + { + _failoverPolicy = policy; + } + + public FailoverPolicy getFailoverPolicy() + { + return _failoverPolicy; + } + + public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) throws JMSException + { + return (QueueSession) createSession(transacted, acknowledgeMode); + } + + public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException + { + return (TopicSession) createSession(transacted, acknowledgeMode); + } + + private boolean channelLimitReached() + { + return _maximumChannelCount != 0 && _sessions.size() == _maximumChannelCount; + } + + public String getClientID() throws JMSException + { + checkNotClosed(); + return _clientName; + } + + public void setClientID(String clientID) throws JMSException + { + checkNotClosed(); + _clientName = clientID; + } + + public ConnectionMetaData getMetaData() throws JMSException + { + checkNotClosed(); + // TODO Auto-generated method stub + return null; + } + + public ExceptionListener getExceptionListener() throws JMSException + { + checkNotClosed(); + return _exceptionListener; + } + + public void setExceptionListener(ExceptionListener listener) throws JMSException + { + checkNotClosed(); + _exceptionListener = listener; + } + + /** + * Start the connection, i.e. start flowing messages. Note that this method must be called only from a single thread + * and is not thread safe (which is legal according to the JMS specification). + * + * @throws JMSException + */ + public void start() throws JMSException + { + checkNotClosed(); + if (!_started) + { + final Iterator it = _sessions.entrySet().iterator(); + while (it.hasNext()) + { + final AMQSession s = (AMQSession) ((Map.Entry) it.next()).getValue(); + s.start(); + } + _started = true; + } + } + + public void stop() throws JMSException + { + checkNotClosed(); + + if (_started) + { + for (Iterator i = _sessions.values().iterator(); i.hasNext();) + { + ((AMQSession) i.next()).stop(); + } + _started = false; + } + } + + public void close() throws JMSException + { + synchronized (getFailoverMutex()) + { + if (!_closed.getAndSet(true)) + { + try + { + closeAllSessions(null); + _protocolHandler.closeConnection(); + } + catch (AMQException e) + { + throw new JMSException("Error closing connection: " + e); + } + } + } + } + + /** + * Marks all sessions and their children as closed without sending any protocol messages. Useful when + * you need to mark objects "visible" in userland as closed after failover or other significant event that + * impacts the connection. + *

    + * The caller must hold the failover mutex before calling this method. + */ + private void markAllSessionsClosed() + { + final LinkedList sessionCopy = new LinkedList(_sessions.values()); + final Iterator it = sessionCopy.iterator(); + while (it.hasNext()) + { + final AMQSession session = (AMQSession) it.next(); + + session.markClosed(); + } + _sessions.clear(); + } + + /** + * Close all the sessions, either due to normal connection closure or due to an error occurring. + * + * @param cause if not null, the error that is causing this shutdown + *

    + * The caller must hold the failover mutex before calling this method. + */ + private void closeAllSessions(Throwable cause) throws JMSException + { + final LinkedList sessionCopy = new LinkedList(_sessions.values()); + final Iterator it = sessionCopy.iterator(); + JMSException sessionException = null; + while (it.hasNext()) + { + final AMQSession session = (AMQSession) it.next(); + if (cause != null) + { + session.closed(cause); + } + else + { + try + { + session.close(); + } + catch (JMSException e) + { + _logger.error("Error closing session: " + e); + sessionException = e; + } + } + } + _sessions.clear(); + if (sessionException != null) + { + throw sessionException; + } + } + + public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) throws JMSException + { + checkNotClosed(); + return null; + } + + public ConnectionConsumer createConnectionConsumer(Queue queue, String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) throws JMSException + { + checkNotClosed(); + return null; + } + + public ConnectionConsumer createConnectionConsumer(Topic topic, String messageSelector, + ServerSessionPool sessionPool, + int maxMessages) throws JMSException + { + checkNotClosed(); + return null; + } + + public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName, + String messageSelector, ServerSessionPool sessionPool, + int maxMessages) + throws JMSException + { + // TODO Auto-generated method stub + checkNotClosed(); + return null; + } + + public long getMaximumChannelCount() + { + checkNotClosed(); + return _maximumChannelCount; + } + + public void setConnectionListener(ConnectionListener listener) + { + _connectionListener = listener; + } + + public ConnectionListener getConnectionListener() + { + return _connectionListener; + } + + public void setMaximumChannelCount(long maximumChannelCount) + { + checkNotClosed(); + _maximumChannelCount = maximumChannelCount; + } + + public void setMaximumFrameSize(long frameMax) + { + _maximumFrameSize = frameMax; + } + + public long getMaximumFrameSize() + { + return _maximumFrameSize; + } + + public Map getSessions() + { + return _sessions; + } + + public String getUsername() + { + return _username; + } + + public String getPassword() + { + return _password; + } + + public String getVirtualHost() + { + return _virtualHost; + } + + public AMQProtocolHandler getProtocolHandler() + { + return _protocolHandler; + } + + public void bytesSent(long writtenBytes) + { + if (_connectionListener != null) + { + _connectionListener.bytesSent(writtenBytes); + } + } + + public void bytesReceived(long receivedBytes) + { + if (_connectionListener != null) + { + _connectionListener.bytesReceived(receivedBytes); + } + } + + /** + * Fire the preFailover event to the registered connection listener (if any) + * + * @param redirect true if this is the result of a redirect request rather than a connection error + * @return true if no listener or listener does not veto change + */ + public boolean firePreFailover(boolean redirect) + { + boolean proceed = true; + if (_connectionListener != null) + { + proceed = _connectionListener.preFailover(redirect); + } + return proceed; + } + + /** + * Fire the preResubscribe event to the registered connection listener (if any). If the listener + * vetoes resubscription then all the sessions are closed. + * + * @return true if no listener or listener does not veto resubscription. + * @throws JMSException + */ + public boolean firePreResubscribe() throws JMSException + { + if (_connectionListener != null) + { + boolean resubscribe = _connectionListener.preResubscribe(); + if (!resubscribe) + { + markAllSessionsClosed(); + } + return resubscribe; + } + else + { + return true; + } + } + + /** + * Fires a failover complete event to the registered connection listener (if any). + */ + public void fireFailoverComplete() + { + if (_connectionListener != null) + { + _connectionListener.failoverComplete(); + } + } + + /** + * In order to protect the consistency of the connection and its child sessions, consumers and producers, + * the "failover mutex" must be held when doing any operations that could be corrupted during failover. + * + * @return a mutex. Guaranteed never to change for the lifetime of this connection even if failover occurs. + */ + public final Object getFailoverMutex() + { + return _failoverMutex; + } + + /** + * If failover is taking place this will block until it has completed. If failover + * is not taking place it will return immediately. + * + * @throws InterruptedException + */ + public void blockUntilNotFailingOver() throws InterruptedException + { + _protocolHandler.blockUntilNotFailingOver(); + } + + /** + * Invoked by the AMQProtocolSession when a protocol session exception has occurred. + * This method sends the exception to a JMS exception listener, if configured, and + * propagates the exception to sessions, which in turn will propagate to consumers. + * This allows synchronous consumers to have exceptions thrown to them. + * + * @param cause the exception + */ + public void exceptionReceived(Throwable cause) + { + + _logger.debug("Connection Close done by:" + Thread.currentThread().getName()); + _logger.debug("exceptionReceived is ", cause); + + final JMSException je; + if (cause instanceof JMSException) + { + je = (JMSException) cause; + } + else + { + je = new JMSException("Exception thrown against " + toString() + ": " + cause); + if (cause instanceof Exception) + { + je.setLinkedException((Exception) cause); + } + } + + // in the case of an IOException, MINA has closed the protocol session so we set _closed to true + // so that any generic client code that tries to close the connection will not mess up this error + // handling sequence + if (cause instanceof IOException) + { + _closed.set(true); + } + + if (_exceptionListener != null) + { + _exceptionListener.onException(je); + } + + if (!(cause instanceof AMQUndeliveredException) && !(cause instanceof AMQAuthenticationException)) + { + try + { + _logger.info("Closing AMQConnection due to :" + cause.getMessage()); + _closed.set(true); + closeAllSessions(cause); // FIXME: when doing this end up with RejectedExecutionException from executor. + } + catch (JMSException e) + { + _logger.error("Error closing all sessions: " + e, e); + } + + } + else + { + _logger.info("Not a hard-error connection not closing."); + } + } + + void registerSession(int channelId, AMQSession session) + { + _sessions.put(channelId, session); + } + + void deregisterSession(int channelId) + { + _sessions.remove(channelId); + } + + /** + * For all sessions, and for all consumers in those sessions, resubscribe. This is called during failover handling. + * The caller must hold the failover mutex before calling this method. + */ + public void resubscribeSessions() throws AMQException + { + ArrayList sessions = new ArrayList(_sessions.values()); + _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: remove? + for (Iterator it = sessions.iterator(); it.hasNext();) + { + AMQSession s = (AMQSession) it.next(); + _protocolHandler.addSessionByChannel(s.getChannelId(), s); + reopenChannel(s.getChannelId(), s.getDefaultPrefetch(), s.getTransacted()); + s.resubscribe(); + } + } + + public String toString() + { + StringBuffer buf = new StringBuffer("AMQConnection:\n"); + if (_failoverPolicy.getCurrentBrokerDetails() == null) + { + buf.append("No active broker connection"); + } + else + { + BrokerDetails bd = _failoverPolicy.getCurrentBrokerDetails(); + buf.append("Host: ").append(String.valueOf(bd.getHost())); + buf.append("\nPort: ").append(String.valueOf(bd.getPort())); + } + buf.append("\nVirtual Host: ").append(String.valueOf(_virtualHost)); + buf.append("\nClient ID: ").append(String.valueOf(_clientName)); + buf.append("\nActive session count: ").append(_sessions == null ? 0 : _sessions.size()); + return buf.toString(); + } + + public String toURL() + { + return _connectionURL.toString(); + } + + public Reference getReference() throws NamingException + { + return new Reference( + AMQConnection.class.getName(), + new StringRefAddr(AMQConnection.class.getName(), toURL()), + AMQConnectionFactory.class.getName(), + null); // factory location + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQConnectionFactory.java b/java/client/src/org/apache/qpid/client/AMQConnectionFactory.java new file mode 100644 index 0000000000..05a484e366 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQConnectionFactory.java @@ -0,0 +1,358 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.url.AMQBindingURL; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.jms.ConnectionURL; + +import javax.jms.*; +import javax.jms.Connection; +import javax.naming.*; +import javax.naming.spi.ObjectFactory; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Hashtable; + + +public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory, ObjectFactory, Referenceable +{ + private String _host; + private int _port; + private String _defaultUsername; + private String _defaultPassword; + private String _virtualPath; + + private ConnectionURL _connectionDetails; + + + public AMQConnectionFactory() + { + } + + public AMQConnectionFactory(String url) throws URLSyntaxException + { + _connectionDetails = new AMQConnectionURL(url); + } + + public AMQConnectionFactory(ConnectionURL url) + { + _connectionDetails = url; + } + + public AMQConnectionFactory(String broker, String username, String password, + String clientName, String virtualHost) throws URLSyntaxException + { + this(new AMQConnectionURL(ConnectionURL.AMQ_PROTOCOL + "://" + + username + ":" + password + "@" + clientName + + virtualHost + "?brokerlist='" + broker + "'")); + } + + public AMQConnectionFactory(String host, int port, String virtualPath) + { + this(host, port, "guest", "guest", virtualPath); + } + + public AMQConnectionFactory(String host, int port, String defaultUsername, String defaultPassword, + String virtualPath) + { + _host = host; + _port = port; + _defaultUsername = defaultUsername; + _defaultPassword = defaultPassword; + _virtualPath = virtualPath; + +//todo when setting Host/Port has been resolved then we can use this otherwise those methods won't work with the following line. +// _connectionDetails = new AMQConnectionURL( +// ConnectionURL.AMQ_PROTOCOL + "://" + +// _defaultUsername + ":" + _defaultPassword + "@" + +// virtualPath + "?brokerlist='tcp://" + host + ":" + port + "'"); + } + + /** + * @return The _defaultPassword. + */ + public final String getDefaultPassword(String password) + { + if (_connectionDetails != null) + { + return _connectionDetails.getPassword(); + } + else + { + return _defaultPassword; + } + } + + /** + * @param password The _defaultPassword to set. + */ + public final void setDefaultPassword(String password) + { + if (_connectionDetails != null) + { + _connectionDetails.setPassword(password); + } + _defaultPassword = password; + } + + /** + * @return The _defaultPassword. + */ + public final String getDefaultUsername(String password) + { + if (_connectionDetails != null) + { + return _connectionDetails.getUsername(); + } + else + { + return _defaultUsername; + } + } + + /** + * @param username The _defaultUsername to set. + */ + public final void setDefaultUsername(String username) + { + if (_connectionDetails != null) + { + _connectionDetails.setUsername(username); + } + _defaultUsername = username; + } + + /** + * @return The _host . + */ + public final String getHost() + { + //todo this doesn't make sense in a multi broker URL as we have no current as that is done by AMQConnection + return _host; + } + + /** + * @param host The _host to set. + */ + public final void setHost(String host) + { + //todo if _connectionDetails is set then run _connectionDetails.addBrokerDetails() + // Should perhaps have this method changed to setBroker(host,port) + _host = host; + } + + /** + * @return _port The _port to set. + */ + public final int getPort() + { + //todo see getHost + return _port; + } + + /** + * @param port The port to set. + */ + public final void setPort(int port) + { + //todo see setHost + _port = port; + } + + /** + * @return he _virtualPath. + */ + public final String getVirtualPath() + { + if (_connectionDetails != null) + { + return _connectionDetails.getVirtualHost(); + } + else + { + return _virtualPath; + } + } + + /** + * @param path The _virtualPath to set. + */ + public final void setVirtualPath(String path) + { + if (_connectionDetails != null) + { + _connectionDetails.setVirtualHost(path); + } + + _virtualPath = path; + } + + static String getUniqueClientID() + { + try + { + InetAddress addr = InetAddress.getLocalHost(); + return addr.getHostName() + System.currentTimeMillis(); + } + catch (UnknownHostException e) + { + return null; + } + } + + public Connection createConnection() throws JMSException + { + try + { + if (_connectionDetails != null) + { + if (_connectionDetails.getClientName() == null || _connectionDetails.getClientName().equals("")) + { + _connectionDetails.setClientName(getUniqueClientID()); + } + return new AMQConnection(_connectionDetails); + } + else + { + return new AMQConnection(_host, _port, _defaultUsername, _defaultPassword, getUniqueClientID(), + _virtualPath); + } + } + catch (Exception e) + { + JMSException jmse = new JMSException("Error creating connection: " + e.getMessage()); + jmse.setLinkedException(e); + throw jmse; + } + + + } + + public Connection createConnection(String userName, String password) throws JMSException + { + try + { + return new AMQConnection(_host, _port, userName, password, getUniqueClientID(), _virtualPath); + } + catch (Exception e) + { + JMSException jmse = new JMSException("Error creating connection: " + e.getMessage()); + jmse.setLinkedException(e); + throw jmse; + } + } + + public QueueConnection createQueueConnection() throws JMSException + { + return (QueueConnection) createConnection(); + } + + public QueueConnection createQueueConnection(String username, String password) throws JMSException + { + return (QueueConnection) createConnection(username, password); + } + + public TopicConnection createTopicConnection() throws JMSException + { + return (TopicConnection) createConnection(); + } + + public TopicConnection createTopicConnection(String username, String password) throws JMSException + { + return (TopicConnection) createConnection(username, password); + } + + + public ConnectionURL getConnectionURL() + { + return _connectionDetails; + } + + /** + * JNDI interface to create objects from References. + * + * @param obj The Reference from JNDI + * @param name + * @param ctx + * @param env + * @return AMQConnection,AMQTopic,AMQQueue, or AMQConnectionFactory. + * @throws Exception + */ + public Object getObjectInstance(Object obj, Name name, Context ctx, + Hashtable env) throws Exception + { + if (obj instanceof Reference) + { + Reference ref = (Reference) obj; + + if (ref.getClassName().equals(AMQConnection.class.getName())) + { + RefAddr addr = ref.get(AMQConnection.class.getName()); + + if (addr != null) + { + return new AMQConnection((String) addr.getContent()); + } + } + + if (ref.getClassName().equals(AMQQueue.class.getName())) + { + RefAddr addr = ref.get(AMQQueue.class.getName()); + + if (addr != null) + { + return new AMQQueue(new AMQBindingURL((String) addr.getContent()).getQueueName()); + } + } + + if (ref.getClassName().equals(AMQTopic.class.getName())) + { + RefAddr addr = ref.get(AMQTopic.class.getName()); + + if (addr != null) + { + return new AMQTopic(new AMQBindingURL((String) addr.getContent()).getDestinationName()); + } + } + + if (ref.getClassName().equals(AMQConnectionFactory.class.getName())) + { + RefAddr addr = ref.get(AMQConnectionFactory.class.getName()); + + if (addr != null) + { + return new AMQConnectionFactory(new AMQConnectionURL((String) addr.getContent())); + } + } + + } + return null; + } + + + public Reference getReference() throws NamingException + { + return new Reference( + AMQConnectionFactory.class.getName(), + new StringRefAddr(AMQConnectionFactory.class.getName(), _connectionDetails.getURL()), + AMQConnectionFactory.class.getName(), + null); // factory location + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQConnectionURL.java b/java/client/src/org/apache/qpid/client/AMQConnectionURL.java new file mode 100644 index 0000000000..b87388b9c8 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQConnectionURL.java @@ -0,0 +1,399 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.jms.BrokerDetails; +import org.apache.qpid.jms.ConnectionURL; +import org.apache.qpid.url.URLHelper; +import org.apache.qpid.url.URLSyntaxException; + +import java.util.*; +import java.net.URI; +import java.net.URISyntaxException; + +public class AMQConnectionURL implements ConnectionURL +{ + private String _url; + private String _failoverMethod; + private HashMap _failoverOptions; + private HashMap _options; + private List _brokers; + private String _clientName; + private String _username; + private String _password; + private String _virtualHost; + + public AMQConnectionURL(String fullURL) throws URLSyntaxException + { + _url = fullURL; + _options = new HashMap(); + _brokers = new LinkedList(); + _failoverOptions = new HashMap(); + + // Connection URL format + //amqp://[user:pass@][clientid]/virtualhost?brokerlist='tcp://host:port?option=\'value\',option=\'value\';vm://:3/virtualpath?option=\'value\'',failover='method?option=\'value\',option='value''" + // Options are of course optional except for requiring a single broker in the broker list. + try + { + URI connection = new URI(fullURL); + + if (connection.getScheme() == null || !(connection.getScheme().equalsIgnoreCase(AMQ_PROTOCOL))) + { + throw new URISyntaxException(fullURL, "Not an AMQP URL"); + } + + if (connection.getHost() == null || connection.getHost().equals("")) + { + String uid = AMQConnectionFactory.getUniqueClientID(); + if (uid == null) + { + URLHelper.parseError(-1, "Client Name not specified", fullURL); + } + else + { + setClientName(uid); + } + + } + else + { + setClientName(connection.getHost()); + } + + String userInfo = connection.getUserInfo(); + + if (userInfo == null) + { + //Fix for Java 1.5 which doesn't parse UserInfo for non http URIs + userInfo = connection.getAuthority(); + + if (userInfo != null) + { + int atIndex = userInfo.indexOf('@'); + + if (atIndex != -1) + { + userInfo = userInfo.substring(0, atIndex); + } + else + { + userInfo = null; + } + } + + } + + if (userInfo == null) + { + URLHelper.parseError(AMQ_PROTOCOL.length() + 3, + "User information not found on url", fullURL); + } + else + { + parseUserInfo(userInfo); + } + String virtualHost = connection.getPath(); + + if (virtualHost != null && (!virtualHost.equals(""))) + { + setVirtualHost(virtualHost); + } + else + { + int authLength = connection.getAuthority().length(); + int start = AMQ_PROTOCOL.length() + 3; + int testIndex = start + authLength; + if (testIndex < fullURL.length() && fullURL.charAt(testIndex) == '?') + { + URLHelper.parseError(start, testIndex - start, "Virtual host found", fullURL); + } + else + { + URLHelper.parseError(-1, "Virtual host not specified", fullURL); + } + + } + + + URLHelper.parseOptions(_options, connection.getQuery()); + + processOptions(); + + //Fragment is #string (not used) + //System.out.println(connection.getFragment()); + + } + catch (URISyntaxException uris) + { + if (uris instanceof URLSyntaxException) + { + throw (URLSyntaxException) uris; + } + + int slash = fullURL.indexOf("\\"); + + if (slash == -1) + { + URLHelper.parseError(uris.getIndex(), uris.getReason(), uris.getInput()); + } + else + { + if (slash != 0 && fullURL.charAt(slash - 1) == ':') + { + URLHelper.parseError(slash - 2, fullURL.indexOf('?') - slash + 2, "Virtual host looks like a windows path, forward slash not allowed in URL", fullURL); + } + else + { + URLHelper.parseError(slash, "Forward slash not allowed in URL", fullURL); + } + } + + } + } + + private void parseUserInfo(String userinfo) throws URLSyntaxException + { + //user info = user:pass + + int colonIndex = userinfo.indexOf(':'); + + if (colonIndex == -1) + { + URLHelper.parseError(AMQ_PROTOCOL.length() + 3, userinfo.length(), + "Null password in user information not allowed.", _url); + } + else + { + setUsername(userinfo.substring(0, colonIndex)); + setPassword(userinfo.substring(colonIndex + 1)); + } + + } + + private void processOptions() throws URLSyntaxException + { + if (_options.containsKey(OPTIONS_BROKERLIST)) + { + String brokerlist = _options.get(OPTIONS_BROKERLIST); + + //brokerlist tcp://host:port?option='value',option='value';vm://:3/virtualpath?option='value' + StringTokenizer st = new StringTokenizer(brokerlist, "" + URLHelper.BROKER_SEPARATOR); + + while (st.hasMoreTokens()) + { + String broker = st.nextToken(); + + _brokers.add(new AMQBrokerDetails(broker)); + } + + _options.remove(OPTIONS_BROKERLIST); + } + + if (_options.containsKey(OPTIONS_FAILOVER)) + { + String failover = _options.get(OPTIONS_FAILOVER); + + // failover='method?option='value',option='value'' + + int methodIndex = failover.indexOf('?'); + + if (methodIndex > -1) + { + _failoverMethod = failover.substring(0, methodIndex); + URLHelper.parseOptions(_failoverOptions, failover.substring(methodIndex + 1)); + } + else + { + _failoverMethod = failover; + } + + _options.remove(OPTIONS_FAILOVER); + } + } + + public String getURL() + { + return _url; + } + + public String getFailoverMethod() + { + return _failoverMethod; + } + + public String getFailoverOption(String key) + { + return _failoverOptions.get(key); + } + + public void setFailoverOption(String key, String value) + { + _failoverOptions.put(key, value); + } + + public int getBrokerCount() + { + return _brokers.size(); + } + + public BrokerDetails getBrokerDetails(int index) + { + if (index < _brokers.size()) + { + return _brokers.get(index); + } + else + { + return null; + } + } + + public void addBrokerDetails(BrokerDetails broker) + { + if (!(_brokers.contains(broker))) + { + _brokers.add(broker); + } + } + + public List getAllBrokerDetails() + { + return _brokers; + } + + public String getClientName() + { + return _clientName; + } + + public void setClientName(String clientName) + { + _clientName = clientName; + } + + public String getUsername() + { + return _username; + } + + public void setUsername(String username) + { + _username = username; + } + + public String getPassword() + { + return _password; + } + + public void setPassword(String password) + { + _password = password; + } + + public String getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(String virtuaHost) + { + _virtualHost = virtuaHost; + } + + public String getOption(String key) + { + return _options.get(key); + } + + public void setOption(String key, String value) + { + _options.put(key, value); + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(AMQ_PROTOCOL); + sb.append("://"); + + if (_username != null) + { + sb.append(_username); + + if (_password != null) + { + sb.append(':'); + sb.append(_password); + } + + sb.append('@'); + } + + sb.append(_clientName); + + sb.append(_virtualHost); + + sb.append(optionsToString()); + + return sb.toString(); + } + + private String optionsToString() + { + StringBuffer sb = new StringBuffer(); + + sb.append("?" + OPTIONS_BROKERLIST + "='"); + + for (BrokerDetails service : _brokers) + { + sb.append(service.toString()); + sb.append(';'); + } + + sb.deleteCharAt(sb.length() - 1); + sb.append("'"); + + if (_failoverMethod != null) + { + sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR); + sb.append(OPTIONS_FAILOVER + "='"); + sb.append(_failoverMethod); + sb.append(URLHelper.printOptions(_failoverOptions)); + sb.append("'"); + } + + return sb.toString(); + } + + + public static void main(String[] args) throws URLSyntaxException + { + + String url2 = "amqp://ritchiem:bob@temp?brokerlist='tcp://localhost:5672;jcp://fancyserver:3000/',failover='roundrobin'"; + //"amqp://user:pass@clientid/virtualhost?brokerlist='tcp://host:1?option1=\'value\',option2=\'value\';vm://:3?option1=\'value\'',failover='method?option1=\'value\',option2='value''"; + + ConnectionURL connectionurl2 = new AMQConnectionURL(url2); + + System.out.println(url2); + System.out.println(connectionurl2); + + } + +} diff --git a/java/client/src/org/apache/qpid/client/AMQDestination.java b/java/client/src/org/apache/qpid/client/AMQDestination.java new file mode 100644 index 0000000000..5deb974c5b --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQDestination.java @@ -0,0 +1,282 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.url.BindingURL; +import org.apache.qpid.url.AMQBindingURL; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.url.URLHelper; +import org.apache.qpid.exchange.ExchangeDefaults; + +import javax.naming.Reference; +import javax.naming.NamingException; +import javax.naming.StringRefAddr; +import javax.naming.Referenceable; +import javax.jms.Destination; + + +public abstract class AMQDestination implements Destination, Referenceable +{ + protected final String _exchangeName; + + protected final String _exchangeClass; + + protected final String _destinationName; + + protected boolean _isDurable; + + protected final boolean _isExclusive; + + protected final boolean _isAutoDelete; + + protected String _queueName; + + protected AMQDestination(String url) throws URLSyntaxException + { + this(new AMQBindingURL(url)); + } + + protected AMQDestination(BindingURL binding) + { + _exchangeName = binding.getExchangeName(); + _exchangeClass = binding.getExchangeClass(); + _destinationName = binding.getDestinationName(); + + _isExclusive = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_EXCLUSIVE)); + _isAutoDelete = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_AUTODELETE)); + _isDurable = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_DURABLE)); + _queueName = binding.getQueueName(); + } + + protected AMQDestination(String exchangeName, String exchangeClass, String destinationName, String queueName) + { + this(exchangeName, exchangeClass, destinationName, false, false, queueName); + } + + protected AMQDestination(String exchangeName, String exchangeClass, String destinationName) + { + this(exchangeName, exchangeClass, destinationName, false, false, null); + } + + protected AMQDestination(String exchangeName, String exchangeClass, String destinationName, boolean isExclusive, + boolean isAutoDelete, String queueName) + { + if (destinationName == null) + { + throw new IllegalArgumentException("Destination name must not be null"); + } + if (exchangeName == null) + { + throw new IllegalArgumentException("Exchange name must not be null"); + } + if (exchangeClass == null) + { + throw new IllegalArgumentException("Exchange class must not be null"); + } + _exchangeName = exchangeName; + _exchangeClass = exchangeClass; + _destinationName = destinationName; + _isExclusive = isExclusive; + _isAutoDelete = isAutoDelete; + _queueName = queueName; + } + + public abstract String getEncodedName(); + + public boolean isDurable() + { + return _isDurable; + } + + public String getExchangeName() + { + return _exchangeName; + } + + public String getExchangeClass() + { + return _exchangeClass; + } + + public boolean isTopic() + { + return ExchangeDefaults.TOPIC_EXCHANGE_NAME.equals(_exchangeName); + } + + public boolean isQueue() + { + return ExchangeDefaults.DIRECT_EXCHANGE_NAME.equals(_exchangeName); + } + + public String getDestinationName() + { + return _destinationName; + } + + public String getQueueName() + { + return _queueName; + } + + public void setQueueName(String queueName) + { + _queueName = queueName; + } + + public abstract String getRoutingKey(); + + public boolean isExclusive() + { + return _isExclusive; + } + + public boolean isAutoDelete() + { + return _isAutoDelete; + } + + public abstract boolean isNameRequired(); + + public String toString() + { + return toURL(); + + /* + return "Destination: " + _destinationName + ", " + + "Queue Name: " + _queueName + ", Exchange: " + _exchangeName + + ", Exchange class: " + _exchangeClass + ", Exclusive: " + _isExclusive + + ", AutoDelete: " + _isAutoDelete + ", Routing Key: " + getRoutingKey(); + */ + } + + public String toURL() + { + StringBuffer sb = new StringBuffer(); + + sb.append(_exchangeClass); + sb.append("://"); + sb.append(_exchangeName); + + sb.append("/"); + + if (_destinationName != null) + { + sb.append(_destinationName); + } + + sb.append("/"); + + if (_queueName != null) + { + sb.append(_queueName); + } + + sb.append("?"); + + if (_isDurable) + { + sb.append(BindingURL.OPTION_DURABLE); + sb.append("='true'"); + sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR); + } + + if (_isExclusive) + { + sb.append(BindingURL.OPTION_EXCLUSIVE); + sb.append("='true'"); + sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR); + } + + if (_isAutoDelete) + { + sb.append(BindingURL.OPTION_AUTODELETE); + sb.append("='true'"); + sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR); + } + + //remove the last char '?' if there is no options , ',' if there are. + sb.deleteCharAt(sb.length() - 1); + + return sb.toString(); + } + + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + final AMQDestination that = (AMQDestination) o; + + if (!_destinationName.equals(that._destinationName)) + { + return false; + } + if (!_exchangeClass.equals(that._exchangeClass)) + { + return false; + } + if (!_exchangeName.equals(that._exchangeName)) + { + return false; + } + if ((_queueName == null && that._queueName != null) || + (_queueName != null && !_queueName.equals(that._queueName))) + { + return false; + } + if (_isExclusive != that._isExclusive) + { + return false; + } + if (_isAutoDelete != that._isAutoDelete) + { + return false; + } + return true; + } + + public int hashCode() + { + int result; + result = _exchangeName.hashCode(); + result = 29 * result + _exchangeClass.hashCode(); + result = 29 * result + _destinationName.hashCode(); + if (_queueName != null) + { + result = 29 * result + _queueName.hashCode(); + } + result = result * (_isExclusive ? 13 : 7); + result = result * (_isAutoDelete ? 13 : 7); + return result; + } + + public Reference getReference() throws NamingException + { + return new Reference( + this.getClass().getName(), + new StringRefAddr(this.getClass().getName(), toURL()), + AMQConnectionFactory.class.getName(), + null); // factory location + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQHeadersExchange.java b/java/client/src/org/apache/qpid/client/AMQHeadersExchange.java new file mode 100644 index 0000000000..5b8de7a13f --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQHeadersExchange.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.exchange.ExchangeDefaults; + +/** + * A destination backed by a headers exchange + */ +public class AMQHeadersExchange extends AMQDestination +{ + public AMQHeadersExchange(String queueName) + { + super(queueName, ExchangeDefaults.HEADERS_EXCHANGE_CLASS, queueName, true, true, null); + } + + public String getEncodedName() + { + return getDestinationName(); + } + + public String getRoutingKey() + { + return getDestinationName(); + } + + public boolean isNameRequired() + { + //Not sure what the best approach is here, probably to treat this like a topic + //and allow server to generate names. As it is AMQ specific it doesn't need to + //fit the JMS API expectations so this is not as yet critical. + return false; + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQNoConsumersException.java b/java/client/src/org/apache/qpid/client/AMQNoConsumersException.java new file mode 100644 index 0000000000..19d4da0b8c --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQNoConsumersException.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQUndeliveredException; +import org.apache.qpid.protocol.AMQConstant; + + +public class AMQNoConsumersException extends AMQUndeliveredException +{ + public AMQNoConsumersException(String msg, Object bounced) + { + super(AMQConstant.NO_CONSUMERS.getCode(), msg, bounced); + } + + +} + + diff --git a/java/client/src/org/apache/qpid/client/AMQNoRouteException.java b/java/client/src/org/apache/qpid/client/AMQNoRouteException.java new file mode 100644 index 0000000000..ad885bcbb3 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQNoRouteException.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQUndeliveredException; +import org.apache.qpid.protocol.AMQConstant; + + +public class AMQNoRouteException extends AMQUndeliveredException +{ + public AMQNoRouteException(String msg, Object bounced) + { + super(AMQConstant.NO_ROUTE.getCode(), msg, bounced); + } + + +} + + diff --git a/java/client/src/org/apache/qpid/client/AMQQueue.java b/java/client/src/org/apache/qpid/client/AMQQueue.java new file mode 100644 index 0000000000..0a8e1bdd7c --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQQueue.java @@ -0,0 +1,91 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.url.BindingURL; +import org.apache.qpid.exchange.ExchangeDefaults; + +import javax.jms.Queue; + +public class AMQQueue extends AMQDestination implements Queue +{ + + /** + * Create a reference to a non temporary queue using a BindingURL object. + * Note this does not actually imply the queue exists. + * @param binding a BindingURL object + */ + public AMQQueue(BindingURL binding) + { + super(binding); + } + + /** + * Create a reference to a non temporary queue. Note this does not actually imply the queue exists. + * @param name the name of the queue + */ + public AMQQueue(String name) + { + this(name, false); + } + + /** + * Create a queue with a specified name. + * + * @param name the destination name (used in the routing key) + * @param temporary if true the broker will generate a queue name, also if true then the queue is autodeleted + * and exclusive + */ + public AMQQueue(String name, boolean temporary) + { + // queue name is set to null indicating that the broker assigns a name in the case of temporary queues + // temporary queues are typically used as response queues + this(name, temporary?null:name, temporary, temporary); + _isDurable = !temporary; + } + + /** + * Create a reference to a queue. Note this does not actually imply the queue exists. + * @param destinationName the queue name + * @param queueName the queue name + * @param exclusive true if the queue should only permit a single consumer + * @param autoDelete true if the queue should be deleted automatically when the last consumers detaches + */ + public AMQQueue(String destinationName, String queueName, boolean exclusive, boolean autoDelete) + { + super(ExchangeDefaults.DIRECT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS, destinationName, exclusive, + autoDelete, queueName); + } + + public String getEncodedName() + { + return 'Q' + getQueueName(); + } + + public String getRoutingKey() + { + return getQueueName(); + } + + public boolean isNameRequired() + { + //If the name is null, we require one to be generated by the client so that it will# + //remain valid if we failover (see BLZ-24) + return getQueueName() == null; + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQSession.java b/java/client/src/org/apache/qpid/client/AMQSession.java new file mode 100644 index 0000000000..6fd4e9cbef --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQSession.java @@ -0,0 +1,1149 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUndeliveredException; +import org.apache.qpid.client.message.AbstractJMSMessage; +import org.apache.qpid.client.message.MessageFactoryRegistry; +import org.apache.qpid.client.message.UnprocessedMessage; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.failover.FailoverSupport; +import org.apache.qpid.client.util.FlowControllingBlockingQueue; +import org.apache.qpid.framing.*; +import org.apache.qpid.jms.Session; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.url.AMQBindingURL; +import org.apache.qpid.url.URLSyntaxException; + +import javax.jms.*; +import javax.jms.IllegalStateException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.text.MessageFormat; + +public class AMQSession extends Closeable implements Session, QueueSession, TopicSession +{ + private static final Logger _logger = Logger.getLogger(AMQSession.class); + + public static final int DEFAULT_PREFETCH = 5000; + + private AMQConnection _connection; + + private boolean _transacted; + + private int _acknowledgeMode; + + private int _channelId; + + private int _defaultPrefetch = DEFAULT_PREFETCH; + + /** + * Used in the consume method. We generate the consume tag on the client so that we can use the nowait + * feature. + */ + private int _nextTag = 1; + + /** + * This queue is bounded and is used to store messages before being dispatched to the consumer + */ + private final FlowControllingBlockingQueue _queue; + + private Dispatcher _dispatcher; + + private MessageFactoryRegistry _messageFactoryRegistry; + + /** + * Set of all producers created by this session + */ + private Map _producers = new ConcurrentHashMap(); + + /** + * Maps from consumer tag (String) to JMSMessageConsumer instance + */ + private Map _consumers = new ConcurrentHashMap(); + + /** + * Default value for immediate flag used by producers created by this session is false, i.e. a consumer does not + * need to be attached to a queue + */ + protected static final boolean DEFAULT_IMMEDIATE = false; + + /** + * Default value for mandatory flag used by producers created by this sessio is true, i.e. server will not silently + * drop messages where no queue is connected to the exchange for the message + */ + protected static final boolean DEFAULT_MANDATORY = true; + + /** + * The counter of the next producer id. This id is generated by the session and used only to allow the + * producer to identify itself to the session when deregistering itself. + * + * Access to this id does not require to be synchronized since according to the JMS specification only one + * thread of control is allowed to create producers for any given session instance. + */ + private long _nextProducerId; + + /** + * Track the 'stopped' state of the dispatcher, a session starts in the stopped state. + */ + private volatile AtomicBoolean _stopped = new AtomicBoolean(true); + + /** + * Responsible for decoding a message fragment and passing it to the appropriate message consumer. + */ + private class Dispatcher extends Thread + { + public Dispatcher() + { + super("Dispatcher-Channel-" + _channelId); + } + + public void run() + { + UnprocessedMessage message; + _stopped.set(false); + try + { + while (!_stopped.get() && (message = (UnprocessedMessage)_queue.take()) != null) + { + dispatchMessage(message); + } + } + catch(InterruptedException e) + { + ; + } + + _logger.info("Dispatcher thread terminating for channel " + _channelId); + } + + private void dispatchMessage(UnprocessedMessage message) + { + if (message.deliverBody != null) + { + final BasicMessageConsumer consumer = (BasicMessageConsumer) _consumers.get(message.deliverBody.consumerTag); + + if (consumer == null) + { + _logger.warn("Received a message from queue " + message.deliverBody.consumerTag + " without a handler - ignoring..."); + } + else + { + consumer.notifyMessage(message, _channelId); + } + } + else + { + try + { + // Bounced message is processed here, away from the mina thread + AbstractJMSMessage bouncedMessage = _messageFactoryRegistry.createMessage(0, + false, + message.contentHeader, + message.bodies); + + int errorCode = message.bounceBody.replyCode; + String reason = message.bounceBody.replyText; + _logger.debug("Message returned with error code " + errorCode + " (" + reason + ")"); + + //Todo should this be moved to an exception handler of sorts. Somewhere errors are converted to correct execeptions. + if (errorCode == AMQConstant.NO_CONSUMERS.getCode()) + { + _connection.exceptionReceived(new AMQNoConsumersException("Error: " + reason, bouncedMessage)); + } + else + { + if (errorCode == AMQConstant.NO_ROUTE.getCode()) + { + _connection.exceptionReceived(new AMQNoRouteException("Error: " + reason, bouncedMessage)); + } + else + { + _connection.exceptionReceived(new AMQUndeliveredException(errorCode, "Error: " + reason, bouncedMessage)); + } + } + } + catch (Exception e) + { + _logger.error("Caught exception trying to raise undelivered message exception (dump follows) - ignoring...", e); + } + } + } + + public void stopDispatcher() + { + _stopped.set(true); + interrupt(); + } + } + + AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, + MessageFactoryRegistry messageFactoryRegistry) + { + this(con, channelId, transacted, acknowledgeMode, messageFactoryRegistry, DEFAULT_PREFETCH); + } + + AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, + MessageFactoryRegistry messageFactoryRegistry, int defaultPrefetch) + { + _connection = con; + _transacted = transacted; + if (transacted) + { + _acknowledgeMode = javax.jms.Session.SESSION_TRANSACTED; + } + else + { + _acknowledgeMode = acknowledgeMode; + } + _channelId = channelId; + _messageFactoryRegistry = messageFactoryRegistry; + _defaultPrefetch = defaultPrefetch; + _queue = new FlowControllingBlockingQueue(_defaultPrefetch, + new FlowControllingBlockingQueue.ThresholdListener() + { + public void aboveThreshold(int currentValue) + { + if(_acknowledgeMode == NO_ACKNOWLEDGE) + { + _logger.warn("Above threshold so suspending channel. Current value is " + currentValue); + suspendChannel(); + } + } + + public void underThreshold(int currentValue) + { + if(_acknowledgeMode == NO_ACKNOWLEDGE) + { + _logger.warn("Below threshold so unsuspending channel. Current value is " + currentValue); + unsuspendChannel(); + } + } + }); + } + + AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode) + { + this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry()); + } + + AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetch) + { + this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetch); + } + + AMQConnection getAMQConnection() + { + return _connection; + } + + public BytesMessage createBytesMessage() throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + try + { + return (BytesMessage) _messageFactoryRegistry.createMessage("application/octet-stream"); + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public MapMessage createMapMessage() throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + try + { + return (MapMessage) _messageFactoryRegistry.createMessage("jms/map-message"); + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public javax.jms.Message createMessage() throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + try + { + return (BytesMessage) _messageFactoryRegistry.createMessage("application/octet-stream"); + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public ObjectMessage createObjectMessage() throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + try + { + return (ObjectMessage) _messageFactoryRegistry.createMessage("application/java-object-stream"); + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public ObjectMessage createObjectMessage(Serializable object) throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + try + { + ObjectMessage msg = (ObjectMessage) _messageFactoryRegistry.createMessage("application/java-object-stream"); + msg.setObject(object); + return msg; + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public StreamMessage createStreamMessage() throws JMSException + { + checkNotClosed(); + throw new UnsupportedOperationException("Stream messages not supported"); + } + + public TextMessage createTextMessage() throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + + try + { + return (TextMessage) _messageFactoryRegistry.createMessage("text/plain"); + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public TextMessage createTextMessage(String text) throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + checkNotClosed(); + try + { + TextMessage msg = (TextMessage) _messageFactoryRegistry.createMessage("text/plain"); + msg.setText(text); + return msg; + } + catch (AMQException e) + { + throw new JMSException("Unable to create message: " + e); + } + } + } + + public boolean getTransacted() + { + checkNotClosed(); + return _transacted; + } + + public int getAcknowledgeMode() throws JMSException + { + checkNotClosed(); + return _acknowledgeMode; + } + + public void commit() throws JMSException + { + checkTransacted(); + try + { + // Acknowledge up to message last delivered (if any) for each consumer. + //need to send ack for messages delivered to consumers so far + for(Iterator i = _consumers.values().iterator(); i.hasNext();) + { + ((BasicMessageConsumer) i.next()).acknowledgeLastDelivered(); + } + + // Commits outstanding messages sent and outstanding acknowledgements. + _connection.getProtocolHandler().syncWrite(TxCommitBody.createAMQFrame(_channelId), TxCommitOkBody.class); + } + catch (AMQException e) + { + JMSException exception = new JMSException("Failed to commit: " + e.getMessage()); + exception.setLinkedException(e); + throw exception; + } + } + + public void rollback() throws JMSException + { + checkTransacted(); + try + { + _connection.getProtocolHandler().syncWrite( + TxRollbackBody.createAMQFrame(_channelId), TxRollbackOkBody.class); + } + catch (AMQException e) + { + throw (JMSException) (new JMSException("Failed to rollback: " + e).initCause(e)); + } + } + + public void close() throws JMSException + { + // We must close down all producers and consumers in an orderly fashion. This is the only method + // that can be called from a different thread of control from the one controlling the session + synchronized (_connection.getFailoverMutex()) + { + _closed.set(true); + + // we pass null since this is not an error case + closeProducersAndConsumers(null); + + try + { + _connection.getProtocolHandler().closeSession(this); + final AMQFrame frame = ChannelCloseBody.createAMQFrame( + getChannelId(), AMQConstant.REPLY_SUCCESS.getCode(), "JMS client closing channel", 0, 0); + _connection.getProtocolHandler().syncWrite(frame, ChannelCloseOkBody.class); + // When control resumes at this point, a reply will have been received that + // indicates the broker has closed the channel successfully + + } + catch (AMQException e) + { + throw new JMSException("Error closing session: " + e); + } + finally + { + _connection.deregisterSession(_channelId); + } + } + } + + /** + * Close all producers or consumers. This is called either in the error case or when closing the session normally. + * @param amqe the exception, may be null to indicate no error has occurred + */ + private void closeProducersAndConsumers(AMQException amqe) + { + try + { + closeProducers(); + } + catch (JMSException e) + { + _logger.error("Error closing session: " + e, e); + } + try + { + closeConsumers(amqe); + } + catch (JMSException e) + { + _logger.error("Error closing session: " + e, e); + } + } + + /** + * Called when the server initiates the closure of the session + * unilaterally. + * @param e the exception that caused this session to be closed. Null causes the + */ + public void closed(Throwable e) + { + synchronized (_connection.getFailoverMutex()) + { + // An AMQException has an error code and message already and will be passed in when closure occurs as a + // result of a channel close request + _closed.set(true); + AMQException amqe; + if (e instanceof AMQException) + { + amqe = (AMQException) e; + } + else + { + amqe = new AMQException(_logger, "Closing session forcibly", e); + } + _connection.deregisterSession(_channelId); + closeProducersAndConsumers(amqe); + } + } + + /** + * Called to mark the session as being closed. Useful when the session needs to be made invalid, e.g. after + * failover when the client has veoted resubscription. + * + * The caller of this method must already hold the failover mutex. + */ + void markClosed() + { + _closed.set(true); + _connection.deregisterSession(_channelId); + markClosedProducersAndConsumers(); + } + + private void markClosedProducersAndConsumers() + { + try + { + // no need for a markClosed* method in this case since there is no protocol traffic closing a producer + closeProducers(); + } + catch (JMSException e) + { + _logger.error("Error closing session: " + e, e); + } + try + { + markClosedConsumers(); + } + catch (JMSException e) + { + _logger.error("Error closing session: " + e, e); + } + } + + /** + * Called to close message producers cleanly. This may or may not be as a result of an error. There is + * currently no way of propagating errors to message producers (this is a JMS limitation). + */ + private void closeProducers() throws JMSException + { + // we need to clone the list of producers since the close() method updates the _producers collection + // which would result in a concurrent modification exception + final ArrayList clonedProducers = new ArrayList(_producers.values()); + + final Iterator it = clonedProducers.iterator(); + while (it.hasNext()) + { + final BasicMessageProducer prod = (BasicMessageProducer) it.next(); + prod.close(); + } + // at this point the _producers map is empty + } + + /** + * Called to close message consumers cleanly. This may or may not be as a result of an error. + * @param error not null if this is a result of an error occurring at the connection level + */ + private void closeConsumers(Throwable error) throws JMSException + { + if (_dispatcher != null) + { + _dispatcher.stopDispatcher(); + } + // we need to clone the list of consumers since the close() method updates the _consumers collection + // which would result in a concurrent modification exception + final ArrayList clonedConsumers = new ArrayList(_consumers.values()); + + final Iterator it = clonedConsumers.iterator(); + while (it.hasNext()) + { + final BasicMessageConsumer con = (BasicMessageConsumer) it.next(); + if (error != null) + { + con.notifyError(error); + } + else + { + con.close(); + } + } + // at this point the _consumers map will be empty + } + + private void markClosedConsumers() throws JMSException + { + if (_dispatcher != null) + { + _dispatcher.stopDispatcher(); + } + // we need to clone the list of consumers since the close() method updates the _consumers collection + // which would result in a concurrent modification exception + final ArrayList clonedConsumers = new ArrayList(_consumers.values()); + + final Iterator it = clonedConsumers.iterator(); + while (it.hasNext()) + { + final BasicMessageConsumer con = (BasicMessageConsumer) it.next(); + con.markClosed(); + } + // at this point the _consumers map will be empty + } + + /** + * Asks the broker to resend all unacknowledged messages for the session. + * @throws JMSException + */ + public void recover() throws JMSException + { + checkNotClosed(); + checkNotTransacted(); // throws IllegalStateException if a transacted session + + _connection.getProtocolHandler().writeFrame(BasicRecoverBody.createAMQFrame(_channelId, false)); + } + + public MessageListener getMessageListener() throws JMSException + { + checkNotClosed(); + throw new java.lang.UnsupportedOperationException("MessageListener interface not supported"); + } + + public void setMessageListener(MessageListener listener) throws JMSException + { + checkNotClosed(); + throw new java.lang.UnsupportedOperationException("MessageListener interface not supported"); + } + + public void run() + { + throw new java.lang.UnsupportedOperationException(); + } + + public MessageProducer createProducer(Destination destination, boolean mandatory, + boolean immediate, boolean waitUntilSent) + throws JMSException + { + return createProducerImpl(destination, mandatory, immediate, waitUntilSent); + } + + public MessageProducer createProducer(Destination destination, boolean mandatory, boolean immediate) + throws JMSException + { + return createProducerImpl(destination, mandatory, immediate); + } + + public MessageProducer createProducer(Destination destination, boolean immediate) + throws JMSException + { + return createProducerImpl(destination, DEFAULT_MANDATORY, immediate); + } + + public MessageProducer createProducer(Destination destination) throws JMSException + { + return createProducerImpl(destination, DEFAULT_MANDATORY, DEFAULT_IMMEDIATE); + } + + private org.apache.qpid.jms.MessageProducer createProducerImpl(Destination destination, boolean mandatory, + boolean immediate) + throws JMSException + { + return createProducerImpl(destination, mandatory, immediate, false); + } + + private org.apache.qpid.jms.MessageProducer createProducerImpl(final Destination destination, final boolean mandatory, + final boolean immediate, final boolean waitUntilSent) + throws JMSException + { + return (org.apache.qpid.jms.MessageProducer) new FailoverSupport() + { + public Object operation() + { + checkNotClosed(); + + return new BasicMessageProducer(_connection, (AMQDestination)destination, _transacted, _channelId, + AMQSession.this, _connection.getProtocolHandler(), + getNextProducerId(), immediate, mandatory, waitUntilSent); + } + }.execute(_connection); + } + + public MessageConsumer createConsumer(Destination destination) throws JMSException + { + return createConsumer(destination, _defaultPrefetch, false, false, null); + } + + public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException + { + return createConsumer(destination, _defaultPrefetch, false, false, messageSelector); + } + + public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal) + throws JMSException + { + return createConsumer(destination, _defaultPrefetch, noLocal, false, messageSelector); + } + + public MessageConsumer createConsumer(Destination destination, + int prefetch, + boolean noLocal, + boolean exclusive, + String selector) throws JMSException + { + return createConsumer(destination, prefetch, noLocal, exclusive, selector, null); + } + + public MessageConsumer createConsumer(Destination destination, + int prefetch, + boolean noLocal, + boolean exclusive, + String selector, + FieldTable rawSelector) throws JMSException + { + return createConsumerImpl(destination, prefetch, noLocal, exclusive, + selector, rawSelector); + } + + protected MessageConsumer createConsumerImpl(final Destination destination, + final int prefetch, + final boolean noLocal, + final boolean exclusive, + final String selector, + final FieldTable rawSelector) throws JMSException + { + return (org.apache.qpid.jms.MessageConsumer) new FailoverSupport() + { + public Object operation() throws JMSException + { + checkNotClosed(); + + AMQDestination amqd = (AMQDestination)destination; + + final AMQProtocolHandler protocolHandler = _connection.getProtocolHandler(); + // TODO: construct the rawSelector from the selector string if rawSelector == null + final FieldTable ft = new FieldTable(); + //if (rawSelector != null) + // ft.put("headers", rawSelector.getDataAsBytes()); + if (rawSelector != null) + { + ft.putAll(rawSelector); + } + BasicMessageConsumer consumer = new BasicMessageConsumer(_channelId, _connection, amqd, selector, noLocal, + _messageFactoryRegistry, AMQSession.this, + protocolHandler, ft, prefetch, exclusive, + _acknowledgeMode); + + try + { + registerConsumer(consumer); + } + catch (AMQException e) + { + JMSException ex = new JMSException("Error registering consumer: " + e); + ex.setLinkedException(e); + throw ex; + } + + return consumer; + } + }.execute(_connection); + } + + public void declareExchange(String name, String type) + { + declareExchange(name, type, _connection.getProtocolHandler()); + } + + private void declareExchange(AMQDestination amqd, AMQProtocolHandler protocolHandler) + { + declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler); + } + + private void declareExchange(String name, String type, AMQProtocolHandler protocolHandler) + { + AMQFrame exchangeDeclare = ExchangeDeclareBody.createAMQFrame(_channelId, 0, name, type, false, false, false, false, true, null); + protocolHandler.writeFrame(exchangeDeclare); + } + + /** + * Declare the queue. + * @param amqd + * @param protocolHandler + * @return the queue name. This is useful where the broker is generating a queue name on behalf of the client. + * @throws AMQException + */ + private String declareQueue(AMQDestination amqd, AMQProtocolHandler protocolHandler) throws AMQException + { + // For queues (but not topics) we generate the name in the client rather than the + // server. This allows the name to be reused on failover if required. In general, + // the destination indicates whether it wants a name generated or not. + if(amqd.isNameRequired()) + { + amqd.setQueueName(protocolHandler.generateQueueName()); + } + + AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, 0, amqd.getQueueName(), + false, amqd.isDurable(), amqd.isExclusive(), + amqd.isAutoDelete(), true, null); + + protocolHandler.writeFrame(queueDeclare); + return amqd.getQueueName(); + } + + private void bindQueue(AMQDestination amqd, String queueName, AMQProtocolHandler protocolHandler, FieldTable ft) throws AMQException + { + AMQFrame queueBind = QueueBindBody.createAMQFrame(_channelId, 0, + queueName, amqd.getExchangeName(), + amqd.getRoutingKey(), true, ft); + + protocolHandler.writeFrame(queueBind); + } + + /** + * Register to consume from the queue. + * @param queueName + * @return the consumer tag generated by the broker + */ + private String consumeFromQueue(String queueName, AMQProtocolHandler protocolHandler, int prefetch, + boolean noLocal, boolean exclusive, int acknowledgeMode) throws AMQException + { + //need to generate a consumer tag on the client so we can exploit the nowait flag + String tag = Integer.toString(_nextTag++); + + AMQFrame jmsConsume = BasicConsumeBody.createAMQFrame(_channelId, 0, + queueName, tag, noLocal, + acknowledgeMode == Session.NO_ACKNOWLEDGE, + exclusive, true); + + protocolHandler.writeFrame(jmsConsume); + return tag; + } + + public Queue createQueue(String queueName) throws JMSException + { + if (queueName.indexOf('/') == -1) + { + return new AMQQueue(queueName); + } + else + { + try{ + return new AMQQueue(new AMQBindingURL(queueName)); + }catch(URLSyntaxException urlse) + { + JMSException jmse = new JMSException(urlse.getReason()); + jmse.setLinkedException(urlse); + + throw jmse; + } + } + } + + public QueueReceiver createReceiver(Queue queue) throws JMSException + { + return (QueueReceiver) createConsumer(queue); + } + + public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException + { + return (QueueReceiver) createConsumer(queue, messageSelector); + } + + public QueueSender createSender(Queue queue) throws JMSException + { + return (QueueSender) createProducer(queue); + } + + public Topic createTopic(String topicName) throws JMSException + { + if (topicName.indexOf('/') == -1) + { + return new AMQTopic(topicName); + } + else + { + try{ + return new AMQTopic(new AMQBindingURL(topicName)); + } + catch (URLSyntaxException urlse) + { + JMSException jmse = new JMSException(urlse.getReason()); + jmse.setLinkedException(urlse); + + throw jmse; + } + } + } + + public TopicSubscriber createSubscriber(Topic topic) throws JMSException + { + return (TopicSubscriber) createConsumer(topic); + } + + public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException + { + return (TopicSubscriber) createConsumer(topic, messageSelector, noLocal); + } + + /** + * Note, currently this does not handle reuse of the same name with different topics correctly. + * If a name is reused in creating a new subscriber with a different topic/selecto or no-local + * flag then the subcriber will receive messages matching the old subscription AND the new one. + * The spec states that the new one should replace the old one. + * TODO: fix it. + */ + public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException + { + AMQTopic dest = new AMQTopic((AMQTopic) topic, _connection.getClientID(), name); + return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); + } + + /** + * Note, currently this does not handle reuse of the same name with different topics correctly. + */ + public TopicSubscriber createDurableSubscriber(Topic topic, String name, String messageSelector, boolean noLocal) + throws JMSException + { + AMQTopic dest = new AMQTopic((AMQTopic) topic, _connection.getClientID(), name); + BasicMessageConsumer consumer = (BasicMessageConsumer) createConsumer(dest, messageSelector, noLocal); + return new TopicSubscriberAdaptor(dest, consumer); + } + + public TopicPublisher createPublisher(Topic topic) throws JMSException + { + return (TopicPublisher) createProducer(topic); + } + + public QueueBrowser createBrowser(Queue queue) throws JMSException + { + throw new UnsupportedOperationException("Queue browsing not supported"); + } + + public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException + { + throw new UnsupportedOperationException("Queue browsing not supported"); + } + + public TemporaryQueue createTemporaryQueue() throws JMSException + { + return new AMQTemporaryQueue(); + } + + public TemporaryTopic createTemporaryTopic() throws JMSException + { + return new AMQTemporaryTopic(); + } + + public void unsubscribe(String name) throws JMSException + { + //send a queue.delete for the subscription + String queue = _connection.getClientID() + ":" + name; + AMQFrame frame = QueueDeleteBody.createAMQFrame(_channelId, 0, queue, false, false, true); + _connection.getProtocolHandler().writeFrame(frame); + } + + private void checkTransacted() throws JMSException + { + if (!getTransacted()) + { + throw new IllegalStateException("Session is not transacted"); + } + } + + private void checkNotTransacted() throws JMSException + { + if (getTransacted()) + { + throw new IllegalStateException("Session is transacted"); + } + } + + /** + * Invoked by the MINA IO thread (indirectly) when a message is received from the transport. + * Puts the message onto the queue read by the dispatcher. + * + * @param message the message that has been received + */ + public void messageReceived(UnprocessedMessage message) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Message received in session with channel id " + _channelId); + } + + _queue.add(message); + } + + /** + * Acknowledge a message or several messages. This method can be called via AbstractJMSMessage or from + * a BasicConsumer. The former where the mode is CLIENT_ACK and the latter where the mode is + * AUTO_ACK or similar. + * @param deliveryTag the tag of the last message to be acknowledged + * @param multiple if true will acknowledge all messages up to and including the one specified by the + * delivery tag + */ + public void acknowledgeMessage(long deliveryTag, boolean multiple) + { + final AMQFrame ackFrame = BasicAckBody.createAMQFrame(_channelId, deliveryTag, multiple); + if (_logger.isDebugEnabled()) + { + _logger.debug("Sending ack for delivery tag " + deliveryTag + " on channel " + _channelId); + } + _connection.getProtocolHandler().writeFrame(ackFrame); + } + + public int getDefaultPrefetch() + { + return _defaultPrefetch; + } + + public int getChannelId() + { + return _channelId; + } + + void start() + { + if(_dispatcher != null) + { + //then we stopped this and are restarting, so signal server to resume delivery + unsuspendChannel(); + } + _dispatcher = new Dispatcher(); + _dispatcher.setDaemon(true); + _dispatcher.start(); + } + + void stop() + { + //stop the server delivering messages to this session + suspendChannel(); + + //stop the dispatcher thread + _stopped.set(true); + } + + boolean isStopped() + { + return _stopped.get(); + } + + /** + * Callers must hold the failover mutex before calling this method. + * @param consumer + * @throws AMQException + */ + void registerConsumer(BasicMessageConsumer consumer) throws AMQException + { + AMQDestination amqd = consumer.getDestination(); + + AMQProtocolHandler protocolHandler = _connection.getProtocolHandler(); + + declareExchange(amqd, protocolHandler); + + String queueName = declareQueue(amqd, protocolHandler); + + bindQueue(amqd, queueName, protocolHandler, consumer.getRawSelectorFieldTable()); + + String consumerTag = consumeFromQueue(queueName, protocolHandler, consumer.getPrefetch(), consumer.isNoLocal(), + consumer.isExclusive(), consumer.getAcknowledgeMode()); + + consumer.setConsumerTag(consumerTag); + _consumers.put(consumerTag, consumer); + } + + /** + * Called by the MessageConsumer when closing, to deregister the consumer from the + * map from consumerTag to consumer instance. + * @param consumerTag the consumer tag, that was broker-generated + */ + void deregisterConsumer(String consumerTag) + { + _consumers.remove(consumerTag); + } + + private void registerProducer(long producerId, MessageProducer producer) + { + _producers.put(new Long(producerId), producer); + } + + void deregisterProducer(long producerId) + { + _producers.remove(new Long(producerId)); + } + + private long getNextProducerId() + { + return ++_nextProducerId; + } + + /** + * Resubscribes all producers and consumers. This is called when performing failover. + * @throws AMQException + */ + void resubscribe() throws AMQException + { + resubscribeProducers(); + resubscribeConsumers(); + } + + private void resubscribeProducers() throws AMQException + { + ArrayList producers = new ArrayList(_producers.values()); + _logger.info(MessageFormat.format("Resubscribing producers = {0} producers.size={1}", producers, producers.size())); // FIXME: remove + for (Iterator it = producers.iterator(); it.hasNext();) + { + BasicMessageProducer producer = (BasicMessageProducer) it.next(); + producer.resubscribe(); + } + } + + private void resubscribeConsumers() throws AMQException + { + ArrayList consumers = new ArrayList(_consumers.values()); + _consumers.clear(); + + for (Iterator it = consumers.iterator(); it.hasNext();) + { + BasicMessageConsumer consumer = (BasicMessageConsumer) it.next(); + registerConsumer(consumer); + } + } + + private void suspendChannel() + { + _logger.warn("Suspending channel"); + AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, false); + _connection.getProtocolHandler().writeFrame(channelFlowFrame); + } + + private void unsuspendChannel() + { + _logger.warn("Unsuspending channel"); + AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, true); + _connection.getProtocolHandler().writeFrame(channelFlowFrame); + } +} diff --git a/java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java b/java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java new file mode 100644 index 0000000000..8c029f4919 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQTemporaryQueue.java @@ -0,0 +1,44 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import javax.jms.JMSException; +import javax.jms.TemporaryQueue; + +/** + * AMQ implementation of a TemporaryQueue. + */ +final class AMQTemporaryQueue extends AMQQueue implements TemporaryQueue { + + /** + * Create a new instance of an AMQTemporaryQueue + */ + public AMQTemporaryQueue() { + super("TempQueue" + Long.toString(System.currentTimeMillis()), + null, true, true); + } + + /** + * @see javax.jms.TemporaryQueue#delete() + */ + public void delete() throws JMSException { + throw new UnsupportedOperationException("Delete not supported, " + + "will auto-delete when connection closed"); + } + +} diff --git a/java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java b/java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java new file mode 100644 index 0000000000..5e96f919e4 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQTemporaryTopic.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import javax.jms.JMSException; +import javax.jms.TemporaryTopic; + +/** + * AMQ implementation of TemporaryTopic. + */ +class AMQTemporaryTopic extends AMQTopic implements TemporaryTopic +{ + + /** + * Create new temporary topic. + */ + public AMQTemporaryTopic() + { + super("TempQueue" + Long.toString(System.currentTimeMillis())); + } + + /** + * @see javax.jms.TemporaryTopic#delete() + */ + public void delete() throws JMSException + { + throw new UnsupportedOperationException("Delete not supported, " + + "will auto-delete when connection closed"); + } + +} diff --git a/java/client/src/org/apache/qpid/client/AMQTopic.java b/java/client/src/org/apache/qpid/client/AMQTopic.java new file mode 100644 index 0000000000..aa6d8f95cf --- /dev/null +++ b/java/client/src/org/apache/qpid/client/AMQTopic.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.url.BindingURL; +import org.apache.qpid.exchange.ExchangeDefaults; + +import javax.jms.JMSException; +import javax.jms.Topic; + +public class AMQTopic extends AMQDestination implements Topic + { + /** + * Constructor for use in creating a topic using a BindingURL. + * + * @param binding The binding url object. + */ + public AMQTopic(BindingURL binding) + { + super(binding); + } + + public AMQTopic(String name) + { + super(ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, name, true, true, null); + _isDurable = false; + } + + /** + * Constructor for use in creating a topic to represent a durable subscription + * @param topic + * @param clientId + * @param subscriptionName + */ + public AMQTopic(AMQTopic topic, String clientId, String subscriptionName) + { + super(ExchangeDefaults.TOPIC_EXCHANGE_NAME, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, topic.getDestinationName(), true, false, clientId + ":" + subscriptionName); + _isDurable = true; + } + + public String getTopicName() throws JMSException + { + return super.getDestinationName(); + } + + public String getEncodedName() + { + return 'T' + getDestinationName(); + } + + public String getRoutingKey() + { + return getDestinationName(); + } + + public boolean isNameRequired() + { + // Topics always rely on a server generated queue name. + return false; + } + + /** + * Override since the queue is always private and we must ensure it remains null. If not, + * reuse of the topic when registering consumers will make all consumers listen on the same (private) queue rather + * than getting their own (private) queue. + * + * This is relatively nasty but it is difficult to come up with a more elegant solution, given + * the requirement in the case on AMQQueue and possibly other AMQDestination subclasses to + * use the underlying queue name even where it is server generated. + */ + public void setQueueName(String queueName) + { + } +} diff --git a/java/client/src/org/apache/qpid/client/BasicMessageConsumer.java b/java/client/src/org/apache/qpid/client/BasicMessageConsumer.java new file mode 100644 index 0000000000..5d13a1cd41 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/BasicMessageConsumer.java @@ -0,0 +1,499 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.message.AbstractJMSMessage; +import org.apache.qpid.client.message.MessageFactoryRegistry; +import org.apache.qpid.client.message.UnprocessedMessage; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.BasicCancelBody; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.jms.MessageConsumer; +import org.apache.qpid.jms.Session; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class BasicMessageConsumer extends Closeable implements MessageConsumer +{ + private static final Logger _logger = Logger.getLogger(BasicMessageConsumer.class); + + /** + * The connection being used by this consumer + */ + private AMQConnection _connection; + + private String _messageSelector; + + private boolean _noLocal; + + private AMQDestination _destination; + + /** + * When true indicates that a blocking receive call is in progress + */ + private final AtomicBoolean _receiving = new AtomicBoolean(false); + /** + * Holds an atomic reference to the listener installed. + */ + private final AtomicReference _messageListener = new AtomicReference(); + + /** + * The consumer tag allows us to close the consumer by sending a jmsCancel method to the + * broker + */ + private String _consumerTag; + + /** + * We need to know the channel id when constructing frames + */ + private int _channelId; + + /** + * Used in the blocking receive methods to receive a message from + * the Session thread. Argument true indicates we want strict FIFO semantics + */ + private final SynchronousQueue _synchronousQueue = new SynchronousQueue(true); + + private MessageFactoryRegistry _messageFactory; + + private AMQSession _session; + + private AMQProtocolHandler _protocolHandler; + + /** + * We need to store the "raw" field table so that we can resubscribe in the event of failover being required + */ + private FieldTable _rawSelectorFieldTable; + + /** + * We store the prefetch field in order to be able to reuse it when resubscribing in the event of failover + */ + private int _prefetch; + + /** + * We store the exclusive field in order to be able to reuse it when resubscribing in the event of failover + */ + private boolean _exclusive; + + /** + * The acknowledge mode in force for this consumer. Note that the AMQP protocol allows different ack modes + * per consumer whereas JMS defines this at the session level, hence why we associate it with the consumer in our + * implementation. + */ + private int _acknowledgeMode; + + /** + * Number of messages unacknowledged in DUPS_OK_ACKNOWLEDGE mode + */ + private int _outstanding; + + /** + * Tag of last message delievered, whoch should be acknowledged on commit in + * transaction mode. + */ + private long _lastDeliveryTag; + + BasicMessageConsumer(int channelId, AMQConnection connection, AMQDestination destination, String messageSelector, + boolean noLocal, MessageFactoryRegistry messageFactory, AMQSession session, + AMQProtocolHandler protocolHandler, FieldTable rawSelectorFieldTable, int prefetch, + boolean exclusive, int acknowledgeMode) + { + _channelId = channelId; + _connection = connection; + _messageSelector = messageSelector; + _noLocal = noLocal; + _destination = destination; + _messageFactory = messageFactory; + _session = session; + _protocolHandler = protocolHandler; + _rawSelectorFieldTable = rawSelectorFieldTable; + _prefetch = prefetch; + _exclusive = exclusive; + _acknowledgeMode = acknowledgeMode; + } + + public AMQDestination getDestination() + { + return _destination; + } + + public String getMessageSelector() throws JMSException + { + return _messageSelector; + } + + public MessageListener getMessageListener() throws JMSException + { + return (MessageListener) _messageListener.get(); + } + + public int getAcknowledgeMode() + { + return _acknowledgeMode; + } + + private boolean isMessageListenerSet() + { + return _messageListener.get() != null; + } + + public void setMessageListener(MessageListener messageListener) throws JMSException + { + checkNotClosed(); + + //if the current listener is non-null and the session is not stopped, then + //it is an error to call this method. + + //i.e. it is only valid to call this method if + // + // (a) the session is stopped, in which case the dispatcher is not running + // OR + // (b) the listener is null AND we are not receiving synchronously at present + // + + if (_session.isStopped()) + { + _messageListener.set(messageListener); + _logger.debug("Message listener set for destination " + _destination); + } + else + { + if (_receiving.get()) + { + throw new javax.jms.IllegalStateException("Another thread is already receiving synchronously."); + } + if (!_messageListener.compareAndSet(null, messageListener)) + { + throw new javax.jms.IllegalStateException("Attempt to alter listener while session is started."); + } + _logger.debug("Message listener set for destination " + _destination); + + if (messageListener != null) + { + //handle case where connection has already been started, and the dispatcher is blocked + //doing a put on the _synchronousQueue + Object msg = _synchronousQueue.poll(); + if (msg != null) + { + AbstractJMSMessage jmsMsg = (AbstractJMSMessage) msg; + messageListener.onMessage(jmsMsg); + postDeliver(jmsMsg); + } + } + } + } + + private void acquireReceiving() throws JMSException + { + if (!_receiving.compareAndSet(false, true)) + { + throw new javax.jms.IllegalStateException("Another thread is already receiving."); + } + if (isMessageListenerSet()) + { + throw new javax.jms.IllegalStateException("A listener has already been set."); + } + } + + private void releaseReceiving() + { + _receiving.set(false); + } + + public FieldTable getRawSelectorFieldTable() + { + return _rawSelectorFieldTable; + } + + public int getPrefetch() + { + return _prefetch; + } + + public boolean isNoLocal() + { + return _noLocal; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public Message receive() throws JMSException + { + return receive(0); + } + + public Message receive(long l) throws JMSException + { + checkNotClosed(); + + acquireReceiving(); + + try + { + Object o = null; + if (l > 0) + { + o = _synchronousQueue.poll(l, TimeUnit.MILLISECONDS); + } + else + { + o = _synchronousQueue.take(); + } + final AbstractJMSMessage m = returnMessageOrThrow(o); + if (m != null) + { + postDeliver(m); + } + return m; + } + catch (InterruptedException e) + { + return null; + } + finally + { + releaseReceiving(); + } + } + + public Message receiveNoWait() throws JMSException + { + checkNotClosed(); + + acquireReceiving(); + + try + { + Object o = _synchronousQueue.poll(); + final AbstractJMSMessage m = returnMessageOrThrow(o); + if (m != null) + { + postDeliver(m); + } + return m; + } + finally + { + releaseReceiving(); + } + } + + /** + * We can get back either a Message or an exception from the queue. This method examines the argument and deals + * with it by throwing it (if an exception) or returning it (in any other case). + * @param o + * @return a message only if o is a Message + * @throws JMSException if the argument is a throwable. If it is a JMSException it is rethrown as is, but if not + * a JMSException is created with the linked exception set appropriately + */ + private AbstractJMSMessage returnMessageOrThrow(Object o) + throws JMSException + { + // errors are passed via the queue too since there is no way of interrupting the poll() via the API. + if (o instanceof Throwable) + { + JMSException e = new JMSException("Message consumer forcibly closed due to error: " + o); + if (o instanceof Exception) + { + e.setLinkedException((Exception) o); + } + throw e; + } + else + { + return (AbstractJMSMessage) o; + } + } + + public void close() throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + if (!_closed.getAndSet(true)) + { + final AMQFrame cancelFrame = BasicCancelBody.createAMQFrame(_channelId, _consumerTag, false); + + try + { + _protocolHandler.syncWrite(cancelFrame, BasicCancelOkBody.class); + } + catch (AMQException e) + { + _logger.error("Error closing consumer: " + e, e); + throw new JMSException("Error closing consumer: " + e); + } + + deregisterConsumer(); + } + } + } + + /** + * Called when you need to invalidate a consumer. Used for example when failover has occurred and the + * client has vetoed automatic resubscription. + * The caller must hold the failover mutex. + */ + void markClosed() + { + _closed.set(true); + deregisterConsumer(); + } + + /** + * Called from the AMQSession when a message has arrived for this consumer. This methods handles both the case + * of a message listener or a synchronous receive() caller. + * @param messageFrame the raw unprocessed mesage + * @param channelId channel on which this message was sent + */ + void notifyMessage(UnprocessedMessage messageFrame, int channelId) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("notifyMessage called with message number " + messageFrame.deliverBody.deliveryTag); + } + try + { + AbstractJMSMessage jmsMessage = _messageFactory.createMessage(messageFrame.deliverBody.deliveryTag, + messageFrame.deliverBody.redelivered, + messageFrame.contentHeader, + messageFrame.bodies); + + _logger.debug("Message is of type: " + jmsMessage.getClass().getName()); + + preDeliver(jmsMessage); + + if (isMessageListenerSet()) + { + //we do not need a lock around the test above, and the dispatch below as it is invalid + //for an application to alter an installed listener while the session is started + getMessageListener().onMessage(jmsMessage); + postDeliver(jmsMessage); + } + else + { + _synchronousQueue.put(jmsMessage); + } + } + catch (Exception e) + { + if (e instanceof InterruptedException) + { + _logger.info("SynchronousQueue.put interupted. Usually result of connection closing"); + } + else + { + _logger.error("Caught exception (dump follows) - ignoring...", e); + } + } + } + + private void preDeliver(AbstractJMSMessage msg) + { + switch (_acknowledgeMode) + { + case Session.PRE_ACKNOWLEDGE: + _session.acknowledgeMessage(msg.getDeliveryTag(), false); + break; + case Session.CLIENT_ACKNOWLEDGE: + // we set the session so that when the user calls acknowledge() it can call the method on session + // to send out the appropriate frame + msg.setAMQSession(_session); + break; + } + } + + private void postDeliver(AbstractJMSMessage msg) + { + switch (_acknowledgeMode) + { + case Session.DUPS_OK_ACKNOWLEDGE: + if (++_outstanding >= _prefetch) + { + _session.acknowledgeMessage(msg.getDeliveryTag(), true); + } + break; + case Session.AUTO_ACKNOWLEDGE: + _session.acknowledgeMessage(msg.getDeliveryTag(), false); + break; + case Session.SESSION_TRANSACTED: + _lastDeliveryTag = msg.getDeliveryTag(); + break; + } + } + + /** + * Acknowledge up to last message delivered (if any). Used when commiting. + */ + void acknowledgeLastDelivered() + { + if (_lastDeliveryTag > 0) + { + _session.acknowledgeMessage(_lastDeliveryTag, true); + _lastDeliveryTag = -1; + } + } + + void notifyError(Throwable cause) + { + _closed.set(true); + + // we have no way of propagating the exception to a message listener - a JMS limitation - so we + // deal with the case where we have a synchronous receive() waiting for a message to arrive + if (!isMessageListenerSet()) + { + // offer only succeeds if there is a thread waiting for an item from the queue + if (_synchronousQueue.offer(cause)) + { + _logger.debug("Passed exception to synchronous queue for propagation to receive()"); + } + } + deregisterConsumer(); + } + + /** + * Perform cleanup to deregister this consumer. This occurs when closing the consumer in both the clean + * case and in the case of an error occurring. + */ + private void deregisterConsumer() + { + _session.deregisterConsumer(_consumerTag); + } + + public String getConsumerTag() + { + return _consumerTag; + } + + public void setConsumerTag(String consumerTag) + { + _consumerTag = consumerTag; + } +} diff --git a/java/client/src/org/apache/qpid/client/BasicMessageProducer.java b/java/client/src/org/apache/qpid/client/BasicMessageProducer.java new file mode 100644 index 0000000000..9ff6d8564b --- /dev/null +++ b/java/client/src/org/apache/qpid/client/BasicMessageProducer.java @@ -0,0 +1,480 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.message.AbstractJMSMessage; +import org.apache.qpid.client.message.JMSBytesMessage; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.framing.*; +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; + +import javax.jms.DeliveryMode; +import javax.jms.Destination; +import javax.jms.JMSException; +import javax.jms.Message; +import java.io.UnsupportedEncodingException; + +public class BasicMessageProducer extends Closeable implements org.apache.qpid.jms.MessageProducer +{ + protected final Logger _logger = Logger.getLogger(getClass()); + + private AMQConnection _connection; + + /** + * If true, messages will not get a timestamp. + */ + private boolean _disableTimestamps; + + /** + * Priority of messages created by this producer. + */ + private int _messagePriority; + + /** + * Time to live of messages. Specified in milliseconds but AMQ has 1 second resolution. + */ + private long _timeToLive; + + /** + * Delivery mode used for this producer. + */ + private int _deliveryMode = DeliveryMode.PERSISTENT; + + /** + * The Destination used for this consumer, if specified upon creation. + */ + protected AMQDestination _destination; + + /** + * Default encoding used for messages produced by this producer. + */ + private String _encoding; + + /** + * Default encoding used for message produced by this producer. + */ + private String _mimeType; + + private AMQProtocolHandler _protocolHandler; + + /** + * True if this producer was created from a transacted session + */ + private boolean _transacted; + + private int _channelId; + + /** + * This is an id generated by the session and is used to tie individual producers to the session. This means we + * can deregister a producer with the session when the producer is clsoed. We need to be able to tie producers + * to the session so that when an error is propagated to the session it can close the producer (meaning that + * a client that happens to hold onto a producer reference will get an error if he tries to use it subsequently). + */ + private long _producerId; + + /** + * The session used to create this producer + */ + private AMQSession _session; + + private final boolean _immediate; + + private final boolean _mandatory; + + private final boolean _waitUntilSent; + + protected BasicMessageProducer(AMQConnection connection, AMQDestination destination, boolean transacted, + int channelId, AMQSession session, AMQProtocolHandler protocolHandler, + long producerId, boolean immediate, boolean mandatory, boolean waitUntilSent) + { + _connection = connection; + _destination = destination; + _transacted = transacted; + _protocolHandler = protocolHandler; + _channelId = channelId; + _session = session; + _producerId = producerId; + if (destination != null) + { + declareDestination(destination); + } + _immediate = immediate; + _mandatory = mandatory; + _waitUntilSent = waitUntilSent; + } + + void resubscribe() throws AMQException + { + if (_destination != null) + { + declareDestination(_destination); + } + } + + private void declareDestination(AMQDestination destination) + { + // Declare the exchange + // Note that the durable and internal arguments are ignored since passive is set to false + AMQFrame declare = ExchangeDeclareBody.createAMQFrame(_channelId, 0, destination.getExchangeName(), + destination.getExchangeClass(), false, + false, false, false, true, null); + _protocolHandler.writeFrame(declare); + } + + public void setDisableMessageID(boolean b) throws JMSException + { + checkNotClosed(); + // IGNORED + } + + public boolean getDisableMessageID() throws JMSException + { + checkNotClosed(); + // Always false for AMQP + return false; + } + + public void setDisableMessageTimestamp(boolean b) throws JMSException + { + checkNotClosed(); + _disableTimestamps = b; + } + + public boolean getDisableMessageTimestamp() throws JMSException + { + checkNotClosed(); + return _disableTimestamps; + } + + public void setDeliveryMode(int i) throws JMSException + { + checkNotClosed(); + if (i != DeliveryMode.NON_PERSISTENT && i != DeliveryMode.PERSISTENT) + { + throw new JMSException("DeliveryMode must be either NON_PERSISTENT or PERSISTENT. Value of " + i + + " is illegal"); + } + _deliveryMode = i; + } + + public int getDeliveryMode() throws JMSException + { + checkNotClosed(); + return _deliveryMode; + } + + public void setPriority(int i) throws JMSException + { + checkNotClosed(); + if (i < 0 || i > 9) + { + throw new IllegalArgumentException("Priority of " + i + " is illegal. Value must be in range 0 to 9"); + } + _messagePriority = i; + } + + public int getPriority() throws JMSException + { + checkNotClosed(); + return _messagePriority; + } + + public void setTimeToLive(long l) throws JMSException + { + checkNotClosed(); + if (l < 0) + { + throw new IllegalArgumentException("Time to live must be non-negative - supplied value was " + l); + } + _timeToLive = l; + } + + public long getTimeToLive() throws JMSException + { + checkNotClosed(); + return _timeToLive; + } + + public Destination getDestination() throws JMSException + { + checkNotClosed(); + return _destination; + } + + public void close() throws JMSException + { + _closed.set(true); + _session.deregisterProducer(_producerId); + } + + public void send(Message message) throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + sendImpl(_destination, (AbstractJMSMessage) message, _deliveryMode, _messagePriority, _timeToLive, + _mandatory, _immediate); + } + } + + public void send(Message message, int deliveryMode) throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + sendImpl(_destination, (AbstractJMSMessage) message, deliveryMode, _messagePriority, _timeToLive, + _mandatory, _immediate); + } + } + + public void send(Message message, int deliveryMode, boolean immediate) throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + sendImpl(_destination, (AbstractJMSMessage) message, deliveryMode, _messagePriority, _timeToLive, + _mandatory, immediate); + } + } + + public void send(Message message, int deliveryMode, int priority, + long timeToLive) throws JMSException + { + synchronized (_connection.getFailoverMutex()) + { + sendImpl(_destination, (AbstractJMSMessage)message, deliveryMode, priority, timeToLive, _mandatory, + _immediate); + } + } + + public void send(Destination destination, Message message) throws JMSException + { + checkNotClosed(); + synchronized (_connection.getFailoverMutex()) + { + validateDestination(destination); + sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, _deliveryMode, _messagePriority, _timeToLive, + _mandatory, _immediate); + } + } + + public void send(Destination destination, Message message, int deliveryMode, + int priority, long timeToLive) + throws JMSException + { + checkNotClosed(); + synchronized (_connection.getFailoverMutex()) + { + validateDestination(destination); + sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, + _mandatory, _immediate); + } + } + + public void send(Destination destination, Message message, int deliveryMode, + int priority, long timeToLive, boolean mandatory) + throws JMSException + { + checkNotClosed(); + synchronized (_connection.getFailoverMutex()) + { + validateDestination(destination); + sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, + mandatory, _immediate); + } + } + + public void send(Destination destination, Message message, int deliveryMode, + int priority, long timeToLive, boolean mandatory, boolean immediate) + throws JMSException + { + checkNotClosed(); + synchronized (_connection.getFailoverMutex()) + { + validateDestination(destination); + sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, + mandatory, immediate); + } + } + + public void send(Destination destination, Message message, int deliveryMode, + int priority, long timeToLive, boolean mandatory, + boolean immediate, boolean waitUntilSent) + throws JMSException + { + checkNotClosed(); + synchronized (_connection.getFailoverMutex()) + { + validateDestination(destination); + sendImpl((AMQDestination) destination, (AbstractJMSMessage) message, deliveryMode, priority, timeToLive, + mandatory, immediate, waitUntilSent); + } + } + + private void validateDestination(Destination destination) throws JMSException + { + if (!(destination instanceof AMQDestination)) + { + throw new JMSException("Unsupported destination class: " + + (destination != null?destination.getClass():null)); + } + declareDestination((AMQDestination)destination); + } + + protected void sendImpl(AMQDestination destination, AbstractJMSMessage message, int deliveryMode, int priority, + long timeToLive, boolean mandatory, boolean immediate) throws JMSException + { + sendImpl(destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, _waitUntilSent); + } + /** + * The caller of this method must hold the failover mutex. + * @param destination + * @param message + * @param deliveryMode + * @param priority + * @param timeToLive + * @param mandatory + * @param immediate + * @throws JMSException + */ + protected void sendImpl(AMQDestination destination, AbstractJMSMessage message, int deliveryMode, int priority, + long timeToLive, boolean mandatory, boolean immediate, boolean wait) throws JMSException + { + AMQFrame publishFrame = BasicPublishBody.createAMQFrame(_channelId, 0, destination.getExchangeName(), + destination.getRoutingKey(), mandatory, immediate); + + long currentTime = 0; + if (!_disableTimestamps) + { + currentTime = System.currentTimeMillis(); + message.setJMSTimestamp(currentTime); + } + // + // Very nasty temporary hack for GRM-206. Will be altered ASAP. + // + if(message instanceof JMSBytesMessage) + { + JMSBytesMessage msg = (JMSBytesMessage) message; + if(!msg.isReadable()) + { + msg.reset(); + } + } + ByteBuffer payload = message.getData(); + BasicContentHeaderProperties contentHeaderProperties = message.getJmsContentHeaderProperties(); + + if (timeToLive > 0) + { + if (!_disableTimestamps) + { + contentHeaderProperties.setExpiration(currentTime + timeToLive); + } + } + else + { + if (!_disableTimestamps) + { + contentHeaderProperties.setExpiration(0); + } + } + contentHeaderProperties.setDeliveryMode((byte) deliveryMode); + contentHeaderProperties.setPriority((byte) priority); + + int size = payload.limit(); + ContentBody[] contentBodies = createContentBodies(payload); + AMQFrame[] frames = new AMQFrame[2 + contentBodies.length]; + for (int i = 0; i < contentBodies.length; i++) + { + frames[2 + i] = ContentBody.createAMQFrame(_channelId, contentBodies[i]); + } + if (contentBodies.length > 0 && _logger.isDebugEnabled()) + { + _logger.debug("Sending content body frames to " + destination); + } + + // weight argument of zero indicates no child content headers, just bodies + AMQFrame contentHeaderFrame = ContentHeaderBody.createAMQFrame(_channelId, BasicConsumeBody.CLASS_ID, 0, + contentHeaderProperties, + size); + if (_logger.isDebugEnabled()) + { + _logger.debug("Sending content header frame to " + destination); + } + + frames[0] = publishFrame; + frames[1] = contentHeaderFrame; + CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames); + _protocolHandler.writeFrame(compositeFrame, wait); + } + + /** + * Create content bodies. This will split a large message into numerous bodies depending on the negotiated + * maximum frame size. + * @param payload + * @return the array of content bodies + */ + private ContentBody[] createContentBodies(ByteBuffer payload) + { + if (payload == null) + { + return null; + } + else if (payload.remaining() == 0) + { + return new ContentBody[0]; + } + // we substract one from the total frame maximum size to account for the end of frame marker in a body frame + // (0xCE byte). + int dataLength = payload.remaining(); + final long framePayloadMax = _session.getAMQConnection().getMaximumFrameSize() - 1; + int lastFrame = (dataLength % framePayloadMax) > 0 ? 1 : 0; + int frameCount = (int) (dataLength/framePayloadMax) + lastFrame; + final ContentBody[] bodies = new ContentBody[frameCount]; + + if (frameCount == 1) + { + bodies[0] = new ContentBody(); + bodies[0].payload = payload; + } + else + { + long remaining = dataLength; + for (int i = 0; i < bodies.length; i++) + { + bodies[i] = new ContentBody(); + payload.position((int)framePayloadMax * i); + int length = (remaining >= framePayloadMax) ? (int)framePayloadMax : (int)remaining; + payload.limit(payload.position() + length); + bodies[i].payload = payload.slice(); + remaining -= length; + } + } + return bodies; + } + + public void setMimeType(String mimeType) + { + checkNotClosed(); + _mimeType = mimeType; + } + + public void setEncoding(String encoding) throws UnsupportedEncodingException + { + checkNotClosed(); + _encoding = encoding; + } +} diff --git a/java/client/src/org/apache/qpid/client/Closeable.java b/java/client/src/org/apache/qpid/client/Closeable.java new file mode 100644 index 0000000000..0381823b69 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/Closeable.java @@ -0,0 +1,48 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import javax.jms.JMSException; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Provides support for orderly shutdown of an object. + */ +public abstract class Closeable +{ + /** + * We use an atomic boolean so that we do not have to synchronized access to this flag. Synchronizing + * access to this flag would mean have a synchronized block in every method. + */ + protected final AtomicBoolean _closed = new AtomicBoolean(false); + + protected void checkNotClosed() + { + if (_closed.get()) + { + throw new IllegalStateException("Object " + toString() + " has been closed"); + } + } + + public boolean isClosed() + { + return _closed.get(); + } + + public abstract void close() throws JMSException; +} diff --git a/java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java b/java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java new file mode 100644 index 0000000000..a3471e9140 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/ConnectionTuneParameters.java @@ -0,0 +1,69 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +public class ConnectionTuneParameters +{ + private long _frameMax; + + private int _channelMax; + + private int _heartbeat; + + private long _txnLimit; + + public long getFrameMax() + { + return _frameMax; + } + + public void setFrameMax(long frameMax) + { + _frameMax = frameMax; + } + + public int getChannelMax() + { + return _channelMax; + } + + public void setChannelMax(int channelMax) + { + _channelMax = channelMax; + } + + public int getHeartbeat() + { + return _heartbeat; + } + + public void setHeartbeat(int hearbeat) + { + _heartbeat = hearbeat; + } + + public long getTxnLimit() + { + return _txnLimit; + } + + public void setTxnLimit(long txnLimit) + { + _txnLimit = txnLimit; + } +} diff --git a/java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java b/java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java new file mode 100644 index 0000000000..caa7e5139b --- /dev/null +++ b/java/client/src/org/apache/qpid/client/TopicSubscriberAdaptor.java @@ -0,0 +1,91 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client; + +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.Topic; +import javax.jms.TopicSubscriber; + +/** + * Wraps a MessageConsumer to fulfill the extended TopicSubscriber contract + * + */ +class TopicSubscriberAdaptor implements TopicSubscriber +{ + private final Topic _topic; + private final MessageConsumer _consumer; + private final boolean _noLocal; + + TopicSubscriberAdaptor(Topic topic, MessageConsumer consumer, boolean noLocal) + { + _topic = topic; + _consumer = consumer; + _noLocal = noLocal; + } + TopicSubscriberAdaptor(Topic topic, BasicMessageConsumer consumer) + { + this(topic, consumer, consumer.isNoLocal()); + } + public Topic getTopic() throws JMSException + { + return _topic; + } + + public boolean getNoLocal() throws JMSException + { + return _noLocal; + } + + public String getMessageSelector() throws JMSException + { + return _consumer.getMessageSelector(); + } + + public MessageListener getMessageListener() throws JMSException + { + return _consumer.getMessageListener(); + } + + public void setMessageListener(MessageListener messageListener) throws JMSException + { + _consumer.setMessageListener(messageListener); + } + + public Message receive() throws JMSException + { + return _consumer.receive(); + } + + public Message receive(long l) throws JMSException + { + return _consumer.receive(l); + } + + public Message receiveNoWait() throws JMSException + { + return _consumer.receiveNoWait(); + } + + public void close() throws JMSException + { + _consumer.close(); + } +} diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverException.java b/java/client/src/org/apache/qpid/client/failover/FailoverException.java new file mode 100644 index 0000000000..c17c56bdf7 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/failover/FailoverException.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.failover; + +/** + * This exception is thrown when failover is taking place and we need to let other + * parts of the client know about this. + */ +public class FailoverException extends RuntimeException +{ + public FailoverException(String message) + { + super(message); + } +} diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverHandler.java b/java/client/src/org/apache/qpid/client/failover/FailoverHandler.java new file mode 100644 index 0000000000..0c68c75122 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/failover/FailoverHandler.java @@ -0,0 +1,180 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.failover; + +import org.apache.mina.common.IoSession; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.failover.FailoverException; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.failover.FailoverState; +import org.apache.qpid.AMQDisconnectedException; +import org.apache.log4j.Logger; + +import java.util.concurrent.CountDownLatch; + +/** + * When failover is required, we need a separate thread to handle the establishment of the new connection and + * the transfer of subscriptions. + *

    + * The reason this needs to be a separate thread is because you cannot do this work inside the MINA IO processor + * thread. One significant task is the connection setup which involves a protocol exchange until a particular state + * is achieved. However if you do this in the MINA thread, you have to block until the state is achieved which means + * the IO processor is not able to do anything at all. + */ +public class FailoverHandler implements Runnable +{ + private static final Logger _logger = Logger.getLogger(FailoverHandler.class); + + private final IoSession _session; + private AMQProtocolHandler _amqProtocolHandler; + + /** + * Used where forcing the failover host + */ + private String _host; + + /** + * Used where forcing the failover port + */ + private int _port; + + public FailoverHandler(AMQProtocolHandler amqProtocolHandler, IoSession session) + { + _amqProtocolHandler = amqProtocolHandler; + _session = session; + } + + public void run() + { + if (Thread.currentThread().isDaemon()) + { + throw new IllegalStateException("FailoverHandler must run on a non-daemon thread."); + } + //Thread.currentThread().setName("Failover Thread"); + + _amqProtocolHandler.setFailoverLatch(new CountDownLatch(1)); + + // We wake up listeners. If they can handle failover, they will extend the + // FailoverSupport class and will in turn block on the latch until failover + // has completed before retrying the operation + _amqProtocolHandler.propagateExceptionToWaiters(new FailoverException("Failing over about to start")); + + // Since failover impacts several structures we protect them all with a single mutex. These structures + // are also in child objects of the connection. This allows us to manipulate them without affecting + // client code which runs in a separate thread. + synchronized (_amqProtocolHandler.getConnection().getFailoverMutex()) + { + _logger.info("Starting failover process"); + + // We switch in a new state manager temporarily so that the interaction to get to the "connection open" + // state works, without us having to terminate any existing "state waiters". We could theoretically + // have a state waiter waiting until the connection is closed for some reason. Or in future we may have + // a slightly more complex state model therefore I felt it was worthwhile doing this. + AMQStateManager existingStateManager = _amqProtocolHandler.getStateManager(); + _amqProtocolHandler.setStateManager(new AMQStateManager()); + if (!_amqProtocolHandler.getConnection().firePreFailover(_host != null)) + { + _amqProtocolHandler.setStateManager(existingStateManager); + if (_host != null) + { + _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Redirect was vetoed by client")); + } + else + { + _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Failover was vetoed by client")); + } + _amqProtocolHandler.getFailoverLatch().countDown(); + _amqProtocolHandler.setFailoverLatch(null); + return; + } + boolean failoverSucceeded; + // when host is non null we have a specified failover host otherwise we all the client to cycle through + // all specified hosts + + // if _host has value then we are performing a redirect. + if (_host != null) + { + failoverSucceeded = _amqProtocolHandler.getConnection().attemptReconnection(_host, _port, _amqProtocolHandler.isUseSSL()); + } + else + { + failoverSucceeded = _amqProtocolHandler.getConnection().attemptReconnection(); + } + if (!failoverSucceeded) + { + _amqProtocolHandler.setStateManager(existingStateManager); + _amqProtocolHandler.getConnection().exceptionReceived( + new AMQDisconnectedException("Server closed connection and no failover " + + "was successful")); + } + else + { + _amqProtocolHandler.setStateManager(existingStateManager); + try + { + if (_amqProtocolHandler.getConnection().firePreResubscribe()) + { + _logger.info("Resubscribing on new connection"); + _amqProtocolHandler.getConnection().resubscribeSessions(); + } + else + { + _logger.info("Client vetoed automatic resubscription"); + } + _amqProtocolHandler.getConnection().fireFailoverComplete(); + _amqProtocolHandler.setFailoverState(FailoverState.NOT_STARTED); + _logger.info("Connection failover completed successfully"); + } + catch (Exception e) + { + _logger.info("Failover process failed - exception being propagated by protocol handler"); + _amqProtocolHandler.setFailoverState(FailoverState.FAILED); + try + { + _amqProtocolHandler.exceptionCaught(_session, e); + } + catch (Exception ex) + { + _logger.error("Error notifying protocol session of error: " + ex, ex); + } + } + } + } + _amqProtocolHandler.getFailoverLatch().countDown(); + } + + public String getHost() + { + return _host; + } + + public void setHost(String host) + { + _host = host; + } + + public int getPort() + { + return _port; + } + + public void setPort(int port) + { + _port = port; + } +} diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverState.java b/java/client/src/org/apache/qpid/client/failover/FailoverState.java new file mode 100644 index 0000000000..20886f391c --- /dev/null +++ b/java/client/src/org/apache/qpid/client/failover/FailoverState.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.failover; + +/** + * Enumeration of failover states. Used to handle failover from within AMQProtocolHandler where MINA events need to be + * dealt with and can happen during failover. + */ +public final class FailoverState +{ + private final String _state; + + /** Failover has not yet been attempted on this connection */ + public static final FailoverState NOT_STARTED = new FailoverState("NOT STARTED"); + + /** Failover has been requested on this connection but has not completed */ + public static final FailoverState IN_PROGRESS = new FailoverState("IN PROGRESS"); + + /** Failover has been attempted and failed */ + public static final FailoverState FAILED = new FailoverState("FAILED"); + + private FailoverState(String state) + { + _state = state; + } + + public String toString() + { + return "FailoverState: " + _state; + } +} diff --git a/java/client/src/org/apache/qpid/client/failover/FailoverSupport.java b/java/client/src/org/apache/qpid/client/failover/FailoverSupport.java new file mode 100644 index 0000000000..e2d797dfac --- /dev/null +++ b/java/client/src/org/apache/qpid/client/failover/FailoverSupport.java @@ -0,0 +1,63 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.failover; + +import org.apache.log4j.Logger; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.failover.FailoverException; + +import javax.jms.JMSException; + +public abstract class FailoverSupport +{ + private static final Logger _log = Logger.getLogger(FailoverSupport.class); + + public Object execute(AMQConnection con) throws JMSException + { + // We wait until we are not in the middle of failover before acquiring the mutex and then proceeding. + // Any method that can potentially block for any reason should use this class so that deadlock will not + // occur. The FailoverException is propagated by the AMQProtocolHandler to any listeners (e.g. frame listeners) + // that might be causing a block. When that happens, the exception is caught here and the mutex is released + // before waiting for the failover to complete (either successfully or unsuccessfully). + while (true) + { + try + { + con.blockUntilNotFailingOver(); + } + catch (InterruptedException e) + { + _log.info("Interrupted: " + e, e); + return null; + } + synchronized (con.getFailoverMutex()) + { + try + { + return operation(); + } + catch (FailoverException e) + { + _log.info("Failover exception caught during operation"); + } + } + } + } + + protected abstract Object operation() throws JMSException; +} diff --git a/java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java new file mode 100644 index 0000000000..ab1e84ee17 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicDeliverBody; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.message.UnprocessedMessage; + +public class BasicDeliverMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(BasicDeliverMethodHandler.class); + + private static final BasicDeliverMethodHandler _instance = new BasicDeliverMethodHandler(); + + public static BasicDeliverMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + final UnprocessedMessage msg = new UnprocessedMessage(); + msg.deliverBody = (BasicDeliverBody) evt.getMethod(); + msg.channelId = evt.getChannelId(); + _logger.debug("New JmsDeliver method received"); + evt.getProtocolSession().unprocessedMessageReceived(msg); + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java new file mode 100644 index 0000000000..ae9d7bcb4a --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/BasicReturnMethodHandler.java @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.message.UnprocessedMessage; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.BasicReturnBody; + +public class BasicReturnMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(BasicReturnMethodHandler.class); + + private static final BasicReturnMethodHandler _instance = new BasicReturnMethodHandler(); + + public static BasicReturnMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + _logger.debug("New JmsBounce method received"); + final UnprocessedMessage msg = new UnprocessedMessage(); + msg.deliverBody = null; + msg.bounceBody = (BasicReturnBody) evt.getMethod(); + msg.channelId = evt.getChannelId(); + + evt.getProtocolSession().unprocessedMessageReceived(msg); + } +} \ No newline at end of file diff --git a/java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java new file mode 100644 index 0000000000..959f1eb4df --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQChannelClosedException; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQNoConsumersException; +import org.apache.qpid.client.AMQNoRouteException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; + +public class ChannelCloseMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseMethodHandler.class); + + private static ChannelCloseMethodHandler _handler = new ChannelCloseMethodHandler(); + + public static ChannelCloseMethodHandler getInstance() + { + return _handler; + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + _logger.debug("ChannelClose method received"); + ChannelCloseBody method = (ChannelCloseBody) evt.getMethod(); + + int errorCode = method.replyCode; + String reason = method.replyText; + if (_logger.isDebugEnabled()) + { + _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason); + } + + AMQFrame frame = ChannelCloseOkBody.createAMQFrame(evt.getChannelId()); + evt.getProtocolSession().writeFrame(frame); + if (errorCode != AMQConstant.REPLY_SUCCESS.getCode()) + { + _logger.error("Channel close received with errorCode " + errorCode + ", and reason " + reason); + if (errorCode == AMQConstant.NO_CONSUMERS.getCode()) + { + throw new AMQNoConsumersException("Error: " + reason, null); + } + else + { + if (errorCode == AMQConstant.NO_ROUTE.getCode()) + { + throw new AMQNoRouteException("Error: " + reason, null); + } + else + { + throw new AMQChannelClosedException(errorCode, "Error: " + reason); + } + } + } + evt.getProtocolSession().channelClosed(evt.getChannelId(), errorCode, reason); + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java new file mode 100644 index 0000000000..b857b5e91b --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class ChannelCloseOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseOkMethodHandler.class); + + private static final ChannelCloseOkMethodHandler _instance = new ChannelCloseOkMethodHandler(); + + public static ChannelCloseOkMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId()); + + //todo this should do the closure + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java new file mode 100644 index 0000000000..f26917b2e3 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java @@ -0,0 +1,46 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.ChannelFlowOkBody; + +public class ChannelFlowOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ChannelFlowOkMethodHandler.class); + private static final ChannelFlowOkMethodHandler _instance = new ChannelFlowOkMethodHandler(); + + public static ChannelFlowOkMethodHandler getInstance() + { + return _instance; + } + + private ChannelFlowOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + ChannelFlowOkBody method = (ChannelFlowOkBody) evt.getMethod(); + _logger.debug("Received Channel.Flow-Ok message, active = " + method.active); + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java new file mode 100644 index 0000000000..6ec5b1cb49 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQConnectionClosedException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.client.AMQAuthenticationException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionCloseOkBody; + +public class ConnectionCloseMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class); + + private static ConnectionCloseMethodHandler _handler = new ConnectionCloseMethodHandler(); + + public static ConnectionCloseMethodHandler getInstance() + { + return _handler; + } + + private ConnectionCloseMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + _logger.info("ConnectionClose frame received"); + ConnectionCloseBody method = (ConnectionCloseBody) evt.getMethod(); + + // does it matter + //stateManager.changeState(AMQState.CONNECTION_CLOSING); + + int errorCode = method.replyCode; + String reason = method.replyText; + + // TODO: check whether channel id of zero is appropriate + evt.getProtocolSession().writeFrame(ConnectionCloseOkBody.createAMQFrame((short)0)); + + if (errorCode != 200) + { + if(errorCode == AMQConstant.NOT_ALLOWED.getCode()) + { + _logger.info("Authentication Error:"+Thread.currentThread().getName()); + + evt.getProtocolSession().closeProtocolSession(); + + //todo this is a bit of a fudge (could be conssidered such as each new connection needs a new state manager or at least a fresh state. + stateManager.changeState(AMQState.CONNECTION_NOT_STARTED); + + throw new AMQAuthenticationException(errorCode, reason); + } + else + { + _logger.info("Connection close received with error code " + errorCode); + + + throw new AMQConnectionClosedException(errorCode, "Error: " + reason); + } + } + + // this actually closes the connection in the case where it is not an error. + + evt.getProtocolSession().closeProtocolSession(); + + stateManager.changeState(AMQState.CONNECTION_CLOSED); + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java new file mode 100644 index 0000000000..73986ed81a --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.protocol.AMQProtocolSession; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.ConnectionOpenOkBody; + +public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener +{ + + private static final Logger _logger = Logger.getLogger(ConnectionOpenOkMethodHandler.class); + + private static final ConnectionOpenOkMethodHandler _instance = new ConnectionOpenOkMethodHandler(); + + public static ConnectionOpenOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionOpenOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + AMQProtocolSession session = evt.getProtocolSession(); + ConnectionOpenOkBody method = (ConnectionOpenOkBody) evt.getMethod(); + stateManager.changeState(AMQState.CONNECTION_OPEN); + } + +} diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java new file mode 100644 index 0000000000..d326191d84 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java @@ -0,0 +1,65 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.ConnectionRedirectBody; + +public class ConnectionRedirectMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionRedirectMethodHandler.class); + + private static final int DEFAULT_REDIRECT_PORT = 5672; + + private static ConnectionRedirectMethodHandler _handler = new ConnectionRedirectMethodHandler(); + + public static ConnectionRedirectMethodHandler getInstance() + { + return _handler; + } + + private ConnectionRedirectMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + _logger.info("ConnectionRedirect frame received"); + ConnectionRedirectBody method = (ConnectionRedirectBody) evt.getMethod(); + + // the host is in the form hostname:port with the port being optional + int portIndex = method.host.indexOf(':'); + String host; + int port; + if (portIndex == -1) + { + host = method.host; + port = DEFAULT_REDIRECT_PORT; + } + else + { + host = method.host.substring(0, portIndex); + port = Integer.parseInt(method.host.substring(portIndex + 1)); + } + evt.getProtocolSession().failover(host, port); + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java new file mode 100644 index 0000000000..106ffc908e --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionSecureOkBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.client.protocol.AMQMethodEvent; + +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +public class ConnectionSecureMethodHandler implements StateAwareMethodListener +{ + private static final ConnectionSecureMethodHandler _instance = new ConnectionSecureMethodHandler(); + + public static ConnectionSecureMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + SaslClient client = evt.getProtocolSession().getSaslClient(); + if (client == null) + { + throw new AMQException("No SASL client set up - cannot proceed with authentication"); + } + + ConnectionSecureBody body = (ConnectionSecureBody) evt.getMethod(); + + try + { + // Evaluate server challenge + byte[] response = client.evaluateChallenge(body.challenge); + AMQFrame responseFrame = ConnectionSecureOkBody.createAMQFrame(evt.getChannelId(), response); + evt.getProtocolSession().writeFrame(responseFrame); + } + catch (SaslException e) + { + throw new AMQException("Error processing SASL challenge: " + e, e); + } + + + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java new file mode 100644 index 0000000000..f0d17e9b55 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java @@ -0,0 +1,184 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.protocol.AMQProtocolSession; +import org.apache.qpid.client.security.AMQCallbackHandler; +import org.apache.qpid.client.security.CallbackHandlerRegistry; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.ConnectionStartBody; +import org.apache.qpid.framing.ConnectionStartOkBody; +import org.apache.qpid.framing.FieldTable; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import java.io.UnsupportedEncodingException; +import java.util.HashSet; +import java.util.StringTokenizer; + +public class ConnectionStartMethodHandler implements StateAwareMethodListener +{ + + private static final Logger _log = Logger.getLogger(ConnectionStartMethodHandler.class); + + private static final ConnectionStartMethodHandler _instance = new ConnectionStartMethodHandler(); + + public static ConnectionStartMethodHandler getInstance() + { + return _instance; + } + + private ConnectionStartMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + ConnectionStartBody body = (ConnectionStartBody) evt.getMethod(); + + try + { + // the mechanism we are going to use + String mechanism; + if (body.mechanisms == null) + { + throw new AMQException("mechanism not specified in ConnectionStart method frame"); + } + else + { + mechanism = chooseMechanism(body.mechanisms); + } + + if (mechanism == null) + { + throw new AMQException("No supported security mechanism found, passed: " + new String(body.mechanisms)); + } + + final AMQProtocolSession ps = evt.getProtocolSession(); + byte[] saslResponse; + try + { + SaslClient sc = Sasl.createSaslClient(new String[]{mechanism}, + null, "AMQP", "localhost", + null, createCallbackHandler(mechanism, ps)); + if (sc == null) + { + throw new AMQException("Client SASL configuration error: no SaslClient could be created for mechanism " + + mechanism + ". Please ensure all factories are registered. See DynamicSaslRegistrar for " + + " details of how to register non-standard SASL client providers."); + } + ps.setSaslClient(sc); + saslResponse = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null); + } + catch (SaslException e) + { + ps.setSaslClient(null); + throw new AMQException("Unable to create SASL client: " + e, e); + } + + if (body.locales == null) + { + throw new AMQException("Locales is not defined in Connection Start method"); + } + final String locales = new String(body.locales, "utf8"); + final StringTokenizer tokenizer = new StringTokenizer(locales, " "); + String selectedLocale = null; + if (tokenizer.hasMoreTokens()) + { + selectedLocale = tokenizer.nextToken(); + } + else + { + throw new AMQException("No locales sent from server, passed: " + locales); + } + + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + FieldTable clientProperties = new FieldTable(); + clientProperties.put("instance", ps.getClientID()); + clientProperties.put("product", "Qpid"); + clientProperties.put("version", "1.0"); + clientProperties.put("platform", getFullSystemInfo()); + ps.writeFrame(ConnectionStartOkBody.createAMQFrame(evt.getChannelId(), clientProperties, mechanism, + saslResponse, selectedLocale)); + } + catch (UnsupportedEncodingException e) + { + throw new AMQException(_log, "Unable to decode data: " + e, e); + } + } + + private String getFullSystemInfo() + { + StringBuffer fullSystemInfo = new StringBuffer(); + fullSystemInfo.append(System.getProperty("java.runtime.name")); + fullSystemInfo.append(", " + System.getProperty("java.runtime.version")); + fullSystemInfo.append(", " + System.getProperty("java.vendor")); + fullSystemInfo.append(", " + System.getProperty("os.arch")); + fullSystemInfo.append(", " + System.getProperty("os.name")); + fullSystemInfo.append(", " + System.getProperty("os.version")); + fullSystemInfo.append(", " + System.getProperty("sun.os.patch.level")); + + return fullSystemInfo.toString(); + } + + private String chooseMechanism(byte[] availableMechanisms) throws UnsupportedEncodingException + { + final String mechanisms = new String(availableMechanisms, "utf8"); + StringTokenizer tokenizer = new StringTokenizer(mechanisms, " "); + HashSet mechanismSet = new HashSet(); + while (tokenizer.hasMoreTokens()) + { + mechanismSet.add(tokenizer.nextToken()); + } + + String preferredMechanisms = CallbackHandlerRegistry.getInstance().getMechanisms(); + StringTokenizer prefTokenizer = new StringTokenizer(preferredMechanisms, " "); + while (prefTokenizer.hasMoreTokens()) + { + String mech = prefTokenizer.nextToken(); + if (mechanismSet.contains(mech)) + { + return mech; + } + } + return null; + } + + private AMQCallbackHandler createCallbackHandler(String mechanism, AMQProtocolSession protocolSession) + throws AMQException + { + Class mechanismClass = CallbackHandlerRegistry.getInstance().getCallbackHandlerClass(mechanism); + try + { + Object instance = mechanismClass.newInstance(); + AMQCallbackHandler cbh = (AMQCallbackHandler) instance; + cbh.initialise(protocolSession); + return cbh; + } + catch (Exception e) + { + throw new AMQException("Unable to create callback handler: " + e, e); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java b/java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java new file mode 100644 index 0000000000..6e32bb692e --- /dev/null +++ b/java/client/src/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.ConnectionTuneParameters; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.protocol.AMQProtocolSession; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.framing.ConnectionOpenBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.framing.AMQFrame; + +public class ConnectionTuneMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = Logger.getLogger(ConnectionTuneMethodHandler.class); + + private static final ConnectionTuneMethodHandler _instance = new ConnectionTuneMethodHandler(); + + public static ConnectionTuneMethodHandler getInstance() + { + return _instance; + } + + protected ConnectionTuneMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException + { + _logger.debug("ConnectionTune frame received"); + ConnectionTuneBody frame = (ConnectionTuneBody) evt.getMethod(); + AMQProtocolSession session = evt.getProtocolSession(); + + ConnectionTuneParameters params = session.getConnectionTuneParameters(); + if (params == null) + { + params = new ConnectionTuneParameters(); + } + + params.setFrameMax(frame.frameMax); + params.setChannelMax(frame.channelMax); + params.setHeartbeat(Integer.getInteger("amqj.heartbeat.delay", frame.heartbeat)); + session.setConnectionTuneParameters(params); + + stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); + session.writeFrame(createTuneOkFrame(evt.getChannelId(), params)); + session.writeFrame(createConnectionOpenFrame(evt.getChannelId(), session.getAMQConnection().getVirtualHost(), null, true)); + } + + protected AMQFrame createConnectionOpenFrame(int channel, String path, String capabilities, boolean insist) + { + return ConnectionOpenBody.createAMQFrame(channel, path, capabilities, insist); + } + + protected AMQFrame createTuneOkFrame(int channel, ConnectionTuneParameters params) + { + return ConnectionTuneOkBody.createAMQFrame(channel, params.getChannelMax(), params.getFrameMax(), params.getHeartbeat()); + } +} diff --git a/java/client/src/org/apache/qpid/client/message/AMQMessage.java b/java/client/src/org/apache/qpid/client/message/AMQMessage.java new file mode 100644 index 0000000000..d281a15607 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/AMQMessage.java @@ -0,0 +1,68 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.framing.ContentHeaderProperties; +import org.apache.qpid.client.AMQSession; + +public class AMQMessage +{ + protected ContentHeaderProperties _contentHeaderProperties; + + /** + * If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required + */ + protected AMQSession _session; + + protected final long _deliveryTag; + + public AMQMessage(ContentHeaderProperties properties, long deliveryTag) + { + _contentHeaderProperties = properties; + _deliveryTag = deliveryTag; + } + + public AMQMessage(ContentHeaderProperties properties) + { + this(properties, -1); + } + + /** + * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user + * calls acknowledge() + * @param s the AMQ session that delivered this message + */ + public void setAMQSession(AMQSession s) + { + _session = s; + } + + public AMQSession getAMQSession() + { + return _session; + } + + /** + * Get the AMQ message number assigned to this message + * @return the message number + */ + public long getDeliveryTag() + { + return _deliveryTag; + } +} diff --git a/java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java b/java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java new file mode 100644 index 0000000000..9cffe2c484 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/AbstractJMSMessage.java @@ -0,0 +1,677 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.AMQTopic; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableKeyEnumeration; +import org.apache.commons.collections.map.ReferenceMap; +import org.apache.commons.lang.NotImplementedException; +import org.apache.mina.common.ByteBuffer; + +import javax.jms.Destination; +import javax.jms.JMSException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; + +public abstract class AbstractJMSMessage extends AMQMessage implements javax.jms.Message +{ + private static final Map _destinationCache = Collections.synchronizedMap(new ReferenceMap()); + +// protected Map _messageProperties; + + public static final char BOOLEAN_PROPERTY_PREFIX = 'B'; + public static final char BYTE_PROPERTY_PREFIX = 'b'; + public static final char SHORT_PROPERTY_PREFIX = 's'; + public static final char INT_PROPERTY_PREFIX = 'i'; + public static final char LONG_PROPERTY_PREFIX = 'l'; + public static final char FLOAT_PROPERTY_PREFIX = 'f'; + public static final char DOUBLE_PROPERTY_PREFIX = 'd'; + public static final char STRING_PROPERTY_PREFIX = 'S'; + + protected boolean _redelivered; + + protected ByteBuffer _data; + + protected AbstractJMSMessage(ByteBuffer data) + { + super(new BasicContentHeaderProperties()); + _data = data; + if (_data != null) + { + _data.acquire(); + } + } + + protected AbstractJMSMessage(long deliveryTag, BasicContentHeaderProperties contentHeader, ByteBuffer data) throws AMQException + { + this(contentHeader, deliveryTag); + _data = data; + if (_data != null) + { + _data.acquire(); + } + } + + protected AbstractJMSMessage(BasicContentHeaderProperties contentHeader, long deliveryTag) + { + super(contentHeader, deliveryTag); + } + + public String getJMSMessageID() throws JMSException + { + if (getJmsContentHeaderProperties().getMessageId() == null) + { + getJmsContentHeaderProperties().setMessageId("ID:" + _deliveryTag); + } + return getJmsContentHeaderProperties().getMessageId(); + } + + public void setJMSMessageID(String messageId) throws JMSException + { + getJmsContentHeaderProperties().setMessageId(messageId); + } + + public long getJMSTimestamp() throws JMSException + { + return new Long(getJmsContentHeaderProperties().getTimestamp()).longValue(); + } + + public void setJMSTimestamp(long timestamp) throws JMSException + { + getJmsContentHeaderProperties().setTimestamp(timestamp); + } + + public byte[] getJMSCorrelationIDAsBytes() throws JMSException + { + return getJmsContentHeaderProperties().getCorrelationId().getBytes(); + } + + public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException + { + getJmsContentHeaderProperties().setCorrelationId(new String(bytes)); + } + + public void setJMSCorrelationID(String correlationId) throws JMSException + { + getJmsContentHeaderProperties().setCorrelationId(correlationId); + } + + public String getJMSCorrelationID() throws JMSException + { + return getJmsContentHeaderProperties().getCorrelationId(); + } + + public Destination getJMSReplyTo() throws JMSException + { + String replyToEncoding = getJmsContentHeaderProperties().getReplyTo(); + if (replyToEncoding == null) + { + return null; + } + else + { + Destination dest = (Destination) _destinationCache.get(replyToEncoding); + if (dest == null) + { + char destType = replyToEncoding.charAt(0); + if (destType == 'Q') + { + dest = new AMQQueue(replyToEncoding.substring(1)); + } + else if (destType == 'T') + { + dest = new AMQTopic(replyToEncoding.substring(1)); + } + else + { + throw new JMSException("Illegal value in JMS_ReplyTo property: " + replyToEncoding); + } + _destinationCache.put(replyToEncoding, dest); + } + return dest; + } + } + + public void setJMSReplyTo(Destination destination) throws JMSException + { + if (destination == null) + { + throw new IllegalArgumentException("Null destination not allowed"); + } + if (!(destination instanceof AMQDestination)) + { + throw new IllegalArgumentException("ReplyTo destination my be an AMQ destination - passed argument was type " + + destination.getClass()); + } + final AMQDestination amqd = (AMQDestination) destination; + + final String encodedDestination = amqd.getEncodedName(); + _destinationCache.put(encodedDestination, destination); + getJmsContentHeaderProperties().setReplyTo(encodedDestination); + } + + public Destination getJMSDestination() throws JMSException + { + // TODO: implement this once we have sorted out how to figure out the exchange class + throw new NotImplementedException(); + } + + public void setJMSDestination(Destination destination) throws JMSException + { + throw new NotImplementedException(); + } + + public int getJMSDeliveryMode() throws JMSException + { + return getJmsContentHeaderProperties().getDeliveryMode(); + } + + public void setJMSDeliveryMode(int i) throws JMSException + { + getJmsContentHeaderProperties().setDeliveryMode((byte) i); + } + + public boolean getJMSRedelivered() throws JMSException + { + return _redelivered; + } + + public void setJMSRedelivered(boolean b) throws JMSException + { + _redelivered = b; + } + + public String getJMSType() throws JMSException + { + return getMimeType(); + } + + public void setJMSType(String string) throws JMSException + { + throw new JMSException("Cannot set JMS Type - it is implicitly defined based on message type"); + } + + public long getJMSExpiration() throws JMSException + { + return new Long(getJmsContentHeaderProperties().getExpiration()).longValue(); + } + + public void setJMSExpiration(long l) throws JMSException + { + getJmsContentHeaderProperties().setExpiration(l); + } + + public int getJMSPriority() throws JMSException + { + return getJmsContentHeaderProperties().getPriority(); + } + + public void setJMSPriority(int i) throws JMSException + { + getJmsContentHeaderProperties().setPriority((byte) i); + } + + public void clearProperties() throws JMSException + { + if (getJmsContentHeaderProperties().getHeaders() != null) + { + getJmsContentHeaderProperties().getHeaders().clear(); + } + } + + public boolean propertyExists(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return false; + } + else + { + // TODO: fix this + return getJmsContentHeaderProperties().getHeaders().containsKey(STRING_PROPERTY_PREFIX + propertyName); + } + } + + public boolean getBooleanProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Boolean.valueOf(null).booleanValue(); + } + else + { + // store as integer as temporary workaround + //Boolean b = (Boolean) getJmsContentHeaderProperties().headers.get(BOOLEAN_PROPERTY_PREFIX + propertyName); + Long b = (Long) getJmsContentHeaderProperties().getHeaders().get(BOOLEAN_PROPERTY_PREFIX + propertyName); + + if (b == null) + { + return Boolean.valueOf(null).booleanValue(); + } + else + { + return b.longValue() != 0; + } + } + } + + public byte getByteProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Byte.valueOf(null).byteValue(); + } + else + { + Byte b = (Byte) getJmsContentHeaderProperties().getHeaders().get(BYTE_PROPERTY_PREFIX + propertyName); + if (b == null) + { + return Byte.valueOf(null).byteValue(); + } + else + { + return b.byteValue(); + } + } + } + + public short getShortProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Short.valueOf(null).shortValue(); + } + else + { + Short s = (Short) getJmsContentHeaderProperties().getHeaders().get(SHORT_PROPERTY_PREFIX + propertyName); + if (s == null) + { + return Short.valueOf(null).shortValue(); + } + else + { + return s.shortValue(); + } + } + } + + public int getIntProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Integer.valueOf(null).intValue(); + } + else + { + Integer i = (Integer) getJmsContentHeaderProperties().getHeaders().get(INT_PROPERTY_PREFIX + propertyName); + if (i == null) + { + return Integer.valueOf(null).intValue(); + } + else + { + return i.intValue(); + } + } + } + + public long getLongProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Long.valueOf(null).longValue(); + } + else + { + Long l = (Long) getJmsContentHeaderProperties().getHeaders().get(LONG_PROPERTY_PREFIX + propertyName); + if (l == null) + { + // temp - the spec says do this but this throws a NumberFormatException + //return Long.valueOf(null).longValue(); + return 0; + } + else + { + return l.longValue(); + } + } + } + + public float getFloatProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Float.valueOf(null).floatValue(); + } + else + { + final Float f = (Float) getJmsContentHeaderProperties().getHeaders().get(FLOAT_PROPERTY_PREFIX + propertyName); + if (f == null) + { + return Float.valueOf(null).floatValue(); + } + else + { + return f.floatValue(); + } + } + } + + public double getDoubleProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return Double.valueOf(null).doubleValue(); + } + else + { + final Double d = (Double) getJmsContentHeaderProperties().getHeaders().get(DOUBLE_PROPERTY_PREFIX + propertyName); + if (d == null) + { + return Double.valueOf(null).doubleValue(); + } + else + { + return d.shortValue(); + } + } + } + + public String getStringProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + return null; + } + else + { + return (String) getJmsContentHeaderProperties().getHeaders().get(STRING_PROPERTY_PREFIX + propertyName); + } + } + + public Object getObjectProperty(String propertyName) throws JMSException + { + checkPropertyName(propertyName); + throw new JMSException("Not implemented yet"); + } + + public Enumeration getPropertyNames() throws JMSException + { + return new FieldTableKeyEnumeration(getJmsContentHeaderProperties().getHeaders()) + { + public Object nextElement() + { + String propName = (String) _iterator.next(); + + //The propertyName has a single Char prefix. Skip this. + return propName.substring(1); + } + }; + } + + public void setBooleanProperty(String propertyName, boolean b) throws JMSException + { + checkPropertyName(propertyName); + //getJmsContentHeaderProperties().headers.put(BOOLEAN_PROPERTY_PREFIX + propertyName, Boolean.valueOf(b)); + getJmsContentHeaderProperties().getHeaders().put(BOOLEAN_PROPERTY_PREFIX + propertyName, b ? new Long(1) : new Long(0)); + } + + public void setByteProperty(String propertyName, byte b) throws JMSException + { + checkPropertyName(propertyName); + getJmsContentHeaderProperties().getHeaders().put(BYTE_PROPERTY_PREFIX + propertyName, new Byte(b)); + } + + public void setShortProperty(String propertyName, short i) throws JMSException + { + checkPropertyName(propertyName); + getJmsContentHeaderProperties().getHeaders().put(SHORT_PROPERTY_PREFIX + propertyName, new Short(i)); + } + + public void setIntProperty(String propertyName, int i) throws JMSException + { + checkPropertyName(propertyName); + getJmsContentHeaderProperties().getHeaders().put(INT_PROPERTY_PREFIX + propertyName, new Integer(i)); + } + + public void setLongProperty(String propertyName, long l) throws JMSException + { + checkPropertyName(propertyName); + getJmsContentHeaderProperties().getHeaders().put(LONG_PROPERTY_PREFIX + propertyName, new Long(l)); + } + + public void setFloatProperty(String propertyName, float f) throws JMSException + { + checkPropertyName(propertyName); + getJmsContentHeaderProperties().getHeaders().put(FLOAT_PROPERTY_PREFIX + propertyName, new Float(f)); + } + + public void setDoubleProperty(String propertyName, double v) throws JMSException + { + checkPropertyName(propertyName); + getJmsContentHeaderProperties().getHeaders().put(DOUBLE_PROPERTY_PREFIX + propertyName, new Double(v)); + } + + public void setStringProperty(String propertyName, String value) throws JMSException + { + checkPropertyName(propertyName); + createPropertyMapIfRequired(); + propertyName = STRING_PROPERTY_PREFIX + propertyName; + getJmsContentHeaderProperties().getHeaders().put(propertyName, value); + } + + private void createPropertyMapIfRequired() + { + if (getJmsContentHeaderProperties().getHeaders() == null) + { + getJmsContentHeaderProperties().setHeaders(new FieldTable()); + } + } + + public void setObjectProperty(String string, Object object) throws JMSException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void acknowledge() throws JMSException + { + // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge + // is not specified. In our case, we only set the session field where client acknowledge mode is specified. + if (_session != null) + { + // we set multiple to true here since acknowledgement implies acknowledge of all previous messages + // received on the session + _session.acknowledgeMessage(_deliveryTag, true); + } + } + + public abstract void clearBody() throws JMSException; + + /** + * Get a String representation of the body of the message. Used in the + * toString() method which outputs this before message properties. + */ + public abstract String toBodyString() throws JMSException; + + public abstract String getMimeType(); + + public String toString() + { + try + { + StringBuffer buf = new StringBuffer("Body:\n"); + buf.append(toBodyString()); + buf.append("\nJMS timestamp: ").append(getJMSTimestamp()); + buf.append("\nJMS expiration: ").append(getJMSExpiration()); + buf.append("\nJMS priority: ").append(getJMSPriority()); + buf.append("\nJMS delivery mode: ").append(getJMSDeliveryMode()); + buf.append("\nJMS reply to: ").append(String.valueOf(getJMSReplyTo())); + buf.append("\nAMQ message number: ").append(_deliveryTag); + buf.append("\nProperties:"); + if (getJmsContentHeaderProperties().getHeaders() == null) + { + buf.append(""); + } + else + { + final Iterator it = getJmsContentHeaderProperties().getHeaders().entrySet().iterator(); + while (it.hasNext()) + { + final Map.Entry entry = (Map.Entry) it.next(); + final String propertyName = (String) entry.getKey(); + if (propertyName == null) + { + buf.append("\nInternal error: Property with NULL key defined"); + } + else + { + buf.append('\n').append(propertyName.substring(1)); + + char typeIdentifier = propertyName.charAt(0); + switch (typeIdentifier) + { + case org.apache.qpid.client.message.AbstractJMSMessage.BOOLEAN_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.BYTE_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.SHORT_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.INT_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.LONG_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.FLOAT_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.DOUBLE_PROPERTY_PREFIX: + buf.append(" "); + break; + case org.apache.qpid.client.message.AbstractJMSMessage.STRING_PROPERTY_PREFIX: + buf.append(" "); + break; + default: + buf.append("= bytes.length ? bytes.length : _data.remaining()); + _data.get(bytes, 0, count); + return count; + } + + public int readBytes(byte[] bytes, int maxLength) throws JMSException + { + if (bytes == null) + { + throw new IllegalArgumentException("byte array must not be null"); + } + if (maxLength > bytes.length) + { + throw new IllegalArgumentException("maxLength must be <= bytes.length"); + } + checkReadable(); + int count = (_data.remaining() >= maxLength ? maxLength : _data.remaining()); + _data.get(bytes, 0, count); + return count; + } + + public void writeBoolean(boolean b) throws JMSException + { + checkWritable(); + _data.put(b?(byte)1:(byte)0); + } + + public void writeByte(byte b) throws JMSException + { + checkWritable(); + _data.put(b); + } + + public void writeShort(short i) throws JMSException + { + checkWritable(); + _data.putShort(i); + } + + public void writeChar(char c) throws JMSException + { + checkWritable(); + _data.putChar(c); + } + + public void writeInt(int i) throws JMSException + { + checkWritable(); + _data.putInt(i); + } + + public void writeLong(long l) throws JMSException + { + checkWritable(); + _data.putLong(l); + } + + public void writeFloat(float v) throws JMSException + { + checkWritable(); + _data.putFloat(v); + } + + public void writeDouble(double v) throws JMSException + { + checkWritable(); + _data.putDouble(v); + } + + public void writeUTF(String string) throws JMSException + { + checkWritable(); + try + { + _data.putString(string, Charset.forName("UTF-8").newEncoder()); + } + catch (CharacterCodingException e) + { + JMSException ex = new JMSException("Unable to encode string: " + e); + ex.setLinkedException(e); + throw ex; + } + } + + public void writeBytes(byte[] bytes) throws JMSException + { + checkWritable(); + _data.put(bytes); + } + + public void writeBytes(byte[] bytes, int offset, int length) throws JMSException + { + checkWritable(); + _data.put(bytes, offset, length); + } + + public void writeObject(Object object) throws JMSException + { + checkWritable(); + if (object == null) + { + throw new NullPointerException("Argument must not be null"); + } + _data.putObject(object); + } + + public void reset() throws JMSException + { + //checkWritable(); + _data.flip(); + _readable = true; + } + + public boolean isReadable() + { + return _readable; + } +} diff --git a/java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java b/java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java new file mode 100644 index 0000000000..0e3aa45047 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/JMSBytesMessageFactory.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; + +import javax.jms.JMSException; + +public class JMSBytesMessageFactory extends AbstractJMSMessageFactory +{ + protected AbstractJMSMessage createMessage(long deliveryTag, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException + { + return new JMSBytesMessage(deliveryTag, data, contentHeader); + } + + public AbstractJMSMessage createMessage() throws JMSException + { + return new JMSBytesMessage(); + } +} diff --git a/java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java b/java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java new file mode 100644 index 0000000000..f0ac87c2d7 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/JMSObjectMessage.java @@ -0,0 +1,175 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.AMQException; +import org.apache.mina.common.ByteBuffer; + +import javax.jms.ObjectMessage; +import javax.jms.JMSException; +import javax.jms.MessageFormatException; +import javax.jms.MessageNotWriteableException; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.CharacterCodingException; + +public class JMSObjectMessage extends AbstractJMSMessage implements ObjectMessage +{ + static final String MIME_TYPE = "application/java-object-stream"; + private final boolean _readonly; + + private static final int DEFAULT_BUFFER_SIZE = 1024; + /** + * Creates empty, writable message for use by producers + */ + JMSObjectMessage() + { + this(null); + } + + private JMSObjectMessage(ByteBuffer data) + { + super(data); + if (data == null) + { + _data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); + _data.setAutoExpand(true); + } + _readonly = (data != null); + getJmsContentHeaderProperties().setContentType(MIME_TYPE); + } + + /** + * Creates read only message for delivery to consumers + */ + JMSObjectMessage(long messageNbr, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException + { + super(messageNbr, (BasicContentHeaderProperties) contentHeader.properties, data); + _readonly = data != null; + } + + public void clearBody() throws JMSException + { + if (_data != null) + { + _data.release(); + } + _data = null; + } + + public String toBodyString() throws JMSException + { + return toString(_data); + } + + public String getMimeType() + { + return MIME_TYPE; + } + + public void setObject(Serializable serializable) throws JMSException + { + if (_readonly) + { + throw new MessageNotWriteableException("Message is not writable."); + } + + if (_data == null) + { + _data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); + _data.setAutoExpand(true); + } + try + { + ObjectOutputStream out = new ObjectOutputStream(_data.asOutputStream()); + out.writeObject(serializable); + out.flush(); + out.close(); + _data.rewind(); + } + catch (IOException e) + { + throw new MessageFormatException("Message not serializable: " + e); + } + } + + public Serializable getObject() throws JMSException + { + ObjectInputStream in = null; + if (_data == null) + { + return null; + } + + try + { + in = new ObjectInputStream(_data.asInputStream()); + return (Serializable) in.readObject(); + } + catch (IOException e) + { + throw new MessageFormatException("Could not deserialize message: " + e); + } + catch (ClassNotFoundException e) + { + throw new MessageFormatException("Could not deserialize message: " + e); + } + finally + { + _data.rewind(); + close(in); + } + } + + private static void close(InputStream in) + { + try + { + if (in != null) + { + in.close(); + } + } + catch (IOException ignore) + { + } + } + + private static String toString(ByteBuffer data) + { + if (data == null) + { + return null; + } + int pos = data.position(); + try + { + return data.getString(Charset.forName("UTF8").newDecoder()); + } + catch (CharacterCodingException e) + { + return null; + } + finally + { + data.position(pos); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java b/java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java new file mode 100644 index 0000000000..cb5f29ec74 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/JMSObjectMessageFactory.java @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; + +import javax.jms.JMSException; + +public class JMSObjectMessageFactory extends AbstractJMSMessageFactory +{ + protected AbstractJMSMessage createMessage(long deliveryTag, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException + { + return new JMSObjectMessage(deliveryTag, data, contentHeader); + } + + public AbstractJMSMessage createMessage() throws JMSException + { + return new JMSObjectMessage(); + } +} diff --git a/java/client/src/org/apache/qpid/client/message/JMSTextMessage.java b/java/client/src/org/apache/qpid/client/message/JMSTextMessage.java new file mode 100644 index 0000000000..b860d55e94 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/JMSTextMessage.java @@ -0,0 +1,159 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.AMQException; +import org.apache.mina.common.ByteBuffer; + +import javax.jms.JMSException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharacterCodingException; + +public class JMSTextMessage extends AbstractJMSMessage implements javax.jms.TextMessage +{ + private static final String MIME_TYPE = "text/plain"; + + private String _decodedValue; + + JMSTextMessage() throws JMSException + { + this(null, null); + } + + JMSTextMessage(ByteBuffer data, String encoding) throws JMSException + { + super(data); // this instantiates a content header + getJmsContentHeaderProperties().setContentType(MIME_TYPE); + getJmsContentHeaderProperties().setEncoding(encoding); + } + + JMSTextMessage(long deliveryTag, ByteBuffer data, BasicContentHeaderProperties contentHeader) + throws AMQException + { + super(deliveryTag, contentHeader, data); + contentHeader.setContentType(MIME_TYPE); + _data = data; + } + + JMSTextMessage(ByteBuffer data) throws JMSException + { + this(data, null); + } + + JMSTextMessage(String text) throws JMSException + { + super((ByteBuffer)null); + setText(text); + } + + public void clearBody() throws JMSException + { + if (_data != null) + { + _data.release(); + } + _data = null; + _decodedValue = null; + } + + public String toBodyString() throws JMSException + { + return getText(); + } + + public void setData(ByteBuffer data) + { + _data = data; + } + + public String getMimeType() + { + return MIME_TYPE; + } + + public void setText(String string) throws JMSException + { + clearBody(); + try + { + _data = ByteBuffer.allocate(string.length()); + _data.limit(string.length()); + //_data.sweep(); + _data.setAutoExpand(true); + if (getJmsContentHeaderProperties().getEncoding() == null) + { + _data.put(string.getBytes()); + } + else + { + _data.put(string.getBytes(getJmsContentHeaderProperties().getEncoding())); + } + _decodedValue = string; + } + catch (UnsupportedEncodingException e) + { + // should never occur + throw new JMSException("Unable to decode string data"); + } + } + + public String getText() throws JMSException + { + if (_data == null && _decodedValue == null) + { + return null; + } + else if (_decodedValue != null) + { + return _decodedValue; + } + else + { + _data.rewind(); + if (getJmsContentHeaderProperties().getEncoding() != null) + { + try + { + _decodedValue = _data.getString(Charset.forName(getJmsContentHeaderProperties().getEncoding()).newDecoder()); + } + catch (CharacterCodingException e) + { + JMSException je = new JMSException("Could not decode string data: " + e); + je.setLinkedException(e); + throw je; + } + } + else + { + try + { + _decodedValue = _data.getString(Charset.defaultCharset().newDecoder()); + } + catch (CharacterCodingException e) + { + JMSException je = new JMSException("Could not decode string data: " + e); + je.setLinkedException(e); + throw je; + } + } + return _decodedValue; + } + } +} diff --git a/java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java b/java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java new file mode 100644 index 0000000000..747965d26e --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/JMSTextMessageFactory.java @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; + +import javax.jms.JMSException; + +public class JMSTextMessageFactory extends AbstractJMSMessageFactory +{ + + public AbstractJMSMessage createMessage() throws JMSException + { + return new JMSTextMessage(); + } + + protected AbstractJMSMessage createMessage(long deliveryTag, ByteBuffer data, ContentHeaderBody contentHeader) throws AMQException + { + return new JMSTextMessage(deliveryTag, data, (BasicContentHeaderProperties)contentHeader.properties); + } +} diff --git a/java/client/src/org/apache/qpid/client/message/MessageFactory.java b/java/client/src/org/apache/qpid/client/message/MessageFactory.java new file mode 100644 index 0000000000..a9c7dd1b48 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/MessageFactory.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; + +import javax.jms.JMSException; +import java.util.List; + + +public interface MessageFactory +{ + AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered, + ContentHeaderBody contentHeader, + List bodies) + throws JMSException, AMQException; + + AbstractJMSMessage createMessage() throws JMSException; +} diff --git a/java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java b/java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java new file mode 100644 index 0000000000..5808f9aa35 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/MessageFactoryRegistry.java @@ -0,0 +1,105 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; + +import javax.jms.JMSException; +import java.util.HashMap; +import java.util.Map; +import java.util.List; + +public class MessageFactoryRegistry +{ + private final Map _mimeToFactoryMap = new HashMap(); + + public void registerFactory(String mimeType, MessageFactory mf) + { + if (mf == null) + { + throw new IllegalArgumentException("Message factory must not be null"); + } + _mimeToFactoryMap.put(mimeType, mf); + } + + public MessageFactory deregisterFactory(String mimeType) + { + return (MessageFactory) _mimeToFactoryMap.remove(mimeType); + } + + /** + * Create a message. This looks up the MIME type from the content header and instantiates the appropriate + * concrete message type. + * @param deliveryTag the AMQ message id + * @param redelivered true if redelivered + * @param contentHeader the content header that was received + * @param bodies a list of ContentBody instances + * @return the message. + * @throws AMQException + * @throws JMSException + */ + public AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered, + ContentHeaderBody contentHeader, + List bodies) throws AMQException, JMSException + { + BasicContentHeaderProperties properties = (BasicContentHeaderProperties) contentHeader.properties; + MessageFactory mf = (MessageFactory) _mimeToFactoryMap.get(properties.getContentType()); + if (mf == null) + { + throw new AMQException("Unsupport MIME type of " + properties.getContentType()); + } + else + { + return mf.createMessage(deliveryTag, redelivered, contentHeader, bodies); + } + } + + public AbstractJMSMessage createMessage(String mimeType) throws AMQException, JMSException + { + if (mimeType == null) + { + throw new IllegalArgumentException("Mime type must not be null"); + } + MessageFactory mf = (MessageFactory) _mimeToFactoryMap.get(mimeType); + if (mf == null) + { + throw new AMQException("Unsupport MIME type of " + mimeType); + } + else + { + return mf.createMessage(); + } + } + + /** + * Construct a new registry with the default message factories registered + * @return a message factory registry + */ + public static MessageFactoryRegistry newDefaultRegistry() + { + MessageFactoryRegistry mf = new MessageFactoryRegistry(); + mf.registerFactory("text/plain", new JMSTextMessageFactory()); + mf.registerFactory("text/xml", new JMSTextMessageFactory()); + mf.registerFactory("application/octet-stream", new JMSBytesMessageFactory()); + mf.registerFactory(JMSObjectMessage.MIME_TYPE, new JMSObjectMessageFactory()); + mf.registerFactory(null, new JMSBytesMessageFactory()); + return mf; + } +} diff --git a/java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java b/java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java new file mode 100644 index 0000000000..f2cb7defd2 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/UnexpectedBodyReceivedException.java @@ -0,0 +1,40 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +public class UnexpectedBodyReceivedException extends AMQException +{ + + public UnexpectedBodyReceivedException(Logger logger, String msg, Throwable t) + { + super(logger, msg, t); + } + + public UnexpectedBodyReceivedException(Logger logger, String msg) + { + super(logger, msg); + } + + public UnexpectedBodyReceivedException(Logger logger, int errorCode, String msg) + { + super(logger, errorCode, msg); + } +} diff --git a/java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java b/java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java new file mode 100644 index 0000000000..7d4ee097d3 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/message/UnprocessedMessage.java @@ -0,0 +1,61 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.message; + +import org.apache.qpid.framing.*; + +import java.util.List; +import java.util.LinkedList; + +/** + * This class contains everything needed to process a JMS message. It assembles the + * deliver body, the content header and the content body/ies. + * + * Note that the actual work of creating a JMS message for the client code's use is done + * outside of the MINA dispatcher thread in order to minimise the amount of work done in + * the MINA dispatcher thread. + * + */ +public class UnprocessedMessage +{ + private long _bytesReceived = 0; + + public BasicDeliverBody deliverBody; + public BasicReturnBody bounceBody; // TODO: check change (gustavo) + public int channelId; + public ContentHeaderBody contentHeader; + + /** + * List of ContentBody instances. Due to fragmentation you don't know how big this will be in general + */ + public List bodies = new LinkedList(); + + public void receiveBody(ContentBody body) throws UnexpectedBodyReceivedException + { + bodies.add(body); + if (body.payload != null) + { + _bytesReceived += body.payload.remaining(); + } + } + + public boolean isAllBodyDataReceived() + { + return _bytesReceived == contentHeader.bodySize; + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java b/java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java new file mode 100644 index 0000000000..b14a7cb8b0 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/AMQMethodEvent.java @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.qpid.framing.AMQMethodBody; + +public class AMQMethodEvent +{ + private AMQMethodBody _method; + + private int _channelId; + + private AMQProtocolSession _protocolSession; + + public AMQMethodEvent(int channelId, AMQMethodBody method, AMQProtocolSession protocolSession) + { + _channelId = channelId; + _method = method; + _protocolSession = protocolSession; + } + + public AMQMethodBody getMethod() + { + return _method; + } + + public int getChannelId() + { + return _channelId; + } + + public AMQProtocolSession getProtocolSession() + { + return _protocolSession; + } + + public String toString() + { + StringBuffer buf = new StringBuffer("Method event: "); + buf.append("\nChannel id: ").append(_channelId); + buf.append("\nMethod: ").append(_method); + return buf.toString(); + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java b/java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java new file mode 100644 index 0000000000..fd20151e95 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/AMQMethodListener.java @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.qpid.AMQException; + +public interface AMQMethodListener +{ + /** + * Invoked when a method frame has been received + * @param evt the event + * @return true if the handler has processed the method frame, false otherwise. Note + * that this does not prohibit the method event being delivered to subsequent listeners + * but can be used to determine if nobody has dealt with an incoming method frame. + * @throws AMQException if an error has occurred. This exception will be delivered + * to all registered listeners using the error() method (see below) allowing them to + * perform cleanup if necessary. + */ + boolean methodReceived(AMQMethodEvent evt) throws AMQException; + + /** + * Callback when an error has occurred. Allows listeners to clean up. + * @param e + */ + void error(Exception e); +} diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java new file mode 100644 index 0000000000..0c4dbcbb26 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolHandler.java @@ -0,0 +1,530 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.log4j.Logger; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.SSLFilter; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.qpid.AMQDisconnectedException; +import org.apache.qpid.AMQException; + +import org.apache.qpid.AMQConnectionClosedException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.failover.FailoverHandler; +import org.apache.qpid.client.failover.FailoverState; + +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.listener.SpecificMethodFrameListener; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.ssl.BogusSSLContextFactory; + +import java.util.Iterator; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.CountDownLatch; + +public class AMQProtocolHandler extends IoHandlerAdapter +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolHandler.class); + + /** + * The connection that this protocol handler is associated with. There is a 1-1 + * mapping between connection instances and protocol handler instances. + */ + private AMQConnection _connection; + + /** + * Used only when determining whether to add the SSL filter or not. This should be made more + * generic in future since we will potentially have many transport layer options + */ + private boolean _useSSL; + + /** + * Our wrapper for a protocol session that provides access to session values + * in a typesafe manner. + */ + private volatile AMQProtocolSession _protocolSession; + + private AMQStateManager _stateManager = new AMQStateManager(); + + private final CopyOnWriteArraySet _frameListeners = new CopyOnWriteArraySet(); + + /** + * We create the failover handler when the session is created since it needs a reference to the IoSession in order + * to be able to send errors during failover back to the client application. The session won't be available in the + * case where we failing over due to a Connection.Redirect message from the broker. + */ + private FailoverHandler _failoverHandler; + + /** + * This flag is used to track whether failover is being attempted. It is used to prevent the application constantly + * attempting failover where it is failing. + */ + private FailoverState _failoverState = FailoverState.NOT_STARTED; + + private CountDownLatch _failoverLatch; + + public AMQProtocolHandler(AMQConnection con) + { + _connection = con; + + // We add a proxy for the state manager so that we can substitute the state manager easily in this class. + // We substitute the state manager when performing failover + _frameListeners.add(new AMQMethodListener() + { + public boolean methodReceived(AMQMethodEvent evt) throws AMQException + { + return _stateManager.methodReceived(evt); + } + + public void error(Exception e) + { + _stateManager.error(e); + } + }); + } + + public boolean isUseSSL() + { + return _useSSL; + } + + public void setUseSSL(boolean useSSL) + { + _useSSL = useSSL; + } + + public void sessionCreated(IoSession session) throws Exception + { + _logger.debug("Protocol session created for session " + System.identityHashCode(session)); + _failoverHandler = new FailoverHandler(this, session); + + final ProtocolCodecFilter pcf = new ProtocolCodecFilter(new AMQCodecFactory(false)); + + if (Boolean.getBoolean("amqj.shared_read_write_pool")) + { + session.getFilterChain().addBefore("AsynchronousWriteFilter", "protocolFilter", pcf); + } + else + { + session.getFilterChain().addLast("protocolFilter", pcf); + } + // we only add the SSL filter where we have an SSL connection + if (_useSSL) + { + //todo FIXME: Bogus context cannot be used in production. + SSLFilter sslFilter = new SSLFilter(BogusSSLContextFactory.getInstance(false)); + sslFilter.setUseClientMode(true); + session.getFilterChain().addBefore("protocolFilter", "ssl", sslFilter); + } + + _protocolSession = new AMQProtocolSession(this, session, _connection); + _protocolSession.init(); + } + + public void sessionOpened(IoSession session) throws Exception + { + System.setProperty("foo", "bar"); + } + + /** + * When the broker connection dies we can either get sessionClosed() called or exceptionCaught() followed by + * sessionClosed() depending on whether we were trying to send data at the time of failure. + * + * @param session + * @throws Exception + */ + public void sessionClosed(IoSession session) throws Exception + { + //todo server just closes session with no warning if auth fails. + if (_connection.isClosed()) + { + _logger.info("Session closed called by client"); + } + else + { + _logger.info("Session closed called with failover state currently " + _failoverState); + + //reconnetablility was introduced here so as not to disturb the client as they have made their intentions + // known through the policy settings. + + if ((_failoverState != FailoverState.IN_PROGRESS) && _connection.failoverAllowed()) + { + _logger.info("FAILOVER STARTING"); + if (_failoverState == FailoverState.NOT_STARTED) + { + _failoverState = FailoverState.IN_PROGRESS; + startFailoverThread(); + } + else + { + _logger.info("Not starting failover as state currently " + _failoverState); + } + } + else + { + _logger.info("Failover not allowed by policy."); + + if (_failoverState != FailoverState.IN_PROGRESS) + { + _logger.info("sessionClose() not allowed to failover"); + _connection.exceptionReceived( + new AMQDisconnectedException("Server closed connection and reconnection " + + "not permitted.")); + } + else + { + _logger.info("sessionClose() failover in progress"); + } + } + } + + _logger.info("Protocol Session [" + this + "] closed"); + } + + /** + * See {@link FailoverHandler} to see rationale for separate thread. + */ + private void startFailoverThread() + { + Thread failoverThread = new Thread(_failoverHandler); + failoverThread.setName("Failover"); + // Do not inherit daemon-ness from current thread as this can be a daemon + // thread such as a AnonymousIoService thread. + failoverThread.setDaemon(false); + failoverThread.start(); + } + + public void sessionIdle(IoSession session, IdleStatus status) throws Exception + { + _logger.debug("Protocol Session [" + this + ":" + session + "] idle: " + status); + if (IdleStatus.WRITER_IDLE.equals(status)) + { + //write heartbeat frame: + _logger.debug("Sent heartbeat"); + session.write(HeartbeatBody.FRAME); + HeartbeatDiagnostics.sent(); + } + else if (IdleStatus.READER_IDLE.equals(status)) + { + //failover: + HeartbeatDiagnostics.timeout(); + _logger.warn("Timed out while waiting for heartbeat from peer."); + session.close(); + } + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + if (_failoverState == FailoverState.NOT_STARTED) + { + //if (!(cause instanceof AMQUndeliveredException) && (!(cause instanceof AMQAuthenticationException))) + if (cause instanceof AMQConnectionClosedException) + { + _logger.info("Exception caught therefore going to attempt failover: " + cause, cause); + // this will attemp failover + + sessionClosed(session); + } + } + // we reach this point if failover was attempted and failed therefore we need to let the calling app + // know since we cannot recover the situation + else if (_failoverState == FailoverState.FAILED) + { + _logger.error("Exception caught by protocol handler: " + cause, cause); + // we notify the state manager of the error in case we have any clients waiting on a state + // change. Those "waiters" will be interrupted and can handle the exception + AMQException amqe = new AMQException("Protocol handler error: " + cause, cause); + propagateExceptionToWaiters(amqe); + _connection.exceptionReceived(cause); + } + } + + /** + * There are two cases where we have other threads potentially blocking for events to be handled by this + * class. These are for the state manager (waiting for a state change) or a frame listener (waiting for a + * particular type of frame to arrive). When an error occurs we need to notify these waiters so that they can + * react appropriately. + * + * @param e the exception to propagate + */ + public void propagateExceptionToWaiters(Exception e) + { + _stateManager.error(e); + final Iterator it = _frameListeners.iterator(); + while (it.hasNext()) + { + final AMQMethodListener ml = (AMQMethodListener) it.next(); + ml.error(e); + } + } + + private static int _messageReceivedCount; + + public void messageReceived(IoSession session, Object message) throws Exception + { + + if (_messageReceivedCount++ % 1000 == 0) + { + _logger.debug("Received " + _messageReceivedCount + " protocol messages"); + } + Iterator it = _frameListeners.iterator(); + AMQFrame frame = (AMQFrame) message; + + HeartbeatDiagnostics.received(frame.bodyFrame instanceof HeartbeatBody); + + if (frame.bodyFrame instanceof AMQMethodBody) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Method frame received: " + frame); + } + + final AMQMethodEvent evt = new AMQMethodEvent(frame.channel, (AMQMethodBody)frame.bodyFrame, _protocolSession); + try + { + boolean wasAnyoneInterested = false; + while (it.hasNext()) + { + final AMQMethodListener listener = (AMQMethodListener) it.next(); + wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; + } + if (!wasAnyoneInterested) + { + throw new AMQException("AMQMethodEvent " + evt + " was not processed by any listener."); + } + } + catch (AMQException e) + { + it = _frameListeners.iterator(); + while (it.hasNext()) + { + final AMQMethodListener listener = (AMQMethodListener) it.next(); + listener.error(e); + } + exceptionCaught(session, e); + } + } + else if (frame.bodyFrame instanceof ContentHeaderBody) + { + _protocolSession.messageContentHeaderReceived(frame.channel, + (ContentHeaderBody) frame.bodyFrame); + } + else if (frame.bodyFrame instanceof ContentBody) + { + _protocolSession.messageContentBodyReceived(frame.channel, + (ContentBody) frame.bodyFrame); + } + else if (frame.bodyFrame instanceof HeartbeatBody) + { + _logger.debug("Received heartbeat"); + } + _connection.bytesReceived(_protocolSession.getIoSession().getReadBytes()); + } + + private static int _messagesOut; + + public void messageSent(IoSession session, Object message) throws Exception + { + if (_messagesOut++ % 1000 == 0) + { + _logger.debug("Sent " + _messagesOut + " protocol messages"); + } + _connection.bytesSent(session.getWrittenBytes()); + if (_logger.isDebugEnabled()) + { + _logger.debug("Sent frame " + message); + } + } + + public void addFrameListener(AMQMethodListener listener) + { + _frameListeners.add(listener); + } + + public void removeFrameListener(AMQMethodListener listener) + { + _frameListeners.remove(listener); + } + + public void attainState(AMQState s) throws AMQException + { + _stateManager.attainState(s); + } + + /** + * Convenience method that writes a frame to the protocol session. Equivalent + * to calling getProtocolSession().write(). + * + * @param frame the frame to write + */ + public void writeFrame(AMQDataBlock frame) + { + _protocolSession.writeFrame(frame); + } + + public void writeFrame(AMQDataBlock frame, boolean wait) + { + _protocolSession.writeFrame(frame, wait); + } + + /** + * Convenience method that writes a frame to the protocol session and waits for + * a particular response. Equivalent to calling getProtocolSession().write() then + * waiting for the response. + * + * @param frame + * @param listener the blocking listener. Note the calling thread will block. + */ + private AMQMethodEvent writeCommandFrameAndWaitForReply(AMQFrame frame, + BlockingMethodFrameListener listener) + throws AMQException + { + _frameListeners.add(listener); + _protocolSession.writeFrame(frame); + return listener.blockForFrame(); + // When control resumes before this line, a reply will have been received + // that matches the criteria defined in the blocking listener + } + + /** + * More convenient method to write a frame and wait for it's response. + */ + public AMQMethodEvent syncWrite(AMQFrame frame, Class responseClass) throws AMQException + { + return writeCommandFrameAndWaitForReply(frame, + new SpecificMethodFrameListener(frame.channel, responseClass)); + } + + /** + * Convenience method to register an AMQSession with the protocol handler. Registering + * a session with the protocol handler will ensure that messages are delivered to the + * consumer(s) on that session. + * + * @param channelId the channel id of the session + * @param session the session instance. + */ + public void addSessionByChannel(int channelId, AMQSession session) + { + _protocolSession.addSessionByChannel(channelId, session); + } + + /** + * Convenience method to deregister an AMQSession with the protocol handler. + * + * @param channelId then channel id of the session + */ + public void removeSessionByChannel(int channelId) + { + _protocolSession.removeSessionByChannel(channelId); + } + + public void closeSession(AMQSession session) throws AMQException + { + _protocolSession.closeSession(session); + } + + public void closeConnection() throws AMQException + { + _stateManager.changeState(AMQState.CONNECTION_CLOSING); + + final AMQFrame frame = ConnectionCloseBody.createAMQFrame( + 0, AMQConstant.REPLY_SUCCESS.getCode(), "JMS client is closing the connection.", 0, 0); + syncWrite(frame, ConnectionCloseOkBody.class); + + _protocolSession.closeProtocolSession(); + } + + /** + * @return the number of bytes read from this protocol session + */ + public long getReadBytes() + { + return _protocolSession.getIoSession().getReadBytes(); + } + + /** + * @return the number of bytes written to this protocol session + */ + public long getWrittenBytes() + { + return _protocolSession.getIoSession().getWrittenBytes(); + } + + public void failover(String host, int port) + { + _failoverHandler.setHost(host); + _failoverHandler.setPort(port); + // see javadoc for FailoverHandler to see rationale for separate thread + startFailoverThread(); + } + + public void blockUntilNotFailingOver() throws InterruptedException + { + if (_failoverLatch != null) + { + _failoverLatch.await(); + } + } + + public String generateQueueName() + { + return _protocolSession.generateQueueName(); + } + + public CountDownLatch getFailoverLatch() + { + return _failoverLatch; + } + + public void setFailoverLatch(CountDownLatch failoverLatch) + { + _failoverLatch = failoverLatch; + } + + public AMQConnection getConnection() + { + return _connection; + } + + public AMQStateManager getStateManager() + { + return _stateManager; + } + + public void setStateManager(AMQStateManager stateManager) + { + _stateManager = stateManager; + } + + FailoverState getFailoverState() + { + return _failoverState; + } + + public void setFailoverState(FailoverState failoverState) + { + _failoverState = failoverState; + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java new file mode 100644 index 0000000000..77685a0222 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/AMQProtocolSession.java @@ -0,0 +1,379 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.log4j.Logger; +import org.apache.mina.common.CloseFuture; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.WriteFuture; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.ConnectionTuneParameters; +import org.apache.qpid.client.message.UnexpectedBodyReceivedException; +import org.apache.qpid.client.message.UnprocessedMessage; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.ProtocolInitiation; +import org.apache.qpid.framing.ProtocolVersionList; + +import javax.jms.JMSException; +import javax.security.sasl.SaslClient; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Wrapper for protocol session that provides type-safe access to session attributes. + * + * The underlying protocol session is still available but clients should not + * use it to obtain session attributes. + */ +public class AMQProtocolSession implements ProtocolVersionList +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolSession.class); + + public static final String PROTOCOL_INITIATION_RECEIVED = "ProtocolInitiatiionReceived"; + + protected static final String CONNECTION_TUNE_PARAMETERS = "ConnectionTuneParameters"; + + private static final String AMQ_CONNECTION = "AMQConnection"; + + private static final String SASL_CLIENT = "SASLClient"; + + private final IoSession _minaProtocolSession; + + /** + * The handler from which this session was created and which is used to handle protocol events. + * We send failover events to the handler. + */ + private final AMQProtocolHandler _protocolHandler; + + /** + * Maps from the channel id to the AMQSession that it represents. + */ + private ConcurrentMap _channelId2SessionMap = new ConcurrentHashMap(); + + private ConcurrentMap _closingChannels = new ConcurrentHashMap(); + + /** + * Maps from a channel id to an unprocessed message. This is used to tie together the + * JmsDeliverBody (which arrives first) with the subsequent content header and content bodies. + */ + private ConcurrentMap _channelId2UnprocessedMsgMap = new ConcurrentHashMap(); + + /** + * Counter to ensure unique queue names + */ + private int _queueId = 1; + private final Object _queueIdLock = new Object(); + + public AMQProtocolSession(AMQProtocolHandler protocolHandler, IoSession protocolSession, AMQConnection connection) + { + _protocolHandler = protocolHandler; + _minaProtocolSession = protocolSession; + // properties of the connection are made available to the event handlers + _minaProtocolSession.setAttribute(AMQ_CONNECTION, connection); + } + + public void init() + { + // start the process of setting up the connection. This is the first place that + // data is written to the server. + /* Find last protocol version in protocol version list. Make sure last protocol version + listed in the build file (build-module.xml) is the latest version which will be used + here. */ + int i = pv.length - 1; + _minaProtocolSession.write(new ProtocolInitiation(pv[i][PROTOCOL_MAJOR], pv[i][PROTOCOL_MINOR])); + } + + public String getClientID() + { + try + { + return getAMQConnection().getClientID(); + } + catch (JMSException e) + { + // we never throw a JMSException here + return null; + } + } + + public void setClientID(String clientID) throws JMSException + { + getAMQConnection().setClientID(clientID); + } + + public String getVirtualHost() + { + return getAMQConnection().getVirtualHost(); + } + + public String getUsername() + { + return getAMQConnection().getUsername(); + } + + public String getPassword() + { + return getAMQConnection().getPassword(); + } + + public IoSession getIoSession() + { + return _minaProtocolSession; + } + + public SaslClient getSaslClient() + { + return (SaslClient) _minaProtocolSession.getAttribute(SASL_CLIENT); + } + + /** + * Store the SASL client currently being used for the authentication handshake + * @param client if non-null, stores this in the session. if null clears any existing client + * being stored + */ + public void setSaslClient(SaslClient client) + { + if (client == null) + { + _minaProtocolSession.removeAttribute(SASL_CLIENT); + } + else + { + _minaProtocolSession.setAttribute(SASL_CLIENT, client); + } + } + + public ConnectionTuneParameters getConnectionTuneParameters() + { + return (ConnectionTuneParameters) _minaProtocolSession.getAttribute(CONNECTION_TUNE_PARAMETERS); + } + + public void setConnectionTuneParameters(ConnectionTuneParameters params) + { + _minaProtocolSession.setAttribute(CONNECTION_TUNE_PARAMETERS, params); + AMQConnection con = getAMQConnection(); + con.setMaximumChannelCount(params.getChannelMax()); + con.setMaximumFrameSize(params.getFrameMax()); + initHeartbeats((int) params.getHeartbeat()); + } + + /** + * Callback invoked from the BasicDeliverMethodHandler when a message has been received. + * This is invoked on the MINA dispatcher thread. + * @param message + * @throws AMQException if this was not expected + */ + public void unprocessedMessageReceived(UnprocessedMessage message) throws AMQException + { + _channelId2UnprocessedMsgMap.put(message.channelId, message); + } + + public void messageContentHeaderReceived(int channelId, ContentHeaderBody contentHeader) + throws AMQException + { + UnprocessedMessage msg = (UnprocessedMessage) _channelId2UnprocessedMsgMap.get(channelId); + if (msg == null) + { + throw new AMQException("Error: received content header without having received a BasicDeliver frame first"); + } + if (msg.contentHeader != null) + { + throw new AMQException("Error: received duplicate content header or did not receive correct number of content body frames"); + } + msg.contentHeader = contentHeader; + if (contentHeader.bodySize == 0) + { + deliverMessageToAMQSession(channelId, msg); + } + } + + public void messageContentBodyReceived(int channelId, ContentBody contentBody) throws AMQException + { + UnprocessedMessage msg = (UnprocessedMessage) _channelId2UnprocessedMsgMap.get(channelId); + if (msg == null) + { + throw new AMQException("Error: received content body without having received a JMSDeliver frame first"); + } + if (msg.contentHeader == null) + { + _channelId2UnprocessedMsgMap.remove(channelId); + throw new AMQException("Error: received content body without having received a ContentHeader frame first"); + } + try + { + msg.receiveBody(contentBody); + } + catch (UnexpectedBodyReceivedException e) + { + _channelId2UnprocessedMsgMap.remove(channelId); + throw e; + } + if (msg.isAllBodyDataReceived()) + { + deliverMessageToAMQSession(channelId, msg); + } + } + + /** + * Deliver a message to the appropriate session, removing the unprocessed message + * from our map + * @param channelId the channel id the message should be delivered to + * @param msg the message + */ + private void deliverMessageToAMQSession(int channelId, UnprocessedMessage msg) + { + AMQSession session = (AMQSession) _channelId2SessionMap.get(channelId); + session.messageReceived(msg); + _channelId2UnprocessedMsgMap.remove(channelId); + } + + /** + * Convenience method that writes a frame to the protocol session. Equivalent + * to calling getProtocolSession().write(). + * + * @param frame the frame to write + */ + public void writeFrame(AMQDataBlock frame) + { + _minaProtocolSession.write(frame); + } + + public void writeFrame(AMQDataBlock frame, boolean wait) + { + WriteFuture f =_minaProtocolSession.write(frame); + if(wait) + { + f.join(); + } + } + + public void addSessionByChannel(int channelId, AMQSession session) + { + if (channelId <=0) + { + throw new IllegalArgumentException("Attempt to register a session with a channel id <= zero"); + } + if (session == null) + { + throw new IllegalArgumentException("Attempt to register a null session"); + } + _logger.debug("Add session with channel id " + channelId); + _channelId2SessionMap.put(channelId, session); + } + + public void removeSessionByChannel(int channelId) + { + if (channelId <=0) + { + throw new IllegalArgumentException("Attempt to deregister a session with a channel id <= zero"); + } + _logger.debug("Removing session with channelId " + channelId); + _channelId2SessionMap.remove(channelId); + } + + /** + * Starts the process of closing a session + * @param session the AMQSession being closed + */ + public void closeSession(AMQSession session) + { + _logger.debug("closeSession called on protocol session for session " + session.getChannelId()); + final int channelId = session.getChannelId(); + if (channelId <=0) + { + throw new IllegalArgumentException("Attempt to close a channel with id < 0"); + } + // we need to know when a channel is closing so that we can respond + // with a channel.close frame when we receive any other type of frame + // on that channel + _closingChannels.putIfAbsent(channelId, session); + } + + /** + * Called from the ChannelClose handler when a channel close frame is received. + * This method decides whether this is a response or an initiation. The latter + * case causes the AMQSession to be closed and an exception to be thrown if + * appropriate. + * @param channelId the id of the channel (session) + * @return true if the client must respond to the server, i.e. if the server + * initiated the channel close, false if the channel close is just the server + * responding to the client's earlier request to close the channel. + */ + public boolean channelClosed(int channelId, int code, String text) + { + final Integer chId = channelId; + // if this is not a response to an earlier request to close the channel + if (_closingChannels.remove(chId) == null) + { + final AMQSession session = (AMQSession) _channelId2SessionMap.get(chId); + session.closed(new AMQException(_logger, code, text)); + return true; + } + else + { + return false; + } + } + + public AMQConnection getAMQConnection() + { + return (AMQConnection) _minaProtocolSession.getAttribute(AMQ_CONNECTION); + } + + public void closeProtocolSession() + { + _logger.debug("Closing protocol session"); + final CloseFuture future = _minaProtocolSession.close(); + future.join(); + } + + public void failover(String host, int port) + { + _protocolHandler.failover(host, port); + } + + String generateQueueName() + { + int id; + synchronized(_queueIdLock) + { + id = _queueId++; + } + //todo remove '/' and ':' from local Address as this doesn't conform to spec. + return "tmp_" + _minaProtocolSession.getLocalAddress() + "_" + id; + } + + /** + * + * @param delay delay in seconds (not ms) + */ + void initHeartbeats(int delay) + { + if (delay > 0) + { + _minaProtocolSession.setIdleTime(IdleStatus.WRITER_IDLE, delay); + _minaProtocolSession.setIdleTime(IdleStatus.READER_IDLE, HeartbeatConfig.CONFIG.getTimeout(delay)); + HeartbeatDiagnostics.init(delay, HeartbeatConfig.CONFIG.getTimeout(delay)); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java b/java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java new file mode 100644 index 0000000000..a7e3ebdd14 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java @@ -0,0 +1,133 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; + +public abstract class BlockingMethodFrameListener implements AMQMethodListener +{ + private volatile boolean _ready = false; + + public abstract boolean processMethod(int channelId, AMQMethodBody frame) throws AMQException; + + private final Object _lock = new Object(); + + /** + * This is set if there is an exception thrown from processCommandFrame and the + * exception is rethrown to the caller of blockForFrame() + */ + private volatile Exception _error; + + protected int _channelId; + + protected AMQMethodEvent _doneEvt = null; + + public BlockingMethodFrameListener(int channelId) + { + _channelId = channelId; + } + + /** + * This method is called by the MINA dispatching thread. Note that it could + * be called before blockForFrame() has been called. + * @param evt the frame event + * @return true if the listener has dealt with this frame + * @throws AMQException + */ + public boolean methodReceived(AMQMethodEvent evt) throws AMQException + { + AMQMethodBody method = evt.getMethod(); + + try + { + boolean ready = (evt.getChannelId() == _channelId) && processMethod(evt.getChannelId(), method); + if (ready) + { + // we only update the flag from inside the synchronized block + // so that the blockForFrame method cannot "miss" an update - it + // will only ever read the flag from within the synchronized block + synchronized (_lock) + { + _doneEvt = evt; + _ready = ready; + _lock.notify(); + } + } + return ready; + } + catch (AMQException e) + { + error(e); + // we rethrow the error here, and the code in the frame dispatcher will go round + // each listener informing them that an exception has been thrown + throw e; + } + } + + /** + * This method is called by the thread that wants to wait for a frame. + */ + public AMQMethodEvent blockForFrame() throws AMQException + { + synchronized (_lock) + { + while (!_ready) + { + try + { + _lock.wait(); + } + catch (InterruptedException e) + { + // IGNORE + } + } + } + if (_error != null) + { + if (_error instanceof AMQException) + { + throw (AMQException)_error; + } + else + { + throw new AMQException("Woken up due to exception", _error); // FIXME: This will wrap FailoverException and prevent it being caught. + } + } + + return _doneEvt; + } + + /** + * This is a callback, called by the MINA dispatcher thread only. It is also called from within this + * class to avoid code repetition but again is only called by the MINA dispatcher thread. + * @param e + */ + public void error(Exception e) + { + // set the error so that the thread that is blocking (against blockForFrame()) + // can pick up the exception and rethrow to the caller + _error = e; + synchronized (_lock) + { + _ready = true; + _lock.notify(); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java b/java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java new file mode 100644 index 0000000000..d6240b919a --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/HeartbeatConfig.java @@ -0,0 +1,57 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.log4j.Logger; + +class HeartbeatConfig +{ + private static final Logger _logger = Logger.getLogger(HeartbeatConfig.class); + static final HeartbeatConfig CONFIG = new HeartbeatConfig(); + + /** + * The factor used to get the timeout from the delay between heartbeats. + */ + private float timeoutFactor = 2; + + HeartbeatConfig() + { + String property = System.getProperty("amqj.heartbeat.timeoutFactor"); + if(property != null) + { + try + { + timeoutFactor = Float.parseFloat(property); + } + catch(NumberFormatException e) + { + _logger.warn("Invalid timeout factor (amqj.heartbeat.timeoutFactor): " + property); + } + } + } + + float getTimeoutFactor() + { + return timeoutFactor; + } + + int getTimeout(int writeDelay) + { + return (int) (timeoutFactor * writeDelay); + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java b/java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java new file mode 100644 index 0000000000..07a9213402 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java @@ -0,0 +1,118 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +class HeartbeatDiagnostics +{ + private static final Diagnostics _impl = init(); + + private static Diagnostics init() + { + return Boolean.getBoolean("amqj.heartbeat.diagnostics") ? new On() : new Off(); + } + + static void sent() + { + _impl.sent(); + } + + static void timeout() + { + _impl.timeout(); + } + + static void received(boolean heartbeat) + { + _impl.received(heartbeat); + } + + static void init(int delay, int timeout) + { + _impl.init(delay, timeout); + } + + private static interface Diagnostics + { + void sent(); + void timeout(); + void received(boolean heartbeat); + void init(int delay, int timeout); + } + + private static class On implements Diagnostics + { + private final String[] messages = new String[50]; + private int i; + + private void save(String msg) + { + messages[i++] = msg; + if(i >= messages.length){ + i = 0;//i.e. a circular buffer + } + } + + public void sent() + { + save(System.currentTimeMillis() + ": sent heartbeat"); + } + + public void timeout() + { + for(int i = 0; i < messages.length; i++) + { + if(messages[i] != null) + { + System.out.println(messages[i]); + } + } + System.out.println(System.currentTimeMillis() + ": timed out"); + } + + public void received(boolean heartbeat) + { + save(System.currentTimeMillis() + ": received " + (heartbeat ? "heartbeat" : "data")); + } + + public void init(int delay, int timeout) + { + System.out.println(System.currentTimeMillis() + ": initialised delay=" + delay + ", timeout=" + timeout); + } + } + + private static class Off implements Diagnostics + { + public void sent() + { + + } + public void timeout() + { + + } + public void received(boolean heartbeat) + { + + } + + public void init(int delay, int timeout) + { + + } + } +} diff --git a/java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java b/java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java new file mode 100644 index 0000000000..88cbd32764 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java @@ -0,0 +1,110 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.protocol; + +import org.apache.mina.common.IoFilterAdapter; +import org.apache.mina.common.IoSession; +import org.apache.log4j.Logger; + +/** + * A MINA filter that monitors the numbers of messages pending to be sent by MINA. It outputs a message + * when a threshold has been exceeded, and has a frequency configuration so that messages are not output + * too often. + * + */ +public class ProtocolBufferMonitorFilter extends IoFilterAdapter +{ + private static final Logger _logger = Logger.getLogger(ProtocolBufferMonitorFilter.class); + + public static long DEFAULT_FREQUENCY = 5000; + + public static int DEFAULT_THRESHOLD = 3000; + + private int _bufferedMessages = 0; + + private int _threshold; + + private long _lastMessageOutputTime; + + private long _outputFrequencyInMillis; + + public ProtocolBufferMonitorFilter() + { + _threshold = DEFAULT_THRESHOLD; + _outputFrequencyInMillis = DEFAULT_FREQUENCY; + } + + public ProtocolBufferMonitorFilter(int threshold, long frequency) + { + _threshold = threshold; + _outputFrequencyInMillis = frequency; + } + + public void messageReceived( NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + _bufferedMessages++; + if (_bufferedMessages > _threshold) + { + long now = System.currentTimeMillis(); + if ((now - _lastMessageOutputTime) > _outputFrequencyInMillis) + { + _logger.warn("Protocol message buffer exceeded threshold of " + _threshold + ". Current backlog: " + + _bufferedMessages); + _lastMessageOutputTime = now; + } + } + + nextFilter.messageReceived(session, message); + } + + public void messageSent( NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + _bufferedMessages--; + nextFilter.messageSent(session, message); + } + + public int getBufferedMessages() + { + return _bufferedMessages; + } + + public int getThreshold() + { + return _threshold; + } + + public void setThreshold(int threshold) + { + _threshold = threshold; + } + + public long getOutputFrequencyInMillis() + { + return _outputFrequencyInMillis; + } + + public void setOutputFrequencyInMillis(long outputFrequencyInMillis) + { + _outputFrequencyInMillis = outputFrequencyInMillis; + } + + public long getLastMessageOutputTime() + { + return _lastMessageOutputTime; + } +} diff --git a/java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java b/java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java new file mode 100644 index 0000000000..43e1e6ab70 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/AMQCallbackHandler.java @@ -0,0 +1,27 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security; + +import org.apache.qpid.client.protocol.AMQProtocolSession; + +import javax.security.auth.callback.CallbackHandler; + +public interface AMQCallbackHandler extends CallbackHandler +{ + void initialise(AMQProtocolSession protocolSession); +} diff --git a/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java new file mode 100644 index 0000000000..8bfa0c32c4 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.java @@ -0,0 +1,154 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security; + +import org.apache.log4j.Logger; + +import java.io.*; +import java.util.*; + +public class CallbackHandlerRegistry +{ + private static final String FILE_PROPERTY = "amq.callbackhandler.properties"; + + private static final Logger _logger = Logger.getLogger(CallbackHandlerRegistry.class); + + private static CallbackHandlerRegistry _instance = new CallbackHandlerRegistry(); + + private Map _mechanismToHandlerClassMap = new HashMap(); + + private String _mechanisms; + + public static CallbackHandlerRegistry getInstance() + { + return _instance; + } + + public Class getCallbackHandlerClass(String mechanism) + { + return (Class) _mechanismToHandlerClassMap.get(mechanism); + } + + public String getMechanisms() + { + return _mechanisms; + } + + private CallbackHandlerRegistry() + { + // first we register any Sasl client factories + DynamicSaslRegistrar.registerSaslProviders(); + + InputStream is = openPropertiesInputStream(); + try + { + Properties props = new Properties(); + props.load(is); + parseProperties(props); + _logger.info("Available SASL mechanisms: " + _mechanisms); + } + catch (IOException e) + { + _logger.error("Error reading properties: " + e, e); + } + finally + { + if (is != null) + { + try + { + is.close(); + + } + catch (IOException e) + { + _logger.error("Unable to close properties stream: " + e, e); + } + } + } + } + + private InputStream openPropertiesInputStream() + { + String filename = System.getProperty(FILE_PROPERTY); + boolean useDefault = true; + InputStream is = null; + if (filename != null) + { + try + { + is = new BufferedInputStream(new FileInputStream(new File(filename))); + useDefault = false; + } + catch (FileNotFoundException e) + { + _logger.error("Unable to read from file " + filename + ": " + e, e); + } + } + + if (useDefault) + { + is = CallbackHandlerRegistry.class.getResourceAsStream("CallbackHandlerRegistry.properties"); + } + + return is; + } + + private void parseProperties(Properties props) + { + Enumeration e = props.propertyNames(); + while (e.hasMoreElements()) + { + String propertyName = (String) e.nextElement(); + int period = propertyName.indexOf("."); + if (period < 0) + { + _logger.warn("Unable to parse property " + propertyName + " when configuring SASL providers"); + continue; + } + String mechanism = propertyName.substring(period + 1); + String className = props.getProperty(propertyName); + Class clazz = null; + try + { + clazz = Class.forName(className); + if (!AMQCallbackHandler.class.isAssignableFrom(clazz)) + { + _logger.warn("SASL provider " + clazz + " does not implement " + AMQCallbackHandler.class + + ". Skipping"); + continue; + } + _mechanismToHandlerClassMap.put(mechanism, clazz); + if (_mechanisms == null) + { + _mechanisms = mechanism; + } + else + { + // one time cost + _mechanisms = _mechanisms + " " + mechanism; + } + } + catch (ClassNotFoundException ex) + { + _logger.warn("Unable to load class " + className + ". Skipping that SASL provider"); + continue; + } + } + } +} diff --git a/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties new file mode 100644 index 0000000000..357f9a1199 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/CallbackHandlerRegistry.properties @@ -0,0 +1,2 @@ +CallbackHandler.CRAM-MD5=org.apache.qpid.client.security.UsernamePasswordCallbackHandler +CallbackHandler.PLAIN=org.apache.qpid.client.security.UsernamePasswordCallbackHandler \ No newline at end of file diff --git a/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java new file mode 100644 index 0000000000..3d81ad505a --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.java @@ -0,0 +1,125 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security; + +import org.apache.log4j.Logger; + +import javax.security.sasl.SaslClientFactory; +import java.io.*; +import java.util.Properties; +import java.util.Enumeration; +import java.util.Map; +import java.util.TreeMap; +import java.security.Security; + +public class DynamicSaslRegistrar +{ + private static final String FILE_PROPERTY = "amq.dynamicsaslregistrar.properties"; + + private static final Logger _logger = Logger.getLogger(DynamicSaslRegistrar.class); + + public static void registerSaslProviders() + { + InputStream is = openPropertiesInputStream(); + try + { + Properties props = new Properties(); + props.load(is); + Map> factories = parseProperties(props); + if (factories.size() > 0) + { + Security.addProvider(new JCAProvider(factories)); + _logger.debug("Dynamic SASL provider added as a security provider"); + } + } + catch (IOException e) + { + _logger.error("Error reading properties: " + e, e); + } + finally + { + if (is != null) + { + try + { + is.close(); + + } + catch (IOException e) + { + _logger.error("Unable to close properties stream: " + e, e); + } + } + } + } + + private static InputStream openPropertiesInputStream() + { + String filename = System.getProperty(FILE_PROPERTY); + boolean useDefault = true; + InputStream is = null; + if (filename != null) + { + try + { + is = new BufferedInputStream(new FileInputStream(new File(filename))); + useDefault = false; + } + catch (FileNotFoundException e) + { + _logger.error("Unable to read from file " + filename + ": " + e, e); + } + } + + if (useDefault) + { + is = CallbackHandlerRegistry.class.getResourceAsStream("DynamicSaslRegistrar.properties"); + } + + return is; + } + + private static Map> parseProperties(Properties props) + { + Enumeration e = props.propertyNames(); + TreeMap> factoriesToRegister = + new TreeMap>(); + while (e.hasMoreElements()) + { + String mechanism = (String) e.nextElement(); + String className = props.getProperty(mechanism); + try + { + Class clazz = Class.forName(className); + if (!(SaslClientFactory.class.isAssignableFrom(clazz))) + { + _logger.error("Class " + clazz + " does not implement " + SaslClientFactory.class + " - skipping"); + continue; + } + factoriesToRegister.put(mechanism, (Class) clazz); + } + catch (Exception ex) + { + _logger.error("Error instantiating SaslClientFactory calss " + className + " - skipping"); + } + } + return factoriesToRegister; + } + + +} diff --git a/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties new file mode 100644 index 0000000000..b853d86d65 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/DynamicSaslRegistrar.properties @@ -0,0 +1 @@ +AMQPLAIN=org.apache.qpid.client.security.amqplain.AmqPlainSaslClientFactory \ No newline at end of file diff --git a/java/client/src/org/apache/qpid/client/security/JCAProvider.java b/java/client/src/org/apache/qpid/client/security/JCAProvider.java new file mode 100644 index 0000000000..4a14063793 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/JCAProvider.java @@ -0,0 +1,43 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security; + +import javax.security.sasl.SaslClientFactory; +import java.security.Provider; +import java.security.Security; +import java.util.Map; + +public class JCAProvider extends Provider +{ + public JCAProvider(Map> providerMap) + { + super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + Security.addProvider(this); + } + + private void register(Map> providerMap) + { + for (Map.Entry> me : + providerMap.entrySet()) + { + put("SaslClientFactory." + me.getKey(), me.getValue().getName()); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java b/java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java new file mode 100644 index 0000000000..201cc04726 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security; + +import org.apache.qpid.client.protocol.AMQProtocolSession; + +import javax.security.auth.callback.*; +import java.io.IOException; + +public class UsernamePasswordCallbackHandler implements AMQCallbackHandler +{ + private AMQProtocolSession _protocolSession; + + public void initialise(AMQProtocolSession protocolSession) + { + _protocolSession = protocolSession; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (int i = 0; i < callbacks.length; i++) + { + Callback cb = callbacks[i]; + if (cb instanceof NameCallback) + { + ((NameCallback)cb).setName(_protocolSession.getUsername()); + } + else if (cb instanceof PasswordCallback) + { + ((PasswordCallback)cb).setPassword(_protocolSession.getPassword().toCharArray()); + } + else + { + throw new UnsupportedCallbackException(cb); + } + } + } +} diff --git a/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java new file mode 100644 index 0000000000..d564e82ec3 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java @@ -0,0 +1,101 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security.amqplain; + +import org.apache.qpid.framing.FieldTable; + +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.Callback; + +/** + * Implements the "AMQPlain" authentication protocol that uses FieldTables to send username and pwd. + * + */ +public class AmqPlainSaslClient implements SaslClient +{ + /** + * The name of this mechanism + */ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + public AmqPlainSaslClient(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return "AMQPLAIN"; + } + + public boolean hasInitialResponse() + { + return true; + } + + public byte[] evaluateChallenge(byte[] challenge) throws SaslException + { + // we do not care about the prompt or the default name + NameCallback nameCallback = new NameCallback("prompt", "defaultName"); + PasswordCallback pwdCallback = new PasswordCallback("prompt", false); + Callback[] callbacks = new Callback[]{nameCallback, pwdCallback}; + try + { + _cbh.handle(callbacks); + } + catch (Exception e) + { + throw new SaslException("Error handling SASL callbacks: " + e, e); + } + FieldTable table = new FieldTable(); + table.put("LOGIN", nameCallback.getName()); + table.put("PASSWORD", pwdCallback.getPassword()); + return table.getDataAsBytes(); + } + + public boolean isComplete() + { + return true; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Not supported"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Not supported"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java new file mode 100644 index 0000000000..b4cc34dd03 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.security.amqplain; + +import javax.security.sasl.SaslClientFactory; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.auth.callback.CallbackHandler; +import java.util.Map; + +public class AmqPlainSaslClientFactory implements SaslClientFactory +{ + public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException + { + for (int i = 0; i < mechanisms.length; i++) + { + if (mechanisms[i].equals(AmqPlainSaslClient.MECHANISM)) + { + if (cbh == null) + { + throw new SaslException("CallbackHandler must not be null"); + } + return new AmqPlainSaslClient(cbh); + } + } + return null; + } + + public String[] getMechanismNames(Map props) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AmqPlainSaslClient.MECHANISM}; + } + } +} diff --git a/java/client/src/org/apache/qpid/client/state/AMQState.java b/java/client/src/org/apache/qpid/client/state/AMQState.java new file mode 100644 index 0000000000..02a391ee71 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/AMQState.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +/** + * States used in the AMQ protocol. Used by the finite state machine to determine + * valid responses. + */ +public class AMQState +{ + private final int _id; + + private final String _name; + + private AMQState(int id, String name) + { + _id = id; + _name = name; + } + + public String toString() + { + return "AMQState: id = " + _id + " name: " + _name; + } + + public static final AMQState CONNECTION_NOT_STARTED = new AMQState(1, "CONNECTION_NOT_STARTED"); + + public static final AMQState CONNECTION_NOT_TUNED = new AMQState(2, "CONNECTION_NOT_TUNED"); + + public static final AMQState CONNECTION_NOT_OPENED = new AMQState(3, "CONNECTION_NOT_OPENED"); + + public static final AMQState CONNECTION_OPEN = new AMQState(4, "CONNECTION_OPEN"); + + public static final AMQState CONNECTION_CLOSING = new AMQState(5, "CONNECTION_CLOSING"); + + public static final AMQState CONNECTION_CLOSED = new AMQState(6, "CONNECTION_CLOSED"); + +} diff --git a/java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java b/java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java new file mode 100644 index 0000000000..91955e37fd --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/AMQStateChangedEvent.java @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +/** + * An event that is fired when the protocol state has changed. + * + */ +public class AMQStateChangedEvent +{ + private final AMQState _oldState; + + private final AMQState _newState; + + public AMQStateChangedEvent(AMQState oldState, AMQState newState) + { + _oldState = oldState; + _newState = newState; + } + + public AMQState getOldState() + { + return _oldState; + } + + public AMQState getNewState() + { + return _newState; + } +} diff --git a/java/client/src/org/apache/qpid/client/state/AMQStateListener.java b/java/client/src/org/apache/qpid/client/state/AMQStateListener.java new file mode 100644 index 0000000000..7f9698dc92 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/AMQStateListener.java @@ -0,0 +1,23 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +public interface AMQStateListener +{ + void stateChanged(AMQStateChangedEvent evt); +} diff --git a/java/client/src/org/apache/qpid/client/state/AMQStateManager.java b/java/client/src/org/apache/qpid/client/state/AMQStateManager.java new file mode 100644 index 0000000000..cdabcf1df4 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/AMQStateManager.java @@ -0,0 +1,224 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.handler.*; +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.client.protocol.AMQMethodListener; +import org.apache.qpid.framing.*; +import org.apache.log4j.Logger; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * The state manager is responsible for managing the state of the protocol session. + *

    + * For each AMQProtocolHandler there is a separate state manager. + */ +public class AMQStateManager implements AMQMethodListener +{ + private static final Logger _logger = Logger.getLogger(AMQStateManager.class); + + /** + * The current state + */ + private AMQState _currentState; + + /** + * Maps from an AMQState instance to a Map from Class to StateTransitionHandler. + * The class must be a subclass of AMQFrame. + */ + private final Map _state2HandlersMap = new HashMap(); + + private final CopyOnWriteArraySet _stateListeners = new CopyOnWriteArraySet(); + + public AMQStateManager() + { + this(AMQState.CONNECTION_NOT_STARTED, true); + } + + protected AMQStateManager(AMQState state, boolean register) + { + _currentState = state; + if(register) + { + registerListeners(); + } + } + + protected void registerListeners() + { + Map frame2handlerMap = new HashMap(); + + // we need to register a map for the null (i.e. all state) handlers otherwise you get + // a stack overflow in the handler searching code when you present it with a frame for which + // no handlers are registered + // + _state2HandlersMap.put(null, frame2handlerMap); + + frame2handlerMap = new HashMap(); + frame2handlerMap.put(ConnectionStartBody.class, ConnectionStartMethodHandler.getInstance()); + frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); + + frame2handlerMap = new HashMap(); + frame2handlerMap.put(ConnectionTuneBody.class, ConnectionTuneMethodHandler.getInstance()); + frame2handlerMap.put(ConnectionSecureBody.class, ConnectionSecureMethodHandler.getInstance()); + frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); + + frame2handlerMap = new HashMap(); + frame2handlerMap.put(ConnectionOpenOkBody.class, ConnectionOpenOkMethodHandler.getInstance()); + frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); + + // + // ConnectionOpen handlers + // + frame2handlerMap = new HashMap(); + frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseMethodHandler.getInstance()); + frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkMethodHandler.getInstance()); + frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); + frame2handlerMap.put(BasicDeliverBody.class, BasicDeliverMethodHandler.getInstance()); + frame2handlerMap.put(BasicReturnBody.class, BasicReturnMethodHandler.getInstance()); + frame2handlerMap.put(ChannelFlowOkBody.class, ChannelFlowOkMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); + } + + public AMQState getCurrentState() + { + return _currentState; + } + + public void changeState(AMQState newState) throws AMQException + { + _logger.debug("State changing to " + newState + " from old state " + _currentState); + final AMQState oldState = _currentState; + _currentState = newState; + + synchronized (_stateListeners) + { + final Iterator it = _stateListeners.iterator(); + while (it.hasNext()) + { + final StateListener l = (StateListener) it.next(); + l.stateChanged(oldState, newState); + } + } + } + + public void error(Exception e) + { + _logger.debug("State manager receive error notification: " + e); + synchronized (_stateListeners) + { + final Iterator it = _stateListeners.iterator(); + while (it.hasNext()) + { + final StateListener l = (StateListener) it.next(); + l.error(e); + } + } + } + + public boolean methodReceived(AMQMethodEvent evt) throws AMQException + { + StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod()); + if (handler != null) + { + handler.methodReceived(this, evt); + return true; + } + return false; + } + + protected StateAwareMethodListener findStateTransitionHandler(AMQState currentState, + AMQMethodBody frame) + throws IllegalStateTransitionException + { + final Class clazz = frame.getClass(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Looking for state transition handler for frame " + clazz); + } + final Map classToHandlerMap = (Map) _state2HandlersMap.get(currentState); + + if (classToHandlerMap == null) + { + // if no specialised per state handler is registered look for a + // handler registered for "all" states + return findStateTransitionHandler(null, frame); + } + final StateAwareMethodListener handler = (StateAwareMethodListener) classToHandlerMap.get(clazz); + if (handler == null) + { + if (currentState == null) + { + _logger.debug("No state transition handler defined for receiving frame " + frame); + return null; + } + else + { + // if no specialised per state handler is registered look for a + // handler registered for "all" states + return findStateTransitionHandler(null, frame); + } + } + else + { + return handler; + } + } + + public void addStateListener(StateListener listener) + { + _logger.debug("Adding state listener"); + _stateListeners.add(listener); + } + + public void removeStateListener(StateListener listener) + { + _stateListeners.remove(listener); + } + + public void attainState(AMQState s) throws AMQException + { + boolean needToWait = false; + StateWaiter sw = null; + synchronized (_stateListeners) + { + if (_currentState != s) + { + _logger.debug("Adding state wait to reach state " + s); + sw = new StateWaiter(s); + addStateListener(sw); + // we use a boolean since we must release the lock before starting to wait + needToWait = true; + } + } + if (needToWait) + { + sw.waituntilStateHasChanged(); + } + // at this point the state will have changed. + } +} diff --git a/java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java b/java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java new file mode 100644 index 0000000000..58bb38352b --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/IllegalStateTransitionException.java @@ -0,0 +1,45 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +import org.apache.qpid.AMQException; + +public class IllegalStateTransitionException extends AMQException +{ + private AMQState _originalState; + + private Class _frame; + + public IllegalStateTransitionException(AMQState originalState, Class frame) + { + super("No valid state transition defined for receiving frame " + frame + + " from state " + originalState); + _originalState = originalState; + _frame = frame; + } + + public AMQState getOriginalState() + { + return _originalState; + } + + public Class getFrameClass() + { + return _frame; + } +} diff --git a/java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java b/java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java new file mode 100644 index 0000000000..6fed4f2df6 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/StateAwareMethodListener.java @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +import org.apache.qpid.client.protocol.AMQMethodEvent; +import org.apache.qpid.AMQException; + +/** + * A frame listener that is informed of the protocl state when invoked and has + * the opportunity to update state. + * + */ +public interface StateAwareMethodListener +{ + void methodReceived(AMQStateManager stateManager, AMQMethodEvent evt) throws AMQException; +} diff --git a/java/client/src/org/apache/qpid/client/state/StateListener.java b/java/client/src/org/apache/qpid/client/state/StateListener.java new file mode 100644 index 0000000000..0a5bee5106 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/StateListener.java @@ -0,0 +1,27 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +import org.apache.qpid.AMQException; + +public interface StateListener +{ + void stateChanged(AMQState oldState, AMQState newState) throws AMQException; + + void error(Throwable t); +} diff --git a/java/client/src/org/apache/qpid/client/state/StateWaiter.java b/java/client/src/org/apache/qpid/client/state/StateWaiter.java new file mode 100644 index 0000000000..53ce452b78 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/StateWaiter.java @@ -0,0 +1,114 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; + +/** + * Waits for a particular state to be reached. + * + */ +public class StateWaiter implements StateListener +{ + private static final Logger _logger = Logger.getLogger(StateWaiter.class); + + private final AMQState _state; + + private volatile boolean _newStateAchieved; + + private volatile Throwable _throwable; + + private final Object _monitor = new Object(); + + public StateWaiter(AMQState state) + { + _state = state; + } + + public void waituntilStateHasChanged() throws AMQException + { + synchronized (_monitor) + { + // + // The guard is required in case we are woken up by a spurious + // notify(). + // + while (!_newStateAchieved && _throwable == null) + { + try + { + _logger.debug("State " + _state + " not achieved so waiting..."); + _monitor.wait(); + } + catch (InterruptedException e) + { + _logger.debug("Interrupted exception caught while waiting: " + e, e); + } + } + } + + if (_throwable != null) + { + _logger.debug("Throwable reached state waiter: " + _throwable); + if (_throwable instanceof AMQException) + { + throw (AMQException) _throwable; + } + else + { + throw new AMQException("Error: " + _throwable, _throwable); // FIXME: this will wrap FailoverException in throwable which will prevent it being caught. + } + } + } + + public void stateChanged(AMQState oldState, AMQState newState) + { + synchronized (_monitor) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("stateChanged called"); + } + if (_state == newState) + { + _newStateAchieved = true; + + if (_logger.isDebugEnabled()) + { + _logger.debug("New state reached so notifying monitor"); + } + _monitor.notifyAll(); + } + } + } + + public void error(Throwable t) + { + synchronized (_monitor) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("exceptionThrown called"); + } + + _throwable = t; + _monitor.notifyAll(); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java b/java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java new file mode 100644 index 0000000000..b2d7d777d8 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java @@ -0,0 +1,38 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.state.listener; + +import org.apache.qpid.client.protocol.BlockingMethodFrameListener; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.AMQException; + +public class SpecificMethodFrameListener extends BlockingMethodFrameListener +{ + private final Class _expectedClass; + + public SpecificMethodFrameListener(int channelId, Class expectedClass) + { + super(channelId); + _expectedClass = expectedClass; + } + + public boolean processMethod(int channelId, AMQMethodBody frame) throws AMQException + { + return _expectedClass.isInstance(frame); + } +} diff --git a/java/client/src/org/apache/qpid/client/transport/ITransportConnection.java b/java/client/src/org/apache/qpid/client/transport/ITransportConnection.java new file mode 100644 index 0000000000..49af2d1bdb --- /dev/null +++ b/java/client/src/org/apache/qpid/client/transport/ITransportConnection.java @@ -0,0 +1,30 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.transport; + +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.jms.BrokerDetails; + +import java.io.IOException; + +public interface ITransportConnection +{ + void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) + throws IOException; +} diff --git a/java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java b/java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java new file mode 100644 index 0000000000..0ec5ba2473 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/transport/SocketTransportConnection.java @@ -0,0 +1,96 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.transport; + +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.pool.ReadWriteThreadModel; +import org.apache.qpid.jms.BrokerDetails; +import org.apache.log4j.Logger; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.transport.socket.nio.SocketConnectorConfig; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; + +import java.io.IOException; +import java.net.InetSocketAddress; + +public class SocketTransportConnection implements ITransportConnection +{ + private static final Logger _logger = Logger.getLogger(SocketTransportConnection.class); + private static final int DEFAULT_BUFFER_SIZE = 32 * 1024; + + private SocketConnectorFactory _socketConnectorFactory; + + static interface SocketConnectorFactory { + IoConnector newSocketConnector(); + } + + public SocketTransportConnection(SocketConnectorFactory socketConnectorFactory) + { + _socketConnectorFactory = socketConnectorFactory; + } + + public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) + throws IOException + { + ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers")); + + // the MINA default is currently to use the pooled allocator although this may change in future + // once more testing of the performance of the simple allocator has been done + if (!Boolean.getBoolean("amqj.enablePooledAllocator")) + { + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + } + + final IoConnector ioConnector = _socketConnectorFactory.newSocketConnector(); + SocketConnectorConfig cfg = (SocketConnectorConfig) ioConnector.getDefaultConfig(); + + // if we do not use our own thread model we get the MINA default which is to use + // its own leader-follower model + boolean readWriteThreading = Boolean.getBoolean("amqj.shared_read_write_pool"); + if (readWriteThreading) + { + cfg.setThreadModel(new ReadWriteThreadModel()); + } + + SocketSessionConfig scfg = (SocketSessionConfig) cfg.getSessionConfig(); + scfg.setTcpNoDelay("true".equalsIgnoreCase(System.getProperty("amqj.tcpNoDelay", "true"))); + scfg.setSendBufferSize(Integer.getInteger("amqj.sendBufferSize", DEFAULT_BUFFER_SIZE)); + _logger.info("send-buffer-size = " + scfg.getSendBufferSize()); + scfg.setReceiveBufferSize(Integer.getInteger("amqj.receiveBufferSize", DEFAULT_BUFFER_SIZE)); + _logger.info("recv-buffer-size = " + scfg.getReceiveBufferSize()); + final InetSocketAddress address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort()); + protocolHandler.setUseSSL(brokerDetail.useSSL()); + _logger.info("Attempting connection to " + address); + ConnectFuture future = ioConnector.connect(address, protocolHandler); + + // wait for connection to complete + if (future.join(brokerDetail.getTimeout())) + { + // we call getSession which throws an IOException if there has been an error connecting + future.getSession(); + } + else + { + throw new IOException("Timeout waiting for connection."); + } + } +} diff --git a/java/client/src/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/org/apache/qpid/client/transport/TransportConnection.java new file mode 100644 index 0000000000..a898e182f7 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/transport/TransportConnection.java @@ -0,0 +1,71 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.transport; + +import org.apache.mina.common.IoConnector; +import org.apache.mina.transport.socket.nio.SocketConnector; + +/** + * The TransportConnection is a helper class responsible for connecting to an AMQ server. It sets up + * the underlying connector, which currently always uses TCP/IP sockets. It creates the + * "protocol handler" which deals with MINA protocol events. + * + * Could be extended in future to support different transport types by turning this into concrete class/interface + * combo. + */ +public class TransportConnection +{ + private static ITransportConnection _instance; + + static + { + if (Boolean.getBoolean("amqj.useBlockingIo")) + { + _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() { + public IoConnector newSocketConnector() { + return new org.apache.qpid.bio.SocketConnector(); // blocking connector + } + }); + } + else + { + _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() { + public IoConnector newSocketConnector() { + SocketConnector result = new SocketConnector(); // non-blocking connector + + // Don't have the connector's worker thread wait around for other connections (we only use + // one SocketConnector per connection at the moment anyway). This allows short-running + // clients (like unit tests) to complete quickly. + result.setWorkerTimeout(0L); + + return result; + } + }); + } + } + + public static void setInstance(ITransportConnection transport) + { + _instance = transport; + } + + public static ITransportConnection getInstance() + { + return _instance; + } +} diff --git a/java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java b/java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java new file mode 100644 index 0000000000..ad2ca7b731 --- /dev/null +++ b/java/client/src/org/apache/qpid/client/util/FlowControllingBlockingQueue.java @@ -0,0 +1,87 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.client.util; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * A blocking queue that emits events above a user specified threshold allowing + * the caller to take action (e.g. flow control) to try to prevent the queue + * growing (much) further. The underlying queue itself is not bounded therefore + * the caller is not obliged to react to the events. + *

    + * This implementation is only safe where we have a single thread adding + * items and a single (different) thread removing items. + * + */ +public class FlowControllingBlockingQueue +{ + /** + * This queue is bounded and is used to store messages before being dispatched to the consumer + */ + private final BlockingQueue _queue = new LinkedBlockingQueue(); + + private final int _flowControlThreshold; + + private final ThresholdListener _listener; + + /** + * We require a separate count so we can track whether we have reached the + * threshold + */ + private int _count; + + public interface ThresholdListener + { + void aboveThreshold(int currentValue); + + void underThreshold(int currentValue); + } + + public FlowControllingBlockingQueue(int threshold, ThresholdListener listener) + { + _flowControlThreshold = threshold; + _listener = listener; + } + + public Object take() throws InterruptedException + { + Object o = _queue.take(); + synchronized (_listener) + { + if (--_count == (_flowControlThreshold - 1)) + { + _listener.underThreshold(_count); + } + } + return o; + } + + public void add(Object o) + { + _queue.add(o); + synchronized (_listener) + { + if (++_count == _flowControlThreshold) + { + _listener.aboveThreshold(_count); + } + } + } +} diff --git a/java/client/src/org/apache/qpid/jms/BrokerDetails.java b/java/client/src/org/apache/qpid/jms/BrokerDetails.java new file mode 100644 index 0000000000..fc8af2091e --- /dev/null +++ b/java/client/src/org/apache/qpid/jms/BrokerDetails.java @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.qpid.jms; + +public interface BrokerDetails +{ + + /* + * Known URL Options + * @see ConnectionURL + */ + public static final String OPTIONS_RETRY = "retries"; + public static final String OPTIONS_SSL = ConnectionURL.OPTIONS_SSL; + public static final String OPTIONS_CONNECT_TIMEOUT = "connecttimeout"; + public static final int DEFAULT_PORT = 5672; + public static final String DEFAULT_TRANSPORT = "tcp"; + + public static final String URL_FORMAT_EXAMPLE = + "://[:][?