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 --- cpp/DESIGN | 89 + cpp/Makefile | 53 + cpp/README | 48 + cpp/broker/Makefile | 47 + cpp/broker/inc/AutoDelete.h | 54 + cpp/broker/inc/Binding.h | 35 + cpp/broker/inc/Channel.h | 87 + cpp/broker/inc/Configuration.h | 125 + cpp/broker/inc/ConnectionToken.h | 35 + cpp/broker/inc/Consumer.h | 34 + cpp/broker/inc/DirectExchange.h | 55 + cpp/broker/inc/Exchange.h | 39 + cpp/broker/inc/ExchangeBinding.h | 45 + cpp/broker/inc/ExchangeRegistry.h | 42 + cpp/broker/inc/FanOutExchange.h | 58 + cpp/broker/inc/Message.h | 73 + cpp/broker/inc/NameGenerator.h | 36 + cpp/broker/inc/Queue.h | 106 + cpp/broker/inc/QueueRegistry.h | 88 + cpp/broker/inc/SessionHandlerFactoryImpl.h | 49 + cpp/broker/inc/SessionHandlerImpl.h | 230 + cpp/broker/inc/TopicExchange.h | 55 + cpp/broker/src/AutoDelete.cpp | 93 + cpp/broker/src/Broker.cpp | 92 + cpp/broker/src/Channel.cpp | 148 + cpp/broker/src/Configuration.cpp | 195 + cpp/broker/src/DirectExchange.cpp | 72 + cpp/broker/src/ExchangeBinding.cpp | 32 + cpp/broker/src/ExchangeRegistry.cpp | 43 + cpp/broker/src/FanOutExchange.cpp | 56 + cpp/broker/src/Message.cpp | 97 + cpp/broker/src/NameGenerator.cpp | 29 + cpp/broker/src/Queue.cpp | 148 + cpp/broker/src/QueueRegistry.cpp | 72 + cpp/broker/src/SessionHandlerFactoryImpl.cpp | 40 + cpp/broker/src/SessionHandlerImpl.cpp | 378 + cpp/broker/src/TopicExchange.cpp | 62 + cpp/broker/test/Makefile | 20 + cpp/broker/test/QueueRegistryTest.cpp | 79 + cpp/broker/test/exchange_test.cpp | 68 + cpp/broker/test/message_test.cpp | 57 + cpp/broker/test/queue_test.cpp | 138 + cpp/client/Makefile | 43 + cpp/client/inc/Channel.h | 127 + cpp/client/inc/Connection.h | 105 + cpp/client/inc/Exchange.h | 49 + cpp/client/inc/IncomingMessage.h | 60 + cpp/client/inc/Message.h | 86 + cpp/client/inc/MessageListener.h | 37 + cpp/client/inc/Queue.h | 47 + cpp/client/inc/ResponseHandler.h | 49 + cpp/client/inc/ReturnedMessageHandler.h | 37 + cpp/client/src/Channel.cpp | 432 + cpp/client/src/Connection.cpp | 237 + cpp/client/src/Exchange.cpp | 30 + cpp/client/src/IncomingMessage.cpp | 85 + cpp/client/src/Message.cpp | 147 + cpp/client/src/Queue.cpp | 47 + cpp/client/src/ResponseHandler.cpp | 63 + cpp/client/test/Makefile | 45 + cpp/client/test/client_test.cpp | 97 + cpp/client/test/topic_listener.cpp | 180 + cpp/client/test/topic_publisher.cpp | 253 + cpp/common/Makefile | 51 + cpp/common/concurrent/inc/APRBase.h | 63 + cpp/common/concurrent/inc/APRMonitor.h | 48 + cpp/common/concurrent/inc/APRThread.h | 48 + cpp/common/concurrent/inc/APRThreadFactory.h | 44 + cpp/common/concurrent/inc/APRThreadPool.h | 67 + cpp/common/concurrent/inc/LMonitor.h | 44 + cpp/common/concurrent/inc/LThreadFactory.h | 37 + cpp/common/concurrent/inc/LockedQueue.h | 68 + cpp/common/concurrent/inc/Monitor.h | 59 + cpp/common/concurrent/inc/MonitorImpl.h | 57 + cpp/common/concurrent/inc/Runnable.h | 34 + cpp/common/concurrent/inc/TaskQueue.h | 200 + cpp/common/concurrent/inc/Thread.h | 37 + cpp/common/concurrent/inc/ThreadFactory.h | 38 + cpp/common/concurrent/inc/ThreadFactoryImpl.h | 52 + cpp/common/concurrent/inc/ThreadPool.h | 40 + cpp/common/concurrent/src/APRBase.cpp | 97 + cpp/common/concurrent/src/APRMonitor.cpp | 60 + cpp/common/concurrent/src/APRThread.cpp | 50 + cpp/common/concurrent/src/APRThreadFactory.cpp | 35 + cpp/common/concurrent/src/APRThreadPool.cpp | 85 + cpp/common/error/inc/QpidError.h | 47 + cpp/common/error/inc/QpidErrorIO.h | 29 + cpp/common/framing/Makefile | 28 + cpp/common/framing/generated/Makefile | 41 + .../framing/generated/stylesheets/amqp_client.xsl | 155 + .../stylesheets/amqp_client_handler_impl.xsl | 187 + .../stylesheets/amqp_client_operations.xsl | 105 + .../framing/generated/stylesheets/amqp_consts.xsl | 77 + .../framing/generated/stylesheets/amqp_server.xsl | 155 + .../stylesheets/amqp_server_handler_impl.xsl | 187 + .../stylesheets/amqp_server_operations.xsl | 113 + .../framing/generated/stylesheets/code_gen.xsl | 91 + .../framing/generated/stylesheets/code_utils.xsl | 210 + .../framing/generated/stylesheets/convert_0.81.xsl | 407 + cpp/common/framing/generated/stylesheets/cpp.xsl | 318 + .../framing/generated/stylesheets/framing.xsl | 49 + .../framing/generated/stylesheets/prepare1.xsl | 104 + .../framing/generated/stylesheets/prepare2.xsl | 54 + .../framing/generated/stylesheets/prepare3.xsl | 54 + .../framing/generated/stylesheets/registry.xsl | 12 + cpp/common/framing/generated/stylesheets/utils.xsl | 194 + cpp/common/framing/inc/AMQBody.h | 46 + cpp/common/framing/inc/AMQContentBody.h | 49 + cpp/common/framing/inc/AMQDataBlock.h | 39 + cpp/common/framing/inc/AMQFrame.h | 61 + cpp/common/framing/inc/AMQHeaderBody.h | 55 + cpp/common/framing/inc/AMQHeartbeatBody.h | 43 + cpp/common/framing/inc/AMQMethodBody.h | 56 + cpp/common/framing/inc/BasicHeaderProperties.h | 93 + cpp/common/framing/inc/BodyHandler.h | 50 + cpp/common/framing/inc/Buffer.h | 77 + cpp/common/framing/inc/FieldTable.h | 68 + cpp/common/framing/inc/HeaderProperties.h | 43 + cpp/common/framing/inc/InitiationHandler.h | 37 + cpp/common/framing/inc/InputHandler.h | 37 + cpp/common/framing/inc/NamedValue.h | 49 + cpp/common/framing/inc/OutputHandler.h | 37 + cpp/common/framing/inc/ProtocolInitiation.h | 48 + cpp/common/framing/inc/Value.h | 109 + cpp/common/framing/inc/amqp_framing.h | 31 + cpp/common/framing/inc/amqp_types.h | 36 + cpp/common/framing/src/AMQContentBody.cpp | 35 + cpp/common/framing/src/AMQFrame.cpp | 147 + cpp/common/framing/src/AMQHeaderBody.cpp | 60 + cpp/common/framing/src/AMQMethodBody.cpp | 43 + cpp/common/framing/src/BasicHeaderProperties.cpp | 102 + cpp/common/framing/src/BodyHandler.cpp | 49 + cpp/common/framing/src/Buffer.cpp | 167 + cpp/common/framing/src/FieldTable.cpp | 127 + cpp/common/framing/src/NamedValue.cpp | 67 + cpp/common/framing/src/ProtocolInitiation.cpp | 53 + cpp/common/framing/src/Value.cpp | 57 + cpp/common/framing/test/BodyHandlerTest.cpp | 107 + cpp/common/framing/test/Makefile | 21 + cpp/common/framing/test/field_table_test.cpp | 55 + cpp/common/framing/test/framing_test.cpp | 147 + cpp/common/framing/test/header_test.cpp | 144 + cpp/common/io/Makefile | 35 + cpp/common/io/inc/APRConnector.h | 95 + cpp/common/io/inc/APRIOProcessor.h | 86 + cpp/common/io/inc/APRSocket.h | 45 + cpp/common/io/inc/Acceptor.h | 38 + cpp/common/io/inc/BlockingAPRAcceptor.h | 62 + cpp/common/io/inc/BlockingAPRSessionContext.h | 94 + cpp/common/io/inc/Connector.h | 56 + cpp/common/io/inc/ConnectorImpl.h | 53 + cpp/common/io/inc/IOSession.h | 45 + cpp/common/io/inc/LConnector.h | 48 + cpp/common/io/inc/LFAcceptor.h | 71 + cpp/common/io/inc/LFProcessor.h | 116 + cpp/common/io/inc/LFSessionContext.h | 89 + cpp/common/io/inc/SessionContext.h | 37 + cpp/common/io/inc/SessionHandler.h | 42 + cpp/common/io/inc/SessionHandlerFactory.h | 38 + cpp/common/io/inc/SessionManager.h | 40 + cpp/common/io/inc/ShutdownHandler.h | 34 + cpp/common/io/inc/TimeoutHandler.h | 36 + cpp/common/io/src/APRConnector.cpp | 198 + cpp/common/io/src/APRIOProcessor.cpp | 100 + cpp/common/io/src/APRSocket.cpp | 76 + cpp/common/io/src/BlockingAPRAcceptor.cpp | 81 + cpp/common/io/src/BlockingAPRSessionContext.cpp | 177 + cpp/common/io/src/LFAcceptor.cpp | 80 + cpp/common/io/src/LFProcessor.cpp | 191 + cpp/common/io/src/LFSessionContext.cpp | 187 + cpp/common/utils/inc/logger.h | 82 + cpp/common/utils/inc/memory.h | 17 + cpp/common/utils/src/Makefile | 40 + cpp/common/utils/src/logger.cpp | 209 + cpp/common/utils/src/logger_test.cpp | 78 + cpp/doxygen/Makefile | 23 + cpp/doxygen/doxygen.cfg | 1238 + cpp/options.mk | 54 + cpp/test_plugins.mk | 42 + cpp/tools/saxon8.jar | Bin 0 -> 3118502 bytes 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 + python/README.txt | 24 + python/amqp-doc | 75 + python/cpp_failing.txt | 0 python/doc/test-requirements.txt | 10 + python/java_failing.txt | 13 + python/pal2py | 255 + python/qpid/__init__.py | 17 + python/qpid/client.py | 111 + python/qpid/codec.py | 221 + python/qpid/connection.py | 265 + python/qpid/content.py | 47 + python/qpid/delegate.py | 52 + python/qpid/message.py | 81 + python/qpid/peer.py | 209 + python/qpid/queue.py | 42 + python/qpid/spec.py | 349 + python/qpid/testlib.py | 221 + python/qpid/xmlutil.py | 116 + python/rule2test | 89 + python/run-tests | 24 + python/tests/__init__.py | 1 + python/tests/basic.py | 115 + python/tests/broker.py | 84 + python/tests/example.py | 91 + python/tests/exchange.py | 234 + python/tests/queue.py | 254 + ruby/client.rb | 106 + ruby/codec.rb | 253 + ruby/connection.rb | 142 + ruby/diff.rb | 57 + ruby/fields.rb | 46 + ruby/peer.rb | 246 + ruby/queue.rb | 49 + ruby/spec.rb | 291 + ruby/test.rb | 24 + ruby/traverse.rb | 61 + specs/amqp-8.0.xml | 3959 + 904 files changed, 166568 insertions(+) create mode 100644 cpp/DESIGN create mode 100644 cpp/Makefile create mode 100644 cpp/README create mode 100644 cpp/broker/Makefile create mode 100644 cpp/broker/inc/AutoDelete.h create mode 100644 cpp/broker/inc/Binding.h create mode 100644 cpp/broker/inc/Channel.h create mode 100644 cpp/broker/inc/Configuration.h create mode 100644 cpp/broker/inc/ConnectionToken.h create mode 100644 cpp/broker/inc/Consumer.h create mode 100644 cpp/broker/inc/DirectExchange.h create mode 100644 cpp/broker/inc/Exchange.h create mode 100644 cpp/broker/inc/ExchangeBinding.h create mode 100644 cpp/broker/inc/ExchangeRegistry.h create mode 100644 cpp/broker/inc/FanOutExchange.h create mode 100644 cpp/broker/inc/Message.h create mode 100644 cpp/broker/inc/NameGenerator.h create mode 100644 cpp/broker/inc/Queue.h create mode 100644 cpp/broker/inc/QueueRegistry.h create mode 100644 cpp/broker/inc/SessionHandlerFactoryImpl.h create mode 100644 cpp/broker/inc/SessionHandlerImpl.h create mode 100644 cpp/broker/inc/TopicExchange.h create mode 100644 cpp/broker/src/AutoDelete.cpp create mode 100644 cpp/broker/src/Broker.cpp create mode 100644 cpp/broker/src/Channel.cpp create mode 100644 cpp/broker/src/Configuration.cpp create mode 100644 cpp/broker/src/DirectExchange.cpp create mode 100644 cpp/broker/src/ExchangeBinding.cpp create mode 100644 cpp/broker/src/ExchangeRegistry.cpp create mode 100644 cpp/broker/src/FanOutExchange.cpp create mode 100644 cpp/broker/src/Message.cpp create mode 100644 cpp/broker/src/NameGenerator.cpp create mode 100644 cpp/broker/src/Queue.cpp create mode 100644 cpp/broker/src/QueueRegistry.cpp create mode 100644 cpp/broker/src/SessionHandlerFactoryImpl.cpp create mode 100644 cpp/broker/src/SessionHandlerImpl.cpp create mode 100644 cpp/broker/src/TopicExchange.cpp create mode 100644 cpp/broker/test/Makefile create mode 100644 cpp/broker/test/QueueRegistryTest.cpp create mode 100644 cpp/broker/test/exchange_test.cpp create mode 100644 cpp/broker/test/message_test.cpp create mode 100644 cpp/broker/test/queue_test.cpp create mode 100644 cpp/client/Makefile create mode 100644 cpp/client/inc/Channel.h create mode 100644 cpp/client/inc/Connection.h create mode 100644 cpp/client/inc/Exchange.h create mode 100644 cpp/client/inc/IncomingMessage.h create mode 100644 cpp/client/inc/Message.h create mode 100644 cpp/client/inc/MessageListener.h create mode 100644 cpp/client/inc/Queue.h create mode 100644 cpp/client/inc/ResponseHandler.h create mode 100644 cpp/client/inc/ReturnedMessageHandler.h create mode 100644 cpp/client/src/Channel.cpp create mode 100644 cpp/client/src/Connection.cpp create mode 100644 cpp/client/src/Exchange.cpp create mode 100644 cpp/client/src/IncomingMessage.cpp create mode 100644 cpp/client/src/Message.cpp create mode 100644 cpp/client/src/Queue.cpp create mode 100644 cpp/client/src/ResponseHandler.cpp create mode 100644 cpp/client/test/Makefile create mode 100644 cpp/client/test/client_test.cpp create mode 100644 cpp/client/test/topic_listener.cpp create mode 100644 cpp/client/test/topic_publisher.cpp create mode 100644 cpp/common/Makefile create mode 100644 cpp/common/concurrent/inc/APRBase.h create mode 100644 cpp/common/concurrent/inc/APRMonitor.h create mode 100644 cpp/common/concurrent/inc/APRThread.h create mode 100644 cpp/common/concurrent/inc/APRThreadFactory.h create mode 100644 cpp/common/concurrent/inc/APRThreadPool.h create mode 100644 cpp/common/concurrent/inc/LMonitor.h create mode 100644 cpp/common/concurrent/inc/LThreadFactory.h create mode 100644 cpp/common/concurrent/inc/LockedQueue.h create mode 100644 cpp/common/concurrent/inc/Monitor.h create mode 100644 cpp/common/concurrent/inc/MonitorImpl.h create mode 100644 cpp/common/concurrent/inc/Runnable.h create mode 100644 cpp/common/concurrent/inc/TaskQueue.h create mode 100644 cpp/common/concurrent/inc/Thread.h create mode 100644 cpp/common/concurrent/inc/ThreadFactory.h create mode 100644 cpp/common/concurrent/inc/ThreadFactoryImpl.h create mode 100644 cpp/common/concurrent/inc/ThreadPool.h create mode 100644 cpp/common/concurrent/src/APRBase.cpp create mode 100644 cpp/common/concurrent/src/APRMonitor.cpp create mode 100644 cpp/common/concurrent/src/APRThread.cpp create mode 100644 cpp/common/concurrent/src/APRThreadFactory.cpp create mode 100644 cpp/common/concurrent/src/APRThreadPool.cpp create mode 100644 cpp/common/error/inc/QpidError.h create mode 100644 cpp/common/error/inc/QpidErrorIO.h create mode 100644 cpp/common/framing/Makefile create mode 100644 cpp/common/framing/generated/Makefile create mode 100644 cpp/common/framing/generated/stylesheets/amqp_client.xsl create mode 100644 cpp/common/framing/generated/stylesheets/amqp_client_handler_impl.xsl create mode 100644 cpp/common/framing/generated/stylesheets/amqp_client_operations.xsl create mode 100644 cpp/common/framing/generated/stylesheets/amqp_consts.xsl create mode 100644 cpp/common/framing/generated/stylesheets/amqp_server.xsl create mode 100644 cpp/common/framing/generated/stylesheets/amqp_server_handler_impl.xsl create mode 100644 cpp/common/framing/generated/stylesheets/amqp_server_operations.xsl create mode 100644 cpp/common/framing/generated/stylesheets/code_gen.xsl create mode 100644 cpp/common/framing/generated/stylesheets/code_utils.xsl create mode 100644 cpp/common/framing/generated/stylesheets/convert_0.81.xsl create mode 100644 cpp/common/framing/generated/stylesheets/cpp.xsl create mode 100644 cpp/common/framing/generated/stylesheets/framing.xsl create mode 100644 cpp/common/framing/generated/stylesheets/prepare1.xsl create mode 100644 cpp/common/framing/generated/stylesheets/prepare2.xsl create mode 100644 cpp/common/framing/generated/stylesheets/prepare3.xsl create mode 100644 cpp/common/framing/generated/stylesheets/registry.xsl create mode 100644 cpp/common/framing/generated/stylesheets/utils.xsl create mode 100644 cpp/common/framing/inc/AMQBody.h create mode 100644 cpp/common/framing/inc/AMQContentBody.h create mode 100644 cpp/common/framing/inc/AMQDataBlock.h create mode 100644 cpp/common/framing/inc/AMQFrame.h create mode 100644 cpp/common/framing/inc/AMQHeaderBody.h create mode 100644 cpp/common/framing/inc/AMQHeartbeatBody.h create mode 100644 cpp/common/framing/inc/AMQMethodBody.h create mode 100644 cpp/common/framing/inc/BasicHeaderProperties.h create mode 100644 cpp/common/framing/inc/BodyHandler.h create mode 100644 cpp/common/framing/inc/Buffer.h create mode 100644 cpp/common/framing/inc/FieldTable.h create mode 100644 cpp/common/framing/inc/HeaderProperties.h create mode 100644 cpp/common/framing/inc/InitiationHandler.h create mode 100644 cpp/common/framing/inc/InputHandler.h create mode 100644 cpp/common/framing/inc/NamedValue.h create mode 100644 cpp/common/framing/inc/OutputHandler.h create mode 100644 cpp/common/framing/inc/ProtocolInitiation.h create mode 100644 cpp/common/framing/inc/Value.h create mode 100644 cpp/common/framing/inc/amqp_framing.h create mode 100644 cpp/common/framing/inc/amqp_types.h create mode 100644 cpp/common/framing/src/AMQContentBody.cpp create mode 100644 cpp/common/framing/src/AMQFrame.cpp create mode 100644 cpp/common/framing/src/AMQHeaderBody.cpp create mode 100644 cpp/common/framing/src/AMQMethodBody.cpp create mode 100644 cpp/common/framing/src/BasicHeaderProperties.cpp create mode 100644 cpp/common/framing/src/BodyHandler.cpp create mode 100644 cpp/common/framing/src/Buffer.cpp create mode 100644 cpp/common/framing/src/FieldTable.cpp create mode 100644 cpp/common/framing/src/NamedValue.cpp create mode 100644 cpp/common/framing/src/ProtocolInitiation.cpp create mode 100644 cpp/common/framing/src/Value.cpp create mode 100644 cpp/common/framing/test/BodyHandlerTest.cpp create mode 100644 cpp/common/framing/test/Makefile create mode 100644 cpp/common/framing/test/field_table_test.cpp create mode 100644 cpp/common/framing/test/framing_test.cpp create mode 100644 cpp/common/framing/test/header_test.cpp create mode 100644 cpp/common/io/Makefile create mode 100644 cpp/common/io/inc/APRConnector.h create mode 100644 cpp/common/io/inc/APRIOProcessor.h create mode 100644 cpp/common/io/inc/APRSocket.h create mode 100644 cpp/common/io/inc/Acceptor.h create mode 100644 cpp/common/io/inc/BlockingAPRAcceptor.h create mode 100644 cpp/common/io/inc/BlockingAPRSessionContext.h create mode 100644 cpp/common/io/inc/Connector.h create mode 100644 cpp/common/io/inc/ConnectorImpl.h create mode 100644 cpp/common/io/inc/IOSession.h create mode 100644 cpp/common/io/inc/LConnector.h create mode 100644 cpp/common/io/inc/LFAcceptor.h create mode 100644 cpp/common/io/inc/LFProcessor.h create mode 100644 cpp/common/io/inc/LFSessionContext.h create mode 100644 cpp/common/io/inc/SessionContext.h create mode 100644 cpp/common/io/inc/SessionHandler.h create mode 100644 cpp/common/io/inc/SessionHandlerFactory.h create mode 100644 cpp/common/io/inc/SessionManager.h create mode 100644 cpp/common/io/inc/ShutdownHandler.h create mode 100644 cpp/common/io/inc/TimeoutHandler.h create mode 100644 cpp/common/io/src/APRConnector.cpp create mode 100644 cpp/common/io/src/APRIOProcessor.cpp create mode 100644 cpp/common/io/src/APRSocket.cpp create mode 100644 cpp/common/io/src/BlockingAPRAcceptor.cpp create mode 100644 cpp/common/io/src/BlockingAPRSessionContext.cpp create mode 100644 cpp/common/io/src/LFAcceptor.cpp create mode 100644 cpp/common/io/src/LFProcessor.cpp create mode 100644 cpp/common/io/src/LFSessionContext.cpp create mode 100644 cpp/common/utils/inc/logger.h create mode 100644 cpp/common/utils/inc/memory.h create mode 100644 cpp/common/utils/src/Makefile create mode 100644 cpp/common/utils/src/logger.cpp create mode 100644 cpp/common/utils/src/logger_test.cpp create mode 100644 cpp/doxygen/Makefile create mode 100644 cpp/doxygen/doxygen.cfg create mode 100644 cpp/options.mk create mode 100644 cpp/test_plugins.mk create mode 100644 cpp/tools/saxon8.jar 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 create mode 100644 python/README.txt create mode 100755 python/amqp-doc create mode 100644 python/cpp_failing.txt create mode 100644 python/doc/test-requirements.txt create mode 100644 python/java_failing.txt create mode 100755 python/pal2py create mode 100644 python/qpid/__init__.py create mode 100644 python/qpid/client.py create mode 100644 python/qpid/codec.py create mode 100644 python/qpid/connection.py create mode 100644 python/qpid/content.py create mode 100644 python/qpid/delegate.py create mode 100644 python/qpid/message.py create mode 100644 python/qpid/peer.py create mode 100644 python/qpid/queue.py create mode 100644 python/qpid/spec.py create mode 100644 python/qpid/testlib.py create mode 100644 python/qpid/xmlutil.py create mode 100755 python/rule2test create mode 100755 python/run-tests create mode 100644 python/tests/__init__.py create mode 100644 python/tests/basic.py create mode 100644 python/tests/broker.py create mode 100644 python/tests/example.py create mode 100644 python/tests/exchange.py create mode 100644 python/tests/queue.py create mode 100644 ruby/client.rb create mode 100644 ruby/codec.rb create mode 100644 ruby/connection.rb create mode 100644 ruby/diff.rb create mode 100644 ruby/fields.rb create mode 100644 ruby/peer.rb create mode 100644 ruby/queue.rb create mode 100644 ruby/spec.rb create mode 100644 ruby/test.rb create mode 100644 ruby/traverse.rb create mode 100644 specs/amqp-8.0.xml diff --git a/cpp/DESIGN b/cpp/DESIGN new file mode 100644 index 0000000000..476fd42bfa --- /dev/null +++ b/cpp/DESIGN @@ -0,0 +1,89 @@ +Qpid C++ AMQP implementation +============================= + +The following is a brief description of the logical design of the +Qpid C++ code. + +Layout + +There are three top level modules. The first two, client and broker, +containi the code required for an AMQP client and an AMQP broker +respectively. The third, common, contains code that is common to both +client and broker implementations. [Note that at present only the +client has been started]. + +Within the common module there are currently four sub-modules. The +largest of these is framing, containing the definitions of classes +corresponding to key AMQP concepts such as frames, content & header +bodies, particular method bodies etc as well as some interfaces and +utilities used in the encoding and decoding of the wire protocol. + +Two of the other sub-modules in common, io and concurrent, provide +abstractions of core io and concurrency constructs used in the client +and broker code. The intention is to allow these to be implemented in +different ways.interaction with the wire protocol. At present the +implementation of the io and concurrency abstractions is based on APR +(Apache Portable Runtime). [Note: the io module currently only +contains the abstractions as seen from the client - the Connector. It +will in due time likely have the analogous broker-side abstraction - +the Acceptor]. + +The final common sub-module is error, containing a simple exception +definition used in all the error handling. + +Client Design + +The client module is primarily concerned with presenting the +functionality offered by AMQP to users through a simple API that +nevertheless allows all the protocol functionality to be exploited. +[Note: it is currently nothing like complete in this regard!] + +The code in the client module is concerned with the logic of the AMQP +protocol and interacts with the lower level transport issues through +the InputHandler and OutputHandler abstractions defined in +common/framing. It uses these in conjunction with the Connector +interface, defined in common/io, for establishing a connection to the +broker and interacting with it through the sending and receiving of +messages represented by AMQFrame (defined in common/framing). + +The Connector implementation is responsible for connection set up, +threading strategy and getting data on and off the wire. It delegates +to the framing module for encode/decode operations. The interface +between the io and the framing modules is primarily through the Buffer +and AMQFrame classes. + +A Buffer allows 'raw' data to be read or written in terms of the AMQP +defined 'types' (octet, short, long, long long, short string, long +string, field table etc.). AMQP is defined in terms frames with +specific bodies and the frame (as well as these different bodies) are +defined in terms of these 'types'. The AMQFrame class allows a frame +to be decoded by reading from the supplied buffer, or it allows a +particular frame to be constructed and then encoded by writing to the +supplied buffer. The io layer can then access the raw data that +'backs' the buffer to either out it on the wire or to populate it from +the wire. + +One minor exception to this is the protocol initiation. AMQP defines +a protocol 'header', that is not a frame, and is sent by a client to +intiate a connection. The Connector allows (indeed requires) such a +frame to be passed in to initialise the connection (the Acceptor, when +defined, will allow an InitiationHandler to be set allowing the broker +to hook into the connection initiation). In order to remove +duplication, the ProtocolInitiation class and the AMQFrame class both +implement a AMQDataBlock class that defines the encode and decode +methods. This allows both types to be treated generically for the +purposes of encoding. In decoding, the context determines which type +is expected and should be used for decoding (this is only relevant to +the broker). + + + + + --------api-------- + Client Impl ...............uses..... +input handler --> --------- --------- <-- output handler . + A | . + | | framing utils + | V . + ------------------- <-- connector . + IO Layer ................uses.... diff --git a/cpp/Makefile b/cpp/Makefile new file mode 100644 index 0000000000..7f83847b04 --- /dev/null +++ b/cpp/Makefile @@ -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. + +# +# Master make file for c++ Qpid project (AMQP) +# +# Calls the makefiles in the various subdirectories in order to +# build them in the correct sequence. +# + +include options.mk + +UNITTESTS=$(wildcard common/*/test/*.so broker/test/*.so) + +.PHONY: all clean doxygen + +test: all + @$(MAKE) -C common test + @$(MAKE) -C broker test + @$(MAKE) -C client test + @$(MAKE) runtests + +runtests: + $(CPPUNIT_HOME)/bin/DllPlugInTester -t -b $(UNITTESTS) + bin/qpidd >> qpidd.log & + cd ../python ; ./run-tests -v -I cpp_failing.txt + +all: + @$(MAKE) -C common all + @$(MAKE) -C broker all + @$(MAKE) -C client all + +clean: + @$(MAKE) -C common clean + @$(MAKE) -C broker clean + @$(MAKE) -C client clean + @$(MAKE) -C doxygen clean + -@rm qpidd.log + +doxygen: + @$(MAKE) -C doxygen all diff --git a/cpp/README b/cpp/README new file mode 100644 index 0000000000..427a0c15c8 --- /dev/null +++ b/cpp/README @@ -0,0 +1,48 @@ += Developer guide to C++ codebase = + +== Prerequisites == + +Apache Portable Runtime 1.2.7: http://apr.apache.org/ +Install in /usr/local/apr or update options.mk if installed elsewhere. + +CppUnit: http://cppunit.sourceforge.net + +Optional: to generate source code documentation you need: + * doxygen: http://sourceforge.net/projects/doxygen/ + * graphviz - http://www.graphviz.org/ + +== Build and test == + +make + +Default target builds and tests everything, see Makefile for other +targets. + +=== Unit tests === +Unit tests are built as .so files containing CppUnit plugins. + +DllPlugInTester is provided as part of cppunit. You can use it to run +any subset of the unit tests. See Makefile for examples. + +=== System tests === + +The Python test suite ../python/run_tests is the main set of broker +system tests. + +There are some C++ client test executables built under client/test. + +== Doxygen == + +Doxygen generates documentation in several formats from source code +using special comments. You can use javadoc style comments if you know +javadoc, if you don't or want to know the fully story on doxygen +markup see http://www.stack.nl/~dimitri/doxygen/ + +Even even if the code is completely uncommented, doxygen generates +UML-esque dependency diagrams that are ''extremely'' useful in navigating +around the code, especially for newcomers. + +To try it out "make doxygen" then open doxygen/html/index.html + + + diff --git a/cpp/broker/Makefile b/cpp/broker/Makefile new file mode 100644 index 0000000000..58ba3a41b5 --- /dev/null +++ b/cpp/broker/Makefile @@ -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. +# + +# +# Build broker library and executable. +# + +QPID_HOME = ../.. +include ${QPID_HOME}/cpp/options.mk + +SOURCES= $(wildcard src/*.cpp) +OBJECTS= $(subst .cpp,.o,$(SOURCES)) +LIB_OBJECTS= $(subst src/Broker.o,,$(OBJECTS)) +EXE_OBJECTS= src/Broker.o + + +.PHONY: all clean test + +all: $(BROKER) + +test: + @$(MAKE) -C test all + +clean: + -@rm -f ${OBJECTS} src/*.d ${BROKER} $(BROKER_LIB) + @$(MAKE) -C test clean + +$(BROKER): $(BROKER_LIB) $(EXE_OBJECTS) + ${CXX} -o $@ $(EXE_OBJECTS) $(LDFLAGS) -lapr-1 $(COMMON_LIB) $(BROKER_LIB) + +$(BROKER_LIB): $(LIB_OBJECTS) + $(CXX) -shared -o $@ $(LDFLAGS) $(LIB_OBJECTS) -lapr-1 $(COMMON_LIB) $(LIBDIR) + +-include $(SOURCES:.cpp=.d) diff --git a/cpp/broker/inc/AutoDelete.h b/cpp/broker/inc/AutoDelete.h new file mode 100644 index 0000000000..864d68358f --- /dev/null +++ b/cpp/broker/inc/AutoDelete.h @@ -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. + * + */ +#ifndef _AutoDelete_ +#define _AutoDelete_ + +#include +#include +#include "MonitorImpl.h" +#include "Queue.h" +#include "QueueRegistry.h" +#include "ThreadFactoryImpl.h" + +namespace qpid { + namespace broker{ + class AutoDelete : private virtual qpid::concurrent::Runnable{ + qpid::concurrent::ThreadFactoryImpl factory; + qpid::concurrent::MonitorImpl lock; + qpid::concurrent::MonitorImpl monitor; + std::queue queues; + QueueRegistry* const registry; + const u_int32_t period; + volatile bool stopped; + qpid::concurrent::Thread* runner; + + Queue::shared_ptr const pop(); + void process(); + virtual void run(); + + public: + AutoDelete(QueueRegistry* const registry, u_int32_t period); + void add(Queue::shared_ptr const); + void start(); + void stop(); + }; + } +} + + +#endif diff --git a/cpp/broker/inc/Binding.h b/cpp/broker/inc/Binding.h new file mode 100644 index 0000000000..b11419e92c --- /dev/null +++ b/cpp/broker/inc/Binding.h @@ -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. + * + */ +#ifndef _Binding_ +#define _Binding_ + +#include "FieldTable.h" + +namespace qpid { + namespace broker { + class Binding{ + public: + virtual void cancel() = 0; + virtual ~Binding(){} + }; + } +} + + +#endif + diff --git a/cpp/broker/inc/Channel.h b/cpp/broker/inc/Channel.h new file mode 100644 index 0000000000..aaf2ce569b --- /dev/null +++ b/cpp/broker/inc/Channel.h @@ -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. + * + */ +#ifndef _Channel_ +#define _Channel_ + +#include +#include "AMQContentBody.h" +#include "AMQHeaderBody.h" +#include "BasicPublishBody.h" +#include "Binding.h" +#include "Consumer.h" +#include "Message.h" +#include "MonitorImpl.h" +#include "NameGenerator.h" +#include "OutputHandler.h" +#include "Queue.h" + +namespace qpid { + namespace broker { + class Channel{ + private: + class ConsumerImpl : public virtual Consumer{ + ConnectionToken* const connection; + Channel* parent; + string tag; + Queue::shared_ptr queue; + public: + ConsumerImpl(Channel* parent, string& tag, Queue::shared_ptr queue, ConnectionToken* const connection); + virtual bool deliver(Message::shared_ptr& msg); + void cancel(); + }; + + typedef std::map::iterator consumer_iterator; + + const int id; + qpid::framing::OutputHandler* out; + u_int64_t deliveryTag; + Queue::shared_ptr defaultQueue; + bool transactional; + std::map consumers; + u_int32_t prefetchSize; + u_int16_t prefetchCount; + u_int32_t framesize; + Message::shared_ptr message; + NameGenerator tagGenerator; + + void deliver(Message::shared_ptr& msg, string& tag); + void publish(ExchangeRegistry* exchanges); + + public: + Channel(qpid::framing::OutputHandler* out, int id, u_int32_t framesize); + ~Channel(); + inline void setDefaultQueue(Queue::shared_ptr queue){ defaultQueue = queue; } + inline Queue::shared_ptr getDefaultQueue(){ return defaultQueue; } + inline u_int32_t setPrefetchSize(u_int32_t size){ prefetchSize = size; } + inline u_int16_t setPrefetchCount(u_int16_t count){ prefetchCount = count; } + void handlePublish(Message* msg); + void handleHeader(qpid::framing::AMQHeaderBody::shared_ptr header, ExchangeRegistry* exchanges); + void handleContent(qpid::framing::AMQContentBody::shared_ptr content, ExchangeRegistry* exchanges); + bool exists(string& consumerTag); + void consume(string& tag, Queue::shared_ptr queue, bool acks, bool exclusive, ConnectionToken* const connection = 0); + void cancel(string& tag); + void begin(); + void close(); + void commit(); + void rollback(); + }; + } +} + + +#endif diff --git a/cpp/broker/inc/Configuration.h b/cpp/broker/inc/Configuration.h new file mode 100644 index 0000000000..5ec70a839b --- /dev/null +++ b/cpp/broker/inc/Configuration.h @@ -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. + * + */ +#ifndef _Configuration_ +#define _Configuration_ + +#include +#include +#include + +namespace qpid { + namespace broker { + class Configuration{ + class Option{ + const std::string flag; + const std::string name; + const std::string desc; + + bool match(const std::string& arg); + + protected: + virtual bool needsValue() const = 0; + virtual void setValue(const std::string& value) = 0; + + public: + Option(const char flag, const std::string& name, const std::string& desc); + Option(const std::string& name, const std::string& desc); + virtual ~Option(); + + bool parse(int& i, char** argv, int argc); + void print(std::ostream& out) const; + }; + + class IntOption : public Option{ + const int defaultValue; + int value; + public: + IntOption(char flag, const std::string& name, const std::string& desc, const int value = 0); + IntOption(const std::string& name, const std::string& desc, const int value = 0); + virtual ~IntOption(); + + int getValue() const; + virtual bool needsValue() const; + virtual void setValue(const std::string& value); + }; + + class StringOption : public Option{ + const std::string defaultValue; + std::string value; + public: + StringOption(char flag, const std::string& name, const std::string& desc, const std::string value = ""); + StringOption(const std::string& name, const std::string& desc, const std::string value = ""); + virtual ~StringOption(); + + const std::string& getValue() const; + virtual bool needsValue() const; + virtual void setValue(const std::string& value); + }; + + class BoolOption : public Option{ + const bool defaultValue; + bool value; + public: + BoolOption(char flag, const std::string& name, const std::string& desc, const bool value = 0); + BoolOption(const std::string& name, const std::string& desc, const bool value = 0); + virtual ~BoolOption(); + + bool getValue() const; + virtual bool needsValue() const; + virtual void setValue(const std::string& value); + }; + + BoolOption trace; + IntOption port; + IntOption workerThreads; + IntOption maxConnections; + IntOption connectionBacklog; + StringOption acceptor; + BoolOption help; + + typedef std::vector::iterator op_iterator; + std::vector options; + + public: + class ParseException{ + public: + const std::string& error; + ParseException(const std::string& _error) : error(_error) {} + }; + + + Configuration(); + ~Configuration(); + + void parse(int argc, char** argv); + + bool isHelp(); + bool isTrace(); + int getPort(); + int getWorkerThreads(); + int getMaxConnections(); + int getConnectionBacklog(); + const std::string& getAcceptor(); + + void usage(); + }; + } +} + + +#endif diff --git a/cpp/broker/inc/ConnectionToken.h b/cpp/broker/inc/ConnectionToken.h new file mode 100644 index 0000000000..1faefec2cc --- /dev/null +++ b/cpp/broker/inc/ConnectionToken.h @@ -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. + * + */ +#ifndef _ConnectionToken_ +#define _ConnectionToken_ + +namespace qpid { + namespace broker { + /** + * An empty interface allowing opaque implementations of some + * form of token to identify a connection. + */ + class ConnectionToken{ + public: + virtual ~ConnectionToken(){} + }; + } +} + + +#endif diff --git a/cpp/broker/inc/Consumer.h b/cpp/broker/inc/Consumer.h new file mode 100644 index 0000000000..af2d5d7812 --- /dev/null +++ b/cpp/broker/inc/Consumer.h @@ -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. + * + */ +#ifndef _Consumer_ +#define _Consumer_ + +#include "Message.h" + +namespace qpid { + namespace broker { + class Consumer{ + public: + virtual bool deliver(Message::shared_ptr& msg) = 0; + virtual ~Consumer(){} + }; + } +} + + +#endif diff --git a/cpp/broker/inc/DirectExchange.h b/cpp/broker/inc/DirectExchange.h new file mode 100644 index 0000000000..bf8c5f0b37 --- /dev/null +++ b/cpp/broker/inc/DirectExchange.h @@ -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. + * + */ +#ifndef _DirectExchange_ +#define _DirectExchange_ + +#include +#include +#include "Exchange.h" +#include "FieldTable.h" +#include "Message.h" +#include "MonitorImpl.h" +#include "Queue.h" + +namespace qpid { +namespace broker { + class DirectExchange : public virtual Exchange{ + const string name; + std::map > bindings; + qpid::concurrent::MonitorImpl lock; + + public: + static const std::string typeName; + + DirectExchange(const string& name); + + inline virtual const string& getName(){ return name; } + + virtual void bind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args); + + virtual void unbind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args); + + virtual void route(Message::shared_ptr& msg, const string& routingKey, qpid::framing::FieldTable* args); + + virtual ~DirectExchange(); + }; +} +} + + +#endif diff --git a/cpp/broker/inc/Exchange.h b/cpp/broker/inc/Exchange.h new file mode 100644 index 0000000000..5f5dc5ce71 --- /dev/null +++ b/cpp/broker/inc/Exchange.h @@ -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. + * + */ +#ifndef _Exchange_ +#define _Exchange_ + +#include "FieldTable.h" +#include "Message.h" +#include "Queue.h" + +namespace qpid { +namespace broker { + class Exchange{ + public: + virtual const string& getName() = 0; + virtual void bind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args) = 0; + virtual void unbind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args) = 0; + virtual void route(Message::shared_ptr& msg, const string& routingKey, qpid::framing::FieldTable* args) = 0; + virtual ~Exchange(){} + }; +} +} + + +#endif diff --git a/cpp/broker/inc/ExchangeBinding.h b/cpp/broker/inc/ExchangeBinding.h new file mode 100644 index 0000000000..4cbb73acbf --- /dev/null +++ b/cpp/broker/inc/ExchangeBinding.h @@ -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. + * + */ +#ifndef _ExchangeBinding_ +#define _ExchangeBinding_ + +#include "Binding.h" +#include "FieldTable.h" +#include "Queue.h" + +namespace qpid { + namespace broker { + class Exchange; + class Queue; + + class ExchangeBinding : public virtual Binding{ + Exchange* e; + Queue::shared_ptr q; + const string key; + qpid::framing::FieldTable* args; + public: + ExchangeBinding(Exchange* _e, Queue::shared_ptr _q, const string& _key, qpid::framing::FieldTable* _args); + virtual void cancel(); + virtual ~ExchangeBinding(); + }; + } +} + + +#endif + diff --git a/cpp/broker/inc/ExchangeRegistry.h b/cpp/broker/inc/ExchangeRegistry.h new file mode 100644 index 0000000000..0f0eaae0d0 --- /dev/null +++ b/cpp/broker/inc/ExchangeRegistry.h @@ -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. + * + */ +#ifndef _ExchangeRegistry_ +#define _ExchangeRegistry_ + +#include +#include "Exchange.h" +#include "Monitor.h" + +namespace qpid { +namespace broker { + class ExchangeRegistry{ + std::map exchanges; + qpid::concurrent::Monitor* lock; + public: + ExchangeRegistry(); + void declare(Exchange* exchange); + void destroy(const string& name); + Exchange* get(const string& name); + inline qpid::concurrent::Monitor* getLock(){ return lock; } + ~ExchangeRegistry(); + }; +} +} + + +#endif diff --git a/cpp/broker/inc/FanOutExchange.h b/cpp/broker/inc/FanOutExchange.h new file mode 100644 index 0000000000..9d0d32bbf8 --- /dev/null +++ b/cpp/broker/inc/FanOutExchange.h @@ -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. + * + */ +#ifndef _FanOutExchange_ +#define _FanOutExchange_ + +#include +#include +#include "Exchange.h" +#include "FieldTable.h" +#include "Message.h" +#include "MonitorImpl.h" +#include "Queue.h" + +namespace qpid { +namespace broker { + +class FanOutExchange : public virtual Exchange { + const string name; + std::vector bindings; + qpid::concurrent::MonitorImpl lock; + + public: + static const std::string typeName; + + FanOutExchange(const string& name); + + inline virtual const string& getName(){ return name; } + + virtual void bind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args); + + virtual void unbind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args); + + virtual void route(Message::shared_ptr& msg, const string& routingKey, qpid::framing::FieldTable* args); + + virtual ~FanOutExchange(); +}; + +} +} + + + +#endif diff --git a/cpp/broker/inc/Message.h b/cpp/broker/inc/Message.h new file mode 100644 index 0000000000..37a0c9b2c8 --- /dev/null +++ b/cpp/broker/inc/Message.h @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _Message_ +#define _Message_ + +#include "memory.h" +#include "AMQContentBody.h" +#include "AMQHeaderBody.h" +#include "BasicHeaderProperties.h" +#include "BasicPublishBody.h" +#include "ConnectionToken.h" +#include "OutputHandler.h" + +namespace qpid { + namespace broker { + class ExchangeRegistry; + + class Message{ + typedef std::vector content_list; + typedef content_list::iterator content_iterator; + + const ConnectionToken* const publisher; + string exchange; + string routingKey; + const bool mandatory; + const bool immediate; + qpid::framing::AMQHeaderBody::shared_ptr header; + content_list content; + + u_int64_t contentSize(); + qpid::framing::BasicHeaderProperties* getHeaderProperties(); + + + public: + typedef std::tr1::shared_ptr shared_ptr; + + Message(const ConnectionToken* const publisher, + const string& exchange, const string& routingKey, + bool mandatory, bool immediate); + ~Message(); + void setHeader(qpid::framing::AMQHeaderBody::shared_ptr header); + void addContent(qpid::framing::AMQContentBody::shared_ptr data); + bool isComplete(); + const ConnectionToken* const getPublisher(); + + void deliver(qpid::framing::OutputHandler* out, int channel, + string& consumerTag, u_int64_t deliveryTag, + u_int32_t framesize); + + friend bool route(Message::shared_ptr& msg, ExchangeRegistry* registry); + + }; + bool route(Message::shared_ptr& msg, ExchangeRegistry* registry); + } +} + + +#endif diff --git a/cpp/broker/inc/NameGenerator.h b/cpp/broker/inc/NameGenerator.h new file mode 100644 index 0000000000..6e6e0acf28 --- /dev/null +++ b/cpp/broker/inc/NameGenerator.h @@ -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. + * + */ +#ifndef _NameGenerator_ +#define _NameGenerator_ + +#include "Message.h" + +namespace qpid { + namespace broker { + class NameGenerator{ + const std::string base; + unsigned int counter; + public: + NameGenerator(const std::string& base); + std::string generate(); + }; + } +} + + +#endif diff --git a/cpp/broker/inc/Queue.h b/cpp/broker/inc/Queue.h new file mode 100644 index 0000000000..2229ba6235 --- /dev/null +++ b/cpp/broker/inc/Queue.h @@ -0,0 +1,106 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _Queue_ +#define _Queue_ + +#include +#include +#include "memory.h" +#include "apr_time.h" +#include "amqp_types.h" +#include "Binding.h" +#include "ConnectionToken.h" +#include "Consumer.h" +#include "Message.h" +#include "MonitorImpl.h" + +namespace qpid { + namespace broker { + + /** + * Thrown when exclusive access would be violated. + */ + struct ExclusiveAccessException{}; + + /** + * The brokers representation of an amqp queue. Messages are + * delivered to a queue from where they can be dispatched to + * registered consumers or be stored until dequeued or until one + * or more consumers registers. + */ + class Queue{ + const string name; + const u_int32_t autodelete; + const bool durable; + const ConnectionToken* const owner; + std::vector consumers; + std::queue bindings; + std::queue messages; + bool queueing; + bool dispatching; + int next; + mutable qpid::concurrent::MonitorImpl lock; + apr_time_t lastUsed; + Consumer* exclusive; + + bool startDispatching(); + bool dispatch(Message::shared_ptr& msg); + + public: + + typedef std::tr1::shared_ptr shared_ptr; + + typedef std::vector vector; + + Queue(const string& name, bool durable = false, u_int32_t autodelete = 0, const ConnectionToken* const owner = 0); + ~Queue(); + /** + * Informs the queue of a binding that should be cancelled on + * destruction of the queue. + */ + void bound(Binding* b); + /** + * Delivers a message to the queue from where it will be + * dispatched to immediately to a consumer if one is + * available or stored for dequeue or later dispatch if + * not. + */ + void deliver(Message::shared_ptr& msg); + /** + * Dispatch any queued messages providing there are + * consumers for them. Only one thread can be dispatching + * at any time, but this method (rather than the caller) + * is responsible for ensuring that. + */ + void dispatch(); + void consume(Consumer* c, bool exclusive = false); + void cancel(Consumer* c); + Message::shared_ptr dequeue(); + u_int32_t purge(); + u_int32_t getMessageCount() const; + u_int32_t getConsumerCount() const; + inline const string& getName() const { return name; } + inline const bool isExclusiveOwner(const ConnectionToken* const o) const { return o == owner; } + inline bool hasExclusiveConsumer() const { return exclusive; } + bool canAutoDelete() const; + }; + } +} + + +#endif diff --git a/cpp/broker/inc/QueueRegistry.h b/cpp/broker/inc/QueueRegistry.h new file mode 100644 index 0000000000..ac12aa8f88 --- /dev/null +++ b/cpp/broker/inc/QueueRegistry.h @@ -0,0 +1,88 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _QueueRegistry_ +#define _QueueRegistry_ + +#include +#include "MonitorImpl.h" +#include "Queue.h" + +namespace qpid { +namespace broker { + +class SessionHandlerImpl; + +/** + * A registry of queues indexed by queue name. + * + * Queues are reference counted using shared_ptr to ensure that they + * are deleted when and only when they are no longer in use. + * + */ +class QueueRegistry{ + + public: + QueueRegistry(); + ~QueueRegistry(); + + /** + * Declare a queue. + * + * @return The queue and a boolean flag which is true if the queue + * was created by this declare call false if it already existed. + */ + std::pair declare(const string& name, bool durable = false, u_int32_t autodelete = 0, const ConnectionToken* const owner = 0); + + /** + * Destroy the named queue. + * + * Note: if the queue is in use it is not actually destroyed until + * all shared_ptrs to it are destroyed. During that time it is + * possible that a new queue with the same name may be + * created. This should not create any problems as the new and + * old queues exist independently. The registry has + * forgotten the old queue so there can be no confusion for + * subsequent calls to find or declare with the same name. + * + */ + void destroy(const string& name); + + /** + * Find the named queue. Return 0 if not found. + */ + Queue::shared_ptr find(const string& name); + + /** + * Generate unique queue name. + */ + string generateName(); + + private: + typedef std::map QueueMap; + QueueMap queues; + qpid::concurrent::MonitorImpl lock; + int counter; + +}; + + +} +} + + +#endif diff --git a/cpp/broker/inc/SessionHandlerFactoryImpl.h b/cpp/broker/inc/SessionHandlerFactoryImpl.h new file mode 100644 index 0000000000..2317a6667b --- /dev/null +++ b/cpp/broker/inc/SessionHandlerFactoryImpl.h @@ -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. + * + */ +#ifndef _SessionHandlerFactoryImpl_ +#define _SessionHandlerFactoryImpl_ + +#include "AMQFrame.h" +#include "AutoDelete.h" +#include "DirectExchange.h" +#include "ExchangeRegistry.h" +#include "ProtocolInitiation.h" +#include "QueueRegistry.h" +#include "SessionHandlerFactory.h" +#include "TimeoutHandler.h" + +namespace qpid { + namespace broker { + + class SessionHandlerFactoryImpl : public virtual qpid::io::SessionHandlerFactory + { + QueueRegistry queues; + ExchangeRegistry exchanges; + const u_int32_t timeout;//timeout for auto-deleted queues (in ms) + AutoDelete cleaner; + public: + SessionHandlerFactoryImpl(u_int32_t timeout = 30000); + virtual qpid::io::SessionHandler* create(qpid::io::SessionContext* ctxt); + virtual ~SessionHandlerFactoryImpl(); + }; + + } +} + + +#endif diff --git a/cpp/broker/inc/SessionHandlerImpl.h b/cpp/broker/inc/SessionHandlerImpl.h new file mode 100644 index 0000000000..14a6404c78 --- /dev/null +++ b/cpp/broker/inc/SessionHandlerImpl.h @@ -0,0 +1,230 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _SessionHandlerImpl_ +#define _SessionHandlerImpl_ + +#include +#include +#include +#include +#include "AMQFrame.h" +#include "AMQP_ServerOperations.h" +#include "AutoDelete.h" +#include "ExchangeRegistry.h" +#include "Channel.h" +#include "ConnectionToken.h" +#include "DirectExchange.h" +#include "OutputHandler.h" +#include "ProtocolInitiation.h" +#include "QueueRegistry.h" +#include "SessionContext.h" +#include "SessionHandler.h" +#include "TimeoutHandler.h" +#include "TopicExchange.h" + +namespace qpid { +namespace broker { + +struct ChannelException : public std::exception { + u_int16_t code; + string text; + ChannelException(u_int16_t _code, string _text) : code(_code), text(_text) {} + ~ChannelException() throw() {} + const char* what() const throw() { return text.c_str(); } +}; + +struct ConnectionException : public std::exception { + u_int16_t code; + string text; + ConnectionException(u_int16_t _code, string _text) : code(_code), text(_text) {} + ~ConnectionException() throw() {} + const char* what() const throw() { return text.c_str(); } +}; + +class SessionHandlerImpl : public virtual qpid::io::SessionHandler, + public virtual qpid::framing::AMQP_ServerOperations, + public virtual ConnectionToken +{ + typedef std::map::iterator channel_iterator; + typedef std::vector::iterator queue_iterator; + + qpid::io::SessionContext* context; + QueueRegistry* queues; + ExchangeRegistry* const exchanges; + AutoDelete* const cleaner; + const u_int32_t timeout;//timeout for auto-deleted queues (in ms) + + ConnectionHandler* connectionHandler; + ChannelHandler* channelHandler; + BasicHandler* basicHandler; + ExchangeHandler* exchangeHandler; + QueueHandler* queueHandler; + + std::map channels; + std::vector exclusiveQueues; + + u_int32_t framemax; + u_int16_t heartbeat; + + void handleHeader(u_int16_t channel, qpid::framing::AMQHeaderBody::shared_ptr body); + void handleContent(u_int16_t channel, qpid::framing::AMQContentBody::shared_ptr body); + void handleHeartbeat(qpid::framing::AMQHeartbeatBody::shared_ptr body); + + /** + * Get named queue, never returns 0. + * @return: named queue or default queue for channel if name="" + * @exception: ChannelException if no queue of that name is found. + * @exception: ConnectionException if no queue specified and channel has not declared one. + */ + Queue::shared_ptr getQueue(const string& name, u_int16_t channel); + + Exchange* findExchange(const string& name); + + public: + SessionHandlerImpl(qpid::io::SessionContext* context, QueueRegistry* queues, + ExchangeRegistry* exchanges, AutoDelete* cleaner, const u_int32_t timeout); + virtual void received(qpid::framing::AMQFrame* frame); + virtual void initiated(qpid::framing::ProtocolInitiation* header); + virtual void idleOut(); + virtual void idleIn(); + virtual void closed(); + virtual ~SessionHandlerImpl(); + + class ConnectionHandlerImpl : public virtual ConnectionHandler{ + SessionHandlerImpl* parent; + public: + inline ConnectionHandlerImpl(SessionHandlerImpl* _parent) : parent(_parent) {} + + virtual void startOk(u_int16_t channel, qpid::framing::FieldTable& clientProperties, string& mechanism, + string& response, string& locale); + + virtual void secureOk(u_int16_t channel, string& response); + + virtual void tuneOk(u_int16_t channel, u_int16_t channelMax, u_int32_t frameMax, u_int16_t heartbeat); + + virtual void open(u_int16_t channel, string& virtualHost, string& capabilities, bool insist); + + virtual void close(u_int16_t channel, u_int16_t replyCode, string& replyText, u_int16_t classId, + u_int16_t methodId); + + virtual void closeOk(u_int16_t channel); + + virtual ~ConnectionHandlerImpl(){} + }; + + class ChannelHandlerImpl : public virtual ChannelHandler{ + SessionHandlerImpl* parent; + public: + inline ChannelHandlerImpl(SessionHandlerImpl* _parent) : parent(_parent) {} + + virtual void open(u_int16_t channel, string& outOfBand); + + virtual void flow(u_int16_t channel, bool active); + + virtual void flowOk(u_int16_t channel, bool active); + + virtual void close(u_int16_t channel, u_int16_t replyCode, string& replyText, + u_int16_t classId, u_int16_t methodId); + + virtual void closeOk(u_int16_t channel); + + virtual ~ChannelHandlerImpl(){} + }; + + class ExchangeHandlerImpl : public virtual ExchangeHandler{ + SessionHandlerImpl* parent; + public: + inline ExchangeHandlerImpl(SessionHandlerImpl* _parent) : parent(_parent) {} + + virtual void declare(u_int16_t channel, u_int16_t ticket, string& exchange, string& type, + bool passive, bool durable, bool autoDelete, bool internal, bool nowait, + qpid::framing::FieldTable& arguments); + + virtual void delete_(u_int16_t channel, u_int16_t ticket, string& exchange, bool ifUnused, bool nowait); + + virtual ~ExchangeHandlerImpl(){} + }; + + + class QueueHandlerImpl : public virtual QueueHandler{ + SessionHandlerImpl* parent; + public: + inline QueueHandlerImpl(SessionHandlerImpl* _parent) : parent(_parent) {} + + virtual void declare(u_int16_t channel, u_int16_t ticket, string& queue, + bool passive, bool durable, bool exclusive, + bool autoDelete, bool nowait, qpid::framing::FieldTable& arguments); + + virtual void bind(u_int16_t channel, u_int16_t ticket, string& queue, + string& exchange, string& routingKey, bool nowait, + qpid::framing::FieldTable& arguments); + + virtual void purge(u_int16_t channel, u_int16_t ticket, string& queue, + bool nowait); + + virtual void delete_(u_int16_t channel, u_int16_t ticket, string& queue, bool ifUnused, bool ifEmpty, + bool nowait); + + virtual ~QueueHandlerImpl(){} + }; + + class BasicHandlerImpl : public virtual BasicHandler{ + SessionHandlerImpl* parent; + public: + inline BasicHandlerImpl(SessionHandlerImpl* _parent) : parent(_parent) {} + + virtual void qos(u_int16_t channel, u_int32_t prefetchSize, u_int16_t prefetchCount, bool global); + + virtual void consume(u_int16_t channel, u_int16_t ticket, string& queue, string& consumerTag, + bool noLocal, bool noAck, bool exclusive, bool nowait); + + virtual void cancel(u_int16_t channel, string& consumerTag, bool nowait); + + virtual void publish(u_int16_t channel, u_int16_t ticket, string& exchange, string& routingKey, + bool mandatory, bool immediate); + + virtual void get(u_int16_t channel, u_int16_t ticket, string& queue, bool noAck); + + virtual void ack(u_int16_t channel, u_int64_t deliveryTag, bool multiple); + + virtual void reject(u_int16_t channel, u_int64_t deliveryTag, bool requeue); + + virtual void recover(u_int16_t channel, bool requeue); + + virtual ~BasicHandlerImpl(){} + }; + + inline virtual ChannelHandler* getChannelHandler(){ return channelHandler; } + inline virtual ConnectionHandler* getConnectionHandler(){ return connectionHandler; } + inline virtual BasicHandler* getBasicHandler(){ return basicHandler; } + inline virtual ExchangeHandler* getExchangeHandler(){ return exchangeHandler; } + inline virtual QueueHandler* getQueueHandler(){ return queueHandler; } + + inline virtual AccessHandler* getAccessHandler(){ return 0; } + inline virtual FileHandler* getFileHandler(){ return 0; } + inline virtual StreamHandler* getStreamHandler(){ return 0; } + inline virtual TxHandler* getTxHandler(){ return 0; } + inline virtual DtxHandler* getDtxHandler(){ return 0; } + inline virtual TunnelHandler* getTunnelHandler(){ return 0; } +}; + +} +} + + +#endif diff --git a/cpp/broker/inc/TopicExchange.h b/cpp/broker/inc/TopicExchange.h new file mode 100644 index 0000000000..d9ff62ecc6 --- /dev/null +++ b/cpp/broker/inc/TopicExchange.h @@ -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. + * + */ +#ifndef _TopicExchange_ +#define _TopicExchange_ + +#include +#include +#include "Exchange.h" +#include "FieldTable.h" +#include "Message.h" +#include "MonitorImpl.h" +#include "Queue.h" + +namespace qpid { +namespace broker { + class TopicExchange : public virtual Exchange{ + const string name; + std::map > bindings;//NOTE: pattern matching not yet supported + qpid::concurrent::MonitorImpl lock; + + public: + static const std::string typeName; + + TopicExchange(const string& name); + + inline virtual const string& getName(){ return name; } + + virtual void bind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args); + + virtual void unbind(Queue::shared_ptr queue, const string& routingKey, qpid::framing::FieldTable* args); + + virtual void route(Message::shared_ptr& msg, const string& routingKey, qpid::framing::FieldTable* args); + + virtual ~TopicExchange(); + }; +} +} + + +#endif diff --git a/cpp/broker/src/AutoDelete.cpp b/cpp/broker/src/AutoDelete.cpp new file mode 100644 index 0000000000..6793ec449d --- /dev/null +++ b/cpp/broker/src/AutoDelete.cpp @@ -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. + * + */ +#include "AutoDelete.h" + +using namespace qpid::broker; + +AutoDelete::AutoDelete(QueueRegistry* const _registry, u_int32_t _period) : registry(_registry), + period(_period), + stopped(true), + runner(0){} + +void AutoDelete::add(Queue::shared_ptr const queue){ + lock.acquire(); + queues.push(queue); + lock.release(); +} + +Queue::shared_ptr const AutoDelete::pop(){ + Queue::shared_ptr next; + lock.acquire(); + if(!queues.empty()){ + next = queues.front(); + queues.pop(); + } + lock.release(); + return next; +} + +void AutoDelete::process(){ + Queue::shared_ptr seen; + for(Queue::shared_ptr q = pop(); q; q = pop()){ + if(seen == q){ + add(q); + break; + }else if(q->canAutoDelete()){ + std::string name(q->getName()); + registry->destroy(name); + std::cout << "INFO: Auto-deleted queue named " << name << std::endl; + }else{ + add(q); + if(!seen) seen = q; + } + } +} + +void AutoDelete::run(){ + monitor.acquire(); + while(!stopped){ + process(); + monitor.wait(period); + } + monitor.release(); +} + +void AutoDelete::start(){ + monitor.acquire(); + if(stopped){ + runner = factory.create(this); + stopped = false; + monitor.release(); + runner->start(); + }else{ + monitor.release(); + } +} + +void AutoDelete::stop(){ + monitor.acquire(); + if(!stopped){ + stopped = true; + monitor.notify(); + monitor.release(); + runner->join(); + delete runner; + }else{ + monitor.release(); + } +} diff --git a/cpp/broker/src/Broker.cpp b/cpp/broker/src/Broker.cpp new file mode 100644 index 0000000000..5d59b63622 --- /dev/null +++ b/cpp/broker/src/Broker.cpp @@ -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. + * + */ +#include +#include +#include "apr_signal.h" + +#include "Acceptor.h" +#include "Configuration.h" +#include "QpidError.h" +#include "SessionHandlerFactoryImpl.h" + +//optional includes: +#ifdef _USE_APR_IO_ + +#include "BlockingAPRAcceptor.h" +#include "LFAcceptor.h" + +#endif + +using namespace qpid::broker; +using namespace qpid::io; + +void handle_signal(int signal); + +Acceptor* createAcceptor(Configuration& config); + +int main(int argc, char** argv) +{ + SessionHandlerFactoryImpl factory; + Configuration config; + try{ + + config.parse(argc, argv); + if(config.isHelp()){ + config.usage(); + }else{ +#ifdef _USE_APR_IO_ + apr_signal(SIGINT, handle_signal); +#endif + try{ + std::auto_ptr acceptor(createAcceptor(config)); + try{ + acceptor->bind(config.getPort(), &factory); + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } + } + }catch(Configuration::ParseException error){ + std::cout << "Error: " << error.error << std::endl; + } + + return 1; +} + +Acceptor* createAcceptor(Configuration& config){ + const string type(config.getAcceptor()); +#ifdef _USE_APR_IO_ + if("blocking" == type){ + std::cout << "Using blocking acceptor " << std::endl; + return new BlockingAPRAcceptor(config.isTrace(), config.getConnectionBacklog()); + }else if("non-blocking" == type){ + std::cout << "Using non-blocking acceptor " << std::endl; + return new LFAcceptor(config.isTrace(), + config.getConnectionBacklog(), + config.getWorkerThreads(), + config.getMaxConnections()); + } +#endif + throw Configuration::ParseException("Unrecognised acceptor: " + type); +} + +void handle_signal(int signal){ + std::cout << "Shutting down..." << std::endl; +} diff --git a/cpp/broker/src/Channel.cpp b/cpp/broker/src/Channel.cpp new file mode 100644 index 0000000000..6980fe5a1b --- /dev/null +++ b/cpp/broker/src/Channel.cpp @@ -0,0 +1,148 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Channel.h" +#include "QpidError.h" +#include +#include +#include + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::concurrent; + +Channel::Channel(OutputHandler* _out, int _id, u_int32_t _framesize) : out(_out), + id(_id), + framesize(_framesize), + transactional(false), + deliveryTag(1), + tagGenerator("sgen"){} + +Channel::~Channel(){ + for(consumer_iterator i = consumers.begin(); i != consumers.end(); i = consumers.begin() ){ + std::cout << "ERROR: Channel consumer appears not to have been cancelled before channel was destroyed." << std::endl; + delete (i->second); + } +} + +bool Channel::exists(string& consumerTag){ + return consumers.find(consumerTag) != consumers.end(); +} + +void Channel::consume(string& tag, Queue::shared_ptr queue, bool acks, bool exclusive, ConnectionToken* const connection){ + if(tag.empty()) tag = tagGenerator.generate(); + + ConsumerImpl* c(new ConsumerImpl(this, tag, queue, connection)); + try{ + queue->consume(c, exclusive);//may throw exception + consumers[tag] = c; + }catch(ExclusiveAccessException& e){ + delete c; + throw e; + } +} + +void Channel::cancel(string& tag){ + ConsumerImpl* c = consumers[tag]; + if(c){ + c->cancel(); + consumers.erase(tag); + delete c; + } +} + +void Channel::close(){ + //cancel all consumers + for(consumer_iterator i = consumers.begin(); i != consumers.end(); i = consumers.begin() ){ + ConsumerImpl* c = i->second; + c->cancel(); + consumers.erase(i); + delete c; + } +} + +void Channel::begin(){ + transactional = true; +} + +void Channel::commit(){ + +} + +void Channel::rollback(){ + +} + +void Channel::deliver(Message::shared_ptr& msg, string& consumerTag){ + //send deliver method, header and content(s) + msg->deliver(out, id, consumerTag, deliveryTag++, framesize); +} + +Channel::ConsumerImpl::ConsumerImpl(Channel* _parent, string& _tag, + Queue::shared_ptr _queue, + ConnectionToken* const _connection) : parent(_parent), + tag(_tag), + queue(_queue), + connection(_connection){ +} + +bool Channel::ConsumerImpl::deliver(Message::shared_ptr& msg){ + if(connection != msg->getPublisher()){ + parent->deliver(msg, tag); + return true; + }else{ + return false; + } +} + +void Channel::ConsumerImpl::cancel(){ + if(queue) queue->cancel(this); +} + +void Channel::handlePublish(Message* msg){ + if(message.get()){ + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Invalid message sequence: got publish before previous content was completed."); + } + message = Message::shared_ptr(msg); +} + +void Channel::handleHeader(AMQHeaderBody::shared_ptr header, ExchangeRegistry* exchanges){ + if(!message.get()){ + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Invalid message sequence: got header before publish."); + } + message->setHeader(header); + if(message->isComplete()){ + publish(exchanges); + } +} + +void Channel::handleContent(AMQContentBody::shared_ptr content, ExchangeRegistry* exchanges){ + if(!message.get()){ + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Invalid message sequence: got content before publish."); + } + message->addContent(content); + if(message->isComplete()){ + publish(exchanges); + } +} + +void Channel::publish(ExchangeRegistry* exchanges){ + if(!route(message, exchanges)){ + std::cout << "WARNING: Could not route message." << std::endl; + } + message.reset(); +} diff --git a/cpp/broker/src/Configuration.cpp b/cpp/broker/src/Configuration.cpp new file mode 100644 index 0000000000..aceb35bc87 --- /dev/null +++ b/cpp/broker/src/Configuration.cpp @@ -0,0 +1,195 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Configuration.h" + +using namespace qpid::broker; +using namespace std; + +Configuration::Configuration() : + trace('t', "trace", "Print incoming & outgoing frames to the console (default=false)", false), + port('p', "port", "Sets the port to listen on (default=5672)", 5672), + workerThreads("worker-threads", "Sets the number of worker threads to use (default=5). Only valid for non-blocking acceptor.", 5), + maxConnections("max-connections", "Sets the maximum number of connections the broker can accept (default=500). Only valid for non-blocking acceptor.", 500), + connectionBacklog("connection-backlog", "Sets the connection backlog for the servers socket (default=10)", 10), + acceptor('a', "acceptor", "Sets the acceptor to use. Currently only two values are recognised, blocking and non-blocking (which is the default)", "non-blocking"), + help("help", "Prints usage information", false) +{ + options.push_back(&trace); + options.push_back(&port); + options.push_back(&workerThreads); + options.push_back(&maxConnections); + options.push_back(&connectionBacklog); + options.push_back(&acceptor); + options.push_back(&help); +} + +Configuration::~Configuration(){} + +void Configuration::parse(int argc, char** argv){ + int position = 1; + while(position < argc){ + bool matched(false); + for(op_iterator i = options.begin(); i < options.end() && !matched; i++){ + matched = (*i)->parse(position, argv, argc); + } + if(!matched){ + std::cout << "Warning: skipping unrecognised option " << argv[position] << std::endl; + position++; + } + } +} + +void Configuration::usage(){ + for(op_iterator i = options.begin(); i < options.end(); i++){ + (*i)->print(std::cout); + } +} + +bool Configuration::isHelp(){ + return help.getValue(); +} + +bool Configuration::isTrace(){ + return trace.getValue(); +} + +int Configuration::getPort(){ + return port.getValue(); +} + +int Configuration::getWorkerThreads(){ + return workerThreads.getValue(); +} + +int Configuration::getMaxConnections(){ + return maxConnections.getValue(); +} + +int Configuration::getConnectionBacklog(){ + return connectionBacklog.getValue(); +} + +const string& Configuration::getAcceptor(){ + return acceptor.getValue(); +} + +Configuration::Option::Option(const char _flag, const string& _name, const string& _desc) : + flag(string("-") + _flag), name("--" +_name), desc(_desc) {} + +Configuration::Option::Option(const string& _name, const string& _desc) : + flag(""), name("--" + _name), desc(_desc) {} + +Configuration::Option::~Option(){} + +bool Configuration::Option::match(const string& arg){ + return flag == arg || name == arg; +} + +bool Configuration::Option::parse(int& i, char** argv, int argc){ + const string arg(argv[i]); + if(match(arg)){ + if(needsValue()){ + if(++i < argc) setValue(argv[i]); + else throw ParseException("Argument " + arg + " requires a value!"); + }else{ + setValue(""); + } + i++; + return true; + }else{ + return false; + } +} + +void Configuration::Option::print(ostream& out) const { + out << " "; + if(flag.length() > 0){ + out << flag << " or "; + } + out << name; + if(needsValue()) out << ""; + out << std::endl; + out << " " << desc << std::endl; +} + + +// String Option: + +Configuration::StringOption::StringOption(const char _flag, const string& _name, const string& _desc, const string _value) : + Option(_flag,_name,_desc), defaultValue(_value), value(_value) {} + +Configuration::StringOption::StringOption(const string& _name, const string& _desc, const string _value) : + Option(_name,_desc), defaultValue(_value), value(_value) {} + +Configuration::StringOption::~StringOption(){} + +const string& Configuration::StringOption::getValue() const { + return value; +} + +bool Configuration::StringOption::needsValue() const { + return true; +} + +void Configuration::StringOption::setValue(const std::string& _value){ + value = _value; +} + +// Int Option: + +Configuration::IntOption::IntOption(const char _flag, const string& _name, const string& _desc, const int _value) : + Option(_flag,_name,_desc), defaultValue(_value), value(_value) {} + +Configuration::IntOption::IntOption(const string& _name, const string& _desc, const int _value) : + Option(_name,_desc), defaultValue(_value), value(_value) {} + +Configuration::IntOption::~IntOption(){} + +int Configuration::IntOption::getValue() const { + return value; +} + +bool Configuration::IntOption::needsValue() const { + return true; +} + +void Configuration::IntOption::setValue(const std::string& _value){ + value = atoi(_value.c_str()); +} + +// Bool Option: + +Configuration::BoolOption::BoolOption(const char _flag, const string& _name, const string& _desc, const bool _value) : + Option(_flag,_name,_desc), defaultValue(_value), value(_value) {} + +Configuration::BoolOption::BoolOption(const string& _name, const string& _desc, const bool _value) : + Option(_name,_desc), defaultValue(_value), value(_value) {} + +Configuration::BoolOption::~BoolOption(){} + +bool Configuration::BoolOption::getValue() const { + return value; +} + +bool Configuration::BoolOption::needsValue() const { + return false; +} + +void Configuration::BoolOption::setValue(const std::string& _value){ + value = true; +} diff --git a/cpp/broker/src/DirectExchange.cpp b/cpp/broker/src/DirectExchange.cpp new file mode 100644 index 0000000000..70f7ee838f --- /dev/null +++ b/cpp/broker/src/DirectExchange.cpp @@ -0,0 +1,72 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "DirectExchange.h" +#include "ExchangeBinding.h" +#include + +using namespace qpid::broker; +using namespace qpid::framing; + +DirectExchange::DirectExchange(const string& _name) : name(_name) { + +} + +void DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, FieldTable* args){ + lock.acquire(); + std::vector& queues(bindings[routingKey]); + std::vector::iterator i = find(queues.begin(), queues.end(), queue); + if(i == queues.end()){ + bindings[routingKey].push_back(queue); + queue->bound(new ExchangeBinding(this, queue, routingKey, args)); + } + lock.release(); +} + +void DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, FieldTable* args){ + lock.acquire(); + std::vector& queues(bindings[routingKey]); + + std::vector::iterator i = find(queues.begin(), queues.end(), queue); + if(i < queues.end()){ + queues.erase(i); + if(queues.empty()){ + bindings.erase(routingKey); + } + } + lock.release(); +} + +void DirectExchange::route(Message::shared_ptr& msg, const string& routingKey, FieldTable* args){ + lock.acquire(); + std::vector& queues(bindings[routingKey]); + int count(0); + for(std::vector::iterator i = queues.begin(); i != queues.end(); i++, count++){ + (*i)->deliver(msg); + } + if(!count){ + std::cout << "WARNING: DirectExchange " << name << " could not route message with key " << routingKey << std::endl; + } + lock.release(); +} + +DirectExchange::~DirectExchange(){ + +} + + +const std::string DirectExchange::typeName("direct"); diff --git a/cpp/broker/src/ExchangeBinding.cpp b/cpp/broker/src/ExchangeBinding.cpp new file mode 100644 index 0000000000..6160a67fd3 --- /dev/null +++ b/cpp/broker/src/ExchangeBinding.cpp @@ -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. + * + */ +#include "ExchangeBinding.h" +#include "Exchange.h" + +using namespace qpid::broker; +using namespace qpid::framing; + +ExchangeBinding::ExchangeBinding(Exchange* _e, Queue::shared_ptr _q, const string& _key, FieldTable* _args) : e(_e), q(_q), key(_key), args(_args){} + +void ExchangeBinding::cancel(){ + e->unbind(q, key, args); + delete this; +} + +ExchangeBinding::~ExchangeBinding(){ +} diff --git a/cpp/broker/src/ExchangeRegistry.cpp b/cpp/broker/src/ExchangeRegistry.cpp new file mode 100644 index 0000000000..0ee581af2f --- /dev/null +++ b/cpp/broker/src/ExchangeRegistry.cpp @@ -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. + * + */ +#include "ExchangeRegistry.h" +#include "MonitorImpl.h" + +using namespace qpid::broker; +using namespace qpid::concurrent; + +ExchangeRegistry::ExchangeRegistry() : lock(new MonitorImpl()){} + +ExchangeRegistry::~ExchangeRegistry(){ + delete lock; +} + +void ExchangeRegistry::declare(Exchange* exchange){ + exchanges[exchange->getName()] = exchange; +} + +void ExchangeRegistry::destroy(const string& name){ + if(exchanges[name]){ + delete exchanges[name]; + exchanges.erase(name); + } +} + +Exchange* ExchangeRegistry::get(const string& name){ + return exchanges[name]; +} diff --git a/cpp/broker/src/FanOutExchange.cpp b/cpp/broker/src/FanOutExchange.cpp new file mode 100644 index 0000000000..7f261d5eda --- /dev/null +++ b/cpp/broker/src/FanOutExchange.cpp @@ -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. + * + */ +#include "FanOutExchange.h" +#include "ExchangeBinding.h" +#include + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::concurrent; + +FanOutExchange::FanOutExchange(const string& _name) : name(_name) {} + +void FanOutExchange::bind(Queue::shared_ptr queue, const string& routingKey, FieldTable* args){ + Locker locker(lock); + // Add if not already present. + Queue::vector::iterator i = std::find(bindings.begin(), bindings.end(), queue); + if (i == bindings.end()) { + bindings.push_back(queue); + queue->bound(new ExchangeBinding(this, queue, routingKey, args)); + } +} + +void FanOutExchange::unbind(Queue::shared_ptr queue, const string& routingKey, FieldTable* args){ + Locker locker(lock); + Queue::vector::iterator i = std::find(bindings.begin(), bindings.end(), queue); + if (i != bindings.end()) { + bindings.erase(i); + // TODO aconway 2006-09-14: What about the ExchangeBinding object? Don't we have to verify routingKey/args match? + } +} + +void FanOutExchange::route(Message::shared_ptr& msg, const string& routingKey, FieldTable* args){ + Locker locker(lock); + for(Queue::vector::iterator i = bindings.begin(); i != bindings.end(); ++i){ + (*i)->deliver(msg); + } +} + +FanOutExchange::~FanOutExchange() {} + +const std::string FanOutExchange::typeName("fanout"); diff --git a/cpp/broker/src/Message.cpp b/cpp/broker/src/Message.cpp new file mode 100644 index 0000000000..7afcd97934 --- /dev/null +++ b/cpp/broker/src/Message.cpp @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "MonitorImpl.h" +#include "Message.h" +#include "ExchangeRegistry.h" +#include + +using namespace std::tr1;//for *_pointer_cast methods +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::concurrent; + + +Message::Message(const ConnectionToken* const _publisher, + const string& _exchange, const string& _routingKey, + bool _mandatory, bool _immediate) : publisher(_publisher), + exchange(_exchange), + routingKey(_routingKey), + mandatory(_mandatory), + immediate(_immediate){ + +} + +Message::~Message(){ +} + +void Message::setHeader(AMQHeaderBody::shared_ptr header){ + this->header = header; +} + +void Message::addContent(AMQContentBody::shared_ptr data){ + content.push_back(data); +} + +bool Message::isComplete(){ + return header.get() && (header->getContentSize() == contentSize()); +} + +void Message::deliver(OutputHandler* out, int channel, + string& consumerTag, u_int64_t deliveryTag, + u_int32_t framesize){ + + out->send(new AMQFrame(channel, new BasicDeliverBody(consumerTag, deliveryTag, false, exchange, routingKey))); + AMQBody::shared_ptr headerBody = static_pointer_cast(header); + out->send(new AMQFrame(channel, headerBody)); + for(content_iterator i = content.begin(); i != content.end(); i++){ + if((*i)->size() > framesize){ + //TODO: need to split it + std::cout << "WARNING: Dropped message. Re-fragmentation not yet implemented." << std::endl; + }else{ + AMQBody::shared_ptr contentBody = static_pointer_cast(*i); + out->send(new AMQFrame(channel, contentBody)); + } + } +} + +BasicHeaderProperties* Message::getHeaderProperties(){ + return dynamic_cast(header->getProperties()); +} + +u_int64_t Message::contentSize(){ + u_int64_t size(0); + for(content_iterator i = content.begin(); i != content.end(); i++){ + size += (*i)->size(); + } + return size; +} + +const ConnectionToken* const Message::getPublisher(){ + return publisher; +} + +bool qpid::broker::route(Message::shared_ptr& msg, ExchangeRegistry* registry){ + Exchange* exchange = registry->get(msg->exchange); + if(exchange){ + exchange->route(msg, msg->routingKey, &(msg->getHeaderProperties()->getHeaders())); + return true; + }else{ + return false; + } +} + diff --git a/cpp/broker/src/NameGenerator.cpp b/cpp/broker/src/NameGenerator.cpp new file mode 100644 index 0000000000..46aa385a7e --- /dev/null +++ b/cpp/broker/src/NameGenerator.cpp @@ -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. + * + */ +#include "NameGenerator.h" +#include + +using namespace qpid::broker; + +NameGenerator::NameGenerator(const std::string& _base) : base(_base), counter(1) {} + +std::string NameGenerator::generate(){ + std::stringstream ss; + ss << base << counter++; + return ss.str(); +} diff --git a/cpp/broker/src/Queue.cpp b/cpp/broker/src/Queue.cpp new file mode 100644 index 0000000000..f7b8605b03 --- /dev/null +++ b/cpp/broker/src/Queue.cpp @@ -0,0 +1,148 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Queue.h" +#include "MonitorImpl.h" +#include + +using namespace qpid::broker; +using namespace qpid::concurrent; + +Queue::Queue(const string& _name, bool _durable, u_int32_t _autodelete, const ConnectionToken* const _owner) : name(_name), + durable(_durable), + autodelete(_autodelete), + owner(_owner), + queueing(false), + dispatching(false), + next(0), + lastUsed(0), + exclusive(0){ + + if(autodelete) lastUsed = apr_time_as_msec(apr_time_now()); +} + +Queue::~Queue(){ + for(Binding* b = bindings.front(); !bindings.empty(); b = bindings.front()){ + b->cancel(); + bindings.pop(); + } +} + +void Queue::bound(Binding* b){ + bindings.push(b); +} + +void Queue::deliver(Message::shared_ptr& msg){ + Locker locker(lock); + if(queueing || !dispatch(msg)){ + queueing = true; + messages.push(msg); + } +} + +bool Queue::dispatch(Message::shared_ptr& msg){ + if(consumers.empty()){ + return false; + }else if(exclusive){ + if(!exclusive->deliver(msg)){ + std::cout << "WARNING: Dropping undeliverable message from queue with exclusive consumer." << std::endl; + } + return true; + }else{ + //deliver to next consumer + next = next % consumers.size(); + Consumer* c = consumers[next]; + int start = next; + while(c){ + next++; + if(c->deliver(msg)) return true; + + next = next % consumers.size(); + c = next == start ? 0 : consumers[next]; + } + return false; + } +} + +bool Queue::startDispatching(){ + Locker locker(lock); + if(queueing && !dispatching){ + dispatching = true; + return true; + }else{ + return false; + } +} + +void Queue::dispatch(){ + bool proceed = startDispatching(); + while(proceed){ + Locker locker(lock); + if(!messages.empty() && dispatch(messages.front())){ + messages.pop(); + }else{ + dispatching = false; + proceed = false; + queueing = !messages.empty(); + } + } +} + +void Queue::consume(Consumer* c, bool requestExclusive){ + Locker locker(lock); + if(exclusive) throw ExclusiveAccessException(); + if(requestExclusive){ + if(!consumers.empty()) throw ExclusiveAccessException(); + exclusive = c; + } + + if(autodelete && consumers.empty()) lastUsed = 0; + consumers.push_back(c); +} + +void Queue::cancel(Consumer* c){ + Locker locker(lock); + consumers.erase(find(consumers.begin(), consumers.end(), c)); + if(autodelete && consumers.empty()) lastUsed = apr_time_as_msec(apr_time_now()); + if(exclusive == c) exclusive = 0; +} + +Message::shared_ptr Queue::dequeue(){ + +} + +u_int32_t Queue::purge(){ + Locker locker(lock); + int count = messages.size(); + while(!messages.empty()) messages.pop(); + return count; +} + +u_int32_t Queue::getMessageCount() const{ + Locker locker(lock); + return messages.size(); +} + +u_int32_t Queue::getConsumerCount() const{ + Locker locker(lock); + return consumers.size(); +} + +bool Queue::canAutoDelete() const{ + Locker locker(lock); + return lastUsed && ((apr_time_as_msec(apr_time_now()) - lastUsed) > autodelete); +} diff --git a/cpp/broker/src/QueueRegistry.cpp b/cpp/broker/src/QueueRegistry.cpp new file mode 100644 index 0000000000..f807415314 --- /dev/null +++ b/cpp/broker/src/QueueRegistry.cpp @@ -0,0 +1,72 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "QueueRegistry.h" +#include "MonitorImpl.h" +#include "SessionHandlerImpl.h" +#include +#include + +using namespace qpid::broker; +using namespace qpid::concurrent; + +QueueRegistry::QueueRegistry() : counter(1){} + +QueueRegistry::~QueueRegistry(){} + +std::pair +QueueRegistry::declare(const string& declareName, bool durable, u_int32_t autoDelete, const ConnectionToken* owner) +{ + Locker locker(lock); + string name = declareName.empty() ? generateName() : declareName; + assert(!name.empty()); + QueueMap::iterator i = queues.find(name); + if (i == queues.end()) { + Queue::shared_ptr queue(new Queue(name, durable, autoDelete, owner)); + queues[name] = queue; + return std::pair(queue, true); + } else { + return std::pair(i->second, false); + } +} + +void QueueRegistry::destroy(const string& name){ + Locker locker(lock); + queues.erase(name); +} + +Queue::shared_ptr QueueRegistry::find(const string& name){ + Locker locker(lock); + QueueMap::iterator i = queues.find(name); + if (i == queues.end()) { + return Queue::shared_ptr(); + } else { + return i->second; + } +} + +string QueueRegistry::generateName(){ + string name; + do { + std::stringstream ss; + ss << "tmp_" << counter++; + name = ss.str(); + // Thread safety: Private function, only called with lock held + // so this is OK. + } while(queues.find(name) != queues.end()); + return name; +} diff --git a/cpp/broker/src/SessionHandlerFactoryImpl.cpp b/cpp/broker/src/SessionHandlerFactoryImpl.cpp new file mode 100644 index 0000000000..661cb4ef81 --- /dev/null +++ b/cpp/broker/src/SessionHandlerFactoryImpl.cpp @@ -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. + * + */ +#include "SessionHandlerFactoryImpl.h" +#include "SessionHandlerImpl.h" +#include "FanOutExchange.h" + +using namespace qpid::broker; +using namespace qpid::io; + +SessionHandlerFactoryImpl::SessionHandlerFactoryImpl(u_int32_t _timeout) : timeout(_timeout), cleaner(&queues, timeout/10){ + exchanges.declare(new DirectExchange("amq.direct")); + exchanges.declare(new TopicExchange("amq.topic")); + exchanges.declare(new FanOutExchange("amq.fanout")); + cleaner.start(); +} + +SessionHandler* SessionHandlerFactoryImpl::create(SessionContext* ctxt){ + return new SessionHandlerImpl(ctxt, &queues, &exchanges, &cleaner, timeout); +} + +SessionHandlerFactoryImpl::~SessionHandlerFactoryImpl(){ + cleaner.stop(); + exchanges.destroy("amq.direct"); + exchanges.destroy("amq.topic"); +} diff --git a/cpp/broker/src/SessionHandlerImpl.cpp b/cpp/broker/src/SessionHandlerImpl.cpp new file mode 100644 index 0000000000..19e243a01b --- /dev/null +++ b/cpp/broker/src/SessionHandlerImpl.cpp @@ -0,0 +1,378 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "SessionHandlerImpl.h" +#include "FanOutExchange.h" +#include "assert.h" + +using namespace std::tr1; +using namespace qpid::broker; +using namespace qpid::io; +using namespace qpid::framing; +using namespace qpid::concurrent; + +SessionHandlerImpl::SessionHandlerImpl(SessionContext* _context, + QueueRegistry* _queues, + ExchangeRegistry* _exchanges, + AutoDelete* _cleaner, + const u_int32_t _timeout) : context(_context), + queues(_queues), + exchanges(_exchanges), + cleaner(_cleaner), + timeout(_timeout), + channelHandler(new ChannelHandlerImpl(this)), + connectionHandler(new ConnectionHandlerImpl(this)), + basicHandler(new BasicHandlerImpl(this)), + exchangeHandler(new ExchangeHandlerImpl(this)), + queueHandler(new QueueHandlerImpl(this)), + framemax(65536), + heartbeat(0){ + +} + +SessionHandlerImpl::~SessionHandlerImpl(){ + // TODO aconway 2006-09-07: Should be auto_ptr or plain members. + delete channelHandler; + delete connectionHandler; + delete basicHandler; + delete exchangeHandler; + delete queueHandler; +} + +Queue::shared_ptr SessionHandlerImpl::getQueue(const string& name, u_int16_t channel){ + Queue::shared_ptr queue; + if (name.empty()) { + queue = channels[channel]->getDefaultQueue(); + if (!queue) throw ConnectionException( 530, "Queue must be specified or previously declared" ); + } else { + queue = queues->find(name); + if (queue == 0) { + throw ChannelException( 404, "Queue not found: " + name); + } + } + return queue; +} + + +Exchange* SessionHandlerImpl::findExchange(const string& name){ + exchanges->getLock()->acquire(); + Exchange* exchange(exchanges->get(name)); + exchanges->getLock()->release(); + return exchange; +} + +void SessionHandlerImpl::received(qpid::framing::AMQFrame* frame){ + u_int16_t channel = frame->getChannel(); + AMQBody::shared_ptr body = frame->getBody(); + AMQMethodBody::shared_ptr method; + + switch(body->type()) + { + case METHOD_BODY: + method = dynamic_pointer_cast(body); + try{ + method->invoke(*this, channel); + }catch(ChannelException& e){ + channels[channel]->close(); + channels.erase(channel); + context->send(new AMQFrame(channel, new ChannelCloseBody(e.code, e.text, method->amqpClassId(), method->amqpMethodId()))); + }catch(ConnectionException& e){ + context->send(new AMQFrame(0, new ConnectionCloseBody(e.code, e.text, method->amqpClassId(), method->amqpMethodId()))); + } + break; + + case HEADER_BODY: + this->handleHeader(channel, dynamic_pointer_cast(body)); + break; + + case CONTENT_BODY: + this->handleContent(channel, dynamic_pointer_cast(body)); + break; + + case HEARTBEAT_BODY: + //channel must be 0 + this->handleHeartbeat(dynamic_pointer_cast(body)); + break; + } +} + +void SessionHandlerImpl::initiated(qpid::framing::ProtocolInitiation* header){ + //send connection start + FieldTable properties; + string mechanisms("PLAIN"); + string locales("en_US"); + context->send(new AMQFrame(0, new ConnectionStartBody(8, 0, properties, mechanisms, locales))); +} + +void SessionHandlerImpl::idleOut(){ + +} + +void SessionHandlerImpl::idleIn(){ + +} + +void SessionHandlerImpl::closed(){ + for(channel_iterator i = channels.begin(); i != channels.end(); i = channels.begin()){ + Channel* c = i->second; + channels.erase(i); + c->close(); + delete c; + } + for(queue_iterator i = exclusiveQueues.begin(); i < exclusiveQueues.end(); i = exclusiveQueues.begin()){ + string name = (*i)->getName(); + queues->destroy(name); + exclusiveQueues.erase(i); + } +} + +void SessionHandlerImpl::handleHeader(u_int16_t channel, AMQHeaderBody::shared_ptr body){ + channels[channel]->handleHeader(body, exchanges); +} + +void SessionHandlerImpl::handleContent(u_int16_t channel, AMQContentBody::shared_ptr body){ + channels[channel]->handleContent(body, exchanges); +} + +void SessionHandlerImpl::handleHeartbeat(AMQHeartbeatBody::shared_ptr body){ + std::cout << "SessionHandlerImpl::handleHeartbeat()" << std::endl; +} + +void SessionHandlerImpl::ConnectionHandlerImpl::startOk(u_int16_t channel, FieldTable& clientProperties, string& mechanism, + string& response, string& locale){ + + parent->context->send(new AMQFrame(0, new ConnectionTuneBody(100, parent->framemax, parent->heartbeat))); +} + +void SessionHandlerImpl::ConnectionHandlerImpl::secureOk(u_int16_t channel, string& response){} + +void SessionHandlerImpl::ConnectionHandlerImpl::tuneOk(u_int16_t channel, u_int16_t channelmax, u_int32_t framemax, u_int16_t heartbeat){ + parent->framemax = framemax; + parent->heartbeat = heartbeat; +} + +void SessionHandlerImpl::ConnectionHandlerImpl::open(u_int16_t channel, string& virtualHost, string& capabilities, bool insist){ + string knownhosts; + parent->context->send(new AMQFrame(0, new ConnectionOpenOkBody(knownhosts))); +} + +void SessionHandlerImpl::ConnectionHandlerImpl::close(u_int16_t channel, u_int16_t replyCode, string& replyText, + u_int16_t classId, u_int16_t methodId){ + + parent->context->send(new AMQFrame(0, new ConnectionCloseOkBody())); + parent->context->close(); +} + +void SessionHandlerImpl::ConnectionHandlerImpl::closeOk(u_int16_t channel){ + parent->context->close(); +} + + + +void SessionHandlerImpl::ChannelHandlerImpl::open(u_int16_t channel, string& outOfBand){ + parent->channels[channel] = new Channel(parent->context, channel, parent->framemax); + parent->context->send(new AMQFrame(channel, new ChannelOpenOkBody())); +} + +void SessionHandlerImpl::ChannelHandlerImpl::flow(u_int16_t channel, bool active){} +void SessionHandlerImpl::ChannelHandlerImpl::flowOk(u_int16_t channel, bool active){} + +void SessionHandlerImpl::ChannelHandlerImpl::close(u_int16_t channel, u_int16_t replyCode, string& replyText, + u_int16_t classId, u_int16_t methodId){ + Channel* c = parent->channels[channel]; + parent->channels.erase(channel); + c->close(); + delete c; + parent->context->send(new AMQFrame(channel, new ChannelCloseOkBody())); +} + +void SessionHandlerImpl::ChannelHandlerImpl::closeOk(u_int16_t channel){} + + + +void SessionHandlerImpl::ExchangeHandlerImpl::declare(u_int16_t channel, u_int16_t ticket, string& exchange, string& type, + bool passive, bool durable, bool autoDelete, bool internal, bool nowait, + FieldTable& arguments){ + + if(!passive && ( + type != TopicExchange::typeName && + type != DirectExchange::typeName && + type != FanOutExchange::typeName) + ) + { + throw ChannelException(540, "Exchange type not implemented: " + type); + } + + parent->exchanges->getLock()->acquire(); + if(!parent->exchanges->get(exchange)){ + if(type == TopicExchange::typeName){ + parent->exchanges->declare(new TopicExchange(exchange)); + }else if(type == DirectExchange::typeName){ + parent->exchanges->declare(new DirectExchange(exchange)); + }else if(type == FanOutExchange::typeName){ + parent->exchanges->declare(new DirectExchange(exchange)); + } + } + parent->exchanges->getLock()->release(); + if(!nowait){ + parent->context->send(new AMQFrame(channel, new ExchangeDeclareOkBody())); + } +} + +void SessionHandlerImpl::ExchangeHandlerImpl::delete_(u_int16_t channel, u_int16_t ticket, string& exchange, bool ifUnused, bool nowait){ + //TODO: implement unused + parent->exchanges->getLock()->acquire(); + parent->exchanges->destroy(exchange); + parent->exchanges->getLock()->release(); + if(!nowait) parent->context->send(new AMQFrame(channel, new ExchangeDeleteOkBody())); +} + + + + +void SessionHandlerImpl::QueueHandlerImpl::declare(u_int16_t channel, u_int16_t ticket, string& name, + bool passive, bool durable, bool exclusive, + bool autoDelete, bool nowait, FieldTable& arguments){ + Queue::shared_ptr queue; + if (passive && !name.empty()) { + queue = parent->getQueue(name, channel); + } else { + std::pair queue_created = parent->queues->declare(name, durable, autoDelete ? parent->timeout : 0, exclusive ? parent : 0); + queue = queue_created.first; + assert(queue); + if (queue_created.second) { // This is a new queue + parent->channels[channel]->setDefaultQueue(queue); + //add default binding: + parent->exchanges->get("amq.direct")->bind(queue, name, 0); + if(exclusive){ + parent->exclusiveQueues.push_back(queue); + } else if(autoDelete){ + parent->cleaner->add(queue); + } + } + } + if(exclusive && !queue->isExclusiveOwner(parent)){ + throw ChannelException(405, "Cannot grant exclusive access to queue"); + } + if(!nowait){ + name = queue->getName(); + QueueDeclareOkBody* response = new QueueDeclareOkBody(name, queue->getMessageCount(), queue->getConsumerCount()); + parent->context->send(new AMQFrame(channel, response)); + } +} + +void SessionHandlerImpl::QueueHandlerImpl::bind(u_int16_t channel, u_int16_t ticket, string& queueName, + string& exchangeName, string& routingKey, bool nowait, + FieldTable& arguments){ + + Queue::shared_ptr queue = parent->getQueue(queueName, channel); + Exchange* exchange = parent->exchanges->get(exchangeName); + if(exchange){ + if(routingKey.size() == 0 && queueName.size() == 0) routingKey = queue->getName(); + exchange->bind(queue, routingKey, &arguments); + if(!nowait) parent->context->send(new AMQFrame(channel, new QueueBindOkBody())); + }else{ + throw ChannelException(404, "Bind failed. No such exchange: " + exchangeName); + } +} + +void SessionHandlerImpl::QueueHandlerImpl::purge(u_int16_t channel, u_int16_t ticket, string& queueName, bool nowait){ + + Queue::shared_ptr queue = parent->getQueue(queueName, channel); + int count = queue->purge(); + if(!nowait) parent->context->send(new AMQFrame(channel, new QueuePurgeOkBody(count))); +} + +void SessionHandlerImpl::QueueHandlerImpl::delete_(u_int16_t channel, u_int16_t ticket, string& queue, + bool ifUnused, bool ifEmpty, bool nowait){ + ChannelException error(0, ""); + int count(0); + Queue::shared_ptr q = parent->getQueue(queue, channel); + if(ifEmpty && q->getMessageCount() > 0){ + throw ChannelException(406, "Queue not empty."); + }else if(ifUnused && q->getConsumerCount() > 0){ + throw ChannelException(406, "Queue in use."); + }else{ + //remove the queue from the list of exclusive queues if necessary + if(q->isExclusiveOwner(parent)){ + queue_iterator i = find(parent->exclusiveQueues.begin(), parent->exclusiveQueues.end(), q); + if(i < parent->exclusiveQueues.end()) parent->exclusiveQueues.erase(i); + } + count = q->getMessageCount(); + parent->queues->destroy(queue); + } + if(!nowait) parent->context->send(new AMQFrame(channel, new QueueDeleteOkBody(count))); +} + + + + +void SessionHandlerImpl::BasicHandlerImpl::qos(u_int16_t channel, u_int32_t prefetchSize, u_int16_t prefetchCount, bool global){ + //TODO: handle global + //TODO: channel doesn't do anything with these qos parameters yet + parent->channels[channel]->setPrefetchSize(prefetchSize); + parent->channels[channel]->setPrefetchCount(prefetchCount); + parent->context->send(new AMQFrame(channel, new BasicQosOkBody())); +} + +void SessionHandlerImpl::BasicHandlerImpl::consume(u_int16_t channelId, u_int16_t ticket, + string& queueName, string& consumerTag, + bool noLocal, bool noAck, bool exclusive, + bool nowait){ + + //TODO: implement nolocal + Queue::shared_ptr queue = parent->getQueue(queueName, channelId); + Channel* channel = parent->channels[channelId]; + if(!consumerTag.empty() && channel->exists(consumerTag)){ + throw ConnectionException(530, "Consumer tags must be unique"); + } + + try{ + channel->consume(consumerTag, queue, !noAck, exclusive, noLocal ? parent : 0); + if(!nowait) parent->context->send(new AMQFrame(channelId, new BasicConsumeOkBody(consumerTag))); + + //allow messages to be dispatched if required as there is now a consumer: + queue->dispatch(); + }catch(ExclusiveAccessException& e){ + if(exclusive) throw ChannelException(403, "Exclusive access cannot be granted"); + else throw ChannelException(403, "Access would violate previously granted exclusivity"); + } + +} + +void SessionHandlerImpl::BasicHandlerImpl::cancel(u_int16_t channel, string& consumerTag, bool nowait){ + parent->channels[channel]->cancel(consumerTag); + if(!nowait) parent->context->send(new AMQFrame(channel, new BasicCancelOkBody(consumerTag))); +} + +void SessionHandlerImpl::BasicHandlerImpl::publish(u_int16_t channel, u_int16_t ticket, + string& exchange, string& routingKey, + bool mandatory, bool immediate){ + + Message* msg = new Message(parent, exchange.length() ? exchange : "amq.direct", routingKey, mandatory, immediate); + parent->channels[channel]->handlePublish(msg); +} + +void SessionHandlerImpl::BasicHandlerImpl::get(u_int16_t channel, u_int16_t ticket, string& queue, bool noAck){} + +void SessionHandlerImpl::BasicHandlerImpl::ack(u_int16_t channel, u_int64_t deliveryTag, bool multiple){} + +void SessionHandlerImpl::BasicHandlerImpl::reject(u_int16_t channel, u_int64_t deliveryTag, bool requeue){} + +void SessionHandlerImpl::BasicHandlerImpl::recover(u_int16_t channel, bool requeue){} + diff --git a/cpp/broker/src/TopicExchange.cpp b/cpp/broker/src/TopicExchange.cpp new file mode 100644 index 0000000000..e0248958f9 --- /dev/null +++ b/cpp/broker/src/TopicExchange.cpp @@ -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. + * + */ +#include "TopicExchange.h" +#include "ExchangeBinding.h" + +using namespace qpid::broker; +using namespace qpid::framing; + +TopicExchange::TopicExchange(const string& _name) : name(_name) { + +} + +void TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, FieldTable* args){ + lock.acquire(); + bindings[routingKey].push_back(queue); + queue->bound(new ExchangeBinding(this, queue, routingKey, args)); + lock.release(); +} + +void TopicExchange::unbind(Queue::shared_ptr queue, const string& routingKey, FieldTable* args){ + lock.acquire(); + std::vector& queues(bindings[routingKey]); + + std::vector::iterator i = find(queues.begin(), queues.end(), queue); + if(i < queues.end()){ + queues.erase(i); + if(queues.empty()){ + bindings.erase(routingKey); + } + } + lock.release(); +} + +void TopicExchange::route(Message::shared_ptr& msg, const string& routingKey, FieldTable* args){ + lock.acquire(); + std::vector& queues(bindings[routingKey]); + for(std::vector::iterator i = queues.begin(); i != queues.end(); i++){ + (*i)->deliver(msg); + } + lock.release(); +} + +TopicExchange::~TopicExchange(){ + +} + +const std::string TopicExchange::typeName("topic"); diff --git a/cpp/broker/test/Makefile b/cpp/broker/test/Makefile new file mode 100644 index 0000000000..172ce564bf --- /dev/null +++ b/cpp/broker/test/Makefile @@ -0,0 +1,20 @@ +# +# Copyright (c) 2006 The Apache Software Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_HOME = ../../.. +LDLIBS=-lapr-1 -lcppunit $(COMMON_LIB) $(BROKER_LIB) +include ${QPID_HOME}/cpp/test_plugins.mk + diff --git a/cpp/broker/test/QueueRegistryTest.cpp b/cpp/broker/test/QueueRegistryTest.cpp new file mode 100644 index 0000000000..c4ad40b5cd --- /dev/null +++ b/cpp/broker/test/QueueRegistryTest.cpp @@ -0,0 +1,79 @@ +#include "QueueRegistry.h" +#include +#include +#include +#include +#include + +using namespace qpid::broker; + +class QueueRegistryTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(QueueRegistryTest); + CPPUNIT_TEST(testDeclare); + CPPUNIT_TEST(testDeclareTmp); + CPPUNIT_TEST(testFind); + CPPUNIT_TEST(testDestroy); + CPPUNIT_TEST_SUITE_END(); + + private: + std::string foo, bar; + QueueRegistry reg; + std::pair qc; + + public: + void setUp() { + foo = "foo"; + bar = "bar"; + } + + void testDeclare() { + qc = reg.declare(foo, false, 0, 0); + Queue::shared_ptr q = qc.first; + CPPUNIT_ASSERT(q); + CPPUNIT_ASSERT(qc.second); // New queue + CPPUNIT_ASSERT_EQUAL(foo, q->getName()); + + qc = reg.declare(foo, false, 0, 0); + CPPUNIT_ASSERT_EQUAL(q, qc.first); + CPPUNIT_ASSERT(!qc.second); + + qc = reg.declare(bar, false, 0, 0); + q = qc.first; + CPPUNIT_ASSERT(q); + CPPUNIT_ASSERT_EQUAL(true, qc.second); + CPPUNIT_ASSERT_EQUAL(bar, q->getName()); + } + + void testDeclareTmp() + { + qc = reg.declare(std::string(), false, 0, 0); + CPPUNIT_ASSERT(qc.second); + CPPUNIT_ASSERT_EQUAL(std::string("tmp_1"), qc.first->getName()); + } + + void testFind() { + CPPUNIT_ASSERT(reg.find(foo) == 0); + + reg.declare(foo, false, 0, 0); + reg.declare(bar, false, 0, 0); + Queue::shared_ptr q = reg.find(bar); + CPPUNIT_ASSERT(q); + CPPUNIT_ASSERT_EQUAL(bar, q->getName()); + } + + void testDestroy() { + qc = reg.declare(foo, false, 0, 0); + reg.destroy(foo); + // Queue is gone from the registry. + CPPUNIT_ASSERT(reg.find(foo) == 0); + // Queue is not actually destroyed till we drop our reference. + CPPUNIT_ASSERT_EQUAL(foo, qc.first->getName()); + // We shoud be the only reference. + CPPUNIT_ASSERT_EQUAL(1L, qc.first.use_count()); + } +}; + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(QueueRegistryTest); diff --git a/cpp/broker/test/exchange_test.cpp b/cpp/broker/test/exchange_test.cpp new file mode 100644 index 0000000000..6605f2685b --- /dev/null +++ b/cpp/broker/test/exchange_test.cpp @@ -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. + * + */ + +#include "DirectExchange.h" +#include "Exchange.h" +#include "Queue.h" +#include "TopicExchange.h" +#include +#include +#include +#include +#include + +using namespace qpid::broker; +using namespace qpid::concurrent; + +class ExchangeTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(ExchangeTest); + CPPUNIT_TEST(testMe); + CPPUNIT_TEST_SUITE_END(); + + public: + + // TODO aconway 2006-09-12: Need more detailed tests. + + void testMe() + { + Queue::shared_ptr queue(new Queue("queue", true, true)); + Queue::shared_ptr queue2(new Queue("queue2", true, true)); + + TopicExchange topic("topic"); + topic.bind(queue, "abc", 0); + topic.bind(queue2, "abc", 0); + + DirectExchange direct("direct"); + direct.bind(queue, "abc", 0); + direct.bind(queue2, "abc", 0); + + queue.reset(); + queue2.reset(); + + Message::shared_ptr msg = Message::shared_ptr(new Message(0, "e", "A", true, true)); + topic.route(msg, "abc", 0); + direct.route(msg, "abc", 0); + + // TODO aconway 2006-09-12: TODO Why no assertions? + } +}; + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(ExchangeTest); diff --git a/cpp/broker/test/message_test.cpp b/cpp/broker/test/message_test.cpp new file mode 100644 index 0000000000..94d25a831e --- /dev/null +++ b/cpp/broker/test/message_test.cpp @@ -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. + * + */ +#include "APRBase.h" +#include "Message.h" +#include +#include +#include +#include +#include + +using namespace qpid::broker; +using namespace qpid::framing; +using namespace qpid::concurrent; + +class MessageTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(MessageTest); + CPPUNIT_TEST(testMe); + CPPUNIT_TEST_SUITE_END(); + + public: + + // TODO aconway 2006-09-12: Need more detailed tests, + // need tests to assert something! + // + void testMe() + { + APRBase::increment(); + const int size(10); + for(int i = 0; i < size; i++){ + Message::shared_ptr msg = Message::shared_ptr(new Message(0, "A", "B", true, true)); + msg->setHeader(AMQHeaderBody::shared_ptr(new AMQHeaderBody())); + msg->addContent(AMQContentBody::shared_ptr(new AMQContentBody())); + msg.reset(); + } + } +}; + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(MessageTest); + diff --git a/cpp/broker/test/queue_test.cpp b/cpp/broker/test/queue_test.cpp new file mode 100644 index 0000000000..aa423e7e08 --- /dev/null +++ b/cpp/broker/test/queue_test.cpp @@ -0,0 +1,138 @@ + /* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Queue.h" +#include "QueueRegistry.h" +#include +#include +#include +#include +#include + +using namespace qpid::broker; +using namespace qpid::concurrent; + + +class TestBinding : public virtual Binding{ + bool cancelled; + +public: + TestBinding(); + virtual void cancel(); + bool isCancelled(); +}; + +class TestConsumer : public virtual Consumer{ +public: + Message::shared_ptr last; + + virtual bool deliver(Message::shared_ptr& msg); +}; + + +class QueueTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(QueueTest); + CPPUNIT_TEST(testMe); + CPPUNIT_TEST_SUITE_END(); + + public: + void testMe() + { + Queue::shared_ptr queue(new Queue("my_queue", true, true)); + + //Test adding consumers: + TestConsumer c1; + TestConsumer c2; + queue->consume(&c1); + queue->consume(&c2); + + CPPUNIT_ASSERT_EQUAL(u_int32_t(2), queue->getConsumerCount()); + + //Test basic delivery: + Message::shared_ptr msg1 = Message::shared_ptr(new Message(0, "e", "A", true, true)); + Message::shared_ptr msg2 = Message::shared_ptr(new Message(0, "e", "B", true, true)); + Message::shared_ptr msg3 = Message::shared_ptr(new Message(0, "e", "C", true, true)); + + queue->deliver(msg1); + CPPUNIT_ASSERT_EQUAL(msg1.get(), c1.last.get()); + + queue->deliver(msg2); + CPPUNIT_ASSERT_EQUAL(msg2.get(), c2.last.get()); + + queue->deliver(msg3); + CPPUNIT_ASSERT_EQUAL(msg3.get(), c1.last.get()); + + //Test cancellation: + queue->cancel(&c1); + CPPUNIT_ASSERT_EQUAL(u_int32_t(1), queue->getConsumerCount()); + queue->cancel(&c2); + CPPUNIT_ASSERT_EQUAL(u_int32_t(0), queue->getConsumerCount()); + + //Test bindings: + TestBinding a; + TestBinding b; + queue->bound(&a); + queue->bound(&b); + + queue.reset(); + + CPPUNIT_ASSERT(a.isCancelled()); + CPPUNIT_ASSERT(b.isCancelled()); + + //Test use of queues in registry: + QueueRegistry registry; + registry.declare("queue1", true, true); + registry.declare("queue2", true, true); + registry.declare("queue3", true, true); + + CPPUNIT_ASSERT(registry.find("queue1")); + CPPUNIT_ASSERT(registry.find("queue2")); + CPPUNIT_ASSERT(registry.find("queue3")); + + registry.destroy("queue1"); + registry.destroy("queue2"); + registry.destroy("queue3"); + + CPPUNIT_ASSERT(!registry.find("queue1")); + CPPUNIT_ASSERT(!registry.find("queue2")); + CPPUNIT_ASSERT(!registry.find("queue3")); + } +}; + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(QueueTest); + +//TestBinding +TestBinding::TestBinding() : cancelled(false) {} + +void TestBinding::cancel(){ + CPPUNIT_ASSERT(!cancelled); + cancelled = true; +} + +bool TestBinding::isCancelled(){ + return cancelled; +} + +//TestConsumer +bool TestConsumer::deliver(Message::shared_ptr& msg){ + last = msg; + return true; +} + diff --git a/cpp/client/Makefile b/cpp/client/Makefile new file mode 100644 index 0000000000..d08b92fe2b --- /dev/null +++ b/cpp/client/Makefile @@ -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. +# + +# +# Build client library. +# + +QPID_HOME = ../.. +include ${QPID_HOME}/cpp/options.mk + +SOURCES := $(wildcard src/*.cpp) +OBJECTS := $(subst .cpp,.o,$(SOURCES)) +CLIENT_LIB=$(LIB_DIR)/libqpid_client.so.1.0 + +.PHONY: all test clean + +all: $(CLIENT_LIB) + +test: + @$(MAKE) -C test all + +clean: + -@rm -f $(CLIENT_LIB) $(OBJECTS) src/*.d + $(MAKE) -C test clean + +$(CLIENT_LIB): $(OBJECTS) + $(CXX) -shared -o $@ $^ $(LDFLAGS) $(COMMON_LIB) + +# Dependencies +-include $(SOURCES:.cpp=.d) diff --git a/cpp/client/inc/Channel.h b/cpp/client/inc/Channel.h new file mode 100644 index 0000000000..debecf922e --- /dev/null +++ b/cpp/client/inc/Channel.h @@ -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. + * + */ +#include +#include +#include +#include "sys/types.h" + +#ifndef _Channel_ +#define _Channel_ + +#include "amqp_framing.h" + +#include "ThreadFactory.h" + +#include "Connection.h" +#include "Exchange.h" +#include "IncomingMessage.h" +#include "Message.h" +#include "MessageListener.h" +#include "Queue.h" +#include "ResponseHandler.h" +#include "ReturnedMessageHandler.h" + +namespace qpid { +namespace client { + enum ack_modes {NO_ACK=0, AUTO_ACK=1, LAZY_ACK=2, CLIENT_ACK=3}; + + class Channel : private virtual qpid::framing::BodyHandler, public virtual qpid::concurrent::Runnable{ + struct Consumer{ + MessageListener* listener; + int ackMode; + int count; + u_int64_t lastDeliveryTag; + }; + typedef std::map::iterator consumer_iterator; + + u_int16_t id; + Connection* con; + qpid::concurrent::ThreadFactory* threadFactory; + qpid::concurrent::Thread* dispatcher; + qpid::framing::OutputHandler* out; + IncomingMessage* incoming; + ResponseHandler responses; + std::queue messages;//holds returned messages or those delivered for a consume + IncomingMessage* retrieved;//holds response to basic.get + qpid::concurrent::Monitor* dispatchMonitor; + qpid::concurrent::Monitor* retrievalMonitor; + std::map consumers; + ReturnedMessageHandler* returnsHandler; + bool closed; + + u_int16_t prefetch; + const bool transactional; + + void enqueue(); + void retrieve(Message& msg); + IncomingMessage* dequeue(); + void dispatch(); + void stop(); + void sendAndReceive(qpid::framing::AMQFrame* frame, const qpid::framing::AMQMethodBody& body); + void deliver(Consumer* consumer, Message& msg); + void setQos(); + void cancelAll(); + + virtual void handleMethod(qpid::framing::AMQMethodBody::shared_ptr body); + virtual void handleHeader(qpid::framing::AMQHeaderBody::shared_ptr body); + virtual void handleContent(qpid::framing::AMQContentBody::shared_ptr body); + virtual void handleHeartbeat(qpid::framing::AMQHeartbeatBody::shared_ptr body); + + public: + Channel(bool transactional = false, u_int16_t prefetch = 500); + ~Channel(); + + void declareExchange(Exchange& exchange, bool synch = true); + void deleteExchange(Exchange& exchange, bool synch = true); + void declareQueue(Queue& queue, bool synch = true); + void deleteQueue(Queue& queue, bool ifunused = false, bool ifempty = false, bool synch = true); + void bind(const Exchange& exchange, const Queue& queue, const std::string& key, + const qpid::framing::FieldTable& args, bool synch = true); + void consume(Queue& queue, std::string& tag, MessageListener* listener, + int ackMode = NO_ACK, bool noLocal = false, bool synch = true); + void cancel(std::string& tag, bool synch = true); + bool get(Message& msg, const Queue& queue, int ackMode = NO_ACK); + void publish(Message& msg, const Exchange& exchange, const std::string& routingKey, + bool mandatory = false, bool immediate = false); + + void commit(); + void rollback(); + + void setPrefetch(u_int16_t prefetch); + + /** + * Start message dispatching on a new thread + */ + void start(); + /** + * Do message dispatching on this thread + */ + void run(); + + void close(); + + void setReturnedMessageHandler(ReturnedMessageHandler* handler); + + friend class Connection; + }; + +} +} + + +#endif diff --git a/cpp/client/inc/Connection.h b/cpp/client/inc/Connection.h new file mode 100644 index 0000000000..89169e92b1 --- /dev/null +++ b/cpp/client/inc/Connection.h @@ -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. + * + */ +#include +#include + +#ifndef _Connection_ +#define _Connection_ + +#include "QpidError.h" +#include "Connector.h" +#include "ShutdownHandler.h" +#include "TimeoutHandler.h" + +#include "amqp_framing.h" +#include "Exchange.h" +#include "IncomingMessage.h" +#include "Message.h" +#include "MessageListener.h" +#include "Queue.h" +#include "ResponseHandler.h" + +namespace qpid { +namespace client { + + class Channel; + + class Connection : public virtual qpid::framing::InputHandler, + public virtual qpid::io::TimeoutHandler, + public virtual qpid::io::ShutdownHandler, + private virtual qpid::framing::BodyHandler{ + + typedef std::map::iterator iterator; + + static u_int16_t channelIdCounter; + + std::string host; + int port; + const u_int32_t max_frame_size; + std::map channels; + qpid::io::Connector* connector; + qpid::framing::OutputHandler* out; + ResponseHandler responses; + volatile bool closed; + + void channelException(Channel* channel, qpid::framing::AMQMethodBody* body, QpidError& e); + void error(int code, const string& msg, int classid = 0, int methodid = 0); + void closeChannel(Channel* channel, u_int16_t code, string& text, u_int16_t classId = 0, u_int16_t methodId = 0); + void sendAndReceive(qpid::framing::AMQFrame* frame, const qpid::framing::AMQMethodBody& body); + + virtual void handleMethod(qpid::framing::AMQMethodBody::shared_ptr body); + virtual void handleHeader(qpid::framing::AMQHeaderBody::shared_ptr body); + virtual void handleContent(qpid::framing::AMQContentBody::shared_ptr body); + virtual void handleHeartbeat(qpid::framing::AMQHeartbeatBody::shared_ptr body); + + public: + + Connection(bool debug = false, u_int32_t max_frame_size = 65536); + ~Connection(); + void open(const std::string& host, int port = 5672, + const std::string& uid = "guest", const std::string& pwd = "guest", + const std::string& virtualhost = "/"); + void close(); + void openChannel(Channel* channel); + /* + * Requests that the server close this channel, then removes + * the association to the channel from this connection + */ + void closeChannel(Channel* channel); + /* + * Removes the channel from association with this connection, + * without sending a close request to the server. + */ + void removeChannel(Channel* channel); + + virtual void received(qpid::framing::AMQFrame* frame); + + virtual void idleOut(); + virtual void idleIn(); + + virtual void shutdown(); + + inline u_int32_t getMaxFrameSize(){ return max_frame_size; } + }; + + +} +} + + +#endif diff --git a/cpp/client/inc/Exchange.h b/cpp/client/inc/Exchange.h new file mode 100644 index 0000000000..66593a41cc --- /dev/null +++ b/cpp/client/inc/Exchange.h @@ -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. + * + */ +#include + +#ifndef _Exchange_ +#define _Exchange_ + +namespace qpid { +namespace client { + + class Exchange{ + const std::string name; + const std::string type; + + public: + + static const std::string DIRECT_EXCHANGE; + static const std::string TOPIC_EXCHANGE; + static const std::string HEADERS_EXCHANGE; + + static const Exchange DEFAULT_DIRECT_EXCHANGE; + static const Exchange DEFAULT_TOPIC_EXCHANGE; + static const Exchange DEFAULT_HEADERS_EXCHANGE; + + Exchange(std::string name, std::string type = DIRECT_EXCHANGE); + const std::string& getName() const; + const std::string& getType() const; + }; + +} +} + + +#endif diff --git a/cpp/client/inc/IncomingMessage.h b/cpp/client/inc/IncomingMessage.h new file mode 100644 index 0000000000..1fee6af433 --- /dev/null +++ b/cpp/client/inc/IncomingMessage.h @@ -0,0 +1,60 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include +#include "amqp_framing.h" + +#ifndef _IncomingMessage_ +#define _IncomingMessage_ + +#include "Message.h" + +namespace qpid { +namespace client { + + class IncomingMessage{ + //content will be preceded by one of these method frames + qpid::framing::BasicDeliverBody::shared_ptr delivered; + qpid::framing::BasicReturnBody::shared_ptr returned; + qpid::framing::BasicGetOkBody::shared_ptr response; + qpid::framing::AMQHeaderBody::shared_ptr header; + std::vector content; + + long contentSize(); + public: + IncomingMessage(qpid::framing::BasicDeliverBody::shared_ptr intro); + IncomingMessage(qpid::framing::BasicReturnBody::shared_ptr intro); + IncomingMessage(qpid::framing::BasicGetOkBody::shared_ptr intro); + ~IncomingMessage(); + void setHeader(qpid::framing::AMQHeaderBody::shared_ptr header); + void addContent(qpid::framing::AMQContentBody::shared_ptr content); + bool isComplete(); + bool isReturn(); + bool isDelivery(); + bool isResponse(); + string& getConsumerTag();//only relevant if isDelivery() + qpid::framing::AMQHeaderBody::shared_ptr& getHeader(); + u_int64_t getDeliveryTag(); + void getData(string& data); + }; + +} +} + + +#endif diff --git a/cpp/client/inc/Message.h b/cpp/client/inc/Message.h new file mode 100644 index 0000000000..f8a5aef565 --- /dev/null +++ b/cpp/client/inc/Message.h @@ -0,0 +1,86 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "amqp_framing.h" + +#ifndef _Message_ +#define _Message_ + + +namespace qpid { +namespace client { + + class Message{ + qpid::framing::AMQHeaderBody::shared_ptr header; + string data; + bool redelivered; + u_int64_t deliveryTag; + + qpid::framing::BasicHeaderProperties* getHeaderProperties(); + Message(qpid::framing::AMQHeaderBody::shared_ptr& header); + public: + Message(); + ~Message(); + + inline std::string getData(){ return data; } + inline void setData(const std::string& data){ this->data = data; } + + inline bool isRedelivered(){ return redelivered; } + inline void setRedelivered(bool redelivered){ this->redelivered = redelivered; } + + inline u_int64_t getDeliveryTag(){ return deliveryTag; } + + std::string& getContentType(); + std::string& getContentEncoding(); + qpid::framing::FieldTable& getHeaders(); + u_int8_t getDeliveryMode(); + u_int8_t getPriority(); + std::string& getCorrelationId(); + std::string& getReplyTo(); + std::string& getExpiration(); + std::string& getMessageId(); + u_int64_t getTimestamp(); + std::string& getType(); + std::string& getUserId(); + std::string& getAppId(); + std::string& getClusterId(); + + void setContentType(std::string& type); + void setContentEncoding(std::string& encoding); + void setHeaders(qpid::framing::FieldTable& headers); + void setDeliveryMode(u_int8_t mode); + void setPriority(u_int8_t priority); + void setCorrelationId(std::string& correlationId); + void setReplyTo(std::string& replyTo); + void setExpiration(std::string& expiration); + void setMessageId(std::string& messageId); + void setTimestamp(u_int64_t timestamp); + void setType(std::string& type); + void setUserId(std::string& userId); + void setAppId(std::string& appId); + void setClusterId(std::string& clusterId); + + + friend class Channel; + }; + +} +} + + +#endif diff --git a/cpp/client/inc/MessageListener.h b/cpp/client/inc/MessageListener.h new file mode 100644 index 0000000000..47307a4df5 --- /dev/null +++ b/cpp/client/inc/MessageListener.h @@ -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. + * + */ +#include + +#ifndef _MessageListener_ +#define _MessageListener_ + +#include "Message.h" + +namespace qpid { +namespace client { + + class MessageListener{ + public: + virtual void received(Message& msg) = 0; + }; + +} +} + + +#endif diff --git a/cpp/client/inc/Queue.h b/cpp/client/inc/Queue.h new file mode 100644 index 0000000000..e0964af774 --- /dev/null +++ b/cpp/client/inc/Queue.h @@ -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. + * + */ +#include + +#ifndef _Queue_ +#define _Queue_ + +namespace qpid { +namespace client { + + class Queue{ + std::string name; + const bool autodelete; + const bool exclusive; + + public: + + Queue(); + Queue(std::string name); + Queue(std::string name, bool temp); + Queue(std::string name, bool autodelete, bool exclusive); + const std::string& getName() const; + void setName(const std::string&); + bool isAutoDelete() const; + bool isExclusive() const; + }; + +} +} + + +#endif diff --git a/cpp/client/inc/ResponseHandler.h b/cpp/client/inc/ResponseHandler.h new file mode 100644 index 0000000000..f5392c954d --- /dev/null +++ b/cpp/client/inc/ResponseHandler.h @@ -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. + * + */ +#include +#include "amqp_framing.h" +#include "Monitor.h" + +#ifndef _ResponseHandler_ +#define _ResponseHandler_ + +namespace qpid { + namespace client { + + class ResponseHandler{ + bool waiting; + qpid::framing::AMQMethodBody::shared_ptr response; + qpid::concurrent::Monitor* monitor; + + public: + ResponseHandler(); + ~ResponseHandler(); + inline bool isWaiting(){ return waiting; } + inline qpid::framing::AMQMethodBody::shared_ptr getResponse(){ return response; } + bool validate(const qpid::framing::AMQMethodBody& expected); + void waitForResponse(); + void signalResponse(qpid::framing::AMQMethodBody::shared_ptr response); + void receive(const qpid::framing::AMQMethodBody& expected); + void expect();//must be called before calling receive + }; + + } +} + + +#endif diff --git a/cpp/client/inc/ReturnedMessageHandler.h b/cpp/client/inc/ReturnedMessageHandler.h new file mode 100644 index 0000000000..0117778fde --- /dev/null +++ b/cpp/client/inc/ReturnedMessageHandler.h @@ -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. + * + */ +#include + +#ifndef _ReturnedMessageHandler_ +#define _ReturnedMessageHandler_ + +#include "Message.h" + +namespace qpid { +namespace client { + + class ReturnedMessageHandler{ + public: + virtual void returned(Message& msg) = 0; + }; + +} +} + + +#endif diff --git a/cpp/client/src/Channel.cpp b/cpp/client/src/Channel.cpp new file mode 100644 index 0000000000..e965f7e5dd --- /dev/null +++ b/cpp/client/src/Channel.cpp @@ -0,0 +1,432 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Channel.h" +#include "MonitorImpl.h" +#include "ThreadFactoryImpl.h" +#include "Message.h" +#include "QpidError.h" + +using namespace std::tr1;//to use dynamic_pointer_cast +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::concurrent; + +Channel::Channel(bool _transactional, u_int16_t _prefetch) : id(0), incoming(0), con(0), out(0), + prefetch(_prefetch), + transactional(_transactional), + dispatcher(0), + closed(true){ + threadFactory = new ThreadFactoryImpl(); + dispatchMonitor = new MonitorImpl(); + retrievalMonitor = new MonitorImpl(); +} + +Channel::~Channel(){ + if(dispatcher){ + stop(); + delete dispatcher; + } + delete retrievalMonitor; + delete dispatchMonitor; + delete threadFactory; +} + +void Channel::setPrefetch(u_int16_t prefetch){ + this->prefetch = prefetch; + if(con != 0 && out != 0){ + setQos(); + } +} + +void Channel::setQos(){ + sendAndReceive(new AMQFrame(id, new BasicQosBody(0, prefetch, false)), basic_qos_ok); + if(transactional){ + sendAndReceive(new AMQFrame(id, new TxSelectBody()), tx_select_ok); + } +} + +void Channel::declareExchange(Exchange& exchange, bool synch){ + string name = exchange.getName(); + string type = exchange.getType(); + FieldTable args; + AMQFrame* frame = new AMQFrame(id, new ExchangeDeclareBody(0, name, type, false, false, false, false, !synch, args)); + if(synch){ + sendAndReceive(frame, exchange_declare_ok); + }else{ + out->send(frame); + } +} + +void Channel::deleteExchange(Exchange& exchange, bool synch){ + string name = exchange.getName(); + AMQFrame* frame = new AMQFrame(id, new ExchangeDeleteBody(0, name, false, !synch)); + if(synch){ + sendAndReceive(frame, exchange_delete_ok); + }else{ + out->send(frame); + } +} + +void Channel::declareQueue(Queue& queue, bool synch){ + string name = queue.getName(); + FieldTable args; + AMQFrame* frame = new AMQFrame(id, new QueueDeclareBody(0, name, false, false, + queue.isExclusive(), + queue.isAutoDelete(), !synch, args)); + if(synch){ + sendAndReceive(frame, queue_declare_ok); + if(queue.getName().length() == 0){ + QueueDeclareOkBody::shared_ptr response = + dynamic_pointer_cast(responses.getResponse()); + queue.setName(response->getQueue()); + } + }else{ + out->send(frame); + } +} + +void Channel::deleteQueue(Queue& queue, bool ifunused, bool ifempty, bool synch){ + //ticket, queue, ifunused, ifempty, nowait + string name = queue.getName(); + AMQFrame* frame = new AMQFrame(id, new QueueDeleteBody(0, name, ifunused, ifempty, !synch)); + if(synch){ + sendAndReceive(frame, queue_delete_ok); + }else{ + out->send(frame); + } +} + +void Channel::bind(const Exchange& exchange, const Queue& queue, const std::string& key, const FieldTable& args, bool synch){ + string e = exchange.getName(); + string q = queue.getName(); + AMQFrame* frame = new AMQFrame(id, new QueueBindBody(0, q, e, (string&) key,!synch, (FieldTable&) args)); + if(synch){ + sendAndReceive(frame, queue_bind_ok); + }else{ + out->send(frame); + } +} + +void Channel::consume(Queue& queue, std::string& tag, MessageListener* listener, + int ackMode, bool noLocal, bool synch){ + + string q = queue.getName(); + AMQFrame* frame = new AMQFrame(id, new BasicConsumeBody(0, q, (string&) tag, noLocal, ackMode == NO_ACK, false, !synch)); + if(synch){ + sendAndReceive(frame, basic_consume_ok); + BasicConsumeOkBody::shared_ptr response = dynamic_pointer_cast(responses.getResponse()); + tag = response->getConsumerTag(); + }else{ + out->send(frame); + } + Consumer* c = new Consumer(); + c->listener = listener; + c->ackMode = ackMode; + c->lastDeliveryTag = 0; + consumers[tag] = c; +} + +void Channel::cancel(std::string& tag, bool synch){ + Consumer* c = consumers[tag]; + if(c->ackMode == LAZY_ACK && c->lastDeliveryTag > 0){ + out->send(new AMQFrame(id, new BasicAckBody(c->lastDeliveryTag, true))); + } + + AMQFrame* frame = new AMQFrame(id, new BasicCancelBody((string&) tag, !synch)); + if(synch){ + sendAndReceive(frame, basic_cancel_ok); + }else{ + out->send(frame); + } + consumers.erase(tag); + if(c != 0){ + delete c; + } +} + +void Channel::cancelAll(){ + int count(consumers.size()); + for(consumer_iterator i = consumers.begin(); i != consumers.end(); i = consumers.begin()){ + Consumer* c = i->second; + if((c->ackMode == LAZY_ACK || c->ackMode == AUTO_ACK) && c->lastDeliveryTag > 0){ + out->send(new AMQFrame(id, new BasicAckBody(c->lastDeliveryTag, true))); + } + consumers.erase(i); + delete c; + } +} + +void Channel::retrieve(Message& msg){ + retrievalMonitor->acquire(); + while(retrieved == 0){ + retrievalMonitor->wait(); + } + + msg.header = retrieved->getHeader(); + msg.deliveryTag = retrieved->getDeliveryTag(); + retrieved->getData(msg.data); + delete retrieved; + retrieved = 0; + + retrievalMonitor->release(); +} + +bool Channel::get(Message& msg, const Queue& queue, int ackMode){ + string name = queue.getName(); + AMQFrame* frame = new AMQFrame(id, new BasicGetBody(0, name, ackMode)); + responses.expect(); + out->send(frame); + responses.waitForResponse(); + AMQMethodBody::shared_ptr response = responses.getResponse(); + if(basic_get_ok.match(response.get())){ + if(incoming != 0){ + std::cout << "Existing message not complete" << std::endl; + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Existing message not complete"); + }else{ + incoming = new IncomingMessage(dynamic_pointer_cast(response)); + } + retrieve(msg); + return true; + }if(basic_get_empty.match(response.get())){ + return false; + }else{ + THROW_QPID_ERROR(PROTOCOL_ERROR + 500, "Unexpected response to basic.get."); + } +} + + +void Channel::publish(Message& msg, const Exchange& exchange, const std::string& routingKey, bool mandatory, bool immediate){ + string e = exchange.getName(); + string key = routingKey; + + out->send(new AMQFrame(id, new BasicPublishBody(0, e, key, mandatory, immediate))); + //break msg up into header frame and content frame(s) and send these + string data = msg.getData(); + msg.header->setContentSize(data.length()); + AMQBody::shared_ptr body(static_pointer_cast(msg.header)); + out->send(new AMQFrame(id, body)); + + int data_length = data.length(); + if(data_length > 0){ + //TODO fragmentation of messages, need to know max frame size for connection + int frag_size = con->getMaxFrameSize() - 4; + if(data_length < frag_size){ + out->send(new AMQFrame(id, new AMQContentBody(data))); + }else{ + int frag_count = data_length / frag_size; + for(int i = 0; i < frag_count; i++){ + int pos = i*frag_size; + int len = i < frag_count - 1 ? frag_size : data_length - pos; + string frag(data.substr(pos, len)); + out->send(new AMQFrame(id, new AMQContentBody(frag))); + } + } + } +} + +void Channel::commit(){ + AMQFrame* frame = new AMQFrame(id, new TxCommitBody()); + sendAndReceive(frame, tx_commit_ok); +} + +void Channel::rollback(){ + AMQFrame* frame = new AMQFrame(id, new TxRollbackBody()); + sendAndReceive(frame, tx_rollback_ok); +} + +void Channel::handleMethod(AMQMethodBody::shared_ptr body){ + //channel.flow, channel.close, basic.deliver, basic.return or a response to a synchronous request + if(responses.isWaiting()){ + responses.signalResponse(body); + }else if(basic_deliver.match(body.get())){ + if(incoming != 0){ + std::cout << "Existing message not complete [deliveryTag=" << incoming->getDeliveryTag() << "]" << std::endl; + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Existing message not complete"); + }else{ + incoming = new IncomingMessage(dynamic_pointer_cast(body)); + } + }else if(basic_return.match(body.get())){ + if(incoming != 0){ + std::cout << "Existing message not complete" << std::endl; + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Existing message not complete"); + }else{ + incoming = new IncomingMessage(dynamic_pointer_cast(body)); + } + }else if(channel_close.match(body.get())){ + con->removeChannel(this); + //need to signal application that channel has been closed through exception + + }else if(channel_flow.match(body.get())){ + + }else{ + //signal error + std::cout << "Unhandled method: " << *body << std::endl; + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Unhandled method"); + } +} + +void Channel::handleHeader(AMQHeaderBody::shared_ptr body){ + if(incoming == 0){ + //handle invalid frame sequence + std::cout << "Invalid message sequence: got header before return or deliver." << std::endl; + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Invalid message sequence: got header before return or deliver."); + }else{ + incoming->setHeader(body); + if(incoming->isComplete()){ + enqueue(); + } + } +} + +void Channel::handleContent(AMQContentBody::shared_ptr body){ + if(incoming == 0){ + //handle invalid frame sequence + std::cout << "Invalid message sequence: got content before return or deliver." << std::endl; + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Invalid message sequence: got content before return or deliver."); + }else{ + incoming->addContent(body); + if(incoming->isComplete()){ + enqueue(); + } + } +} + +void Channel::handleHeartbeat(AMQHeartbeatBody::shared_ptr body){ + THROW_QPID_ERROR(PROTOCOL_ERROR + 504, "Channel received heartbeat"); +} + +void Channel::start(){ + dispatcher = threadFactory->create(this); + dispatcher->start(); +} + +void Channel::stop(){ + closed = true; + dispatchMonitor->acquire(); + dispatchMonitor->notify(); + dispatchMonitor->release(); + if(dispatcher){ + dispatcher->join(); + } +} + +void Channel::run(){ + dispatch(); +} + +void Channel::enqueue(){ + if(incoming->isResponse()){ + retrievalMonitor->acquire(); + retrieved = incoming; + retrievalMonitor->notify(); + retrievalMonitor->release(); + }else{ + dispatchMonitor->acquire(); + messages.push(incoming); + dispatchMonitor->notify(); + dispatchMonitor->release(); + } + incoming = 0; +} + +IncomingMessage* Channel::dequeue(){ + dispatchMonitor->acquire(); + while(messages.empty() && !closed){ + dispatchMonitor->wait(); + } + IncomingMessage* msg = 0; + if(!messages.empty()){ + msg = messages.front(); + messages.pop(); + } + dispatchMonitor->release(); + return msg; +} + +void Channel::deliver(Consumer* consumer, Message& msg){ + //record delivery tag: + consumer->lastDeliveryTag = msg.getDeliveryTag(); + + //allow registered listener to handle the message + consumer->listener->received(msg); + + //if the handler calls close on the channel or connection while + //handling this message, then consumer will now have been deleted. + if(!closed){ + bool multiple(false); + switch(consumer->ackMode){ + case LAZY_ACK: + multiple = true; + if(++(consumer->count) < prefetch) break; + //else drop-through + case AUTO_ACK: + out->send(new AMQFrame(id, new BasicAckBody(msg.getDeliveryTag(), multiple))); + consumer->lastDeliveryTag = 0; + } + } + + //as it stands, transactionality is entirely orthogonal to ack + //mode, though the acks will not be processed by the broker under + //a transaction until it commits. +} + +void Channel::dispatch(){ + while(!closed){ + IncomingMessage* incomingMsg = dequeue(); + if(incomingMsg){ + //Note: msg is currently only valid for duration of this call + Message msg(incomingMsg->getHeader()); + incomingMsg->getData(msg.data); + if(incomingMsg->isReturn()){ + if(returnsHandler == 0){ + //print warning to log/console + std::cout << "Message returned: " << msg.getData() << std::endl; + }else{ + returnsHandler->returned(msg); + } + }else{ + msg.deliveryTag = incomingMsg->getDeliveryTag(); + std::string tag = incomingMsg->getConsumerTag(); + + if(consumers[tag] == 0){ + //signal error + std::cout << "Unknown consumer: " << tag << std::endl; + }else{ + deliver(consumers[tag], msg); + } + } + delete incomingMsg; + } + } +} + +void Channel::setReturnedMessageHandler(ReturnedMessageHandler* handler){ + returnsHandler = handler; +} + +void Channel::sendAndReceive(AMQFrame* frame, const AMQMethodBody& body){ + responses.expect(); + out->send(frame); + responses.receive(body); +} + +void Channel::close(){ + if(con != 0){ + con->closeChannel(this); + } +} diff --git a/cpp/client/src/Connection.cpp b/cpp/client/src/Connection.cpp new file mode 100644 index 0000000000..eeb2330561 --- /dev/null +++ b/cpp/client/src/Connection.cpp @@ -0,0 +1,237 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Connection.h" +#include "Channel.h" +#include "ConnectorImpl.h" +#include "Message.h" +#include "QpidError.h" +#include + +using namespace qpid::client; +using namespace qpid::framing; +using namespace qpid::io; +using namespace qpid::concurrent; + +u_int16_t Connection::channelIdCounter; + +Connection::Connection(bool debug, u_int32_t _max_frame_size) : max_frame_size(_max_frame_size), closed(true){ + connector = new ConnectorImpl(debug, _max_frame_size); +} + +Connection::~Connection(){ + delete connector; +} + +void Connection::open(const std::string& host, int port, const std::string& uid, const std::string& pwd, const std::string& virtualhost){ + this->host = host; + this->port = port; + connector->setInputHandler(this); + connector->setTimeoutHandler(this); + connector->setShutdownHandler(this); + out = connector->getOutputHandler(); + connector->connect(host, port); + + ProtocolInitiation* header = new ProtocolInitiation(8, 0); + responses.expect(); + connector->init(header); + responses.receive(connection_start); + + FieldTable props; + string mechanism("PLAIN"); + string response = ((char)0) + uid + ((char)0) + pwd; + string locale("en_US"); + responses.expect(); + out->send(new AMQFrame(0, new ConnectionStartOkBody(props, mechanism, response, locale))); + + /** + * Assume for now that further challenges will not be required + //receive connection.secure + responses.receive(connection_secure)); + //send connection.secure-ok + out->send(new AMQFrame(0, new ConnectionSecureOkBody(response))); + **/ + + responses.receive(connection_tune); + + ConnectionTuneBody::shared_ptr proposal = std::tr1::dynamic_pointer_cast(responses.getResponse()); + out->send(new AMQFrame(0, new ConnectionTuneOkBody(proposal->getChannelMax(), max_frame_size, proposal->getHeartbeat()))); + + u_int16_t heartbeat = proposal->getHeartbeat(); + connector->setReadTimeout(heartbeat * 2); + connector->setWriteTimeout(heartbeat); + + //send connection.open + string capabilities; + string vhost = virtualhost; + responses.expect(); + out->send(new AMQFrame(0, new ConnectionOpenBody(vhost, capabilities, true))); + //receive connection.open-ok (or redirect, but ignore that for now esp. as using force=true). + responses.waitForResponse(); + if(responses.validate(connection_open_ok)){ + //ok + }else if(responses.validate(connection_redirect)){ + //ignore for now + ConnectionRedirectBody::shared_ptr redirect(std::tr1::dynamic_pointer_cast(responses.getResponse())); + std::cout << "Received redirection to " << redirect->getHost() << std::endl; + }else{ + THROW_QPID_ERROR(PROTOCOL_ERROR, "Bad response"); + } + +} + +void Connection::close(){ + if(!closed){ + u_int16_t code(200); + string text("Ok"); + u_int16_t classId(0); + u_int16_t methodId(0); + + sendAndReceive(new AMQFrame(0, new ConnectionCloseBody(code, text, classId, methodId)), connection_close_ok); + connector->close(); + } +} + +void Connection::openChannel(Channel* channel){ + channel->con = this; + channel->id = ++channelIdCounter; + channel->out = out; + channels[channel->id] = channel; + //now send frame to open channel and wait for response + string oob; + channel->sendAndReceive(new AMQFrame(channel->id, new ChannelOpenBody(oob)), channel_open_ok); + channel->setQos(); + channel->closed = false; +} + +void Connection::closeChannel(Channel* channel){ + //send frame to close channel + u_int16_t code(200); + string text("Ok"); + u_int16_t classId(0); + u_int16_t methodId(0); + closeChannel(channel, code, text, classId, methodId); +} + +void Connection::closeChannel(Channel* channel, u_int16_t code, string& text, u_int16_t classId, u_int16_t methodId){ + //send frame to close channel + channel->cancelAll(); + channel->closed = true; + channel->sendAndReceive(new AMQFrame(channel->id, new ChannelCloseBody(code, text, classId, methodId)), channel_close_ok); + channel->con = 0; + channel->out = 0; + removeChannel(channel); +} + +void Connection::removeChannel(Channel* channel){ + //send frame to close channel + + channels.erase(channel->id); + channel->out = 0; + channel->id = 0; + channel->con = 0; +} + +void Connection::received(AMQFrame* frame){ + u_int16_t channelId = frame->getChannel(); + + if(channelId == 0){ + this->handleBody(frame->getBody()); + }else{ + Channel* channel = channels[channelId]; + if(channel == 0){ + error(504, "Unknown channel"); + }else{ + try{ + channel->handleBody(frame->getBody()); + }catch(qpid::QpidError e){ + channelException(channel, dynamic_cast(frame->getBody().get()), e); + } + } + } +} + +void Connection::handleMethod(AMQMethodBody::shared_ptr body){ + //connection.close, basic.deliver, basic.return or a response to a synchronous request + if(responses.isWaiting()){ + responses.signalResponse(body); + }else if(connection_close.match(body.get())){ + //send back close ok + //close socket + ConnectionCloseBody* request = dynamic_cast(body.get()); + std::cout << "Connection closed by server: " << request->getReplyCode() << ":" << request->getReplyText() << std::endl; + connector->close(); + }else{ + std::cout << "Unhandled method for connection: " << *body << std::endl; + error(504, "Unrecognised method", body->amqpClassId(), body->amqpMethodId()); + } +} + +void Connection::handleHeader(AMQHeaderBody::shared_ptr body){ + error(504, "Channel error: received header body with channel 0."); +} + +void Connection::handleContent(AMQContentBody::shared_ptr body){ + error(504, "Channel error: received content body with channel 0."); +} + +void Connection::handleHeartbeat(AMQHeartbeatBody::shared_ptr body){ +} + +void Connection::sendAndReceive(AMQFrame* frame, const AMQMethodBody& body){ + responses.expect(); + out->send(frame); + responses.receive(body); +} + +void Connection::error(int code, const string& msg, int classid, int methodid){ + std::cout << "Connection exception generated: " << code << msg; + if(classid || methodid){ + std::cout << " [" << methodid << ":" << classid << "]"; + } + std::cout << std::endl; + sendAndReceive(new AMQFrame(0, new ConnectionCloseBody(code, (string&) msg, classid, methodid)), connection_close_ok); + connector->close(); +} + +void Connection::channelException(Channel* channel, AMQMethodBody* method, QpidError& e){ + std::cout << "Caught error from channel [" << e.code << "] " << e.msg << " (" << e.file << ":" << e.line << ")" << std::endl; + int code = e.code == PROTOCOL_ERROR ? e.code - PROTOCOL_ERROR : 500; + string msg = e.msg; + if(method == 0){ + closeChannel(channel, code, msg); + }else{ + closeChannel(channel, code, msg, method->amqpClassId(), method->amqpMethodId()); + } +} + +void Connection::idleIn(){ + std::cout << "Connection timed out due to abscence of heartbeat." << std::endl; + connector->close(); +} + +void Connection::idleOut(){ + out->send(new AMQFrame(0, new AMQHeartbeatBody())); +} + +void Connection::shutdown(){ + closed = true; + //close all channels + for(iterator i = channels.begin(); i != channels.end(); i++){ + i->second->stop(); + } +} diff --git a/cpp/client/src/Exchange.cpp b/cpp/client/src/Exchange.cpp new file mode 100644 index 0000000000..681068dc4c --- /dev/null +++ b/cpp/client/src/Exchange.cpp @@ -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. + * + */ +#include "Exchange.h" + +qpid::client::Exchange::Exchange(std::string _name, std::string _type) : name(_name), type(_type){} +const std::string& qpid::client::Exchange::getName() const { return name; } +const std::string& qpid::client::Exchange::getType() const { return type; } + +const std::string qpid::client::Exchange::DIRECT_EXCHANGE = "direct"; +const std::string qpid::client::Exchange::TOPIC_EXCHANGE = "topic"; +const std::string qpid::client::Exchange::HEADERS_EXCHANGE = "headers"; + +const qpid::client::Exchange qpid::client::Exchange::DEFAULT_DIRECT_EXCHANGE("amq.direct", DIRECT_EXCHANGE); +const qpid::client::Exchange qpid::client::Exchange::DEFAULT_TOPIC_EXCHANGE("amq.topic", TOPIC_EXCHANGE); +const qpid::client::Exchange qpid::client::Exchange::DEFAULT_HEADERS_EXCHANGE("amq.headers", HEADERS_EXCHANGE); diff --git a/cpp/client/src/IncomingMessage.cpp b/cpp/client/src/IncomingMessage.cpp new file mode 100644 index 0000000000..8e2604c4cb --- /dev/null +++ b/cpp/client/src/IncomingMessage.cpp @@ -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. + * + */ +#include "IncomingMessage.h" +#include "QpidError.h" +#include + +using namespace qpid::client; +using namespace qpid::framing; + +IncomingMessage::IncomingMessage(BasicDeliverBody::shared_ptr intro) : delivered(intro){} +IncomingMessage::IncomingMessage(BasicReturnBody::shared_ptr intro): returned(intro){} +IncomingMessage::IncomingMessage(BasicGetOkBody::shared_ptr intro): response(intro){} + +IncomingMessage::~IncomingMessage(){ +} + +void IncomingMessage::setHeader(AMQHeaderBody::shared_ptr header){ + this->header = header; +} + +void IncomingMessage::addContent(AMQContentBody::shared_ptr content){ + this->content.push_back(content); +} + +bool IncomingMessage::isComplete(){ + return header != 0 && header->getContentSize() == contentSize(); +} + +bool IncomingMessage::isReturn(){ + return returned; +} + +bool IncomingMessage::isDelivery(){ + return delivered; +} + +bool IncomingMessage::isResponse(){ + return response; +} + +string& IncomingMessage::getConsumerTag(){ + if(!isDelivery()) THROW_QPID_ERROR(CLIENT_ERROR, "Consumer tag only valid for delivery"); + return delivered->getConsumerTag(); +} + +u_int64_t IncomingMessage::getDeliveryTag(){ + if(!isDelivery()) THROW_QPID_ERROR(CLIENT_ERROR, "Delivery tag only valid for delivery"); + return delivered->getDeliveryTag(); +} + +AMQHeaderBody::shared_ptr& IncomingMessage::getHeader(){ + return header; +} + +void IncomingMessage::getData(string& s){ + int count(content.size()); + for(int i = 0; i < count; i++){ + if(i == 0) s = content[i]->getData(); + else s += content[i]->getData(); + } +} + +long IncomingMessage::contentSize(){ + long size(0); + int count(content.size()); + for(int i = 0; i < count; i++){ + size += content[i]->size(); + } + return size; +} diff --git a/cpp/client/src/Message.cpp b/cpp/client/src/Message.cpp new file mode 100644 index 0000000000..71befe57b1 --- /dev/null +++ b/cpp/client/src/Message.cpp @@ -0,0 +1,147 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Message.h" + +using namespace qpid::client; +using namespace qpid::framing; + +Message::Message(){ + header = AMQHeaderBody::shared_ptr(new AMQHeaderBody(BASIC)); +} + +Message::Message(AMQHeaderBody::shared_ptr& _header) : header(_header){ +} + +Message::~Message(){ +} + +BasicHeaderProperties* Message::getHeaderProperties(){ + return dynamic_cast(header->getProperties()); +} + +std::string& Message::getContentType(){ + return getHeaderProperties()->getContentType(); +} + +std::string& Message::getContentEncoding(){ + return getHeaderProperties()->getContentEncoding(); +} + +FieldTable& Message::getHeaders(){ + return getHeaderProperties()->getHeaders(); +} + +u_int8_t Message::getDeliveryMode(){ + return getHeaderProperties()->getDeliveryMode(); +} + +u_int8_t Message::getPriority(){ + return getHeaderProperties()->getPriority(); +} + +std::string& Message::getCorrelationId(){ + return getHeaderProperties()->getCorrelationId(); +} + +std::string& Message::getReplyTo(){ + return getHeaderProperties()->getReplyTo(); +} + +std::string& Message::getExpiration(){ + return getHeaderProperties()->getExpiration(); +} + +std::string& Message::getMessageId(){ + return getHeaderProperties()->getMessageId(); +} + +u_int64_t Message::getTimestamp(){ + return getHeaderProperties()->getTimestamp(); +} + +std::string& Message::getType(){ + return getHeaderProperties()->getType(); +} + +std::string& Message::getUserId(){ + return getHeaderProperties()->getUserId(); +} + +std::string& Message::getAppId(){ + return getHeaderProperties()->getAppId(); +} + +std::string& Message::getClusterId(){ + return getHeaderProperties()->getClusterId(); +} + +void Message::setContentType(std::string& type){ + getHeaderProperties()->setContentType(type); +} + +void Message::setContentEncoding(std::string& encoding){ + getHeaderProperties()->setContentEncoding(encoding); +} + +void Message::setHeaders(FieldTable& headers){ + getHeaderProperties()->setHeaders(headers); +} + +void Message::setDeliveryMode(u_int8_t mode){ + getHeaderProperties()->setDeliveryMode(mode); +} + +void Message::setPriority(u_int8_t priority){ + getHeaderProperties()->setPriority(priority); +} + +void Message::setCorrelationId(std::string& correlationId){ + getHeaderProperties()->setCorrelationId(correlationId); +} + +void Message::setReplyTo(std::string& replyTo){ + getHeaderProperties()->setReplyTo(replyTo); +} + +void Message::setExpiration(std::string& expiration){ + getHeaderProperties()->setExpiration(expiration); +} + +void Message::setMessageId(std::string& messageId){ + getHeaderProperties()->setMessageId(messageId); +} + +void Message::setTimestamp(u_int64_t timestamp){ + getHeaderProperties()->setTimestamp(timestamp); +} + +void Message::setType(std::string& type){ + getHeaderProperties()->setType(type); +} + +void Message::setUserId(std::string& userId){ + getHeaderProperties()->setUserId(userId); +} + +void Message::setAppId(std::string& appId){ + getHeaderProperties()->setAppId(appId); +} + +void Message::setClusterId(std::string& clusterId){ + getHeaderProperties()->setClusterId(clusterId); +} diff --git a/cpp/client/src/Queue.cpp b/cpp/client/src/Queue.cpp new file mode 100644 index 0000000000..cb957dd993 --- /dev/null +++ b/cpp/client/src/Queue.cpp @@ -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. + * + */ +#include "Queue.h" + +qpid::client::Queue::Queue() : name(""), autodelete(true), exclusive(true){} + +qpid::client::Queue::Queue(std::string _name) : name(_name), autodelete(false), exclusive(false){} + +qpid::client::Queue::Queue(std::string _name, bool temp) : name(_name), autodelete(temp), exclusive(temp){} + +qpid::client::Queue::Queue(std::string _name, bool _autodelete, bool _exclusive) + : name(_name), autodelete(_autodelete), exclusive(_exclusive){} + +const std::string& qpid::client::Queue::getName() const{ + return name; +} + +void qpid::client::Queue::setName(const std::string& name){ + this->name = name; +} + +bool qpid::client::Queue::isAutoDelete() const{ + return autodelete; +} + +bool qpid::client::Queue::isExclusive() const{ + return exclusive; +} + + + + diff --git a/cpp/client/src/ResponseHandler.cpp b/cpp/client/src/ResponseHandler.cpp new file mode 100644 index 0000000000..837bba37fd --- /dev/null +++ b/cpp/client/src/ResponseHandler.cpp @@ -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. + * + */ +#include "ResponseHandler.h" +#include "MonitorImpl.h" +#include "QpidError.h" + +qpid::client::ResponseHandler::ResponseHandler() : waiting(false){ + monitor = new qpid::concurrent::MonitorImpl(); +} + +qpid::client::ResponseHandler::~ResponseHandler(){ + delete monitor; +} + +bool qpid::client::ResponseHandler::validate(const qpid::framing::AMQMethodBody& expected){ + return expected.match(response.get()); +} + +void qpid::client::ResponseHandler::waitForResponse(){ + monitor->acquire(); + if(waiting){ + monitor->wait(); + } + monitor->release(); +} + +void qpid::client::ResponseHandler::signalResponse(qpid::framing::AMQMethodBody::shared_ptr response){ + this->response = response; + monitor->acquire(); + waiting = false; + monitor->notify(); + monitor->release(); +} + +void qpid::client::ResponseHandler::receive(const qpid::framing::AMQMethodBody& expected){ + monitor->acquire(); + if(waiting){ + monitor->wait(); + } + monitor->release(); + if(!validate(expected)){ + THROW_QPID_ERROR(PROTOCOL_ERROR, "Protocol Error"); + } +} + +void qpid::client::ResponseHandler::expect(){ + waiting = true; +} diff --git a/cpp/client/test/Makefile b/cpp/client/test/Makefile new file mode 100644 index 0000000000..f35aab3e17 --- /dev/null +++ b/cpp/client/test/Makefile @@ -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. +# + +QPID_HOME = ../../.. +include ${QPID_HOME}/cpp/options.mk + +# TODO aconway 2006-09-12: These are system tests, not unit tests. +# We need client side unit tests. +# We should separate them from the system tets. +# We need an approach to automate the C++ client/server system tests. +# + +SOURCES=$(wildcard *.cpp) +TESTS=$(SOURCES:.cpp=) +DEPS= $(SOURCES:.cpp=.d) + +INCLUDES = $(TEST_INCLUDES) +LDLIBS= -lapr-1 $(COMMON_LIB) $(CLIENT_LIB) + +.PHONY: all clean + +all: $(TESTS) + +clean: + -@rm -f $(TESTS) $(DEPS) + +# Rule to build test programs. +%: %.cpp + $(CXX) -o $@ $< $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) + +# Dependencies +-include $(DEPS) diff --git a/cpp/client/test/client_test.cpp b/cpp/client/test/client_test.cpp new file mode 100644 index 0000000000..e33beb3b67 --- /dev/null +++ b/cpp/client/test/client_test.cpp @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include + +#include "QpidError.h" +#include "Channel.h" +#include "Connection.h" +#include "FieldTable.h" +#include "Message.h" +#include "MessageListener.h" + +#include "MonitorImpl.h" + + +using namespace qpid::client; +using namespace qpid::concurrent; + +class SimpleListener : public virtual MessageListener{ + Monitor* monitor; + +public: + inline SimpleListener(Monitor* _monitor) : monitor(_monitor){} + + inline virtual void received(Message& msg){ + std::cout << "Received message " /**<< msg **/<< std::endl; + monitor->acquire(); + monitor->notify(); + monitor->release(); + } +}; + +int main(int argc, char** argv) +{ + try{ + Connection con(argc > 1); + Channel channel; + Exchange exchange("MyExchange", Exchange::TOPIC_EXCHANGE); + Queue queue("MyQueue", true); + + string host("localhost"); + + con.open(host); + std::cout << "Opened connection." << std::endl; + con.openChannel(&channel); + std::cout << "Opened channel." << std::endl; + channel.declareExchange(exchange); + std::cout << "Declared exchange." << std::endl; + channel.declareQueue(queue); + std::cout << "Declared queue." << std::endl; + qpid::framing::FieldTable args; + channel.bind(exchange, queue, "MyTopic", args); + std::cout << "Bound queue to exchange." << std::endl; + + //set up a message listener + MonitorImpl monitor; + SimpleListener listener(&monitor); + string tag("MyTag"); + channel.consume(queue, tag, &listener); + channel.start(); + std::cout << "Registered consumer." << std::endl; + + Message msg; + string data("MyMessage"); + msg.setData(data); + channel.publish(msg, exchange, "MyTopic"); + std::cout << "Published message." << std::endl; + + monitor.acquire(); + monitor.wait(); + monitor.release(); + + + con.closeChannel(&channel); + std::cout << "Closed channel." << std::endl; + con.close(); + std::cout << "Closed connection." << std::endl; + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + return 1; + } + return 0; +} diff --git a/cpp/client/test/topic_listener.cpp b/cpp/client/test/topic_listener.cpp new file mode 100644 index 0000000000..707b3443a1 --- /dev/null +++ b/cpp/client/test/topic_listener.cpp @@ -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. + * + */ +#include +#include +#include "apr_time.h" +#include "QpidError.h" +#include "Channel.h" +#include "Connection.h" +#include "Exchange.h" +#include "MessageListener.h" +#include "Queue.h" + +using namespace qpid::client; + +class Listener : public MessageListener{ + Channel* const channel; + const std::string responseQueue; + const bool transactional; + bool init; + int count; + apr_time_t start; + + void shutdown(); + void report(); +public: + Listener(Channel* channel, const std::string& reponseQueue, bool tx); + virtual void received(Message& msg); +}; + +class Args{ + string host; + int port; + int ackMode; + bool transactional; + int prefetch; + bool trace; + bool help; +public: + inline Args() : host("localhost"), port(5672), ackMode(NO_ACK), transactional(false), prefetch(1000), trace(false), help(false){} + void parse(int argc, char** argv); + void usage(); + + inline const string& getHost() const { return host;} + inline int getPort() const { return port; } + inline int getAckMode(){ return ackMode; } + inline bool getTransactional() const { return transactional; } + inline int getPrefetch(){ return prefetch; } + inline bool getTrace() const { return trace; } + inline bool getHelp() const { return help; } +}; + +int main(int argc, char** argv){ + Args args; + args.parse(argc, argv); + if(args.getHelp()){ + args.usage(); + }else{ + try{ + Connection connection(args.getTrace()); + connection.open(args.getHost(), args.getPort()); + Channel channel(args.getTransactional(), args.getPrefetch()); + connection.openChannel(&channel); + + //declare exchange, queue and bind them: + Queue response("response"); + channel.declareQueue(response); + + Queue control; + channel.declareQueue(control); + qpid::framing::FieldTable bindArgs; + channel.bind(Exchange::DEFAULT_TOPIC_EXCHANGE, control, "topic_control", bindArgs); + //set up listener + Listener listener(&channel, response.getName(), args.getTransactional()); + std::string tag; + channel.consume(control, tag, &listener, args.getAckMode()); + channel.run(); + connection.close(); + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } + } +} + +Listener::Listener(Channel* _channel, const std::string& _responseq, bool tx) : + channel(_channel), responseQueue(_responseq), transactional(tx), init(false), count(0){} + +void Listener::received(Message& message){ + if(!init){ + start = apr_time_as_msec(apr_time_now()); + count = 0; + init = true; + } + std::string type(message.getHeaders().getString("TYPE")); + + if(type == "TERMINATION_REQUEST"){ + shutdown(); + }else if(type == "REPORT_REQUEST"){ + //send a report: + report(); + init = false; + }else if (++count % 100 == 0){ + std::cout <<"Received " << count << " messages." << std::endl; + } +} + +void Listener::shutdown(){ + channel->close(); +} + +void Listener::report(){ + apr_time_t finish = apr_time_as_msec(apr_time_now()); + apr_time_t time = finish - start; + std::stringstream report; + report << "Received " << count << " messages in " << time << " ms."; + Message msg; + msg.setData(report.str()); + channel->publish(msg, Exchange::DEFAULT_DIRECT_EXCHANGE, responseQueue); + if(transactional){ + channel->commit(); + } +} + + +void Args::parse(int argc, char** argv){ + for(int i = 1; i < argc; i++){ + string name(argv[i]); + if("-help" == name){ + help = true; + break; + }else if("-host" == name){ + host = argv[++i]; + }else if("-port" == name){ + port = atoi(argv[++i]); + }else if("-ack_mode" == name){ + ackMode = atoi(argv[++i]); + }else if("-transactional" == name){ + transactional = true; + }else if("-prefetch" == name){ + prefetch = atoi(argv[++i]); + }else if("-trace" == name){ + trace = true; + }else{ + std::cout << "Warning: unrecognised option " << name << std::endl; + } + } +} + +void Args::usage(){ + std::cout << "Options:" << std::endl; + std::cout << " -help" << std::endl; + std::cout << " Prints this usage message" << std::endl; + std::cout << " -host " << std::endl; + std::cout << " Specifies host to connect to (default is localhost)" << std::endl; + std::cout << " -port " << std::endl; + std::cout << " Specifies port to conect to (default is 5762)" << std::endl; + std::cout << " -ack_mode " << std::endl; + std::cout << " Sets the acknowledgement mode" << std::endl; + std::cout << " 0=NO_ACK (default), 1=AUTO_ACK, 2=LAZY_ACK" << std::endl; + std::cout << " -transactional" << std::endl; + std::cout << " Indicates the client should use transactions" << std::endl; + std::cout << " -prefetch " << std::endl; + std::cout << " Specifies the prefetch count (default is 1000)" << std::endl; + std::cout << " -trace" << std::endl; + std::cout << " Indicates that the frames sent and received should be logged" << std::endl; +} diff --git a/cpp/client/test/topic_publisher.cpp b/cpp/client/test/topic_publisher.cpp new file mode 100644 index 0000000000..fc6b7f3b30 --- /dev/null +++ b/cpp/client/test/topic_publisher.cpp @@ -0,0 +1,253 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include +#include "unistd.h" +#include "apr_time.h" +#include "MonitorImpl.h" +#include "QpidError.h" +#include "Channel.h" +#include "Connection.h" +#include "Exchange.h" +#include "MessageListener.h" +#include "Queue.h" + +using namespace qpid::client; +using namespace qpid::concurrent; + +class Publisher : public MessageListener{ + Channel* const channel; + const std::string controlTopic; + const bool transactional; + MonitorImpl monitor; + int count; + + void waitForCompletion(int msgs); + string generateData(int size); + +public: + Publisher(Channel* channel, const std::string& controlTopic, bool tx); + virtual void received(Message& msg); + apr_time_t publish(int msgs, int listeners, int size); + void terminate(); +}; + +class Args{ + string host; + int port; + int messages; + int subscribers; + int ackMode; + bool transactional; + int prefetch; + int batches; + int delay; + int size; + bool trace; + bool help; +public: + inline Args() : host("localhost"), port(5672), messages(1000), subscribers(1), + ackMode(NO_ACK), transactional(false), prefetch(1000), batches(1), + delay(0), size(256), trace(false), help(false){} + + void parse(int argc, char** argv); + void usage(); + + inline const string& getHost() const { return host;} + inline int getPort() const { return port; } + inline int getMessages() const { return messages; } + inline int getSubscribers() const { return subscribers; } + inline int getAckMode(){ return ackMode; } + inline bool getTransactional() const { return transactional; } + inline int getPrefetch(){ return prefetch; } + inline int getBatches(){ return batches; } + inline int getDelay(){ return delay; } + inline int getSize(){ return size; } + inline bool getTrace() const { return trace; } + inline bool getHelp() const { return help; } +}; + +int main(int argc, char** argv){ + Args args; + args.parse(argc, argv); + if(args.getHelp()){ + args.usage(); + }else{ + try{ + Connection connection(args.getTrace()); + connection.open(args.getHost(), args.getPort()); + Channel channel(args.getTransactional(), args.getPrefetch()); + connection.openChannel(&channel); + + //declare queue (relying on default binding): + Queue response("response"); + channel.declareQueue(response); + + //set up listener + Publisher publisher(&channel, "topic_control", args.getTransactional()); + std::string tag("mytag"); + channel.consume(response, tag, &publisher, args.getAckMode()); + channel.start(); + + int batchSize(args.getBatches()); + apr_time_t max(0); + apr_time_t min(0); + apr_time_t sum(0); + for(int i = 0; i < batchSize; i++){ + if(i > 0 && args.getDelay()) sleep(args.getDelay()); + apr_time_t time = publisher.publish(args.getMessages(), args.getSubscribers(), args.getSize()); + if(!max || time > max) max = time; + if(!min || time < min) min = time; + sum += time; + std::cout << "Completed " << (i+1) << " of " << batchSize << " in " << time << "ms" << std::endl; + } + publisher.terminate(); + apr_time_t avg = sum / batchSize; + if(batchSize > 1){ + std::cout << batchSize << " batches completed. avg=" << avg << + ", max=" << max << ", min=" << min << std::endl; + } + channel.close(); + connection.close(); + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } + } +} + +Publisher::Publisher(Channel* _channel, const std::string& _controlTopic, bool tx) : + channel(_channel), controlTopic(_controlTopic), transactional(tx){} + +void Publisher::received(Message& msg){ + //count responses and when all are received end the current batch + monitor.acquire(); + if(--count == 0){ + monitor.notify(); + } + std::cout << "Received report: " << msg.getData() << " (" << count << " remaining)." << std::endl; + monitor.release(); +} + +void Publisher::waitForCompletion(int msgs){ + count = msgs; + monitor.wait(); +} + +apr_time_t Publisher::publish(int msgs, int listeners, int size){ + monitor.acquire(); + Message msg; + msg.setData(generateData(size)); + apr_time_t start(apr_time_as_msec(apr_time_now())); + for(int i = 0; i < msgs; i++){ + channel->publish(msg, Exchange::DEFAULT_TOPIC_EXCHANGE, controlTopic); + } + //send report request + Message reportRequest; + reportRequest.getHeaders().setString("TYPE", "REPORT_REQUEST"); + channel->publish(reportRequest, Exchange::DEFAULT_TOPIC_EXCHANGE, controlTopic); + if(transactional){ + channel->commit(); + } + + waitForCompletion(listeners); + monitor.release(); + apr_time_t finish(apr_time_as_msec(apr_time_now())); + + return finish - start; +} + +string Publisher::generateData(int size){ + string data; + for(int i = 0; i < size; i++){ + data += ('A' + (i / 26)); + } + return data; +} + +void Publisher::terminate(){ + //send termination request + Message terminationRequest; + terminationRequest.getHeaders().setString("TYPE", "TERMINATION_REQUEST"); + channel->publish(terminationRequest, Exchange::DEFAULT_TOPIC_EXCHANGE, controlTopic); + if(transactional){ + channel->commit(); + } +} + +void Args::parse(int argc, char** argv){ + for(int i = 1; i < argc; i++){ + string name(argv[i]); + if("-help" == name){ + help = true; + break; + }else if("-host" == name){ + host = argv[++i]; + }else if("-port" == name){ + port = atoi(argv[++i]); + }else if("-messages" == name){ + messages = atoi(argv[++i]); + }else if("-subscribers" == name){ + subscribers = atoi(argv[++i]); + }else if("-ack_mode" == name){ + ackMode = atoi(argv[++i]); + }else if("-transactional" == name){ + transactional = true; + }else if("-prefetch" == name){ + prefetch = atoi(argv[++i]); + }else if("-batches" == name){ + batches = atoi(argv[++i]); + }else if("-delay" == name){ + delay = atoi(argv[++i]); + }else if("-size" == name){ + size = atoi(argv[++i]); + }else if("-trace" == name){ + trace = true; + }else{ + std::cout << "Warning: unrecognised option " << name << std::endl; + } + } +} + +void Args::usage(){ + std::cout << "Options:" << std::endl; + std::cout << " -help" << std::endl; + std::cout << " Prints this usage message" << std::endl; + std::cout << " -host " << std::endl; + std::cout << " Specifies host to connect to (default is localhost)" << std::endl; + std::cout << " -port " << std::endl; + std::cout << " Specifies port to conect to (default is 5762)" << std::endl; + std::cout << " -messages " << std::endl; + std::cout << " Specifies how many messages to send" << std::endl; + std::cout << " -subscribers " << std::endl; + std::cout << " Specifies how many subscribers to expect reports from" << std::endl; + std::cout << " -ack_mode " << std::endl; + std::cout << " Sets the acknowledgement mode" << std::endl; + std::cout << " 0=NO_ACK (default), 1=AUTO_ACK, 2=LAZY_ACK" << std::endl; + std::cout << " -transactional" << std::endl; + std::cout << " Indicates the client should use transactions" << std::endl; + std::cout << " -prefetch " << std::endl; + std::cout << " Specifies the prefetch count (default is 1000)" << std::endl; + std::cout << " -batches " << std::endl; + std::cout << " Specifies how many batches to run" << std::endl; + std::cout << " -delay " << std::endl; + std::cout << " Causes a delay between each batch" << std::endl; + std::cout << " -size " << std::endl; + std::cout << " Sets the size of the published messages (default is 256 bytes)" << std::endl; + std::cout << " -trace" << std::endl; + std::cout << " Indicates that the frames sent and received should be logged" << std::endl; +} diff --git a/cpp/common/Makefile b/cpp/common/Makefile new file mode 100644 index 0000000000..5fe815b8da --- /dev/null +++ b/cpp/common/Makefile @@ -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. +# + +# +# Make file to build qpid_common library. +# + +QPID_HOME=../.. +include $(QPID_HOME)/cpp/options.mk + +TARGET = $(LIB_DIR)/libqpid_common.so.1.0 + +CXXFLAGS = $(DEBUG) $(OPT) -MMD -fpic $(COMMON_INCLUDES) + +SOURCES = $(wildcard */src/*.cpp framing/generated/*.cpp) +OBJECTS = $(SOURCES:.cpp=.o) +DEPS = $(SOURCES:.cpp=.d) + +GENERATED_OBJECTS = framing/generated/amqp_methods.o + +.PHONY: all test clean + +# We have to do two separate makes to ensure we pick up all generated files. +all: + @$(MAKE) -C framing all + @make $(TARGET) + +test: + @$(MAKE) -C framing test + +clean: + @$(MAKE) -C framing clean + -@rm -f $(TARGET) $(OBJECTS) $(DEPS) + +$(TARGET): $(OBJECTS) + $(CXX) -shared -o $@ $(OBJECTS) $(LDFLAGS) -lapr-1 + +-include $(DEPS) diff --git a/cpp/common/concurrent/inc/APRBase.h b/cpp/common/concurrent/inc/APRBase.h new file mode 100644 index 0000000000..e0b526faa1 --- /dev/null +++ b/cpp/common/concurrent/inc/APRBase.h @@ -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. + * + */ +#ifndef _APRBase_ +#define _APRBase_ + +#include +#include "apr_thread_mutex.h" +#include "apr_errno.h" + +namespace qpid { +namespace concurrent { + + /** + * Use of APR libraries necessitates explicit init and terminate + * calls. Any class using APR libs should obtain the reference to + * this singleton and increment on construction, decrement on + * destruction. This class can then correctly initialise apr + * before the first use and terminate after the last use. + */ + class APRBase{ + static APRBase* instance; + apr_pool_t* pool; + apr_thread_mutex_t* mutex; + int count; + + APRBase(); + ~APRBase(); + static APRBase* getInstance(); + bool _increment(); + void _decrement(); + public: + static void increment(); + static void decrement(); + }; + + //this is also a convenient place for a helper function for error checking: + void check(apr_status_t status, const std::string& file, const int line); + std::string get_desc(apr_status_t status); + +#define CHECK_APR_SUCCESS(A) check(A, __FILE__, __LINE__); + +} +} + + + + +#endif diff --git a/cpp/common/concurrent/inc/APRMonitor.h b/cpp/common/concurrent/inc/APRMonitor.h new file mode 100644 index 0000000000..bf72596564 --- /dev/null +++ b/cpp/common/concurrent/inc/APRMonitor.h @@ -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. + * + */ +#ifndef _APRMonitor_ +#define _APRMonitor_ + +#include "apr_thread_mutex.h" +#include "apr_thread_cond.h" +#include "Monitor.h" + +namespace qpid { +namespace concurrent { + + class APRMonitor : public virtual Monitor + { + apr_pool_t* pool; + apr_thread_mutex_t* mutex; + apr_thread_cond_t* condition; + + public: + APRMonitor(); + virtual ~APRMonitor(); + virtual void wait(); + virtual void wait(u_int64_t time); + virtual void notify(); + virtual void notifyAll(); + virtual void acquire(); + virtual void release(); + }; +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/APRThread.h b/cpp/common/concurrent/inc/APRThread.h new file mode 100644 index 0000000000..d5034ce3b7 --- /dev/null +++ b/cpp/common/concurrent/inc/APRThread.h @@ -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. + * + */ +#ifndef _APRThread_ +#define _APRThread_ + +#include "apr_thread_proc.h" +#include "APRThread.h" +#include "Runnable.h" +#include "Thread.h" + +namespace qpid { +namespace concurrent { + + class APRThread : public virtual Thread + { + const Runnable* runnable; + apr_pool_t* pool; + apr_thread_t* runner; + + public: + APRThread(apr_pool_t* pool, Runnable* runnable); + virtual ~APRThread(); + virtual void start(); + virtual void join(); + virtual void interrupt(); + static unsigned int currentThread(); + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/APRThreadFactory.h b/cpp/common/concurrent/inc/APRThreadFactory.h new file mode 100644 index 0000000000..87b240025d --- /dev/null +++ b/cpp/common/concurrent/inc/APRThreadFactory.h @@ -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. + * + */ +#ifndef _APRThreadFactory_ +#define _APRThreadFactory_ + +#include "apr_thread_proc.h" + +#include "APRThread.h" +#include "Thread.h" +#include "ThreadFactory.h" +#include "Runnable.h" + +namespace qpid { +namespace concurrent { + + class APRThreadFactory : public virtual ThreadFactory + { + apr_pool_t* pool; + public: + APRThreadFactory(); + virtual ~APRThreadFactory(); + virtual Thread* create(Runnable* runnable); + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/APRThreadPool.h b/cpp/common/concurrent/inc/APRThreadPool.h new file mode 100644 index 0000000000..cf6d30774c --- /dev/null +++ b/cpp/common/concurrent/inc/APRThreadPool.h @@ -0,0 +1,67 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _APRThreadPool_ +#define _APRThreadPool_ + +#include +#include +#include "APRMonitor.h" +#include "Thread.h" +#include "ThreadFactory.h" +#include "ThreadPool.h" +#include "Runnable.h" + +namespace qpid { +namespace concurrent { + + class APRThreadPool : public virtual ThreadPool + { + class Worker : public virtual Runnable{ + APRThreadPool* pool; + public: + inline Worker(APRThreadPool* _pool) : pool(_pool){} + inline virtual void run(){ + while(pool->running){ + pool->runTask(); + } + } + }; + const bool deleteFactory; + const int size; + ThreadFactory* factory; + APRMonitor lock; + std::vector threads; + std::queue tasks; + Worker* worker; + volatile bool running; + + void runTask(); + public: + APRThreadPool(int size); + APRThreadPool(int size, ThreadFactory* factory); + virtual void start(); + virtual void stop(); + virtual void addTask(Runnable* task); + virtual ~APRThreadPool(); + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/LMonitor.h b/cpp/common/concurrent/inc/LMonitor.h new file mode 100644 index 0000000000..8e2569921d --- /dev/null +++ b/cpp/common/concurrent/inc/LMonitor.h @@ -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. + * + */ +#ifndef _LMonitor_ +#define _LMonitor_ + +/* Native Linux Monitor - Based of Kernel patch 19/20 */ + +#include "Monitor.h" + +namespace qpid { +namespace concurrent { + + class LMonitor : public virtual Monitor + { + + public: + LMonitor(); + virtual ~LMonitor(); + virtual void wait(); + virtual void notify(); + virtual void notifyAll(); + virtual void acquire(); + virtual void release(); + }; +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/LThreadFactory.h b/cpp/common/concurrent/inc/LThreadFactory.h new file mode 100644 index 0000000000..4a573d1bd1 --- /dev/null +++ b/cpp/common/concurrent/inc/LThreadFactory.h @@ -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. + * + */ +#ifndef _LAPRThreadFactory_ +#define _LAPRThreadFactory_ + + +namespace qpid { +namespace concurrent { + + class LThreadFactory + { + public: + LThreadFactory(); + virtual ~LThreadFactory(); + virtual Thread* create(Runnable* runnable); + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/LockedQueue.h b/cpp/common/concurrent/inc/LockedQueue.h new file mode 100644 index 0000000000..ef3f0b8381 --- /dev/null +++ b/cpp/common/concurrent/inc/LockedQueue.h @@ -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. + * + */ +#ifndef _LockedQueue_ +#define _LockedQueue_ + +#include +#include "Monitor.h" + +/** + * A threadsafe queue abstraction + */ +namespace qpid { +namespace concurrent { + template class LockedQueue + { + L lock; + std::queue queue; + + public: + void put(T* item); + T* take(); + bool empty(); + }; + + template void LockedQueue::put(T* item){ + lock.acquire(); + queue.push(item); + lock.release(); + } + + template T* LockedQueue::take(){ + lock.acquire(); + T* item = 0; + if(!queue.empty()){ + item = queue.front(); + queue.pop(); + } + lock.release(); + return item; + } + + template bool LockedQueue::empty(){ + lock.acquire(); + bool result = queue.empty(); + lock.release(); + return result; + } + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/Monitor.h b/cpp/common/concurrent/inc/Monitor.h new file mode 100644 index 0000000000..7f1a299c6a --- /dev/null +++ b/cpp/common/concurrent/inc/Monitor.h @@ -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. + * + */ +#ifndef _Monitor_ +#define _Monitor_ + +#include "amqp_types.h" + +namespace qpid { +namespace concurrent { + +class Monitor +{ + public: + virtual ~Monitor(){} + virtual void wait() = 0; + virtual void wait(u_int64_t time) = 0; + virtual void notify() = 0; + virtual void notifyAll() = 0; + virtual void acquire() = 0; + virtual void release() = 0; +}; + +/** + * Scoped locker for a monitor. + */ +class Locker +{ + public: + Locker(Monitor& lock_) : lock(lock_) { lock.acquire(); } + ~Locker() { lock.release(); } + + private: + Monitor& lock; + + // private and unimplemented to prevent copying + Locker(const Locker&); + void operator=(const Locker&); +}; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/MonitorImpl.h b/cpp/common/concurrent/inc/MonitorImpl.h new file mode 100644 index 0000000000..e96e81d795 --- /dev/null +++ b/cpp/common/concurrent/inc/MonitorImpl.h @@ -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. + * + */ + + +#ifndef _MonitorImpl_ +#define _MonitorImpl_ + +#ifdef _USE_APR_IO_ +#include "APRMonitor.h" +#else /* use POSIX Monitor */ +#include "LMonitor.h" +#endif + + +namespace qpid { +namespace concurrent { + +#ifdef _USE_APR_IO_ + class MonitorImpl : public virtual APRMonitor + { + + public: + MonitorImpl() : APRMonitor(){}; + virtual ~MonitorImpl(){}; + + }; +#else + class MonitorImpl : public virtual LMonitor + { + + public: + MonitorImpl() : LMonitor(){}; + virtual ~MonitorImpl(){}; + + }; +#endif + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/Runnable.h b/cpp/common/concurrent/inc/Runnable.h new file mode 100644 index 0000000000..523ad813f7 --- /dev/null +++ b/cpp/common/concurrent/inc/Runnable.h @@ -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. + * + */ +#ifndef _Runnable_ +#define _Runnable_ + +namespace qpid { +namespace concurrent { + + class Runnable + { + public: + virtual void run() = 0; + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/TaskQueue.h b/cpp/common/concurrent/inc/TaskQueue.h new file mode 100644 index 0000000000..e06a3ce069 --- /dev/null +++ b/cpp/common/concurrent/inc/TaskQueue.h @@ -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. + * + */ +#ifndef _TaskQueue_ +#define _TaskQueue_ + +#include +#include +#include +#include "LockedQueue.h" +#include "Runnable.h" +#include "ThreadPool.h" + +namespace qpid { +namespace concurrent { + template class TaskQueue : public virtual Runnable + { + const int max_iterations_per_run; + L lock; + //LockedQueue queue; + std::queue queue; + ThreadPool* const pool; + T* work; + bool running; + volatile bool stopped; + TaskQueue* next; + + volatile bool inrun; + + bool hasWork(); + void completed(); + + T* take(); + + protected: + /** + * Callback though which the task is executed + */ + virtual void execute(T* t) = 0; + /** + * Allows a task to be completed asynchronously to the + * execute() call if required. + */ + virtual bool isComplete(T* t); + /** + * Should be called to signal completion of a task that was + * signalled as not complete through the isComplete() methods + * return value. This will allow normal processing to resume. + */ + virtual void complete(); + + public: + TaskQueue(ThreadPool* const pool, int max_iterations_per_run = 100); + virtual void run(); + void trigger(); + bool append(T* t); + void stop(bool drain); + inline void setNext(TaskQueue* next){ this->next = next; } + }; + + template TaskQueue::TaskQueue(ThreadPool* const _pool, int _max_iterations_per_run) : + pool(_pool), + max_iterations_per_run(_max_iterations_per_run), + work(0), + running(false), + stopped(false), + next(0), inrun(false){ + } + + template void TaskQueue::run(){ + if(inrun) std::cout << "Already running" << std::endl; + inrun = true; + + bool blocked = false; + int count = max_iterations_per_run; + while(!blocked && hasWork() && count){ + execute(work); + if(isComplete(work)){ + completed(); + }else{ + blocked = true; + } + count--; + } + inrun = false; + + if(!blocked && count == 0){//performed max_iterations_per_run, requeue task to ensure fairness + //running will still be true at this point + lock.acquire(); + running = false; + if(stopped) lock.notify(); + lock.release(); + + trigger(); + }else if(hasWork()){//task was added to queue after we exited the loop above; should not need this? + trigger(); + } + } + + template void TaskQueue::trigger(){ + lock.acquire(); + if(!running){ + running = true; + pool->addTask(this); + } + lock.release(); + } + + template bool TaskQueue::hasWork(){ + lock.acquire(); + if(!work) work = take();//queue.take(); + if(!work){ + running = false; + if(stopped) lock.notify(); + } + lock.release(); + return work; + } + + template bool TaskQueue::append(T* item){ + if(!stopped){ + lock.acquire(); + + //queue.put(item); + queue.push(item); + + if(!running){ + running = true; + pool->addTask(this); + } + lock.release(); + //} + return true; + }else{ + return false; + } + } + + template bool TaskQueue::isComplete(T* item){ + return true;//by default assume all tasks are synchronous w.r.t. execute() + } + + + template void TaskQueue::completed(){ + if(next){ + if(!next->append(work)){ + std::cout << "Warning: dropping task as next queue appears to have stopped." << std::endl; + } + }else{ + delete work; + } + work = 0; + } + + template void TaskQueue::complete(){ + completed(); + lock.acquire(); + running = false; + if(stopped) lock.notify(); + lock.release(); + } + + template void TaskQueue::stop(bool drain){ + //prevent new tasks from being added + stopped = true; + //wait until no longer running + lock.acquire(); + while(running && (drain && hasWork())){ + lock.wait(); + } + lock.release(); + } + + template T* TaskQueue::take(){ + T* item = 0; + if(!queue.empty()){ + item = queue.front(); + queue.pop(); + } + return item; + } +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/Thread.h b/cpp/common/concurrent/inc/Thread.h new file mode 100644 index 0000000000..6bd2a379ce --- /dev/null +++ b/cpp/common/concurrent/inc/Thread.h @@ -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. + * + */ +#ifndef _Thread_ +#define _Thread_ + +namespace qpid { +namespace concurrent { + + class Thread + { + public: + virtual ~Thread(){} + virtual void start() = 0; + virtual void join() = 0; + virtual void interrupt() = 0; + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/ThreadFactory.h b/cpp/common/concurrent/inc/ThreadFactory.h new file mode 100644 index 0000000000..53be000ff3 --- /dev/null +++ b/cpp/common/concurrent/inc/ThreadFactory.h @@ -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. + * + */ +#ifndef _ThreadFactory_ +#define _ThreadFactory_ + +#include "Thread.h" +#include "Runnable.h" + +namespace qpid { +namespace concurrent { + + class ThreadFactory + { + public: + virtual ~ThreadFactory(){} + virtual Thread* create(Runnable* runnable) = 0; + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/ThreadFactoryImpl.h b/cpp/common/concurrent/inc/ThreadFactoryImpl.h new file mode 100644 index 0000000000..a534b3c1e2 --- /dev/null +++ b/cpp/common/concurrent/inc/ThreadFactoryImpl.h @@ -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. + * + */ +#ifndef _ThreadFactoryImpl_ +#define _ThreadFactoryImpl_ + + +#ifdef _USE_APR_IO_ +#include "APRThreadFactory.h" +#else +#include "LThreadFactory.h" +#endif + + +namespace qpid { +namespace concurrent { + + +#ifdef _USE_APR_IO_ + class ThreadFactoryImpl : public virtual APRThreadFactory + { + public: + ThreadFactoryImpl(): APRThreadFactory() {}; + virtual ~ThreadFactoryImpl() {}; + }; +#else + class ThreadFactoryImpl : public virtual LThreadFactory + { + public: + ThreadFactoryImpl(): LThreadFactory() {}; + virtual ~ThreadFactoryImpl() {}; + }; +#endif +} +} + + +#endif diff --git a/cpp/common/concurrent/inc/ThreadPool.h b/cpp/common/concurrent/inc/ThreadPool.h new file mode 100644 index 0000000000..679c889ff3 --- /dev/null +++ b/cpp/common/concurrent/inc/ThreadPool.h @@ -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. + * + */ +#ifndef _ThreadPool_ +#define _ThreadPool_ + +#include "Thread.h" +#include "Runnable.h" + +namespace qpid { +namespace concurrent { + + class ThreadPool + { + public: + virtual void start() = 0; + virtual void stop() = 0; + virtual void addTask(Runnable* runnable) = 0; + virtual ~ThreadPool(){} + }; + +} +} + + +#endif diff --git a/cpp/common/concurrent/src/APRBase.cpp b/cpp/common/concurrent/src/APRBase.cpp new file mode 100644 index 0000000000..f87ea9e25f --- /dev/null +++ b/cpp/common/concurrent/src/APRBase.cpp @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "APRBase.h" +#include "QpidError.h" + +using namespace qpid::concurrent; + +APRBase* APRBase::instance = 0; + +APRBase* APRBase::getInstance(){ + if(instance == 0){ + instance = new APRBase(); + } + return instance; +} + + +APRBase::APRBase() : count(0){ + apr_initialize(); + CHECK_APR_SUCCESS(apr_pool_create(&pool, 0)); + CHECK_APR_SUCCESS(apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_NESTED, pool)); +} + +APRBase::~APRBase(){ + CHECK_APR_SUCCESS(apr_thread_mutex_destroy(mutex)); + apr_pool_destroy(pool); + apr_terminate(); +} + +bool APRBase::_increment(){ + bool deleted(false); + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); + if(this == instance){ + count++; + }else{ + deleted = true; + } + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); + return !deleted; +} + +void APRBase::_decrement(){ + APRBase* copy = 0; + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); + if(--count == 0){ + copy = instance; + instance = 0; + } + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); + if(copy != 0){ + delete copy; + } +} + +void APRBase::increment(){ + int count = 0; + while(count++ < 2 && !getInstance()->_increment()){ + std::cout << "WARNING: APR initialization triggered concurrently with termination." << std::endl; + } +} + +void APRBase::decrement(){ + getInstance()->_decrement(); +} + +void qpid::concurrent::check(apr_status_t status, const std::string& file, const int line){ + if (status != APR_SUCCESS){ + const int size = 50; + char tmp[size]; + std::string msg(apr_strerror(status, tmp, size)); + throw QpidError(APR_ERROR + ((int) status), msg, file, line); + } +} + +std::string qpid::concurrent::get_desc(apr_status_t status){ + const int size = 50; + char tmp[size]; + std::string msg(apr_strerror(status, tmp, size)); + return msg; +} + diff --git a/cpp/common/concurrent/src/APRMonitor.cpp b/cpp/common/concurrent/src/APRMonitor.cpp new file mode 100644 index 0000000000..428d76dff9 --- /dev/null +++ b/cpp/common/concurrent/src/APRMonitor.cpp @@ -0,0 +1,60 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "APRBase.h" +#include "APRMonitor.h" +#include + +qpid::concurrent::APRMonitor::APRMonitor(){ + APRBase::increment(); + CHECK_APR_SUCCESS(apr_pool_create(&pool, NULL)); + CHECK_APR_SUCCESS(apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_NESTED, pool)); + CHECK_APR_SUCCESS(apr_thread_cond_create(&condition, pool)); +} + +qpid::concurrent::APRMonitor::~APRMonitor(){ + CHECK_APR_SUCCESS(apr_thread_cond_destroy(condition)); + CHECK_APR_SUCCESS(apr_thread_mutex_destroy(mutex)); + apr_pool_destroy(pool); + APRBase::decrement(); +} + +void qpid::concurrent::APRMonitor::wait(){ + CHECK_APR_SUCCESS(apr_thread_cond_wait(condition, mutex)); +} + + +void qpid::concurrent::APRMonitor::wait(u_int64_t time){ + apr_status_t status = apr_thread_cond_timedwait(condition, mutex, time * 1000); + if(!status == APR_TIMEUP) CHECK_APR_SUCCESS(status); +} + +void qpid::concurrent::APRMonitor::notify(){ + CHECK_APR_SUCCESS(apr_thread_cond_signal(condition)); +} + +void qpid::concurrent::APRMonitor::notifyAll(){ + CHECK_APR_SUCCESS(apr_thread_cond_broadcast(condition)); +} + +void qpid::concurrent::APRMonitor::acquire(){ + CHECK_APR_SUCCESS(apr_thread_mutex_lock(mutex)); +} + +void qpid::concurrent::APRMonitor::release(){ + CHECK_APR_SUCCESS(apr_thread_mutex_unlock(mutex)); +} diff --git a/cpp/common/concurrent/src/APRThread.cpp b/cpp/common/concurrent/src/APRThread.cpp new file mode 100644 index 0000000000..4202fe81b6 --- /dev/null +++ b/cpp/common/concurrent/src/APRThread.cpp @@ -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. + * + */ +#include "APRBase.h" +#include "APRThread.h" +#include "apr_portable.h" + +using namespace qpid::concurrent; + +void* APR_THREAD_FUNC ExecRunnable(apr_thread_t* thread, void *data){ + ((Runnable*) data)->run(); + CHECK_APR_SUCCESS(apr_thread_exit(thread, APR_SUCCESS)); + return NULL; +} + +APRThread::APRThread(apr_pool_t* _pool, Runnable* _runnable) : pool(_pool), runnable(_runnable){} + +APRThread::~APRThread(){ +} + +void APRThread::start(){ + CHECK_APR_SUCCESS(apr_thread_create(&runner, NULL, ExecRunnable,(void*) runnable, pool)); +} + +void APRThread::join(){ + apr_status_t status; + CHECK_APR_SUCCESS(apr_thread_join(&status, runner)); +} + +void APRThread::interrupt(){ + CHECK_APR_SUCCESS(apr_thread_exit(runner, APR_SUCCESS)); +} + +unsigned int qpid::concurrent::APRThread::currentThread(){ + return apr_os_thread_current(); +} diff --git a/cpp/common/concurrent/src/APRThreadFactory.cpp b/cpp/common/concurrent/src/APRThreadFactory.cpp new file mode 100644 index 0000000000..9ba68e9e56 --- /dev/null +++ b/cpp/common/concurrent/src/APRThreadFactory.cpp @@ -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. + * + */ +#include "APRBase.h" +#include "APRThreadFactory.h" + +using namespace qpid::concurrent; + +APRThreadFactory::APRThreadFactory(){ + APRBase::increment(); + CHECK_APR_SUCCESS(apr_pool_create(&pool, NULL)); +} + +APRThreadFactory::~APRThreadFactory(){ + apr_pool_destroy(pool); + APRBase::decrement(); +} + +Thread* APRThreadFactory::create(Runnable* runnable){ + return new APRThread(pool, runnable); +} diff --git a/cpp/common/concurrent/src/APRThreadPool.cpp b/cpp/common/concurrent/src/APRThreadPool.cpp new file mode 100644 index 0000000000..e0fcb804e6 --- /dev/null +++ b/cpp/common/concurrent/src/APRThreadPool.cpp @@ -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. + * + */ +#include "APRThreadFactory.h" +#include "APRThreadPool.h" +#include "QpidError.h" +#include + +using namespace qpid::concurrent; + +APRThreadPool::APRThreadPool(int _size) : size(_size), factory(new APRThreadFactory()), + deleteFactory(true), running(false){ + worker = new Worker(this); +} + +APRThreadPool::APRThreadPool(int _size, ThreadFactory* _factory) : size(_size), factory(_factory), + deleteFactory(false), running(false){ + worker = new Worker(this); +} + +APRThreadPool::~APRThreadPool(){ + if(deleteFactory) delete factory; +} + +void APRThreadPool::addTask(Runnable* task){ + lock.acquire(); + tasks.push(task); + lock.notifyAll(); + lock.release(); +} + +void APRThreadPool::runTask(){ + lock.acquire(); + while(tasks.empty()){ + lock.wait(); + } + Runnable* task = tasks.front(); + tasks.pop(); + lock.release(); + try{ + task->run(); + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } +} + +void APRThreadPool::start(){ + if(!running){ + running = true; + for(int i = 0; i < size; i++){ + Thread* t = factory->create(worker); + t->start(); + threads.push_back(t); + } + } +} + +void APRThreadPool::stop(){ + if(!running){ + running = false; + lock.acquire(); + lock.notifyAll(); + lock.release(); + for(int i = 0; i < size; i++){ + threads[i]->join(); + delete threads[i]; + } + } +} + + diff --git a/cpp/common/error/inc/QpidError.h b/cpp/common/error/inc/QpidError.h new file mode 100644 index 0000000000..a739b506c7 --- /dev/null +++ b/cpp/common/error/inc/QpidError.h @@ -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. + * + */ +#include + +#ifndef __QpidError__ +#define __QpidError__ + +namespace qpid { + +class QpidError{ +public: + const int code; + const std::string msg; + const std::string file; + const int line; + + inline QpidError(int _code, const std::string& _msg, const std::string& _file, int _line) : code(_code), msg(_msg), file(_file), line(_line) {} + ~QpidError(){} + +}; + +#define THROW_QPID_ERROR(A, B) throw QpidError(A, B, __FILE__, __LINE__) + +} + +#define PROTOCOL_ERROR 10000 +#define APR_ERROR 20000 +#define FRAMING_ERROR 30000 +#define CLIENT_ERROR 40000 +#define INTERNAL_ERROR 50000 + +#endif diff --git a/cpp/common/error/inc/QpidErrorIO.h b/cpp/common/error/inc/QpidErrorIO.h new file mode 100644 index 0000000000..4f9bd3ce26 --- /dev/null +++ b/cpp/common/error/inc/QpidErrorIO.h @@ -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. + * + */ + +#include "QpidError.h" +#include + +std::ostream& operator <<(std::ostream& out, const QpidError& error) +{ + out << "Qpid Error [" << error.code << "] " << error.msg + << " (" << error.file << ":" << error.line << ")"; + return out; +} + + diff --git a/cpp/common/framing/Makefile b/cpp/common/framing/Makefile new file mode 100644 index 0000000000..1dfc286050 --- /dev/null +++ b/cpp/common/framing/Makefile @@ -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. +# + +.PHONY: all clean test + +all: + @$(MAKE) -C generated all + +test: + @$(MAKE) -C test all + +clean : + @$(MAKE) -C generated clean + @$(MAKE) -C test clean + diff --git a/cpp/common/framing/generated/Makefile b/cpp/common/framing/generated/Makefile new file mode 100644 index 0000000000..12ec402760 --- /dev/null +++ b/cpp/common/framing/generated/Makefile @@ -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. +# + +QPID_HOME = ../../../.. +include ${QPID_HOME}/cpp/options.mk + +STYLESHEET_DIR = stylesheets +JAVA = java +XSLTP = ${TOOLS_DIR}/saxon8.jar + +SPEC = ${SPEC_DIR}/amqp-8.0.xml +STYLESHEETS = $(wildcard stylesheets/*.xsl) + +GENERATED_SOURCES=amqp_methods.cpp # Seed generation + +.PHONY: all clean + +all: ${GENERATED_SOURCES} + +clean : + -@rm -f *.cpp *.h + +${GENERATED_SOURCES}: ${STYLESHEETS} ${SPEC} + ${JAVA} -jar ${XSLTP} -o results.out ${SPEC} ${STYLESHEET_DIR}/code_gen.xsl + ${JAVA} -jar ${XSLTP} -o results.out ${SPEC} ${STYLESHEET_DIR}/framing.xsl + +-include $(GENERATED_SOURCES:.cpp=.d) + diff --git a/cpp/common/framing/generated/stylesheets/amqp_client.xsl b/cpp/common/framing/generated/stylesheets/amqp_client.xsl new file mode 100644 index 0000000000..f0fa3d7890 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_client.xsl @@ -0,0 +1,155 @@ + + + + + + + + + + + +#ifndef _AMQP_Client_ +#define _AMQP_Client_ + +#include "AMQP_ServerOperations.h" +#include "FieldTable.h" +#include "OutputHandler.h" + +namespace qpid { +namespace framing { + +class AMQP_Client : virtual public AMQP_ServerOperations +{ + OutputHandler* out; + + public: + AMQP_Client(OutputHandler* _out); + virtual ~AMQP_Client() {} + + + + /** ===== Class: ===== + + */ + + class : virtual public AMQP_ServerOperations::Handler + { + OutputHandler* out; + + public: + /* Constructors and destructors */ + (OutputHandler* _out); + virtual ~(); + + /* Protocol methods */ + + + + + /** ----- Method: . ----- + + */ + + + /** + Rule "": + */ + + virtual void + ( u_int16_t channel, + + + + + , + + + + ); + + + }; /* class */ + + }; /* class AMQP_Client */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + + + + + + + + +#include "AMQP_Client.h" + +namespace qpid { +namespace framing { + +AMQP_Client::AMQP_Client(OutputHandler* _out) : + out(_out) +{ +} + + + /* ++++++++++ Class: ++++++++++ */ + +AMQP_Client::::(OutputHandler* _out) : + out(_out) +{ +} + +AMQP_Client::::~() {} + + + void AMQP_Client:::: + ( u_int16_t channel + , + + + + + , + + + + ) +{ + out->send( new AMQFrame( channel, + new ( + + + + , + + + ) ) ); +} + + + + + +} /* namespace framing */ +} /* namespace qpid */ + + + + diff --git a/cpp/common/framing/generated/stylesheets/amqp_client_handler_impl.xsl b/cpp/common/framing/generated/stylesheets/amqp_client_handler_impl.xsl new file mode 100644 index 0000000000..aa095eaf79 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_client_handler_impl.xsl @@ -0,0 +1,187 @@ + + + + + + + + + + + +#ifndef _AMQP_ClientHandlerImpl_ +#define _AMQP_ClientHandlerImpl_ + +#include "AMQP_ClientOperations.h" +#include "FieldTable.h" + +namespace qpid { +namespace framing { + +class AMQP_ClientHandlerImpl : virtual public AMQP_ClientOperations +{ + + + + + AMQP_ClientOperations::* + Ptr; + + + public: + AMQP_ClientHandlerImpl(); + virtual ~AMQP_ClientHandlerImpl(); + + + + + inline AMQP_ClientOperations:: + * get + () { return Ptr; } + + + + + + + + + + /** + ===== Class: Impl ===== + + */ + + + + class + Impl : virtual public AMQP_ClientOperations:: + { + public: + /* Constructors and destructors */ + Impl(); + virtual ~Impl(); + + /* Protocol methods */ + + + + + + + + + /** + ----- Method: + Impl. ----- + + */ + + + /** + Rule "": + + */ + + + + virtual void + ( u_int16_t channel + + + + , + + + + + , + + + + ); + + + }; /* class Impl */ + + }; /* AMQP_ClientHandlerImpl */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + + + + + + +#include "AMQP_ClientHandlerImpl.h" + +namespace qpid { +namespace framing { + +AMQP_ClientHandlerImpl::AMQP_ClientHandlerImpl() : + + + + HandlerPtr( new HandlerImpl() ) + + , + + + +{ +} + +AMQP_ClientHandlerImpl::~AMQP_ClientHandlerImpl() +{ + + delete HandlerPtr; + } + + + + /* ===== Class: HandlerImpl ===== */ + AMQP_ClientHandlerImpl::HandlerImpl:: + HandlerImpl() { } + AMQP_ClientHandlerImpl::HandlerImpl::~ + HandlerImpl() { } + + + void AMQP_ClientHandlerImpl::HandlerImpl:: + ( u_int16_t channel + + , + + + + + , + + + ) { } + + + + + +} /* namespace framing */ +} /* namespace qpid */ + + + + diff --git a/cpp/common/framing/generated/stylesheets/amqp_client_operations.xsl b/cpp/common/framing/generated/stylesheets/amqp_client_operations.xsl new file mode 100644 index 0000000000..234b7080ba --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_client_operations.xsl @@ -0,0 +1,105 @@ + + + + + + + + + + + +#ifndef _AMQP_ClientOperations_ +#define _AMQP_ClientOperations_ + +#include "AMQP_Constants.h" +#include "FieldTable.h" + +namespace qpid { +namespace framing { + +class AMQP_ClientOperations +{ + public: + AMQP_ClientOperations() {} + virtual ~AMQP_ClientOperations() {} + inline u_int16_t getAmqpMajor() { return (u_int16_t); } + inline u_int16_t getAmqpMinor() { return (u_int16_t); } + + + + + + + + /** ===== Class: ===== + + */ + + + + class + { + public: + /* Constructors and destructors */ + () {} + virtual ~() {} + + /* Protocol methods */ + + + + + + + + + /** ----- Method: . + ----- + + */ + + + /** + Rule "": + + */ + + + + virtual void + ( u_int16_t channel + + + + , + + + + + , + + + + ) = 0; + + + }; /* class */ + + }; /* class AMQP_ClientOperations */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + diff --git a/cpp/common/framing/generated/stylesheets/amqp_consts.xsl b/cpp/common/framing/generated/stylesheets/amqp_consts.xsl new file mode 100644 index 0000000000..c1c927f941 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_consts.xsl @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#ifndef _AMQP_Constants_ +#define _AMQP_Constants_ + +#include "amqp_types.h" + +namespace qpid { +namespace framing { + +/**** Constants ****/ + + + /* + + */ + + const u_int16_t ; + + + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + diff --git a/cpp/common/framing/generated/stylesheets/amqp_server.xsl b/cpp/common/framing/generated/stylesheets/amqp_server.xsl new file mode 100644 index 0000000000..4ad29a4b95 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_server.xsl @@ -0,0 +1,155 @@ + + + + + + + + + + + +#ifndef _AMQP_Server_ +#define _AMQP_Server_ + +#include "AMQP_ClientOperations.h" +#include "FieldTable.h" +#include "OutputHandler.h" + +namespace qpid { +namespace framing { + +class AMQP_Server : virtual public AMQP_ClientOperations +{ + OutputHandler* out; + + public: + AMQP_Server(OutputHandler* _out); + virtual ~AMQP_Server() {} + + + + /** ===== Class: ===== + + */ + + class : virtual public AMQP_ClientOperations::Handler + { + OutputHandler* out; + + public: + /* Constructors and destructors */ + (OutputHandler* _out); + virtual ~(); + + /* Protocol methods */ + + + + + /** ----- Method: . ----- + + */ + + + /** + Rule "": + */ + + virtual void + ( u_int16_t channel, + + + + + , + + + + ); + + + }; /* class */ + + }; /* class AMQP_Server */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + + + + + + + + +#include "AMQP_Server.h" + +namespace qpid { +namespace framing { + +AMQP_Server::AMQP_Server(OutputHandler* _out) : + out(_out) +{ +} + + + /* ++++++++++ Class: ++++++++++ */ + +AMQP_Server::::(OutputHandler* _out) : + out(_out) +{ +} + +AMQP_Server::::~() {} + + + void AMQP_Server:::: + ( u_int16_t channel + , + + + + + , + + + + ) +{ + out->send( new AMQFrame( channel, + new ( + + + + , + + + ) ) ); +} + + + + + +} /* namespace framing */ +} /* namespace qpid */ + + + + diff --git a/cpp/common/framing/generated/stylesheets/amqp_server_handler_impl.xsl b/cpp/common/framing/generated/stylesheets/amqp_server_handler_impl.xsl new file mode 100644 index 0000000000..de879a5670 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_server_handler_impl.xsl @@ -0,0 +1,187 @@ + + + + + + + + + + + +#ifndef _AMQP_ServerHandlerImpl_ +#define _AMQP_ServerHandlerImpl_ + +#include "AMQP_ServerOperations.h" +#include "FieldTable.h" + +namespace qpid { +namespace framing { + +class AMQP_ServerHandlerImpl : virtual public AMQP_ServerOperations +{ + + + + + AMQP_ServerOperations::* + Ptr; + + + public: + AMQP_ServerHandlerImpl(); + virtual ~AMQP_ServerHandlerImpl(); + + + + + virtual inline AMQP_ServerOperations:: + * get + () { return Ptr; } + + + + + + + + + + /** + ===== Class: Impl ===== + + */ + + + + class + Impl : virtual public AMQP_ServerOperations:: + { + public: + /* Constructors and destructors */ + Impl(); + virtual ~Impl(); + + /* Protocol methods */ + + + + + + + + + /** + ----- Method: + Impl. ----- + + */ + + + /** + Rule "": + + */ + + + + virtual void + ( u_int16_t channel + + + + , + + + + + , + + + + ); + + + }; /* class Impl */ + + }; /* AMQP_ServerHandlerImpl */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + + + + + + +#include "AMQP_ServerHandlerImpl.h" + +namespace qpid { +namespace framing { + +AMQP_ServerHandlerImpl::AMQP_ServerHandlerImpl() : + + + + HandlerPtr( new HandlerImpl() ) + + , + + + +{ +} + +AMQP_ServerHandlerImpl::~AMQP_ServerHandlerImpl() +{ + + delete HandlerPtr; + } + + + + /* ===== Class: HandlerImpl ===== */ + AMQP_ServerHandlerImpl::HandlerImpl:: + HandlerImpl() { } + AMQP_ServerHandlerImpl::HandlerImpl::~ + HandlerImpl() { } + + + void AMQP_ServerHandlerImpl::HandlerImpl:: + ( u_int16_t channel + + , + + + + + , + + + ) { } + + + + + +} /* namespace framing */ +} /* namespace qpid */ + + + + diff --git a/cpp/common/framing/generated/stylesheets/amqp_server_operations.xsl b/cpp/common/framing/generated/stylesheets/amqp_server_operations.xsl new file mode 100644 index 0000000000..b42242e8fe --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/amqp_server_operations.xsl @@ -0,0 +1,113 @@ + + + + + + + + + + + +#ifndef _AMQP_ServerOperations_ +#define _AMQP_ServerOperations_ + +#include "AMQP_Constants.h" +#include "FieldTable.h" + +namespace qpid { +namespace framing { + +class AMQP_ServerOperations +{ + public: + AMQP_ServerOperations() {} + virtual ~AMQP_ServerOperations() {} + inline u_int16_t getAmqpMajor() { return (u_int16_t); } + inline u_int16_t getAmqpMinor() { return (u_int16_t); } + + + + + + + + /** ===== Class: ===== + + */ + + + + class + { + public: + /* Constructors and destructors */ + () {} + virtual ~() {} + + /* Protocol methods */ + + + + + + + + + /** ----- Method: . + ----- + + */ + + /** + /** + Rule "": + + */ + + + + virtual void + ( u_int16_t channel + + + + , + + + + + , + + + + ) = 0; + + + }; /* class */ + + + + + virtual AMQP_ServerOperations:: + * get + () = 0; + + + }; /* class AMQP_ServerOperations */ + +} /* namespace framing */ +} /* namespace qpid */ + +#endif + + + + diff --git a/cpp/common/framing/generated/stylesheets/code_gen.xsl b/cpp/common/framing/generated/stylesheets/code_gen.xsl new file mode 100644 index 0000000000..5e9f4ef8f0 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/code_gen.xsl @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/code_utils.xsl b/cpp/common/framing/generated/stylesheets/code_utils.xsl new file mode 100644 index 0000000000..f4a0f6e5ce --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/code_utils.xsl @@ -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. +* +*/ + +/** +* +* NOTE: This file is generated directly from the AMQP XML specification. +* === DO NOT EDIT === +* +*/ + + + + + + + + + + + + + delete_ + return_ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + u_int8_t + u_int16_t + string + string + bool + u_int32_t + u_int64_t + u_int64_t + + FieldTable + + unknown_type /* WARNING: undefined type */ + + + + + + + + & + & + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/convert_0.81.xsl b/cpp/common/framing/generated/stylesheets/convert_0.81.xsl new file mode 100644 index 0000000000..9924f165da --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/convert_0.81.xsl @@ -0,0 +1,407 @@ + + + + + + + + + + + + + + + + + + + + + + ==================== + Constants + ==================== + + + + + + + + + + ==================== + Domains + ==================== + + + + + + + + + Elementary domains + + bit + bit + single bit + + + octet + octet + single octet + + + short + short + 16-bit integer + + + long + long + 32-bit integer + + + longlong + longlong + 64-bit integer + + + shortstr + shortstr + short string + + + longstr + longstr + long string + + + timestamp + timestamp + 64-bit timestamp + + + table + table + field table + + + + + + ==================== + Classes + ==================== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +rule__ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +doc_rule__ + + + + + + + + + + + +grammar + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/cpp.xsl b/cpp/common/framing/generated/stylesheets/cpp.xsl new file mode 100644 index 0000000000..ae66f65745 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/cpp.xsl @@ -0,0 +1,318 @@ + + + + + + + + + + + + + + + + + + +#include "amqp_framing.h" + + + + + + + + + + + + +#include "amqp_types.h" +#include "AMQP_ServerOperations.h" +#include "AMQMethodBody.h" +#include "Buffer.h" +#include "FieldTable.h" + +#ifndef __ +#define __ + +namespace qpid { +namespace framing { + + + + +} +} + +#endif + + + + + + + + +/** + * This class is autogenerated, do not modify. [From ] + */ +class : virtual public AMQMethodBody +{ + + + + ; + + +public: + typedef std::tr1::shared_ptr<> shared_ptr; + + virtual ~() {} + + + inline ; } + + + + inline void print(std::ostream& out) const{ + out << "" + + << ", + ="<< + + + ; + } + + inline u_int16_t amqpClassId() const { + return ; + } + + inline u_int16_t amqpMethodId() const { + return ; + } + + inline u_int32_t bodySize() const { + + + return + + + + + + + ; + + return 0; + + } + + + inline void invoke(AMQP_ServerOperations& target, u_int16_t channel) { + + + ); + + + ; + + } + + + inline void encodeContent(Buffer& buffer) const + { + + u_int8_t flags = 0; + + ; + + + + + ; + + + buffer.putOctet(flags); + + + } + + inline void decodeContent(Buffer& buffer) + { + + u_int8_t maxbit = ; + + + + + u_int8_t flags = buffer.getOctet(); + ; + + + ; + + + + } + + + + inline () : + { + } + + + inline () + { + } +}; + + + + + +/** + * This file is autogenerated, do not modify. + */ + +#ifndef AMQ_METHODS_H +#define AMQ_METHODS_H + + +#include ".h" + + +namespace qpid { +namespace framing { + + +const ; + + +AMQMethodBody* createAMQMethodBody(u_int16_t classId, u_int16_t methodId); + +} +} + +#endif + + + + + +#include "amqp_methods.h" +#include "QpidError.h" + +namespace qpid { +namespace framing { +/** + * This method is autogenerated, do not modify. + */ +AMQMethodBody* createAMQMethodBody(u_int16_t classId, u_int16_t methodId){ + switch(classId * 1000 + methodId) + { + + case + + * 1000 + + + : return new + (); + + } + THROW_QPID_ERROR(FRAMING_ERROR, "Unknown method"); +} + +} +} + + + + + +#include "amqp_types.h" +#include "FieldTable.h" + +#ifndef _AMQPServer_ +#define _AMQPServer_ + +namespace qpid { +namespace framing { + +class AMQPServer +{ + public: + + + class { + public: + + + virtual void (u_int16_t channel, ) = 0; + + + virtual void (u_int16_t channel) = 0; + + + virtual ~(){} + }; + + virtual () = 0; + + + virtual ~AMQPServer(){} +}; + +} +} + +#endif + + + +#include "amqp_types.h" +#include "FieldTable.h" + +#ifndef _AMQPClient_ +#define _AMQPClient_ + +namespace qpid { +namespace framing { + +class AMQPClient +{ + public: + + + class { + public: + + + virtual void (u_int16_t channel, ) = 0; + + + virtual void (u_int16_t channel) = 0; + + + virtual ~(){} + }; + + virtual () = 0; + + + + virtual ~AMQPClient(){} +}; + +} +} + +#endif + + + + + diff --git a/cpp/common/framing/generated/stylesheets/framing.xsl b/cpp/common/framing/generated/stylesheets/framing.xsl new file mode 100644 index 0000000000..c63e719a77 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/framing.xsl @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/prepare1.xsl b/cpp/common/framing/generated/stylesheets/prepare1.xsl new file mode 100644 index 0000000000..2aeda89677 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/prepare1.xsl @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + ( + major= + , minor= + ) + + + + + + + + + + + + + + + + + + + + + + + + Could not inherit from ; file not found. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + true + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/prepare2.xsl b/cpp/common/framing/generated/stylesheets/prepare2.xsl new file mode 100644 index 0000000000..331319de57 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/prepare2.xsl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/prepare3.xsl b/cpp/common/framing/generated/stylesheets/prepare3.xsl new file mode 100644 index 0000000000..27a4764e4f --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/prepare3.xsl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/registry.xsl b/cpp/common/framing/generated/stylesheets/registry.xsl new file mode 100644 index 0000000000..a818a0a871 --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/registry.xsl @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/cpp/common/framing/generated/stylesheets/utils.xsl b/cpp/common/framing/generated/stylesheets/utils.xsl new file mode 100644 index 0000000000..829d38433e --- /dev/null +++ b/cpp/common/framing/generated/stylesheets/utils.xsl @@ -0,0 +1,194 @@ + + + + + + + + + + u_int8_t + u_int16_t + string + string + bool + u_int32_t + u_int64_t + FieldTable + Object /*WARNING: undefined type*/ + + + + + + u_int8_t + u_int16_t + string& + string& + bool + u_int32_t + u_int64_t + FieldTable& + Object /*WARNING: undefined type*/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /* WARNING: COULD NOT DETERMINE FIELD SIZE */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /* WARNING: COULD NOT DETERMINE ENCODER */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /* WARNING: COULD NOT DETERMINE DECODER */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cpp/common/framing/inc/AMQBody.h b/cpp/common/framing/inc/AMQBody.h new file mode 100644 index 0000000000..d4b436c949 --- /dev/null +++ b/cpp/common/framing/inc/AMQBody.h @@ -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. + * + */ +#include "memory.h" +#include "amqp_types.h" +#include "Buffer.h" + +#ifndef _AMQBody_ +#define _AMQBody_ + +namespace qpid { + namespace framing { + + class AMQBody + { + public: + typedef std::tr1::shared_ptr shared_ptr; + + virtual u_int32_t size() const = 0; + virtual u_int8_t type() const = 0; + virtual void encode(Buffer& buffer) const = 0; + virtual void decode(Buffer& buffer, u_int32_t size) = 0; + inline virtual ~AMQBody(){} + }; + + enum body_types {METHOD_BODY = 1, HEADER_BODY = 2, CONTENT_BODY = 3, HEARTBEAT_BODY = 8}; + + } +} + + +#endif diff --git a/cpp/common/framing/inc/AMQContentBody.h b/cpp/common/framing/inc/AMQContentBody.h new file mode 100644 index 0000000000..8e97c31edb --- /dev/null +++ b/cpp/common/framing/inc/AMQContentBody.h @@ -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. + * + */ +#include "amqp_types.h" +#include "AMQBody.h" +#include "Buffer.h" + +#ifndef _AMQContentBody_ +#define _AMQContentBody_ + +namespace qpid { +namespace framing { + +class AMQContentBody : virtual public AMQBody +{ + string data; + +public: + typedef std::tr1::shared_ptr shared_ptr; + + AMQContentBody(); + AMQContentBody(string& data); + inline virtual ~AMQContentBody(){} + inline u_int8_t type() const { return CONTENT_BODY; }; + inline string& getData(){ return data; } + u_int32_t size() const; + void encode(Buffer& buffer) const; + void decode(Buffer& buffer, u_int32_t size); +}; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/AMQDataBlock.h b/cpp/common/framing/inc/AMQDataBlock.h new file mode 100644 index 0000000000..6c47c78864 --- /dev/null +++ b/cpp/common/framing/inc/AMQDataBlock.h @@ -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. + * + */ +#include "Buffer.h" + +#ifndef _AMQDataBlock_ +#define _AMQDataBlock_ + +namespace qpid { +namespace framing { + +class AMQDataBlock +{ +public: + virtual ~AMQDataBlock() {} + virtual void encode(Buffer& buffer) = 0; + virtual bool decode(Buffer& buffer) = 0; + virtual u_int32_t size() const = 0; +}; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/AMQFrame.h b/cpp/common/framing/inc/AMQFrame.h new file mode 100644 index 0000000000..5656d20377 --- /dev/null +++ b/cpp/common/framing/inc/AMQFrame.h @@ -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. + * + */ +#include "amqp_methods.h" +#include "amqp_types.h" +#include "AMQBody.h" +#include "AMQDataBlock.h" +#include "AMQMethodBody.h" +#include "AMQHeaderBody.h" +#include "AMQContentBody.h" +#include "AMQHeartbeatBody.h" +#include "Buffer.h" + +#ifndef _AMQFrame_ +#define _AMQFrame_ + +namespace qpid { + namespace framing { + + class AMQFrame : virtual public AMQDataBlock + { + u_int16_t channel; + u_int8_t type;//used if the body is decoded separately from the 'head' + AMQBody::shared_ptr body; + + public: + AMQFrame(); + AMQFrame(u_int16_t channel, AMQBody* body); + AMQFrame(u_int16_t channel, AMQBody::shared_ptr& body); + virtual ~AMQFrame(); + virtual void encode(Buffer& buffer); + virtual bool decode(Buffer& buffer); + virtual u_int32_t size() const; + u_int16_t getChannel(); + AMQBody::shared_ptr& getBody(); + + u_int32_t decodeHead(Buffer& buffer); + void decodeBody(Buffer& buffer, uint32_t size); + + friend std::ostream& operator<<(std::ostream& out, const AMQFrame& body); + }; + + } +} + + +#endif diff --git a/cpp/common/framing/inc/AMQHeaderBody.h b/cpp/common/framing/inc/AMQHeaderBody.h new file mode 100644 index 0000000000..369db8a9c8 --- /dev/null +++ b/cpp/common/framing/inc/AMQHeaderBody.h @@ -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. + * + */ +#include "amqp_types.h" +#include "AMQBody.h" +#include "Buffer.h" +#include "HeaderProperties.h" + +#ifndef _AMQHeaderBody_ +#define _AMQHeaderBody_ + +namespace qpid { +namespace framing { + +class AMQHeaderBody : virtual public AMQBody +{ + HeaderProperties* properties; + u_int16_t weight; + u_int64_t contentSize; + + void createProperties(int classId); +public: + typedef std::tr1::shared_ptr shared_ptr; + + AMQHeaderBody(int classId); + AMQHeaderBody(); + inline u_int8_t type() const { return HEADER_BODY; } + HeaderProperties* getProperties(){ return properties; } + inline u_int64_t getContentSize() const { return contentSize; } + inline void setContentSize(u_int64_t size) { contentSize = size; } + virtual ~AMQHeaderBody(); + virtual u_int32_t size() const; + virtual void encode(Buffer& buffer) const; + virtual void decode(Buffer& buffer, u_int32_t size); +}; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/AMQHeartbeatBody.h b/cpp/common/framing/inc/AMQHeartbeatBody.h new file mode 100644 index 0000000000..ca2def977a --- /dev/null +++ b/cpp/common/framing/inc/AMQHeartbeatBody.h @@ -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. + * + */ +#include "amqp_types.h" +#include "AMQBody.h" +#include "Buffer.h" + +#ifndef _AMQHeartbeatBody_ +#define _AMQHeartbeatBody_ + +namespace qpid { +namespace framing { + +class AMQHeartbeatBody : virtual public AMQBody +{ +public: + typedef std::tr1::shared_ptr shared_ptr; + + virtual ~AMQHeartbeatBody() {} + inline u_int32_t size() const { return 0; } + inline u_int8_t type() const { return HEARTBEAT_BODY; } + inline void encode(Buffer& buffer) const {} + inline void decode(Buffer& buffer, u_int32_t size) {} +}; + +} +} + +#endif diff --git a/cpp/common/framing/inc/AMQMethodBody.h b/cpp/common/framing/inc/AMQMethodBody.h new file mode 100644 index 0000000000..59d5dd5212 --- /dev/null +++ b/cpp/common/framing/inc/AMQMethodBody.h @@ -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. + * + */ +#include +#include "amqp_types.h" +#include "AMQBody.h" +#include "Buffer.h" +#include "AMQP_ServerOperations.h" + +#ifndef _AMQMethodBody_ +#define _AMQMethodBody_ + +namespace qpid { +namespace framing { + +class AMQMethodBody : virtual public AMQBody +{ +public: + typedef std::tr1::shared_ptr shared_ptr; + + inline u_int8_t type() const { return METHOD_BODY; } + inline u_int32_t size() const { return 4 + bodySize(); } + inline virtual ~AMQMethodBody(){} + virtual void print(std::ostream& out) const = 0; + virtual u_int16_t amqpMethodId() const = 0; + virtual u_int16_t amqpClassId() const = 0; + virtual void invoke(AMQP_ServerOperations& target, u_int16_t channel); + virtual void encodeContent(Buffer& buffer) const = 0; + virtual void decodeContent(Buffer& buffer) = 0; + virtual u_int32_t bodySize() const = 0; + void encode(Buffer& buffer) const; + void decode(Buffer& buffer, u_int32_t size); + bool match(AMQMethodBody* other) const; +}; + +std::ostream& operator<<(std::ostream& out, const AMQMethodBody& body); + +} +} + + +#endif diff --git a/cpp/common/framing/inc/BasicHeaderProperties.h b/cpp/common/framing/inc/BasicHeaderProperties.h new file mode 100644 index 0000000000..8688a37bf9 --- /dev/null +++ b/cpp/common/framing/inc/BasicHeaderProperties.h @@ -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. + * + */ +#include "amqp_types.h" +#include "amqp_methods.h" +#include "Buffer.h" +#include "HeaderProperties.h" + +#ifndef _BasicHeaderProperties_ +#define _BasicHeaderProperties_ + +namespace qpid { +namespace framing { + + //TODO: This could be easily generated from the spec + class BasicHeaderProperties : public HeaderProperties + { + string contentType; + string contentEncoding; + FieldTable headers; + u_int8_t deliveryMode; + u_int8_t priority; + string correlationId; + string replyTo; + string expiration; + string messageId; + u_int64_t timestamp; + string type; + string userId; + string appId; + string clusterId; + + u_int16_t getFlags() const; + + public: + BasicHeaderProperties(); + virtual ~BasicHeaderProperties(); + virtual u_int32_t size() const; + virtual void encode(Buffer& buffer) const; + virtual void decode(Buffer& buffer, u_int32_t size); + + inline virtual u_int8_t classId(){ return BASIC; } + + inline string& getContentType(){ return contentType; } + inline string& getContentEncoding(){ return contentEncoding; } + inline FieldTable& getHeaders(){ return headers; } + inline u_int8_t getDeliveryMode(){ return deliveryMode; } + inline u_int8_t getPriority(){ return priority; } + inline string& getCorrelationId(){return correlationId; } + inline string& getReplyTo(){ return replyTo; } + inline string& getExpiration(){ return expiration; } + inline string& getMessageId(){return messageId; } + inline u_int64_t getTimestamp(){ return timestamp; } + inline string& getType(){ return type; } + inline string& getUserId(){ return userId; } + inline string& getAppId(){ return appId; } + inline string& getClusterId(){ return clusterId; } + + void inline setContentType(string& type){ contentType = type; } + void inline setContentEncoding(string& encoding){ contentEncoding = encoding; } + void inline setHeaders(FieldTable& headers){ this->headers = headers; } + void inline setDeliveryMode(u_int8_t mode){ deliveryMode = mode; } + void inline setPriority(u_int8_t priority){ this->priority = priority; } + void inline setCorrelationId(string& correlationId){ this->correlationId = correlationId; } + void inline setReplyTo(string& replyTo){ this->replyTo = replyTo;} + void inline setExpiration(string& expiration){ this->expiration = expiration; } + void inline setMessageId(string& messageId){ this->messageId = messageId; } + void inline setTimestamp(u_int64_t timestamp){ this->timestamp = timestamp; } + void inline setType(string& type){ this->type = type; } + void inline setUserId(string& userId){ this->userId = userId; } + void inline setAppId(string& appId){this->appId = appId; } + void inline setClusterId(string& clusterId){ this->clusterId = clusterId; } + }; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/BodyHandler.h b/cpp/common/framing/inc/BodyHandler.h new file mode 100644 index 0000000000..f92ae66804 --- /dev/null +++ b/cpp/common/framing/inc/BodyHandler.h @@ -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. + * + */ +#include + +#ifndef _BodyHandler_ +#define _BodyHandler_ + +#include "AMQMethodBody.h" +#include "AMQHeaderBody.h" +#include "AMQContentBody.h" +#include "AMQHeartbeatBody.h" + +namespace qpid { +namespace framing { + + class BodyHandler{ + public: + virtual void handleMethod(AMQMethodBody::shared_ptr body) = 0; + virtual void handleHeader(AMQHeaderBody::shared_ptr body) = 0; + virtual void handleContent(AMQContentBody::shared_ptr body) = 0; + virtual void handleHeartbeat(AMQHeartbeatBody::shared_ptr body) = 0; + + void handleBody(AMQBody::shared_ptr& body); + }; + + class UnknownBodyType{ + public: + const u_int16_t type; + inline UnknownBodyType(u_int16_t _type) : type(_type){} + }; +} +} + + +#endif diff --git a/cpp/common/framing/inc/Buffer.h b/cpp/common/framing/inc/Buffer.h new file mode 100644 index 0000000000..1ff4611f1f --- /dev/null +++ b/cpp/common/framing/inc/Buffer.h @@ -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. + * + */ +#include "amqp_types.h" +#include "FieldTable.h" + +#ifndef _Buffer_ +#define _Buffer_ + +namespace qpid { +namespace framing { + +class Buffer +{ + const int size; + char* data; + int position; + int limit; + int r_position; + int r_limit; + +public: + + Buffer(int size); + ~Buffer(); + + void flip(); + void clear(); + void compact(); + void record(); + void restore(); + int available(); + char* start(); + void move(int bytes); + + void putOctet(u_int8_t i); + void putShort(u_int16_t i); + void putLong(u_int32_t i); + void putLongLong(u_int64_t i); + + u_int8_t getOctet(); + u_int16_t getShort(); + u_int32_t getLong(); + u_int64_t getLongLong(); + + void putShortString(const string& s); + void putLongString(const string& s); + void getShortString(string& s); + void getLongString(string& s); + + void putFieldTable(const FieldTable& t); + void getFieldTable(FieldTable& t); + + void putRawData(const string& s); + void getRawData(string& s, u_int32_t size); + +}; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/FieldTable.h b/cpp/common/framing/inc/FieldTable.h new file mode 100644 index 0000000000..cf935d3284 --- /dev/null +++ b/cpp/common/framing/inc/FieldTable.h @@ -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. + * + */ +#include +#include +#include "amqp_types.h" + +#ifndef _FieldTable_ +#define _FieldTable_ + +namespace qpid { +namespace framing { + + class NamedValue; + class Value; + class Buffer; + + class FieldTable + { + std::vector values; + NamedValue* find(const std::string& name) const; + + Value* getValue(const std::string& name) const; + void setValue(const std::string& name, Value* value); + + public: + ~FieldTable(); + u_int32_t size() const; + int count() const; + void setString(const std::string& name, const std::string& value); + void setInt(const std::string& name, int value); + void setTimestamp(const std::string& name, u_int64_t value); + void setTable(const std::string& name, const FieldTable& value); + //void setDecimal(string& name, xxx& value); + std::string getString(const std::string& name); + int getInt(const std::string& name); + u_int64_t getTimestamp(const std::string& name); + void getTable(const std::string& name, FieldTable& value); + //void getDecimal(string& name, xxx& value); + + void encode(Buffer& buffer) const; + void decode(Buffer& buffer); + + friend std::ostream& operator<<(std::ostream& out, const FieldTable& body); + }; + + class FieldNotFoundException{}; + class UnknownFieldName : public FieldNotFoundException{}; + class IncorrectFieldType : public FieldNotFoundException{}; +} +} + + +#endif diff --git a/cpp/common/framing/inc/HeaderProperties.h b/cpp/common/framing/inc/HeaderProperties.h new file mode 100644 index 0000000000..f84345c203 --- /dev/null +++ b/cpp/common/framing/inc/HeaderProperties.h @@ -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. + * + */ +#include "amqp_types.h" +#include "Buffer.h" + +#ifndef _HeaderProperties_ +#define _HeaderProperties_ + +namespace qpid { +namespace framing { + + enum header_classes{BASIC = 60}; + + class HeaderProperties + { + + public: + inline virtual ~HeaderProperties(){} + virtual u_int8_t classId() = 0; + virtual u_int32_t size() const = 0; + virtual void encode(Buffer& buffer) const = 0; + virtual void decode(Buffer& buffer, u_int32_t size) = 0; + }; +} +} + + +#endif diff --git a/cpp/common/framing/inc/InitiationHandler.h b/cpp/common/framing/inc/InitiationHandler.h new file mode 100644 index 0000000000..2e8d1e652b --- /dev/null +++ b/cpp/common/framing/inc/InitiationHandler.h @@ -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. + * + */ +#include + +#ifndef _InitiationHandler_ +#define _InitiationHandler_ + +#include "ProtocolInitiation.h" + +namespace qpid { +namespace framing { + + class InitiationHandler{ + public: + virtual void initiated(ProtocolInitiation* header) = 0; + }; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/InputHandler.h b/cpp/common/framing/inc/InputHandler.h new file mode 100644 index 0000000000..2722cae0ed --- /dev/null +++ b/cpp/common/framing/inc/InputHandler.h @@ -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. + * + */ +#include + +#ifndef _InputHandler_ +#define _InputHandler_ + +#include "AMQFrame.h" + +namespace qpid { +namespace framing { + + class InputHandler{ + public: + virtual void received(AMQFrame* frame) = 0; + }; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/NamedValue.h b/cpp/common/framing/inc/NamedValue.h new file mode 100644 index 0000000000..729b5d08a7 --- /dev/null +++ b/cpp/common/framing/inc/NamedValue.h @@ -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. + * + */ +#include +#include +#include "amqp_types.h" +#include "Value.h" + +#ifndef _NamedValue_ +#define _NamedValue_ + +namespace qpid { +namespace framing { + + class Buffer; + + class NamedValue{ + string name; + Value* value; + public: + NamedValue(); + NamedValue(const string& name, Value* value); + ~NamedValue(); + void encode(Buffer& buffer); + void decode(Buffer& buffer); + u_int32_t size() const; + inline const string& getName() const { return name; } + inline Value* getValue() const { return value; } + inline void setValue(Value* val) { value = val; } + }; +} +} + + +#endif diff --git a/cpp/common/framing/inc/OutputHandler.h b/cpp/common/framing/inc/OutputHandler.h new file mode 100644 index 0000000000..7fe63660c3 --- /dev/null +++ b/cpp/common/framing/inc/OutputHandler.h @@ -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. + * + */ +#include + +#ifndef _OutputHandler_ +#define _OutputHandler_ + +#include "AMQFrame.h" + +namespace qpid { +namespace framing { + + class OutputHandler{ + public: + virtual void send(AMQFrame* frame) = 0; + }; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/ProtocolInitiation.h b/cpp/common/framing/inc/ProtocolInitiation.h new file mode 100644 index 0000000000..ab9734e6b3 --- /dev/null +++ b/cpp/common/framing/inc/ProtocolInitiation.h @@ -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. + * + */ +#include "amqp_types.h" +#include "Buffer.h" +#include "AMQDataBlock.h" + +#ifndef _ProtocolInitiation_ +#define _ProtocolInitiation_ + +namespace qpid { +namespace framing { + +class ProtocolInitiation : virtual public AMQDataBlock +{ + u_int8_t pmajor; + u_int8_t pminor; + +public: + ProtocolInitiation(); + ProtocolInitiation(u_int8_t major, u_int8_t minor); + virtual ~ProtocolInitiation(); + virtual void encode(Buffer& buffer); + virtual bool decode(Buffer& buffer); + inline virtual u_int32_t size() const { return 8; } + inline u_int8_t getMajor(){ return pmajor; } + inline u_int8_t getMinor(){ return pminor; } +}; + +} +} + + +#endif diff --git a/cpp/common/framing/inc/Value.h b/cpp/common/framing/inc/Value.h new file mode 100644 index 0000000000..e3d2a2c1d6 --- /dev/null +++ b/cpp/common/framing/inc/Value.h @@ -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. + * + */ +#include +#include +#include "amqp_types.h" +#include "FieldTable.h" + +#ifndef _Value_ +#define _Value_ + +namespace qpid { +namespace framing { + + class Buffer; + + class Value{ + public: + inline virtual ~Value(){} + virtual u_int32_t size() const = 0; + virtual char getType() const = 0; + virtual void encode(Buffer& buffer) = 0; + virtual void decode(Buffer& buffer) = 0; + }; + + class StringValue : public virtual Value{ + string value; + + public: + inline StringValue(const string& v) : value(v){} + inline StringValue(){} + inline string getValue(){ return value; } + ~StringValue(){} + inline virtual u_int32_t size() const { return 4 + value.length(); } + inline virtual char getType() const { return 'S'; } + virtual void encode(Buffer& buffer); + virtual void decode(Buffer& buffer); + }; + + class IntegerValue : public virtual Value{ + int value; + public: + inline IntegerValue(int v) : value(v){} + inline IntegerValue(){} + inline int getValue(){ return value; } + ~IntegerValue(){} + inline virtual u_int32_t size() const { return 4; } + inline virtual char getType() const { return 'I'; } + virtual void encode(Buffer& buffer); + virtual void decode(Buffer& buffer); + }; + + class TimeValue : public virtual Value{ + u_int64_t value; + public: + inline TimeValue(int v) : value(v){} + inline TimeValue(){} + inline u_int64_t getValue(){ return value; } + ~TimeValue(){} + inline virtual u_int32_t size() const { return 8; } + inline virtual char getType() const { return 'T'; } + virtual void encode(Buffer& buffer); + virtual void decode(Buffer& buffer); + }; + + class DecimalValue : public virtual Value{ + u_int8_t decimals; + u_int32_t value; + public: + inline DecimalValue(int v) : value(v){} + inline DecimalValue(){} + ~DecimalValue(){} + inline virtual u_int32_t size() const { return 5; } + inline virtual char getType() const { return 'D'; } + virtual void encode(Buffer& buffer); + virtual void decode(Buffer& buffer); + }; + + class FieldTableValue : public virtual Value{ + FieldTable value; + public: + inline FieldTableValue(const FieldTable& v) : value(v){} + inline FieldTableValue(){} + inline FieldTable getValue(){ return value; } + ~FieldTableValue(){} + inline virtual u_int32_t size() const { return 4 + value.size(); } + inline virtual char getType() const { return 'F'; } + virtual void encode(Buffer& buffer); + virtual void decode(Buffer& buffer); + }; +} +} + + +#endif diff --git a/cpp/common/framing/inc/amqp_framing.h b/cpp/common/framing/inc/amqp_framing.h new file mode 100644 index 0000000000..adb0045ee5 --- /dev/null +++ b/cpp/common/framing/inc/amqp_framing.h @@ -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. + * + */ +#include "amqp_types.h" +#include "AMQFrame.h" +#include "AMQBody.h" +#include "BodyHandler.h" +#include "AMQMethodBody.h" +#include "AMQHeaderBody.h" +#include "AMQContentBody.h" +#include "AMQHeartbeatBody.h" +#include "amqp_methods.h" +#include "InputHandler.h" +#include "OutputHandler.h" +#include "InitiationHandler.h" +#include "ProtocolInitiation.h" +#include "BasicHeaderProperties.h" diff --git a/cpp/common/framing/inc/amqp_types.h b/cpp/common/framing/inc/amqp_types.h new file mode 100644 index 0000000000..6f8ef0862a --- /dev/null +++ b/cpp/common/framing/inc/amqp_types.h @@ -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. + * + */ +#include +#ifdef _WINDOWS +#include "windows.h" +typedef unsigned char u_int8_t; +typedef unsigned short u_int16_t; +typedef unsigned int u_int32_t; +typedef unsigned __int64 u_int64_t; +#endif +#ifndef _WINDOWS +#include "sys/types.h" +#endif + +#ifndef AMQP_TYPES_H +#define AMQP_TYPES_H + + +typedef std::string string; + +#endif diff --git a/cpp/common/framing/src/AMQContentBody.cpp b/cpp/common/framing/src/AMQContentBody.cpp new file mode 100644 index 0000000000..c8aadc8108 --- /dev/null +++ b/cpp/common/framing/src/AMQContentBody.cpp @@ -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. + * + */ +#include "AMQContentBody.h" + +qpid::framing::AMQContentBody::AMQContentBody(){ +} + +qpid::framing::AMQContentBody::AMQContentBody(string& _data) : data(_data){ +} + +u_int32_t qpid::framing::AMQContentBody::size() const{ + return data.size(); +} +void qpid::framing::AMQContentBody::encode(Buffer& buffer) const{ + buffer.putRawData(data); +} +void qpid::framing::AMQContentBody::decode(Buffer& buffer, u_int32_t size){ + buffer.getRawData(data, size); +} + diff --git a/cpp/common/framing/src/AMQFrame.cpp b/cpp/common/framing/src/AMQFrame.cpp new file mode 100644 index 0000000000..70f71010ff --- /dev/null +++ b/cpp/common/framing/src/AMQFrame.cpp @@ -0,0 +1,147 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "AMQFrame.h" +#include "QpidError.h" + +using namespace qpid::framing; + +AMQFrame::AMQFrame(){} + +AMQFrame::AMQFrame(u_int16_t _channel, AMQBody* _body) : channel(_channel), body(_body){} + +AMQFrame::AMQFrame(u_int16_t _channel, AMQBody::shared_ptr& _body) : channel(_channel), body(_body){} + +AMQFrame::~AMQFrame(){ +} + +u_int16_t AMQFrame::getChannel(){ + return channel; +} + +AMQBody::shared_ptr& AMQFrame::getBody(){ + return body; +} + +void AMQFrame::encode(Buffer& buffer) +{ + buffer.putOctet(body->type()); + buffer.putShort(channel); + buffer.putLong(body->size()); + body->encode(buffer); + buffer.putOctet(0xCE); +} + +AMQBody::shared_ptr createMethodBody(Buffer& buffer){ + u_int16_t classId = buffer.getShort(); + u_int16_t methodId = buffer.getShort(); + AMQBody::shared_ptr body(createAMQMethodBody(classId, methodId)); + return body; +} + +u_int32_t AMQFrame::size() const{ + if(!body.get()) THROW_QPID_ERROR(INTERNAL_ERROR, "Attempt to get size of frame with no body set!"); + return 1/*type*/ + 2/*channel*/ + 4/*body size*/ + body->size() + 1/*0xCE*/; +} + +bool AMQFrame::decode(Buffer& buffer) +{ + if(buffer.available() < 7) return false; + buffer.record(); + u_int8_t type = buffer.getOctet(); + channel = buffer.getShort(); + u_int32_t size = buffer.getLong(); + if(buffer.available() < size + 1){ + buffer.restore(); + return false; + } + switch(type) + { + case METHOD_BODY: + body = createMethodBody(buffer); + break; + case HEADER_BODY: + body = AMQBody::shared_ptr(new AMQHeaderBody()); + break; + case CONTENT_BODY: + body = AMQBody::shared_ptr(new AMQContentBody()); + break; + case HEARTBEAT_BODY: + body = AMQBody::shared_ptr(new AMQHeartbeatBody()); + break; + default: + string msg("Unknown body type: "); + msg += type; + THROW_QPID_ERROR(FRAMING_ERROR, msg); + } + body->decode(buffer, size); + u_int8_t end = buffer.getOctet(); + if(end != 0xCE) THROW_QPID_ERROR(FRAMING_ERROR, "Frame end not found"); + return true; +} + +u_int32_t AMQFrame::decodeHead(Buffer& buffer){ + type = buffer.getOctet(); + channel = buffer.getShort(); + return buffer.getLong(); +} + +void AMQFrame::decodeBody(Buffer& buffer, uint32_t size) +{ + switch(type) + { + case METHOD_BODY: + body = createMethodBody(buffer); + break; + case HEADER_BODY: + body = AMQBody::shared_ptr(new AMQHeaderBody()); + break; + case CONTENT_BODY: + body = AMQBody::shared_ptr(new AMQContentBody()); + break; + case HEARTBEAT_BODY: + body = AMQBody::shared_ptr(new AMQHeartbeatBody()); + break; + default: + string msg("Unknown body type: "); + msg += type; + THROW_QPID_ERROR(FRAMING_ERROR, msg); + } + body->decode(buffer, size); +} + +std::ostream& qpid::framing::operator<<(std::ostream& out, const AMQFrame& t){ + out << "Frame[channel=" << t.channel << "; "; + if(t.body.get() == 0){ + out << "empty"; + }else if(t.body->type() == METHOD_BODY){ + (dynamic_cast(t.body.get()))->print(out); + }else if(t.body->type() == HEADER_BODY){ + out << "header, content_size=" << + (dynamic_cast(t.body.get()))->getContentSize() + << " (" << t.body->size() << " bytes)"; + }else if(t.body->type() == CONTENT_BODY){ + out << "content (" << t.body->size() << " bytes)"; + }else if(t.body->type() == HEARTBEAT_BODY){ + out << "heartbeat"; + }else{ + out << "unknown type, " << t.body->type(); + } + out << "]"; + return out; +} + diff --git a/cpp/common/framing/src/AMQHeaderBody.cpp b/cpp/common/framing/src/AMQHeaderBody.cpp new file mode 100644 index 0000000000..4bf1626a8a --- /dev/null +++ b/cpp/common/framing/src/AMQHeaderBody.cpp @@ -0,0 +1,60 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "AMQHeaderBody.h" +#include "QpidError.h" +#include "BasicHeaderProperties.h" + +qpid::framing::AMQHeaderBody::AMQHeaderBody(int classId) : weight(0), contentSize(0){ + createProperties(classId); +} + +qpid::framing::AMQHeaderBody::AMQHeaderBody() : properties(0), weight(0), contentSize(0){ +} + +qpid::framing::AMQHeaderBody::~AMQHeaderBody(){ + delete properties; +} + +u_int32_t qpid::framing::AMQHeaderBody::size() const{ + return 12 + properties->size(); +} + +void qpid::framing::AMQHeaderBody::encode(Buffer& buffer) const { + buffer.putShort(properties->classId()); + buffer.putShort(weight); + buffer.putLongLong(contentSize); + properties->encode(buffer); +} + +void qpid::framing::AMQHeaderBody::decode(Buffer& buffer, u_int32_t size){ + u_int16_t classId = buffer.getShort(); + weight = buffer.getShort(); + contentSize = buffer.getLongLong(); + createProperties(classId); + properties->decode(buffer, size - 12); +} + +void qpid::framing::AMQHeaderBody::createProperties(int classId){ + switch(classId){ + case BASIC: + properties = new qpid::framing::BasicHeaderProperties(); + break; + default: + THROW_QPID_ERROR(FRAMING_ERROR, "Unknown header class"); + } +} diff --git a/cpp/common/framing/src/AMQMethodBody.cpp b/cpp/common/framing/src/AMQMethodBody.cpp new file mode 100644 index 0000000000..73862bb2bf --- /dev/null +++ b/cpp/common/framing/src/AMQMethodBody.cpp @@ -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. + * + */ +#include "AMQMethodBody.h" +#include "QpidError.h" + +void qpid::framing::AMQMethodBody::encode(Buffer& buffer) const{ + buffer.putShort(amqpClassId()); + buffer.putShort(amqpMethodId()); + encodeContent(buffer); +} + +void qpid::framing::AMQMethodBody::decode(Buffer& buffer, u_int32_t size){ + decodeContent(buffer); +} + +bool qpid::framing::AMQMethodBody::match(AMQMethodBody* other) const{ + return other != 0 && other->amqpClassId() == amqpClassId() && other->amqpMethodId() == amqpMethodId(); +} + +void qpid::framing::AMQMethodBody::invoke(AMQP_ServerOperations& target, u_int16_t channel){ + THROW_QPID_ERROR(PROTOCOL_ERROR, "Method not supported by AMQP Server."); +} + + +std::ostream& qpid::framing::operator<<(std::ostream& out, const AMQMethodBody& m){ + m.print(out); + return out; +} diff --git a/cpp/common/framing/src/BasicHeaderProperties.cpp b/cpp/common/framing/src/BasicHeaderProperties.cpp new file mode 100644 index 0000000000..4219d33a8f --- /dev/null +++ b/cpp/common/framing/src/BasicHeaderProperties.cpp @@ -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. + * + */ +#include "BasicHeaderProperties.h" + +//TODO: This could be easily generated from the spec + +qpid::framing::BasicHeaderProperties::BasicHeaderProperties() : deliveryMode(0), priority(0), timestamp(0){} +qpid::framing::BasicHeaderProperties::~BasicHeaderProperties(){} + +u_int32_t qpid::framing::BasicHeaderProperties::size() const{ + u_int32_t size = 2;//flags + if(contentType.length() > 0) size += contentType.length() + 1; + if(contentEncoding.length() > 0) size += contentEncoding.length() + 1; + if(headers.count() > 0) size += headers.size(); + if(deliveryMode != 0) size += 1; + if(priority != 0) size += 1; + if(correlationId.length() > 0) size += correlationId.length() + 1; + if(replyTo.length() > 0) size += replyTo.length() + 1; + if(expiration.length() > 0) size += expiration.length() + 1; + if(messageId.length() > 0) size += messageId.length() + 1; + if(timestamp != 0) size += 8; + if(type.length() > 0) size += type.length() + 1; + if(userId.length() > 0) size += userId.length() + 1; + if(appId.length() > 0) size += appId.length() + 1; + if(clusterId.length() > 0) size += clusterId.length() + 1; + + return size; +} + +void qpid::framing::BasicHeaderProperties::encode(qpid::framing::Buffer& buffer) const{ + u_int16_t flags = getFlags(); + buffer.putShort(flags); + + if(contentType.length() > 0) buffer.putShortString(contentType); + if(contentEncoding.length() > 0) buffer.putShortString(contentEncoding); + if(headers.count() > 0) buffer.putFieldTable(headers); + if(deliveryMode != 0) buffer.putOctet(deliveryMode); + if(priority != 0) buffer.putOctet(priority); + if(correlationId.length() > 0) buffer.putShortString(correlationId); + if(replyTo.length() > 0) buffer.putShortString(replyTo); + if(expiration.length() > 0) buffer.putShortString(expiration); + if(messageId.length() > 0) buffer.putShortString(messageId); + if(timestamp != 0) buffer.putLongLong(timestamp);; + if(type.length() > 0) buffer.putShortString(type); + if(userId.length() > 0) buffer.putShortString(userId); + if(appId.length() > 0) buffer.putShortString(appId); + if(clusterId.length() > 0) buffer.putShortString(clusterId); +} + +void qpid::framing::BasicHeaderProperties::decode(qpid::framing::Buffer& buffer, u_int32_t size){ + u_int16_t flags = buffer.getShort(); + int shift = 15; + if(flags & (1 << 15)) buffer.getShortString(contentType); + if(flags & (1 << 14)) buffer.getShortString(contentEncoding); + if(flags & (1 << 13)) buffer.getFieldTable(headers); + if(flags & (1 << 12)) deliveryMode = buffer.getOctet(); + if(flags & (1 << 11)) priority = buffer.getOctet(); + if(flags & (1 << 10)) buffer.getShortString(correlationId); + if(flags & (1 << 9)) buffer.getShortString(replyTo); + if(flags & (1 << 8)) buffer.getShortString(expiration); + if(flags & (1 << 7)) buffer.getShortString(messageId); + if(flags & (1 << 6)) timestamp = buffer.getLongLong(); + if(flags & (1 << 5)) buffer.getShortString(type); + if(flags & (1 << 4)) buffer.getShortString(userId); + if(flags & (1 << 3)) buffer.getShortString(appId); + if(flags & (1 << 2)) buffer.getShortString(clusterId); +} + +u_int16_t qpid::framing::BasicHeaderProperties::getFlags() const{ + u_int16_t flags(0); + int shift = 15; + if(contentType.length() > 0) flags |= (1 << 15); + if(contentEncoding.length() > 0) flags |= (1 << 14); + if(headers.count() > 0) flags |= (1 << 13); + if(deliveryMode != 0) flags |= (1 << 12); + if(priority != 0) flags |= (1 << 11); + if(correlationId.length() > 0) flags |= (1 << 10); + if(replyTo.length() > 0) flags |= (1 << 9); + if(expiration.length() > 0) flags |= (1 << 8); + if(messageId.length() > 0) flags |= (1 << 7); + if(timestamp != 0) flags |= (1 << 6); + if(type.length() > 0) flags |= (1 << 5); + if(userId.length() > 0) flags |= (1 << 4); + if(appId.length() > 0) flags |= (1 << 3); + if(clusterId.length() > 0) flags |= (1 << 2); + return flags; +} diff --git a/cpp/common/framing/src/BodyHandler.cpp b/cpp/common/framing/src/BodyHandler.cpp new file mode 100644 index 0000000000..4e4e2e02f7 --- /dev/null +++ b/cpp/common/framing/src/BodyHandler.cpp @@ -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. + * + */ +#include "memory.h" +#include "BodyHandler.h" + +using namespace qpid::framing; +using namespace std::tr1; + +void BodyHandler::handleBody(AMQBody::shared_ptr& body){ + + switch(body->type()) + { + + case METHOD_BODY: + handleMethod(dynamic_pointer_cast(body)); + break; + + case HEADER_BODY: + handleHeader(dynamic_pointer_cast(body)); + break; + + case CONTENT_BODY: + handleContent(dynamic_pointer_cast(body)); + break; + + case HEARTBEAT_BODY: + handleHeartbeat(dynamic_pointer_cast(body)); + break; + + default: + throw UnknownBodyType(body->type()); + } + +} diff --git a/cpp/common/framing/src/Buffer.cpp b/cpp/common/framing/src/Buffer.cpp new file mode 100644 index 0000000000..5264491980 --- /dev/null +++ b/cpp/common/framing/src/Buffer.cpp @@ -0,0 +1,167 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "Buffer.h" + +qpid::framing::Buffer::Buffer(int _size) : size(_size), position(0), limit(_size){ + data = new char[size]; +} + +qpid::framing::Buffer::~Buffer(){ + delete[] data; +} + +void qpid::framing::Buffer::flip(){ + limit = position; + position = 0; +} + +void qpid::framing::Buffer::clear(){ + limit = size; + position = 0; +} + +void qpid::framing::Buffer::compact(){ + int p = limit - position; + //copy p chars from position to 0 + memmove(data, data + position, p); + limit = size; + position = p; +} + +void qpid::framing::Buffer::record(){ + r_position = position; + r_limit = limit; +} + +void qpid::framing::Buffer::restore(){ + position = r_position; + limit = r_limit; +} + +int qpid::framing::Buffer::available(){ + return limit - position; +} + +char* qpid::framing::Buffer::start(){ + return data + position; +} + +void qpid::framing::Buffer::move(int bytes){ + position += bytes; +} + +void qpid::framing::Buffer::putOctet(u_int8_t i){ + data[position++] = i; +} + +void qpid::framing::Buffer::putShort(u_int16_t i){ + u_int16_t b = i; + data[position++] = (u_int8_t) (0xFF & (b >> 8)); + data[position++] = (u_int8_t) (0xFF & b); +} + +void qpid::framing::Buffer::putLong(u_int32_t i){ + u_int32_t b = i; + data[position++] = (u_int8_t) (0xFF & (b >> 24)); + data[position++] = (u_int8_t) (0xFF & (b >> 16)); + data[position++] = (u_int8_t) (0xFF & (b >> 8)); + data[position++] = (u_int8_t) (0xFF & b); +} + +void qpid::framing::Buffer::putLongLong(u_int64_t i){ + u_int32_t hi = i >> 32; + u_int32_t lo = i; + putLong(hi); + putLong(lo); +} + +u_int8_t qpid::framing::Buffer::getOctet(){ + return (u_int8_t) data[position++]; +} + +u_int16_t qpid::framing::Buffer::getShort(){ + u_int16_t hi = (unsigned char) data[position++]; + hi = hi << 8; + hi |= (unsigned char) data[position++]; + return hi; +} + +u_int32_t qpid::framing::Buffer::getLong(){ + u_int32_t a = (unsigned char) data[position++]; + u_int32_t b = (unsigned char) data[position++]; + u_int32_t c = (unsigned char) data[position++]; + u_int32_t d = (unsigned char) data[position++]; + a = a << 24; + a |= b << 16; + a |= c << 8; + a |= d; + return a; +} + +u_int64_t qpid::framing::Buffer::getLongLong(){ + u_int64_t hi = getLong(); + u_int64_t lo = getLong(); + hi = hi << 32; + return hi | lo; +} + + +void qpid::framing::Buffer::putShortString(const string& s){ + u_int8_t size = s.length(); + putOctet(size); + s.copy(data + position, size); + position += size; +} + +void qpid::framing::Buffer::putLongString(const string& s){ + u_int32_t size = s.length(); + putLong(size); + s.copy(data + position, size); + position += size; +} + +void qpid::framing::Buffer::getShortString(string& s){ + u_int8_t size = getOctet(); + s.assign(data + position, size); + position += size; +} + +void qpid::framing::Buffer::getLongString(string& s){ + u_int32_t size = getLong(); + s.assign(data + position, size); + position += size; +} + +void qpid::framing::Buffer::putFieldTable(const FieldTable& t){ + t.encode(*this); +} + +void qpid::framing::Buffer::getFieldTable(FieldTable& t){ + t.decode(*this); +} + +void qpid::framing::Buffer::putRawData(const string& s){ + u_int32_t size = s.length(); + s.copy(data + position, size); + position += size; +} + +void qpid::framing::Buffer::getRawData(string& s, u_int32_t size){ + s.assign(data + position, size); + position += size; +} diff --git a/cpp/common/framing/src/FieldTable.cpp b/cpp/common/framing/src/FieldTable.cpp new file mode 100644 index 0000000000..048cefa83c --- /dev/null +++ b/cpp/common/framing/src/FieldTable.cpp @@ -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. + * + */ +#include "FieldTable.h" +#include "NamedValue.h" +#include "QpidError.h" +#include "Buffer.h" +#include "Value.h" + +qpid::framing::FieldTable::~FieldTable(){ + int count(values.size()); + for(int i = 0; i < count; i++){ + delete values[i]; + } +} + +u_int32_t qpid::framing::FieldTable::size() const { + u_int32_t size(4); + int count(values.size()); + for(int i = 0; i < count; i++){ + size += values[i]->size(); + } + return size; +} + +int qpid::framing::FieldTable::count() const { + return values.size(); +} + +std::ostream& qpid::framing::operator<<(std::ostream& out, const FieldTable& t){ + out << "field_table{}"; + return out; +} + +void qpid::framing::FieldTable::setString(const std::string& name, const std::string& value){ + setValue(name, new StringValue(value)); +} + +void qpid::framing::FieldTable::setInt(const std::string& name, int value){ + setValue(name, new IntegerValue(value)); +} + +void qpid::framing::FieldTable::setTimestamp(const std::string& name, u_int64_t value){ + setValue(name, new TimeValue(value)); +} + +void qpid::framing::FieldTable::setTable(const std::string& name, const FieldTable& value){ + setValue(name, new FieldTableValue(value)); +} + +std::string qpid::framing::FieldTable::getString(const std::string& name){ + StringValue* val = dynamic_cast(getValue(name)); + return (val == 0 ? "" : val->getValue()); +} + +int qpid::framing::FieldTable::getInt(const std::string& name){ + IntegerValue* val = dynamic_cast(getValue(name)); + return (val == 0 ? 0 : val->getValue()); +} + +u_int64_t qpid::framing::FieldTable::getTimestamp(const std::string& name){ + TimeValue* val = dynamic_cast(getValue(name)); + return (val == 0 ? 0 : val->getValue()); +} + +void qpid::framing::FieldTable::getTable(const std::string& name, FieldTable& value){ + FieldTableValue* val = dynamic_cast(getValue(name)); + if(val != 0) value = val->getValue(); +} + +qpid::framing::NamedValue* qpid::framing::FieldTable::find(const std::string& name) const{ + int count(values.size()); + for(int i = 0; i < count; i++){ + if(values[i]->getName() == name) return values[i]; + } + return 0; +} + +qpid::framing::Value* qpid::framing::FieldTable::getValue(const std::string& name) const{ + NamedValue* val = find(name); + return val == 0 ? 0 : val->getValue(); +} + +void qpid::framing::FieldTable::setValue(const std::string& name, Value* value){ + NamedValue* val = find(name); + if(val == 0){ + val = new NamedValue(name, value); + values.push_back(val); + }else{ + Value* old = val->getValue(); + if(old != 0) delete old; + val->setValue(value); + } +} + +void qpid::framing::FieldTable::encode(Buffer& buffer) const{ + buffer.putLong(size() - 4); + int count(values.size()); + for(int i = 0; i < count; i++){ + values[i]->encode(buffer); + } +} + +void qpid::framing::FieldTable::decode(Buffer& buffer){ + u_int32_t size = buffer.getLong(); + int leftover = buffer.available() - size; + + while(buffer.available() > leftover){ + NamedValue* value = new NamedValue(); + value->decode(buffer); + values.push_back(value); + } +} diff --git a/cpp/common/framing/src/NamedValue.cpp b/cpp/common/framing/src/NamedValue.cpp new file mode 100644 index 0000000000..e80aea433c --- /dev/null +++ b/cpp/common/framing/src/NamedValue.cpp @@ -0,0 +1,67 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "NamedValue.h" +#include "QpidError.h" +#include "Buffer.h" +#include "FieldTable.h" + +qpid::framing::NamedValue::NamedValue() : value(0){} + +qpid::framing::NamedValue::NamedValue(const string& n, Value* v) : name(n), value(v){} + +qpid::framing::NamedValue::~NamedValue(){ + if(value != 0){ + delete value; + } +} + +u_int32_t qpid::framing::NamedValue::size() const{ + return value ? 1/*size of name*/ + name.length() + 1/*type char*/ + value->size() : 0; +} + +void qpid::framing::NamedValue::encode(Buffer& buffer){ + buffer.putShortString(name); + u_int8_t type = value->getType(); + buffer.putOctet(type); + value->encode(buffer); +} + +void qpid::framing::NamedValue::decode(Buffer& buffer){ + buffer.getShortString(name); + u_int8_t type = buffer.getOctet(); + switch(type){ + case 'S': + value = new StringValue(); + break; + case 'I': + value = new IntegerValue(); + break; + case 'D': + value = new DecimalValue(); + break; + case 'T': + value = new TimeValue(); + break; + case 'F': + value = new FieldTableValue(); + break; + default: + THROW_QPID_ERROR(FRAMING_ERROR, "Unknown field table value type"); + } + value->decode(buffer); +} diff --git a/cpp/common/framing/src/ProtocolInitiation.cpp b/cpp/common/framing/src/ProtocolInitiation.cpp new file mode 100644 index 0000000000..6806d73b55 --- /dev/null +++ b/cpp/common/framing/src/ProtocolInitiation.cpp @@ -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. + * + */ +#include "ProtocolInitiation.h" + +qpid::framing::ProtocolInitiation::ProtocolInitiation(){} + +qpid::framing::ProtocolInitiation::ProtocolInitiation(u_int8_t _major, u_int8_t _minor) : pmajor(_major), pminor(_minor){} + +qpid::framing::ProtocolInitiation::~ProtocolInitiation(){} + +void qpid::framing::ProtocolInitiation::encode(Buffer& buffer){ + buffer.putOctet('A'); + buffer.putOctet('M'); + buffer.putOctet('Q'); + buffer.putOctet('P'); + buffer.putOctet(1);//class + buffer.putOctet(1);//instance + buffer.putOctet(pmajor); + buffer.putOctet(pminor); +} + +bool qpid::framing::ProtocolInitiation::decode(Buffer& buffer){ + if(buffer.available() >= 8){ + buffer.getOctet();//A + buffer.getOctet();//M + buffer.getOctet();//Q + buffer.getOctet();//P + buffer.getOctet();//class + buffer.getOctet();//instance + pmajor = buffer.getOctet(); + pminor = buffer.getOctet(); + return true; + }else{ + return false; + } +} + +//TODO: this should prbably be generated from the spec at some point to keep the version numbers up to date diff --git a/cpp/common/framing/src/Value.cpp b/cpp/common/framing/src/Value.cpp new file mode 100644 index 0000000000..240b086696 --- /dev/null +++ b/cpp/common/framing/src/Value.cpp @@ -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. + * + */ +#include "Value.h" +#include "Buffer.h" +#include "FieldTable.h" + +void qpid::framing::StringValue::encode(Buffer& buffer){ + buffer.putLongString(value); +} +void qpid::framing::StringValue::decode(Buffer& buffer){ + buffer.getLongString(value); +} + +void qpid::framing::IntegerValue::encode(Buffer& buffer){ + buffer.putLong((u_int32_t) value); +} +void qpid::framing::IntegerValue::decode(Buffer& buffer){ + value = buffer.getLong(); +} + +void qpid::framing::TimeValue::encode(Buffer& buffer){ + buffer.putLongLong(value); +} +void qpid::framing::TimeValue::decode(Buffer& buffer){ + value = buffer.getLongLong(); +} + +void qpid::framing::DecimalValue::encode(Buffer& buffer){ + buffer.putOctet(decimals); + buffer.putLong(value); +} +void qpid::framing::DecimalValue::decode(Buffer& buffer){ + decimals = buffer.getOctet(); + value = buffer.getLong(); +} + +void qpid::framing::FieldTableValue::encode(Buffer& buffer){ + buffer.putFieldTable(value); +} +void qpid::framing::FieldTableValue::decode(Buffer& buffer){ + buffer.getFieldTable(value); +} diff --git a/cpp/common/framing/test/BodyHandlerTest.cpp b/cpp/common/framing/test/BodyHandlerTest.cpp new file mode 100644 index 0000000000..94038d9a6c --- /dev/null +++ b/cpp/common/framing/test/BodyHandlerTest.cpp @@ -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. + * + */ +#include +#include "amqp_framing.h" +#include +#include +#include +#include + +using namespace qpid::framing; + +class BodyHandlerTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(BodyHandlerTest); + CPPUNIT_TEST(testMethod); + CPPUNIT_TEST(testHeader); + CPPUNIT_TEST(testContent); + CPPUNIT_TEST(testHeartbeat); + CPPUNIT_TEST_SUITE_END(); +private: + + class TestBodyHandler : public BodyHandler{ + AMQMethodBody* const method; + AMQHeaderBody* const header; + AMQContentBody* const content; + AMQHeartbeatBody* const heartbeat; + + public: + + TestBodyHandler(AMQMethodBody* _method) : method(_method), header(0), content(0), heartbeat(0){} + TestBodyHandler(AMQHeaderBody* _header) : method(0), header(_header), content(0), heartbeat(0){} + TestBodyHandler(AMQContentBody* _content) : method(0), header(0), content(_content), heartbeat(0){} + TestBodyHandler(AMQHeartbeatBody* _heartbeat) : method(0), header(0), content(0), heartbeat(_heartbeat){} + + virtual void handleMethod(AMQMethodBody::shared_ptr body){ + CPPUNIT_ASSERT(method); + CPPUNIT_ASSERT_EQUAL(method, body.get()); + } + virtual void handleHeader(AMQHeaderBody::shared_ptr body){ + CPPUNIT_ASSERT(header); + CPPUNIT_ASSERT_EQUAL(header, body.get()); + } + virtual void handleContent(AMQContentBody::shared_ptr body){ + CPPUNIT_ASSERT(content); + CPPUNIT_ASSERT_EQUAL(content, body.get()); + } + virtual void handleHeartbeat(AMQHeartbeatBody::shared_ptr body){ + CPPUNIT_ASSERT(heartbeat); + CPPUNIT_ASSERT_EQUAL(heartbeat, body.get()); + } + }; + +public: + + void testMethod() + { + AMQMethodBody* method = new QueueDeclareBody(); + AMQFrame frame(0, method); + TestBodyHandler handler(method); + handler.handleBody(frame.getBody()); + } + + void testHeader() + { + AMQHeaderBody* header = new AMQHeaderBody(); + AMQFrame frame(0, header); + TestBodyHandler handler(header); + handler.handleBody(frame.getBody()); + } + + void testContent() + { + AMQContentBody* content = new AMQContentBody(); + AMQFrame frame(0, content); + TestBodyHandler handler(content); + handler.handleBody(frame.getBody()); + } + + void testHeartbeat() + { + AMQHeartbeatBody* heartbeat = new AMQHeartbeatBody(); + AMQFrame frame(0, heartbeat); + TestBodyHandler handler(heartbeat); + handler.handleBody(frame.getBody()); + } +}; + + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(BodyHandlerTest); + diff --git a/cpp/common/framing/test/Makefile b/cpp/common/framing/test/Makefile new file mode 100644 index 0000000000..487b8d537b --- /dev/null +++ b/cpp/common/framing/test/Makefile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2006 The Apache Software Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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_HOME = ../../../.. +LDLIBS=-lapr-1 -lcppunit $(COMMON_LIB) +INCLUDES=$(TEST_INCLUDES) -I ../generated +include ${QPID_HOME}/cpp/test_plugins.mk + diff --git a/cpp/common/framing/test/field_table_test.cpp b/cpp/common/framing/test/field_table_test.cpp new file mode 100644 index 0000000000..48332e05bc --- /dev/null +++ b/cpp/common/framing/test/field_table_test.cpp @@ -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. + * + */ +#include +#include "amqp_framing.h" +#include +#include +#include +#include + +using namespace qpid::framing; + +class FieldTableTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(FieldTableTest); + CPPUNIT_TEST(testMe); + CPPUNIT_TEST_SUITE_END(); + + public: + + void testMe() + { + FieldTable ft; + ft.setString("A", "BCDE"); + CPPUNIT_ASSERT_EQUAL(std::string("BCDE"), ft.getString("A")); + + Buffer buffer(100); + buffer.putFieldTable(ft); + buffer.flip(); + FieldTable ft2; + buffer.getFieldTable(ft2); + CPPUNIT_ASSERT_EQUAL(std::string("BCDE"), ft2.getString("A")); + + } +}; + + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(FieldTableTest); + diff --git a/cpp/common/framing/test/framing_test.cpp b/cpp/common/framing/test/framing_test.cpp new file mode 100644 index 0000000000..1978c2cbed --- /dev/null +++ b/cpp/common/framing/test/framing_test.cpp @@ -0,0 +1,147 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "amqp_framing.h" +#include "ConnectionRedirectBody.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace qpid::framing; + +// TODO aconway 2006-09-12: Why do we need explicit qpid::framing:: below? + +template +std::string tostring(const T& x) +{ + std::ostringstream out; + out << x; + return out.str(); +} + +class FramingTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(FramingTest); + CPPUNIT_TEST(testBasicQosBody); + CPPUNIT_TEST(testConnectionSecureBody); + CPPUNIT_TEST(testConnectionRedirectBody); + CPPUNIT_TEST(testAccessRequestBody); + CPPUNIT_TEST(testBasicConsumeBody); + CPPUNIT_TEST(ConnectionRedirectBody); + CPPUNIT_TEST(BasicConsumeOkBody); + CPPUNIT_TEST_SUITE_END(); + + private: + Buffer buffer; + + public: + + FramingTest() : buffer(100) {} + + void testBasicQosBody() + { + BasicQosBody in(0xCAFEBABE, 0xABBA, true); + in.encodeContent(buffer); + buffer.flip(); + BasicQosBody out; + out.decodeContent(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + + void testConnectionSecureBody() + { + std::string s = "security credential"; + ConnectionSecureBody in(s); + in.encodeContent(buffer); + buffer.flip(); + ConnectionSecureBody out; + out.decodeContent(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + + void testConnectionRedirectBody() + { + std::string a = "hostA"; + std::string b = "hostB"; + qpid::framing::ConnectionRedirectBody in(a, b); + in.encodeContent(buffer); + buffer.flip(); + qpid::framing::ConnectionRedirectBody out; + out.decodeContent(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + + void testAccessRequestBody() + { + std::string s = "text"; + AccessRequestBody in(s, true, false, true, false, true); + in.encodeContent(buffer); + buffer.flip(); + AccessRequestBody out; + out.decodeContent(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + + void testBasicConsumeBody() + { + std::string q = "queue"; + std::string t = "tag"; + BasicConsumeBody in(0, q, t, false, true, false, false); + in.encodeContent(buffer); + buffer.flip(); + BasicConsumeBody out; + out.decodeContent(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + + + void ConnectionRedirectBody() + { + std::string a = "hostA"; + std::string b = "hostB"; + AMQFrame in(999, new qpid::framing::ConnectionRedirectBody(a, b)); + in.encode(buffer); + buffer.flip(); + AMQFrame out; + out.decode(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + + void BasicConsumeOkBody() + { + std::string s = "hostA"; + AMQFrame in(999, new qpid::framing::BasicConsumeOkBody(s)); + in.encode(buffer); + buffer.flip(); + AMQFrame out; + for(int i = 0; i < 5; i++){ + out.decode(buffer); + CPPUNIT_ASSERT_EQUAL(tostring(in), tostring(out)); + } + } +}; + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(FramingTest); + + + diff --git a/cpp/common/framing/test/header_test.cpp b/cpp/common/framing/test/header_test.cpp new file mode 100644 index 0000000000..0ff6d47d57 --- /dev/null +++ b/cpp/common/framing/test/header_test.cpp @@ -0,0 +1,144 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "amqp_framing.h" +#include +#include +#include +#include + +using namespace qpid::framing; + +class HeaderTest : public CppUnit::TestCase +{ + CPPUNIT_TEST_SUITE(HeaderTest); + CPPUNIT_TEST(testGenericProperties); + CPPUNIT_TEST(testAllSpecificProperties); + CPPUNIT_TEST(testSomeSpecificProperties); + CPPUNIT_TEST_SUITE_END(); + +public: + + // TODO aconway 2006-09-12: Need more detailed tests, + // need tests to assert something! + // + void testGenericProperties() + { + AMQHeaderBody body(BASIC); + dynamic_cast(body.getProperties())->getHeaders().setString("A", "BCDE"); + Buffer buffer(100); + + body.encode(buffer); + buffer.flip(); + AMQHeaderBody body2; + body2.decode(buffer, body.size()); + BasicHeaderProperties* props = + dynamic_cast(body2.getProperties()); + CPPUNIT_ASSERT_EQUAL(std::string("BCDE"), + props->getHeaders().getString("A")); + } + + void testAllSpecificProperties(){ + string contentType("text/html"); + string contentEncoding("UTF8"); + u_int8_t deliveryMode(2); + u_int8_t priority(3); + string correlationId("abc"); + string replyTo("no-address"); + string expiration("why is this a string?"); + string messageId("xyz"); + u_int64_t timestamp(0xabcd); + string type("eh?"); + string userId("guest"); + string appId("just testing"); + string clusterId("no clustering required"); + + AMQHeaderBody body(BASIC); + BasicHeaderProperties* properties = + dynamic_cast(body.getProperties()); + properties->setContentType(contentType); + properties->getHeaders().setString("A", "BCDE"); + properties->setDeliveryMode(deliveryMode); + properties->setPriority(priority); + properties->setCorrelationId(correlationId); + properties->setReplyTo(replyTo); + properties->setExpiration(expiration); + properties->setMessageId(messageId); + properties->setTimestamp(timestamp); + properties->setType(type); + properties->setUserId(userId); + properties->setAppId(appId); + properties->setClusterId(clusterId); + + Buffer buffer(10000); + body.encode(buffer); + buffer.flip(); + AMQHeaderBody temp; + temp.decode(buffer, body.size()); + properties = dynamic_cast(temp.getProperties()); + + CPPUNIT_ASSERT_EQUAL(contentType, properties->getContentType()); + CPPUNIT_ASSERT_EQUAL(std::string("BCDE"), properties->getHeaders().getString("A")); + CPPUNIT_ASSERT_EQUAL(deliveryMode, properties->getDeliveryMode()); + CPPUNIT_ASSERT_EQUAL(priority, properties->getPriority()); + CPPUNIT_ASSERT_EQUAL(correlationId, properties->getCorrelationId()); + CPPUNIT_ASSERT_EQUAL(replyTo, properties->getReplyTo()); + CPPUNIT_ASSERT_EQUAL(expiration, properties->getExpiration()); + CPPUNIT_ASSERT_EQUAL(messageId, properties->getMessageId()); + CPPUNIT_ASSERT_EQUAL(timestamp, properties->getTimestamp()); + CPPUNIT_ASSERT_EQUAL(type, properties->getType()); + CPPUNIT_ASSERT_EQUAL(userId, properties->getUserId()); + CPPUNIT_ASSERT_EQUAL(appId, properties->getAppId()); + CPPUNIT_ASSERT_EQUAL(clusterId, properties->getClusterId()); + } + + void testSomeSpecificProperties(){ + string contentType("application/octet-stream"); + u_int8_t deliveryMode(5); + u_int8_t priority(6); + string expiration("Z"); + u_int64_t timestamp(0xabe4a34a); + + AMQHeaderBody body(BASIC); + BasicHeaderProperties* properties = + dynamic_cast(body.getProperties()); + properties->setContentType(contentType); + properties->setDeliveryMode(deliveryMode); + properties->setPriority(priority); + properties->setExpiration(expiration); + properties->setTimestamp(timestamp); + + Buffer buffer(100); + body.encode(buffer); + buffer.flip(); + AMQHeaderBody temp; + temp.decode(buffer, body.size()); + properties = dynamic_cast(temp.getProperties()); + + CPPUNIT_ASSERT_EQUAL(contentType, properties->getContentType()); + CPPUNIT_ASSERT_EQUAL((int) deliveryMode, (int) properties->getDeliveryMode()); + CPPUNIT_ASSERT_EQUAL((int) priority, (int) properties->getPriority()); + CPPUNIT_ASSERT_EQUAL(expiration, properties->getExpiration()); + CPPUNIT_ASSERT_EQUAL(timestamp, properties->getTimestamp()); + } +}; + +// Make this test suite a plugin. +CPPUNIT_PLUGIN_IMPLEMENT(); +CPPUNIT_TEST_SUITE_REGISTRATION(HeaderTest); + diff --git a/cpp/common/io/Makefile b/cpp/common/io/Makefile new file mode 100644 index 0000000000..e94e802afa --- /dev/null +++ b/cpp/common/io/Makefile @@ -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. + # + +QPID_HOME = ../../.. +include ${QPID_HOME}/cpp/options.mk + +# Compiler flags +CXXFLAGS = ${DEBUG} ${OPT} -MMD -I inc -I ../concurrent/inc -I ../error/inc -I ../framing/inc -I ../framing/generated -I ${APR_HOME}/include/apr-1/ + +SOURCES := $(wildcard src/*.cpp) +OBJECTS := $(subst .cpp,.o,$(SOURCES)) +DEPS := $(subst .cpp,.d,$(SOURCES)) + +.PHONY: all clean + +all: ${OBJECTS} + +-include $(DEPS) + +clean : + -@rm -f ${OBJECTS} src/*.d + diff --git a/cpp/common/io/inc/APRConnector.h b/cpp/common/io/inc/APRConnector.h new file mode 100644 index 0000000000..c292c4d7e0 --- /dev/null +++ b/cpp/common/io/inc/APRConnector.h @@ -0,0 +1,95 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _APRConnector_ +#define _APRConnector_ + +#include "apr_network_io.h" +#include "apr_time.h" + +#include "InputHandler.h" +#include "OutputHandler.h" +#include "InitiationHandler.h" +#include "ProtocolInitiation.h" +#include "ShutdownHandler.h" +#include "Thread.h" +#include "ThreadFactory.h" +#include "Connector.h" +#include "APRMonitor.h" + +namespace qpid { +namespace io { + + class APRConnector : public virtual qpid::framing::OutputHandler, + public virtual Connector, + private virtual qpid::concurrent::Runnable + { + const bool debug; + const int receive_buffer_size; + const int send_buffer_size; + + bool closed; + + apr_time_t lastIn; + apr_time_t lastOut; + apr_interval_time_t timeout; + u_int32_t idleIn; + u_int32_t idleOut; + + TimeoutHandler* timeoutHandler; + ShutdownHandler* shutdownHandler; + qpid::framing::InputHandler* input; + qpid::framing::InitiationHandler* initialiser; + qpid::framing::OutputHandler* output; + + qpid::framing::Buffer inbuf; + qpid::framing::Buffer outbuf; + + qpid::concurrent::APRMonitor* writeLock; + qpid::concurrent::ThreadFactory* threadFactory; + qpid::concurrent::Thread* receiver; + + apr_pool_t* pool; + apr_socket_t* socket; + + void checkIdle(apr_status_t status); + void writeBlock(qpid::framing::AMQDataBlock* data); + void writeToSocket(char* data, int available); + void setSocketTimeout(); + + void run(); + + public: + APRConnector(bool debug = false, u_int32_t buffer_size = 1024); + virtual ~APRConnector(); + virtual void connect(const std::string& host, int port); + virtual void init(qpid::framing::ProtocolInitiation* header); + virtual void close(); + virtual void setInputHandler(qpid::framing::InputHandler* handler); + virtual void setTimeoutHandler(TimeoutHandler* handler); + virtual void setShutdownHandler(ShutdownHandler* handler); + virtual qpid::framing::OutputHandler* getOutputHandler(); + virtual void send(qpid::framing::AMQFrame* frame); + virtual void setReadTimeout(u_int16_t timeout); + virtual void setWriteTimeout(u_int16_t timeout); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/APRIOProcessor.h b/cpp/common/io/inc/APRIOProcessor.h new file mode 100644 index 0000000000..de0d67a9c4 --- /dev/null +++ b/cpp/common/io/inc/APRIOProcessor.h @@ -0,0 +1,86 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _APRIOProcessor_ +#define _APRIOProcessor_ + +#include "apr_poll.h" +#include +#include +#include "APRMonitor.h" +#include "APRThread.h" +#include "IOSession.h" +#include "Runnable.h" + +namespace qpid { +namespace io { + + /** + * Manages non-blocking io through the APR polling + * routines. Interacts with the actual io tasks to be performed + * through the IOSession interface, an implementing instance of + * which must be set as the client_data of the apr_pollfd_t + * structures registered. + */ + class APRIOProcessor : private virtual qpid::concurrent::Runnable + { + const int size; + const apr_interval_time_t timeout; + apr_pollset_t* pollset; + int count; + qpid::concurrent::APRThread thread; + qpid::concurrent::APRMonitor lock; + volatile bool stopped; + + void poll(); + virtual void run(); + + public: + APRIOProcessor(apr_pool_t* pool, int size, int timeout); + /** + * Add the fd to the poll set. Relies on the client_data being + * an instance implementing IOSession, through which the write + * and read operations will be performed when readiness is + * indicated by the poll response. + */ + void add(apr_pollfd_t* const fd); + /** + * Remove the fd from the poll set. + */ + void remove(apr_pollfd_t* const fd); + /** + * Indicates whether the capacity of this processor has been + * reached (or whether it can still handle further fd's). + */ + bool full(); + /** + * Indicates whether there are any fd's registered. + */ + bool empty(); + /** + * Stop processing. + */ + void stop(); + + ~APRIOProcessor(); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/APRSocket.h b/cpp/common/io/inc/APRSocket.h new file mode 100644 index 0000000000..610cf0e175 --- /dev/null +++ b/cpp/common/io/inc/APRSocket.h @@ -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. + * + */ +#ifndef _APRSocket_ +#define _APRSocket_ + +#include "apr_network_io.h" +#include "Buffer.h" + +namespace qpid { +namespace io { + + class APRSocket + { + apr_socket_t* const socket; + volatile bool closed; + public: + APRSocket(apr_socket_t* socket); + void read(qpid::framing::Buffer& b); + void write(qpid::framing::Buffer& b); + void close(); + bool isOpen(); + u_int8_t read(); + ~APRSocket(); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/Acceptor.h b/cpp/common/io/inc/Acceptor.h new file mode 100644 index 0000000000..5c690c546f --- /dev/null +++ b/cpp/common/io/inc/Acceptor.h @@ -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. + * + */ +#ifndef _Acceptor_ +#define _Acceptor_ + +#include "SessionHandlerFactory.h" + + +namespace qpid { +namespace io { + + class Acceptor + { + public: + virtual void bind(int port, SessionHandlerFactory* factory) = 0; + virtual ~Acceptor(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/BlockingAPRAcceptor.h b/cpp/common/io/inc/BlockingAPRAcceptor.h new file mode 100644 index 0000000000..b77371b02e --- /dev/null +++ b/cpp/common/io/inc/BlockingAPRAcceptor.h @@ -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. + * + */ +#ifndef _BlockingAPRAcceptor_ +#define _BlockingAPRAcceptor_ + +#include +#include "apr_network_io.h" +#include "apr_poll.h" +#include "apr_time.h" + +#include "Acceptor.h" +#include "APRMonitor.h" +#include "BlockingAPRSessionContext.h" +#include "Runnable.h" +#include "SessionContext.h" +#include "SessionHandlerFactory.h" +#include "Thread.h" +#include "ThreadFactory.h" +#include "ThreadPool.h" + +namespace qpid { +namespace io { + + class BlockingAPRAcceptor : public virtual Acceptor + { + typedef std::vector::iterator iterator; + + const bool debug; + apr_pool_t* apr_pool; + qpid::concurrent::ThreadFactory* threadFactory; + std::vector sessions; + apr_socket_t* socket; + const int connectionBacklog; + volatile bool running; + + public: + BlockingAPRAcceptor(bool debug = false, int connectionBacklog = 10); + virtual void bind(int port, SessionHandlerFactory* factory); + virtual ~BlockingAPRAcceptor(); + void closed(BlockingAPRSessionContext* session); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/BlockingAPRSessionContext.h b/cpp/common/io/inc/BlockingAPRSessionContext.h new file mode 100644 index 0000000000..038ebd6662 --- /dev/null +++ b/cpp/common/io/inc/BlockingAPRSessionContext.h @@ -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. + * + */ +#ifndef _BlockingAPRSessionContext_ +#define _BlockingAPRSessionContext_ + +#include +#include + +#include "apr_network_io.h" +#include "apr_time.h" + +#include "AMQFrame.h" +#include "APRMonitor.h" +#include "Buffer.h" +#include "Runnable.h" +#include "SessionContext.h" +#include "SessionHandler.h" +#include "SessionHandlerFactory.h" +#include "ShutdownHandler.h" +#include "Thread.h" +#include "ThreadFactory.h" + +namespace qpid { +namespace io { + + class BlockingAPRAcceptor; + + class BlockingAPRSessionContext : public virtual SessionContext + { + class Reader : public virtual qpid::concurrent::Runnable{ + BlockingAPRSessionContext* parent; + public: + inline Reader(BlockingAPRSessionContext* p) : parent(p){} + inline virtual void run(){ parent->read(); } + inline virtual ~Reader(){} + }; + + class Writer : public virtual qpid::concurrent::Runnable{ + BlockingAPRSessionContext* parent; + public: + inline Writer(BlockingAPRSessionContext* p) : parent(p){} + inline virtual void run(){ parent->write(); } + inline virtual ~Writer(){} + }; + + apr_socket_t* socket; + const bool debug; + SessionHandler* handler; + BlockingAPRAcceptor* acceptor; + std::queue outframes; + qpid::framing::Buffer inbuf; + qpid::framing::Buffer outbuf; + qpid::concurrent::APRMonitor outlock; + Reader* reader; + Writer* writer; + qpid::concurrent::Thread* rThread; + qpid::concurrent::Thread* wThread; + + volatile bool closed; + + void read(); + void write(); + public: + BlockingAPRSessionContext(apr_socket_t* socket, + qpid::concurrent::ThreadFactory* factory, + BlockingAPRAcceptor* acceptor, + bool debug = false); + ~BlockingAPRSessionContext(); + virtual void send(qpid::framing::AMQFrame* frame); + virtual void close(); + void shutdown(); + void init(SessionHandler* handler); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/Connector.h b/cpp/common/io/inc/Connector.h new file mode 100644 index 0000000000..52684329f1 --- /dev/null +++ b/cpp/common/io/inc/Connector.h @@ -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. + * + */ +#ifndef _Connector_ +#define _Connector_ + +#include "InputHandler.h" +#include "OutputHandler.h" +#include "InitiationHandler.h" +#include "ProtocolInitiation.h" +#include "ShutdownHandler.h" +#include "TimeoutHandler.h" + +namespace qpid { +namespace io { + + class Connector + { + public: + virtual void connect(const std::string& host, int port) = 0; + virtual void init(qpid::framing::ProtocolInitiation* header) = 0; + virtual void close() = 0; + virtual void setInputHandler(qpid::framing::InputHandler* handler) = 0; + virtual void setTimeoutHandler(TimeoutHandler* handler) = 0; + virtual void setShutdownHandler(ShutdownHandler* handler) = 0; + virtual qpid::framing::OutputHandler* getOutputHandler() = 0; + /** + * Set the timeout for reads, in secs. + */ + virtual void setReadTimeout(u_int16_t timeout) = 0; + /** + * Set the timeout for writes, in secs. + */ + virtual void setWriteTimeout(u_int16_t timeout) = 0; + virtual ~Connector(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/ConnectorImpl.h b/cpp/common/io/inc/ConnectorImpl.h new file mode 100644 index 0000000000..242f3aed49 --- /dev/null +++ b/cpp/common/io/inc/ConnectorImpl.h @@ -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. + * + */ +#ifndef _APRConnectorImpl_ +#define _APRConnectorImpl_ + +#ifdef _USE_APR_IO_ +#include "APRConnector.h" +#else +#include "LConnector.h" +#endif + +namespace qpid { +namespace io { + +#ifdef _USE_APR_IO_ + class ConnectorImpl : public virtual APRConnector + { + + public: + ConnectorImpl(bool debug = false, u_int32_t buffer_size = 1024):APRConnector(debug,buffer_size){}; + virtual ~ConnectorImpl(){}; + }; +#else + class ConnectorImpl : public virtual LConnector + { + + public: + ConnectorImpl(bool debug = false, u_int32_t buffer_size = 1024):LConnector(debug, buffer_size){}; + virtual ~ConnectorImpl(){}; + }; + +#endif + +} +} + + +#endif diff --git a/cpp/common/io/inc/IOSession.h b/cpp/common/io/inc/IOSession.h new file mode 100644 index 0000000000..45e84d29b1 --- /dev/null +++ b/cpp/common/io/inc/IOSession.h @@ -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. + * + */ +#ifndef _IOSession_ +#define _IOSession_ + +namespace qpid { +namespace io { + + class IOSession + { + public: + virtual void read() = 0; + virtual void write() = 0; + virtual ~IOSession(){} + }; + + + class IOSessionHolder + { + IOSession* session; + public: + IOSessionHolder(IOSession* _session) : session(_session) {} + void read(){ session->read(); } + void write(){ session->write(); } + }; +} +} + + +#endif diff --git a/cpp/common/io/inc/LConnector.h b/cpp/common/io/inc/LConnector.h new file mode 100644 index 0000000000..59d95a6b57 --- /dev/null +++ b/cpp/common/io/inc/LConnector.h @@ -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. + * + */ +#ifndef _LConnector_ +#define _LConnector_ + + +#include "InputHandler.h" +#include "OutputHandler.h" +#include "InitiationHandler.h" +#include "ProtocolInitiation.h" +#include "Thread.h" +#include "ThreadFactory.h" +#include "Connector.h" + +namespace qpid { +namespace io { + + class LConnector : public virtual qpid::framing::OutputHandler, + public virtual Connector, + private virtual qpid::concurrent::Runnable + { + + public: + LConnector(bool debug = false, u_int32_t buffer_size = 1024){}; + virtual ~LConnector(){}; + + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/LFAcceptor.h b/cpp/common/io/inc/LFAcceptor.h new file mode 100644 index 0000000000..314f811827 --- /dev/null +++ b/cpp/common/io/inc/LFAcceptor.h @@ -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. + * + */ +#ifndef _LFAcceptor_ +#define _LFAcceptor_ + +#include +#include "apr_network_io.h" +#include "apr_poll.h" +#include "apr_time.h" + +#include "Acceptor.h" +#include "APRMonitor.h" +#include "APRThreadFactory.h" +#include "APRThreadPool.h" +#include "LFProcessor.h" +#include "LFSessionContext.h" +#include "Runnable.h" +#include "SessionContext.h" +#include "SessionHandlerFactory.h" +#include "Thread.h" + +namespace qpid { +namespace io { + + class LFAcceptor : public virtual Acceptor + { + class APRPool{ + public: + apr_pool_t* pool; + APRPool(); + ~APRPool(); + }; + + APRPool aprPool; + LFProcessor processor; + + const int max_connections_per_processor; + const bool debug; + const int connectionBacklog; + + volatile bool running; + + public: + LFAcceptor(bool debug = false, + int connectionBacklog = 10, + int worker_threads = 5, + int max_connections_per_processor = 500); + virtual void bind(int port, SessionHandlerFactory* factory); + virtual ~LFAcceptor(); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/LFProcessor.h b/cpp/common/io/inc/LFProcessor.h new file mode 100644 index 0000000000..7d99d51943 --- /dev/null +++ b/cpp/common/io/inc/LFProcessor.h @@ -0,0 +1,116 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef _LFProcessor_ +#define _LFProcessor_ + +#include "apr_poll.h" +#include +#include +#include "APRMonitor.h" +#include "APRThreadFactory.h" +#include "IOSession.h" +#include "Runnable.h" + +namespace qpid { +namespace io { + + class LFSessionContext; + + /** + * This class processes a poll set using the leaders-followers + * pattern for thread synchronization: the leader will poll and on + * the poll returning, it will remove a session, promote a + * follower to leadership, then process the session. + */ + class LFProcessor : private virtual qpid::concurrent::Runnable + { + typedef std::vector::iterator iterator; + + const int size; + const apr_interval_time_t timeout; + apr_pollset_t* pollset; + int signalledCount; + int current; + const apr_pollfd_t* signalledFDs; + int count; + const int workerCount; + qpid::concurrent::Thread** const workers; + qpid::concurrent::APRMonitor leadLock; + qpid::concurrent::APRMonitor countLock; + qpid::concurrent::APRThreadFactory factory; + std::vector sessions; + bool hasLeader; + volatile bool stopped; + + const apr_pollfd_t* getNextEvent(); + void waitToLead(); + void relinquishLead(); + void poll(); + virtual void run(); + + public: + LFProcessor(apr_pool_t* pool, int workers, int size, int timeout); + /** + * Add the fd to the poll set. Relies on the client_data being + * an instance of LFSessionContext. + */ + void add(const apr_pollfd_t* const fd); + /** + * Remove the fd from the poll set. + */ + void remove(const apr_pollfd_t* const fd); + /** + * Signal that the fd passed in, already part of the pollset, + * has had its flags altered. + */ + void update(const apr_pollfd_t* const fd); + /** + * Add an fd back to the poll set after deactivation. + */ + void reactivate(const apr_pollfd_t* const fd); + /** + * Temporarily remove the fd from the poll set. Called when processing + * is about to begin. + */ + void deactivate(const apr_pollfd_t* const fd); + /** + * Indicates whether the capacity of this processor has been + * reached (or whether it can still handle further fd's). + */ + bool full(); + /** + * Indicates whether there are any fd's registered. + */ + bool empty(); + /** + * Stop processing. + */ + void stop(); + /** + * Start processing. + */ + void start(); + + ~LFProcessor(); + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/LFSessionContext.h b/cpp/common/io/inc/LFSessionContext.h new file mode 100644 index 0000000000..ef6a0d07b0 --- /dev/null +++ b/cpp/common/io/inc/LFSessionContext.h @@ -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. + * + */ +#ifndef _LFSessionContext_ +#define _LFSessionContext_ + +#include + +#include "apr_network_io.h" +#include "apr_poll.h" +#include "apr_time.h" + +#include "AMQFrame.h" +#include "APRMonitor.h" +#include "APRSocket.h" +#include "Buffer.h" +#include "IOSession.h" +#include "LFProcessor.h" +#include "SessionContext.h" +#include "SessionHandler.h" + +namespace qpid { +namespace io { + + + class LFSessionContext : public virtual SessionContext, public virtual IOSession + { + const bool debug; + APRSocket socket; + bool initiated; + + qpid::framing::Buffer in; + qpid::framing::Buffer out; + + SessionHandler* handler; + LFProcessor* const processor; + + apr_pollfd_t fd; + + std::queue framesToWrite; + qpid::concurrent::APRMonitor writeLock; + + bool processing; + bool closing; + + //these are just for debug, as a crude way of detecting concurrent access + volatile unsigned int reading; + volatile unsigned int writing; + + static qpid::concurrent::APRMonitor logLock; + void log(const std::string& desc, qpid::framing::AMQFrame* const frame); + + public: + LFSessionContext(apr_pool_t* pool, apr_socket_t* socket, + LFProcessor* const processor, + bool debug = false); + ~LFSessionContext(); + virtual void send(qpid::framing::AMQFrame* frame); + virtual void close(); + virtual void read(); + virtual void write(); + void init(SessionHandler* handler); + void startProcessing(); + void stopProcessing(); + void handleClose(); + void shutdown(); + inline apr_pollfd_t* const getFd(){ return &fd; } + inline bool isClosed(){ return !socket.isOpen(); } + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/SessionContext.h b/cpp/common/io/inc/SessionContext.h new file mode 100644 index 0000000000..f223a70daa --- /dev/null +++ b/cpp/common/io/inc/SessionContext.h @@ -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. + * + */ +#ifndef _SessionContext_ +#define _SessionContext_ + +#include "OutputHandler.h" + +namespace qpid { +namespace io { + + class SessionContext : public virtual qpid::framing::OutputHandler + { + public: + virtual void close() = 0; + virtual ~SessionContext(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/SessionHandler.h b/cpp/common/io/inc/SessionHandler.h new file mode 100644 index 0000000000..21a992ab65 --- /dev/null +++ b/cpp/common/io/inc/SessionHandler.h @@ -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. + * + */ +#ifndef _SessionHandler_ +#define _SessionHandler_ + +#include "InputHandler.h" +#include "InitiationHandler.h" +#include "ProtocolInitiation.h" +#include "TimeoutHandler.h" + +namespace qpid { +namespace io { + + class SessionHandler : public virtual qpid::framing::InitiationHandler, + public virtual qpid::framing::InputHandler, + public virtual TimeoutHandler + { + public: + virtual void closed() = 0; + virtual ~SessionHandler(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/SessionHandlerFactory.h b/cpp/common/io/inc/SessionHandlerFactory.h new file mode 100644 index 0000000000..67d968b72e --- /dev/null +++ b/cpp/common/io/inc/SessionHandlerFactory.h @@ -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. + * + */ +#ifndef _SessionHandlerFactory_ +#define _SessionHandlerFactory_ + +#include "SessionContext.h" +#include "SessionHandler.h" + +namespace qpid { +namespace io { + + class SessionHandlerFactory + { + public: + virtual SessionHandler* create(SessionContext* ctxt) = 0; + virtual ~SessionHandlerFactory(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/SessionManager.h b/cpp/common/io/inc/SessionManager.h new file mode 100644 index 0000000000..30c5208532 --- /dev/null +++ b/cpp/common/io/inc/SessionManager.h @@ -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. + * + */ +#ifndef _SessionManager_ +#define _SessionManager_ + +#include "SessionContext.h" +#include "SessionHandler.h" + +namespace qpid { +namespace io { + + class SessionManager + { + public: + virtual SessionHandler* init(SessionContext* ctxt) = 0; + virtual void close(SessionContext* ctxt) = 0; + virtual void updateInterest(SessionContext* ctxt, bool read, bool write) = 0; + virtual ~SessionManager(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/inc/ShutdownHandler.h b/cpp/common/io/inc/ShutdownHandler.h new file mode 100644 index 0000000000..186d9eeca4 --- /dev/null +++ b/cpp/common/io/inc/ShutdownHandler.h @@ -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. + * + */ +#ifndef _ShutdownHandler_ +#define _ShutdownHandler_ + +namespace qpid { +namespace io { + + class ShutdownHandler + { + public: + virtual void shutdown() = 0; + virtual ~ShutdownHandler(){} + }; + +} +} + +#endif diff --git a/cpp/common/io/inc/TimeoutHandler.h b/cpp/common/io/inc/TimeoutHandler.h new file mode 100644 index 0000000000..c92220fd6e --- /dev/null +++ b/cpp/common/io/inc/TimeoutHandler.h @@ -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. + * + */ +#ifndef _TimeoutHandler_ +#define _TimeoutHandler_ + +namespace qpid { +namespace io { + + class TimeoutHandler + { + public: + virtual void idleOut() = 0; + virtual void idleIn() = 0; + virtual ~TimeoutHandler(){} + }; + +} +} + + +#endif diff --git a/cpp/common/io/src/APRConnector.cpp b/cpp/common/io/src/APRConnector.cpp new file mode 100644 index 0000000000..0e022a8c73 --- /dev/null +++ b/cpp/common/io/src/APRConnector.cpp @@ -0,0 +1,198 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include +#include "APRBase.h" +#include "APRConnector.h" +#include "APRThreadFactory.h" +#include "QpidError.h" + +using namespace qpid::io; +using namespace qpid::concurrent; +using namespace qpid::framing; +using qpid::QpidError; + +APRConnector::APRConnector(bool _debug, u_int32_t buffer_size) : closed(true), debug(_debug), + idleIn(0), idleOut(0), timeout(0), + timeoutHandler(0), + shutdownHandler(0), + lastIn(0), lastOut(0), + receive_buffer_size(buffer_size), + send_buffer_size(buffer_size), + inbuf(receive_buffer_size), + outbuf(send_buffer_size){ + + APRBase::increment(); + + CHECK_APR_SUCCESS(apr_pool_create(&pool, NULL)); + CHECK_APR_SUCCESS(apr_socket_create(&socket, APR_INET, SOCK_STREAM, APR_PROTO_TCP, pool)); + + threadFactory = new APRThreadFactory(); + writeLock = new APRMonitor(); +} + +APRConnector::~APRConnector(){ + delete receiver; + delete writeLock; + delete threadFactory; + apr_pool_destroy(pool); + + APRBase::decrement(); +} + +void APRConnector::connect(const std::string& host, int port){ + apr_sockaddr_t* address; + CHECK_APR_SUCCESS(apr_sockaddr_info_get(&address, host.c_str(), APR_UNSPEC, port, APR_IPV4_ADDR_OK, pool)); + CHECK_APR_SUCCESS(apr_socket_connect(socket, address)); + closed = false; + + receiver = threadFactory->create(this); + receiver->start(); +} + +void APRConnector::init(ProtocolInitiation* header){ + writeBlock(header); + delete header; +} + +void APRConnector::close(){ + closed = true; + CHECK_APR_SUCCESS(apr_socket_close(socket)); + receiver->join(); +} + +void APRConnector::setInputHandler(InputHandler* handler){ + input = handler; +} + +void APRConnector::setShutdownHandler(ShutdownHandler* handler){ + shutdownHandler = handler; +} + +OutputHandler* APRConnector::getOutputHandler(){ + return this; +} + +void APRConnector::send(AMQFrame* frame){ + writeBlock(frame); + if(debug) std::cout << "SENT: " << *frame << std::endl; + delete frame; +} + +void APRConnector::writeBlock(AMQDataBlock* data){ + writeLock->acquire(); + data->encode(outbuf); + + //transfer data to wire + outbuf.flip(); + writeToSocket(outbuf.start(), outbuf.available()); + outbuf.clear(); + writeLock->release(); +} + +void APRConnector::writeToSocket(char* data, int available){ + apr_size_t bytes(available); + apr_size_t written(0); + while(written < available && !closed){ + apr_status_t status = apr_socket_send(socket, data + written, &bytes); + if(status == APR_TIMEUP){ + std::cout << "Write request timed out." << std::endl; + } + if(bytes == 0){ + std::cout << "Write request wrote 0 bytes." << std::endl; + } + lastOut = apr_time_as_msec(apr_time_now()); + written += bytes; + bytes = available - written; + } +} + +void APRConnector::checkIdle(apr_status_t status){ + if(timeoutHandler){ + apr_time_t now = apr_time_as_msec(apr_time_now()); + if(APR_STATUS_IS_TIMEUP(status)){ + if(idleIn && (now - lastIn > idleIn)){ + timeoutHandler->idleIn(); + } + }else if(APR_STATUS_IS_EOF(status)){ + closed = true; + CHECK_APR_SUCCESS(apr_socket_close(socket)); + if(shutdownHandler) shutdownHandler->shutdown(); + }else{ + lastIn = now; + } + if(idleOut && (now - lastOut > idleOut)){ + timeoutHandler->idleOut(); + } + } +} + +void APRConnector::setReadTimeout(u_int16_t t){ + idleIn = t * 1000;//t is in secs + if(idleIn && (!timeout || idleIn < timeout)){ + timeout = idleIn; + setSocketTimeout(); + } + +} + +void APRConnector::setWriteTimeout(u_int16_t t){ + idleOut = t * 1000;//t is in secs + if(idleOut && (!timeout || idleOut < timeout)){ + timeout = idleOut; + setSocketTimeout(); + } +} + +void APRConnector::setSocketTimeout(){ + //interval is in microseconds, timeout in milliseconds + //want the interval to be a bit shorter than the timeout, hence multiply + //by 800 rather than 1000. + apr_interval_time_t interval(timeout * 800); + apr_socket_timeout_set(socket, interval); +} + +void APRConnector::setTimeoutHandler(TimeoutHandler* handler){ + timeoutHandler = handler; +} + +void APRConnector::run(){ + try{ + while(!closed){ + apr_size_t bytes(inbuf.available()); + if(bytes < 1){ + THROW_QPID_ERROR(INTERNAL_ERROR, "Frame exceeds buffer size."); + } + checkIdle(apr_socket_recv(socket, inbuf.start(), &bytes)); + + if(bytes > 0){ + inbuf.move(bytes); + inbuf.flip();//position = 0, limit = total data read + + AMQFrame frame; + while(frame.decode(inbuf)){ + if(debug) std::cout << "RECV: " << frame << std::endl; + input->received(&frame); + } + //need to compact buffer to preserve any 'extra' data + inbuf.compact(); + } + } + }catch(QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } +} diff --git a/cpp/common/io/src/APRIOProcessor.cpp b/cpp/common/io/src/APRIOProcessor.cpp new file mode 100644 index 0000000000..d630f2f315 --- /dev/null +++ b/cpp/common/io/src/APRIOProcessor.cpp @@ -0,0 +1,100 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "APRIOProcessor.h" +#include "APRBase.h" +#include "QpidError.h" + +using namespace qpid::io; +using namespace qpid::concurrent; + +APRIOProcessor::APRIOProcessor(apr_pool_t* pool, int _size, int _timeout) : size(_size), + timeout(_timeout), + count(0), + thread(pool, this), + stopped(false){ + + CHECK_APR_SUCCESS(apr_pollset_create(&pollset, size, pool, APR_POLLSET_THREADSAFE)); + thread.start(); +} + +void APRIOProcessor::add(apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_add(pollset, fd)); + lock.acquire(); + if(!count++) lock.notify(); + lock.release(); +} + +void APRIOProcessor::remove(apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_remove(pollset, fd)); + lock.acquire(); + count--; + lock.release(); +} + +bool APRIOProcessor::full(){ + lock.acquire(); + bool full = count == size; + lock.release(); + return full; +} + +bool APRIOProcessor::empty(){ + lock.acquire(); + bool empty = count == 0; + lock.release(); + return empty; +} + +void APRIOProcessor::poll(){ + try{ + int signalledCount; + const apr_pollfd_t* signalledFDs; + apr_status_t status = apr_pollset_poll(pollset, timeout, &signalledCount, &signalledFDs); + if(status == APR_SUCCESS){ + for(int i = 0; i < signalledCount; i++){ + IOSessionHolder* session = reinterpret_cast(signalledFDs[i].client_data); + if(signalledFDs[i].rtnevents & APR_POLLIN) session->read(); + if(signalledFDs[i].rtnevents & APR_POLLOUT) session->write(); + } + } + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } + +} + +void APRIOProcessor::run(){ + while(!stopped){ + lock.acquire(); + while(count == 0) lock.wait(); + lock.release(); + poll(); + } +} + +void APRIOProcessor::stop(){ + lock.acquire(); + stopped = true; + lock.notify(); + lock.release(); +} + +APRIOProcessor::~APRIOProcessor(){ + CHECK_APR_SUCCESS(apr_pollset_destroy(pollset)); +} + diff --git a/cpp/common/io/src/APRSocket.cpp b/cpp/common/io/src/APRSocket.cpp new file mode 100644 index 0000000000..32861ea442 --- /dev/null +++ b/cpp/common/io/src/APRSocket.cpp @@ -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. + * + */ +#include "APRBase.h" +#include "APRSocket.h" + +#include + +using namespace qpid::io; +using namespace qpid::framing; +using namespace qpid::concurrent; + +APRSocket::APRSocket(apr_socket_t* _socket) : socket(_socket), closed(false){ + +} + +void APRSocket::read(qpid::framing::Buffer& buffer){ + apr_size_t bytes; + bytes = buffer.available(); + apr_status_t s = apr_socket_recv(socket, buffer.start(), &bytes); + buffer.move(bytes); + if(APR_STATUS_IS_TIMEUP(s)){ + //timed out + }else if(APR_STATUS_IS_EOF(s)){ + close(); + } +} + +void APRSocket::write(qpid::framing::Buffer& buffer){ + apr_size_t bytes; + do{ + bytes = buffer.available(); + apr_status_t s = apr_socket_send(socket, buffer.start(), &bytes); + buffer.move(bytes); + }while(bytes > 0); +} + +void APRSocket::close(){ + if(!closed){ + std::cout << "Closing socket " << socket << "@" << this << std::endl; + CHECK_APR_SUCCESS(apr_socket_close(socket)); + closed = true; + } +} + +bool APRSocket::isOpen(){ + return !closed; +} + +u_int8_t APRSocket::read(){ + char data[1]; + apr_size_t bytes = 1; + apr_status_t s = apr_socket_recv(socket, data, &bytes); + if(APR_STATUS_IS_EOF(s) || bytes == 0){ + return 0; + }else{ + return *data; + } +} + +APRSocket::~APRSocket(){ +} diff --git a/cpp/common/io/src/BlockingAPRAcceptor.cpp b/cpp/common/io/src/BlockingAPRAcceptor.cpp new file mode 100644 index 0000000000..380318bcfa --- /dev/null +++ b/cpp/common/io/src/BlockingAPRAcceptor.cpp @@ -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. + * + */ +#include +#include "BlockingAPRAcceptor.h" +#include "APRBase.h" +#include "APRThreadFactory.h" + +using namespace qpid::concurrent; +using namespace qpid::framing; +using namespace qpid::io; + +BlockingAPRAcceptor::BlockingAPRAcceptor(bool _debug, int c) : connectionBacklog(c), + threadFactory(new APRThreadFactory()), + debug(_debug){ + + APRBase::increment(); + CHECK_APR_SUCCESS(apr_pool_create(&apr_pool, NULL)); +} + +void BlockingAPRAcceptor::bind(int port, SessionHandlerFactory* factory){ + apr_sockaddr_t* address; + CHECK_APR_SUCCESS(apr_sockaddr_info_get(&address, APR_ANYADDR, APR_UNSPEC, port, APR_IPV4_ADDR_OK, apr_pool)); + CHECK_APR_SUCCESS(apr_socket_create(&socket, APR_INET, SOCK_STREAM, APR_PROTO_TCP, apr_pool)); + CHECK_APR_SUCCESS(apr_socket_bind(socket, address)); + CHECK_APR_SUCCESS(apr_socket_listen(socket, connectionBacklog)); + running = true; + std::cout << "Listening on port " << port << "..." << std::endl; + while(running){ + apr_socket_t* client; + apr_status_t status = apr_socket_accept(&client, socket, apr_pool); + if(status == APR_SUCCESS){ + //configure socket: + CHECK_APR_SUCCESS(apr_socket_timeout_set(client, 1000000/* i.e. 1 sec*/)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_TCP_NODELAY, 1)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_SO_SNDBUF, 32768)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_SO_RCVBUF, 32768)); + + BlockingAPRSessionContext* session = new BlockingAPRSessionContext(client, threadFactory, this, debug); + session->init(factory->create(session)); + sessions.push_back(session); + }else{ + running = false; + if(status != APR_EINTR){ + std::cout << "ERROR: " << get_desc(status) << std::endl; + } + } + } + for(iterator i = sessions.begin(); i < sessions.end(); i++){ + (*i)->shutdown(); + } + + CHECK_APR_SUCCESS(apr_socket_close(socket)); +} + +BlockingAPRAcceptor::~BlockingAPRAcceptor(){ + delete threadFactory; + apr_pool_destroy(apr_pool); + APRBase::decrement(); +} + + +void BlockingAPRAcceptor::closed(BlockingAPRSessionContext* session){ + sessions.erase(find(sessions.begin(), sessions.end(), session)); + delete this; +} + diff --git a/cpp/common/io/src/BlockingAPRSessionContext.cpp b/cpp/common/io/src/BlockingAPRSessionContext.cpp new file mode 100644 index 0000000000..99352c90d5 --- /dev/null +++ b/cpp/common/io/src/BlockingAPRSessionContext.cpp @@ -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. + * + */ +#include +#include "BlockingAPRSessionContext.h" +#include "BlockingAPRAcceptor.h" +#include "APRBase.h" +#include "QpidError.h" + +using namespace qpid::concurrent; +using namespace qpid::framing; +using namespace qpid::io; + + +BlockingAPRSessionContext::BlockingAPRSessionContext(apr_socket_t* _socket, + ThreadFactory* factory, + BlockingAPRAcceptor* _acceptor, + bool _debug) + : socket(_socket), + debug(_debug), + inbuf(65536), + outbuf(65536), + handler(0), + acceptor(_acceptor), + closed(false){ + + reader = new Reader(this); + writer = new Writer(this); + + rThread = factory->create(reader); + wThread = factory->create(writer); +} + +BlockingAPRSessionContext::~BlockingAPRSessionContext(){ + delete reader; + delete writer; + + delete rThread; + delete wThread; + + delete handler; +} + +void BlockingAPRSessionContext::read(){ + try{ + bool initiated(false); + while(!closed){ + apr_size_t bytes(inbuf.available()); + if(bytes < 1){ + THROW_QPID_ERROR(INTERNAL_ERROR, "Frame exceeds buffer size."); + } + apr_status_t s = apr_socket_recv(socket, inbuf.start(), &bytes); + if(APR_STATUS_IS_TIMEUP(s)){ + //timed out, check closed on loop + }else if(APR_STATUS_IS_EOF(s) || bytes == 0){ + closed = true; + }else{ + inbuf.move(bytes); + inbuf.flip(); + + if(!initiated){ + ProtocolInitiation* init = new ProtocolInitiation(); + if(init->decode(inbuf)){ + handler->initiated(init); + if(debug) std::cout << "RECV: [" << &socket << "]: Initialised " << std::endl; + initiated = true; + } + }else{ + AMQFrame frame; + while(frame.decode(inbuf)){ + if(debug) std::cout << "RECV: [" << &socket << "]:" << frame << std::endl; + handler->received(&frame); + } + } + //need to compact buffer to preserve any 'extra' data + inbuf.compact(); + } + } + + //close socket + }catch(qpid::QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } +} + +void BlockingAPRSessionContext::write(){ + while(!closed){ + //get next frame + outlock.acquire(); + while(outframes.empty() && !closed){ + outlock.wait(); + } + if(!closed){ + AMQFrame* frame = outframes.front(); + outframes.pop(); + outlock.release(); + + //encode + frame->encode(outbuf); + if(debug) std::cout << "SENT [" << &socket << "]:" << *frame << std::endl; + delete frame; + outbuf.flip(); + + //write from outbuf to socket + char* data = outbuf.start(); + const int available = outbuf.available(); + int written = 0; + apr_size_t bytes = available; + while(available > written){ + apr_status_t s = apr_socket_send(socket, data + written, &bytes); + written += bytes; + bytes = available - written; + } + outbuf.clear(); + }else{ + outlock.release(); + } + } +} + +void BlockingAPRSessionContext::send(AMQFrame* frame){ + if(!closed){ + outlock.acquire(); + bool was_empty(outframes.empty()); + outframes.push(frame); + if(was_empty){ + outlock.notify(); + } + outlock.release(); + }else{ + std::cout << "WARNING: Session closed[" << &socket << "], dropping frame. " << &frame << std::endl; + } +} + +void BlockingAPRSessionContext::init(SessionHandler* handler){ + this->handler = handler; + //start the threads + rThread->start(); + wThread->start(); +} + +void BlockingAPRSessionContext::close(){ + closed = true; + wThread->join(); + CHECK_APR_SUCCESS(apr_socket_close(socket)); + if(debug) std::cout << "RECV: [" << &socket << "]: Closed " << std::endl; + handler->closed(); + acceptor->closed(this); + delete this; +} + +void BlockingAPRSessionContext::shutdown(){ + closed = true; + outlock.acquire(); + outlock.notify(); + outlock.release(); + + wThread->join(); + CHECK_APR_SUCCESS(apr_socket_close(socket)); + rThread->join(); + handler->closed(); + delete this; +} diff --git a/cpp/common/io/src/LFAcceptor.cpp b/cpp/common/io/src/LFAcceptor.cpp new file mode 100644 index 0000000000..6653e926db --- /dev/null +++ b/cpp/common/io/src/LFAcceptor.cpp @@ -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. + * + */ +#include "LFAcceptor.h" +#include "APRBase.h" + +using namespace qpid::concurrent; +using namespace qpid::io; + +LFAcceptor::LFAcceptor(bool _debug, int c, int worker_threads, int m) : processor(aprPool.pool, worker_threads, 1000, 5000000), + connectionBacklog(c), + max_connections_per_processor(m), + debug(_debug){ + +} + + +void LFAcceptor::bind(int port, SessionHandlerFactory* factory){ + apr_socket_t* socket; + apr_sockaddr_t* address; + CHECK_APR_SUCCESS(apr_sockaddr_info_get(&address, APR_ANYADDR, APR_UNSPEC, port, APR_IPV4_ADDR_OK, aprPool.pool)); + CHECK_APR_SUCCESS(apr_socket_create(&socket, APR_INET, SOCK_STREAM, APR_PROTO_TCP, aprPool.pool)); + CHECK_APR_SUCCESS(apr_socket_opt_set(socket, APR_SO_REUSEADDR, 1)); + CHECK_APR_SUCCESS(apr_socket_bind(socket, address)); + CHECK_APR_SUCCESS(apr_socket_listen(socket, connectionBacklog)); + running = true; + processor.start(); + + std::cout << "Listening on port " << port << "..." << std::endl; + while(running){ + apr_socket_t* client; + apr_status_t status = apr_socket_accept(&client, socket, aprPool.pool); + if(status == APR_SUCCESS){ + //make this socket non-blocking: + CHECK_APR_SUCCESS(apr_socket_timeout_set(client, 0)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_SO_NONBLOCK, 1)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_TCP_NODELAY, 1)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_SO_SNDBUF, 32768)); + CHECK_APR_SUCCESS(apr_socket_opt_set(client, APR_SO_RCVBUF, 32768)); + LFSessionContext* session = new LFSessionContext(aprPool.pool, client, &processor, debug); + session->init(factory->create(session)); + }else{ + running = false; + if(status != APR_EINTR){ + std::cout << "ERROR: " << get_desc(status) << std::endl; + } + } + } + + processor.stop(); + CHECK_APR_SUCCESS(apr_socket_close(socket)); +} + + +LFAcceptor::~LFAcceptor(){ +} + +LFAcceptor::APRPool::APRPool(){ + APRBase::increment(); + CHECK_APR_SUCCESS(apr_pool_create(&pool, NULL)); +} + +LFAcceptor::APRPool::~APRPool(){ + apr_pool_destroy(pool); + APRBase::decrement(); +} diff --git a/cpp/common/io/src/LFProcessor.cpp b/cpp/common/io/src/LFProcessor.cpp new file mode 100644 index 0000000000..8ef3543b8f --- /dev/null +++ b/cpp/common/io/src/LFProcessor.cpp @@ -0,0 +1,191 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "LFProcessor.h" +#include "APRBase.h" +#include "LFSessionContext.h" +#include "QpidError.h" +#include + +using namespace qpid::io; +using namespace qpid::concurrent; +using qpid::QpidError; + +LFProcessor::LFProcessor(apr_pool_t* pool, int _workers, int _size, int _timeout) : size(_size), + timeout(_timeout), + signalledCount(0), + current(0), + count(0), + hasLeader(false), + workerCount(_workers), + workers(new Thread*[_workers]), + stopped(false){ + + CHECK_APR_SUCCESS(apr_pollset_create(&pollset, size, pool, APR_POLLSET_THREADSAFE)); + //create & start the required number of threads + for(int i = 0; i < workerCount; i++){ + workers[i] = factory.create(this); + } +} + + +LFProcessor::~LFProcessor(){ + for(int i = 0; i < workerCount; i++){ + delete workers[i]; + } + delete[] workers; + CHECK_APR_SUCCESS(apr_pollset_destroy(pollset)); +} + +void LFProcessor::start(){ + for(int i = 0; i < workerCount; i++){ + workers[i]->start(); + } +} + +void LFProcessor::add(const apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_add(pollset, fd)); + countLock.acquire(); + sessions.push_back(reinterpret_cast(fd->client_data)); + count++; + countLock.release(); +} + +void LFProcessor::remove(const apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_remove(pollset, fd)); + countLock.acquire(); + sessions.erase(find(sessions.begin(), sessions.end(), reinterpret_cast(fd->client_data))); + count--; + countLock.release(); +} + +void LFProcessor::reactivate(const apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_add(pollset, fd)); +} + +void LFProcessor::deactivate(const apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_remove(pollset, fd)); +} + +void LFProcessor::update(const apr_pollfd_t* const fd){ + CHECK_APR_SUCCESS(apr_pollset_remove(pollset, fd)); + CHECK_APR_SUCCESS(apr_pollset_add(pollset, fd)); +} + +bool LFProcessor::full(){ + countLock.acquire(); + bool full = count == size; + countLock.release(); + return full; +} + +bool LFProcessor::empty(){ + countLock.acquire(); + bool empty = count == 0; + countLock.release(); + return empty; +} + +void LFProcessor::poll(){ + apr_status_t status; + do{ + current = 0; + if(!stopped){ + status = apr_pollset_poll(pollset, timeout, &signalledCount, &signalledFDs); + } + }while(status != APR_SUCCESS && !stopped); +} + +void LFProcessor::run(){ + try{ + while(!stopped){ + leadLock.acquire(); + waitToLead(); + if(!stopped){ + const apr_pollfd_t* evt = getNextEvent(); + if(evt){ + LFSessionContext* session = reinterpret_cast(evt->client_data); + session->startProcessing(); + + relinquishLead(); + leadLock.release(); + + //process event: + if(evt->rtnevents & APR_POLLIN) session->read(); + if(evt->rtnevents & APR_POLLOUT) session->write(); + + if(session->isClosed()){ + session->handleClose(); + countLock.acquire(); + sessions.erase(find(sessions.begin(), sessions.end(), session)); + count--; + countLock.release(); + }else{ + session->stopProcessing(); + } + + }else{ + leadLock.release(); + } + }else{ + leadLock.release(); + } + } + }catch(QpidError error){ + std::cout << "Error [" << error.code << "] " << error.msg << " (" << error.file << ":" << error.line << ")" << std::endl; + } +} + +void LFProcessor::waitToLead(){ + while(hasLeader && !stopped) leadLock.wait(); + hasLeader = !stopped; +} + +void LFProcessor::relinquishLead(){ + hasLeader = false; + leadLock.notify(); +} + +const apr_pollfd_t* LFProcessor::getNextEvent(){ + while(true){ + if(stopped){ + return 0; + }else if(current < signalledCount){ + //use result of previous poll if one is available + return signalledFDs + (current++); + }else{ + //else poll to get new events + poll(); + } + } +} + +void LFProcessor::stop(){ + stopped = true; + leadLock.acquire(); + leadLock.notifyAll(); + leadLock.release(); + + for(int i = 0; i < workerCount; i++){ + workers[i]->join(); + } + + for(iterator i = sessions.begin(); i < sessions.end(); i++){ + (*i)->shutdown(); + } +} + diff --git a/cpp/common/io/src/LFSessionContext.cpp b/cpp/common/io/src/LFSessionContext.cpp new file mode 100644 index 0000000000..d786cb5e98 --- /dev/null +++ b/cpp/common/io/src/LFSessionContext.cpp @@ -0,0 +1,187 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "LFSessionContext.h" +#include "APRBase.h" +#include "QpidError.h" +#include + +using namespace qpid::concurrent; +using namespace qpid::io; +using namespace qpid::framing; + +LFSessionContext::LFSessionContext(apr_pool_t* _pool, apr_socket_t* _socket, + LFProcessor* const _processor, + bool _debug) : socket(_socket), + processor(_processor), + initiated(false), + processing(false), + closing(false), + in(32768), + out(32768), + reading(0), + writing(0), + debug(_debug){ + + fd.p = _pool; + fd.desc_type = APR_POLL_SOCKET; + fd.reqevents = APR_POLLIN; + fd.client_data = this; + fd.desc.s = _socket; + + out.flip(); +} + +LFSessionContext::~LFSessionContext(){ + +} + +void LFSessionContext::read(){ + assert(!reading); // No concurrent read. + reading = APRThread::currentThread(); + + socket.read(in); + in.flip(); + if(initiated){ + AMQFrame frame; + while(frame.decode(in)){ + if(debug) log("RECV", &frame); + handler->received(&frame); + } + }else{ + ProtocolInitiation init; + if(init.decode(in)){ + handler->initiated(&init); + initiated = true; + if(debug) std::cout << "INIT [" << &socket << "]" << std::endl; + } + } + in.compact(); + + reading = 0; +} + +void LFSessionContext::write(){ + assert(!writing); // No concurrent writes. + writing = APRThread::currentThread(); + + bool done = isClosed(); + while(!done){ + if(out.available() > 0){ + socket.write(out); + if(out.available() > 0){ + writing = 0; + + //incomplete write, leave flags to receive notification of readiness to write + done = true;//finished processing for now, but write is still in progress + } + }else{ + //do we have any frames to write? + writeLock.acquire(); + if(!framesToWrite.empty()){ + out.clear(); + bool encoded(false); + AMQFrame* frame = framesToWrite.front(); + while(frame && out.available() >= frame->size()){ + encoded = true; + frame->encode(out); + if(debug) log("SENT", frame); + delete frame; + framesToWrite.pop(); + frame = framesToWrite.empty() ? 0 : framesToWrite.front(); + } + if(!encoded) THROW_QPID_ERROR(FRAMING_ERROR, "Could not write frame, too large for buffer."); + out.flip(); + }else{ + //reset flags, don't care about writability anymore + fd.reqevents = APR_POLLIN; + done = true; + + writing = 0; + + if(closing){ + socket.close(); + } + } + writeLock.release(); + } + } +} + +void LFSessionContext::send(AMQFrame* frame){ + writeLock.acquire(); + if(!closing){ + framesToWrite.push(frame); + if(!(fd.reqevents & APR_POLLOUT)){ + fd.reqevents |= APR_POLLOUT; + if(!processing){ + processor->update(&fd); + } + } + } + writeLock.release(); +} + +void LFSessionContext::startProcessing(){ + writeLock.acquire(); + processing = true; + processor->deactivate(&fd); + writeLock.release(); +} + +void LFSessionContext::stopProcessing(){ + writeLock.acquire(); + processor->reactivate(&fd); + processing = false; + writeLock.release(); +} + +void LFSessionContext::close(){ + closing = true; + writeLock.acquire(); + if(!processing){ + //allow pending frames to be written to socket + fd.reqevents = APR_POLLOUT; + processor->update(&fd); + } + writeLock.release(); +} + +void LFSessionContext::handleClose(){ + handler->closed(); + std::cout << "Session closed [" << &socket << "]" << std::endl; + delete handler; + delete this; +} + +void LFSessionContext::shutdown(){ + socket.close(); + handleClose(); +} + +void LFSessionContext::init(SessionHandler* handler){ + this->handler = handler; + processor->add(&fd); +} + +void LFSessionContext::log(const std::string& desc, AMQFrame* const frame){ + logLock.acquire(); + std::cout << desc << " [" << &socket << "]: " << *frame << std::endl; + logLock.release(); +} + +APRMonitor LFSessionContext::logLock; diff --git a/cpp/common/utils/inc/logger.h b/cpp/common/utils/inc/logger.h new file mode 100644 index 0000000000..8a57854476 --- /dev/null +++ b/cpp/common/utils/inc/logger.h @@ -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. + * + */ +/********************************************************************* +* +* NOTE: This is a lightweight logging class intended for debugging and +* verification purposes only. +* +* DO NOT USE FOR PRODUCT DEVELOPMENT - Rather, use an agreed upon +* established logging class (such as Apache's log4cxx) for product +* development purposes. +* +*********************************************************************/ + +#ifndef __LOGGER__ +#define __LOGGER__ + +#include +#include + +namespace qpid { +namespace utils { + +class Logger : public std::ofstream +{ + private: + bool echo_flag; + bool timestamp_flag; + bool eol_flag; + char buff[128]; // Buffer for writing formatted strings + + void write_timestamp(); + + public: + Logger(const char* filename, const bool append); + Logger(std::string& filename, const bool append); + ~Logger(); + + bool getEchoFlag() {return echo_flag;} + bool setEchoFlag(const bool _echo_flag) {echo_flag = _echo_flag;} + bool getTimestampFlag() {return timestamp_flag;} + bool setTimestampFlag(const bool _timestamp_flag) {timestamp_flag = _timestamp_flag;} + + void log(const char* message); + void log(const char* message, const bool echo); + void log(const char* message, const bool echo, const bool timestamp); + + Logger& operator<< (bool b); + Logger& operator<< (const short s); + Logger& operator<< (const unsigned short us); + Logger& operator<< (const int i); + Logger& operator<< (const unsigned int ui); + Logger& operator<< (const long l); + Logger& operator<< (const unsigned long ul); + Logger& operator<< (const long long l); + Logger& operator<< (const unsigned long long ul); + Logger& operator<< (const float f); + Logger& operator<< (const double d); + Logger& operator<< (const long double ld); + Logger& operator<< (const char* cstr); + Logger& operator<< (const std::string& str); +}; + +} +} + + +#endif diff --git a/cpp/common/utils/inc/memory.h b/cpp/common/utils/inc/memory.h new file mode 100644 index 0000000000..2d65877adb --- /dev/null +++ b/cpp/common/utils/inc/memory.h @@ -0,0 +1,17 @@ +#ifndef __UTIL_MEMORY__ +#define __UTIL_MEMORY__ + +#if __GNUC__ < 4 + #include "boost/shared_ptr.hpp" + namespace std { + namespace tr1 { + using boost::shared_ptr; + using boost::dynamic_pointer_cast; + using boost::static_pointer_cast; + } + } +#else + #include +#endif +#endif + diff --git a/cpp/common/utils/src/Makefile b/cpp/common/utils/src/Makefile new file mode 100644 index 0000000000..0185ab9975 --- /dev/null +++ b/cpp/common/utils/src/Makefile @@ -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. + # +##### Options ##### +QPID_HOME = ../../../.. + +include ${QPID_HOME}/cpp/options.mk + +##### Compiler flags ##### +CXXFLAGS = -I ../inc -I ${APR_HOME}/include/apr-1/ + +##### Targets ##### +# Add additional source files to SOURCE LIST to include them in the build. +COMMON_SOURCE_LIST = logger.cpp + +COMMON_OBJ_LIST = $(COMMON_SOURCE_LIST:.cpp=.o) +LOGGER_TEST_EXE = logger_test + + +.PHONY: all clean + +all: $(LOGGER_TEST_EXE) + +$(LOGGER_TEST_EXE) : $(COMMON_OBJ_LIST) $(LOGGER_TEST_EXE).o + $(CXX) -o $@ $^ -l apr-1 -L /usr/local/apr/lib/ + +clean: + -@rm -f $(LOGGER_TEST_EXE) $(LOGGER_TEST_EXE).o $(COMMON_OBJ_LIST) test_log.txt *~ ../inc/*~ diff --git a/cpp/common/utils/src/logger.cpp b/cpp/common/utils/src/logger.cpp new file mode 100644 index 0000000000..603fa6574e --- /dev/null +++ b/cpp/common/utils/src/logger.cpp @@ -0,0 +1,209 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/********************************************************************* +* +* NOTE: This is a lightweight logging class intended for debugging and +* verification purposes only. +* +* DO NOT USE FOR PRODUCT DEVELOPMENT - Rather, use an agreed upon +* established logging class (such as Apache's log4cxx) for product +* development purposes. +* +*********************************************************************/ + +#include +#include +#include +#include "apr_time.h" +#include "logger.h" + +namespace qpid { +namespace utils { + +Logger::Logger(const char* filename, const bool append): + std::ofstream(filename, append ? std::ios::app : std::ios::out) +{ + echo_flag = false; + timestamp_flag = true; + eol_flag = true; +} + +Logger::Logger(std::string& filename, const bool append): + std::ofstream(filename.c_str(), append ? std::ios::app : std::ios::out) +{ + echo_flag = false; + timestamp_flag = true; + eol_flag = true; +} + +Logger::~Logger() +{ + close(); +} + +void Logger::write_timestamp() +{ + int len; + apr_time_exp_t now; + apr_time_exp_lt(&now, apr_time_now()); + sprintf(buff, "%4d/%02d/%02d %02d:%02d:%02d.%06d : ", 1900+now.tm_year, now.tm_mon, + now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec); + write(buff, strlen(buff)); +} + + +void Logger::log(const char* message) +{ + if (timestamp_flag && eol_flag) + { + eol_flag = false; + write_timestamp(); + } + write(message, strlen(message)); + if (echo_flag) + std::cout << message; + if (strchr(message, '\n')) + eol_flag = true; +} + +void Logger::log(const char* message, const bool echo) +{ + if (timestamp_flag && eol_flag) + { + eol_flag = false; + write_timestamp(); + } + write(message, strlen(message)); + if (echo) + std::cout << message; + if (strchr(message, '\n')) + eol_flag = true; +} + +void Logger::log(const char* message, const bool echo, const bool timestamp) +{ + if (timestamp && eol_flag) + { + eol_flag = false; + write_timestamp(); + } + write(message, strlen(message)); + if (echo) + std::cout << message; + if (strchr(message, '\n')) + eol_flag = true; +} + +Logger& Logger::operator<< (const bool b) +{ + log(b ? "true" : "false"); + return *this; +} + +Logger& Logger::operator<< (const short s) +{ + sprintf(buff, "%d", s); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const unsigned short us) +{ + sprintf(buff, "%u", us); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const int i) +{ + sprintf(buff, "%d", i); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const unsigned int ui) +{ + sprintf(buff, "%u", ui); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const long l) +{ + sprintf(buff, "%ld", l); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const unsigned long ul) +{ + sprintf(buff, "%lu", ul); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const long long l) +{ + sprintf(buff, "%ld", l); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const unsigned long long ul) +{ + sprintf(buff, "%lu", ul); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const float f) +{ + sprintf(buff, "%f", f); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const double d) +{ + sprintf(buff, "%lf", d); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const long double ld) +{ + sprintf(buff, "%Lf", ld); + log(buff); + return *this; +} + +Logger& Logger::operator<< (const char* cstr) +{ + log(cstr); + return *this; +} + +Logger& Logger::operator<< (const std::string& str) +{ + log(str.c_str()); + return *this; +} + +} +} + diff --git a/cpp/common/utils/src/logger_test.cpp b/cpp/common/utils/src/logger_test.cpp new file mode 100644 index 0000000000..1866af9fbb --- /dev/null +++ b/cpp/common/utils/src/logger_test.cpp @@ -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. + * + */ +#include +#include +#include "logger.h" + +using namespace qpid::utils; + +void run_sequence(Logger& log) +{ + bool b = true; + short s = -5; + unsigned short us = 12; + int i = -2345; + unsigned int ui = 34567; + long l = -12345678; + unsigned long ul = 23456789; + long long ll = -1234567890; + unsigned long long ull = 1234567890; + float f = -123.45678; + double d = 123.45678901; + long double ld = 23456.789012345678; + char* cstr = "This is a test C string."; + char* cr = "\n"; + std::string str("This is a test std::string"); + log << "bool = " << b << cr; + log << "short = " << s << cr; + log << "unsigned sort = " << us << cr; + log << "int = " << i << cr; + log << "unsigned int = " << ui << cr; + log << "long = " << l << cr; + log << "unsigned long = " << ul << cr; + log << "long long = " << ll << cr; + log << "unsigned long long = " << ull << cr; + log << "float = " << f << cr; + log << "double = " << d << cr; + log << "long double = " << ld << cr; + log << "char* = " << cstr << cr; + log << "std::string = " << str << cr; + log << "String 1\n"; + log << "String 2\n" << "String 3 " << "String 4\n"; + log << "Literal bool = " << false << cr; + log << "Literal unsigned int = " << 15 << cr; + log << "Literal double = " << (double)15 << cr; +} + +int main(int argc, char** argv) +{ + Logger log("test_log.txt", false); + std::cout << "****** Initial state (echo off, timestamp on)" << std::endl; + run_sequence(log); + std::cout << std::endl << "****** (echo off, timestamp off)" << std::endl; + log.setTimestampFlag(false); + run_sequence(log); + std::cout << std::endl << "****** (echo on, timestamp on)" << std::endl; + log.setEchoFlag(true); + log.setTimestampFlag(true); + run_sequence(log); + std::cout << std::endl << "****** (echo on, timestamp off)" << std::endl; + log.setTimestampFlag(false); + run_sequence(log); + return 0; +} diff --git a/cpp/doxygen/Makefile b/cpp/doxygen/Makefile new file mode 100644 index 0000000000..187fe698a0 --- /dev/null +++ b/cpp/doxygen/Makefile @@ -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. + # + +.PHONY: all + +all: + doxygen doxygen.cfg + +clean: + rm -rf html diff --git a/cpp/doxygen/doxygen.cfg b/cpp/doxygen/doxygen.cfg new file mode 100644 index 0000000000..d31c1bb134 --- /dev/null +++ b/cpp/doxygen/doxygen.cfg @@ -0,0 +1,1238 @@ +# NB: requires doxygen and graphviz - install with yum. + +# Doxyfile 1.4.6 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = Qpid + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 0 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = . + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, +# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, +# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, +# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, +# Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# This tag can be used to specify the encoding used in the generated output. +# The encoding is not always determined by the language that is chosen, +# but also whether or not the output is meant for Windows or non-Windows users. +# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES +# forces the Windows encoding (this is the default for the Windows binary), +# whereas setting the tag to NO uses a Unix-style encoding (the default for +# all platforms other than Windows). + +USE_WINDOWS_ENCODING = NO + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like the Qt-style comments (thus requiring an +# explicit @brief command for a brief description. + +JAVADOC_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = YES + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = YES + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = .. + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: + + +FILE_PATTERNS = *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = NO + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = NO + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_WIDTH = 1024 + +# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height +# (in pixels) of the graphs generated by dot. If a graph becomes larger than +# this value, doxygen will try to truncate the graph, so that it fits within +# the specified constraint. Beware that most browsers cannot cope with very +# large images. + +MAX_DOT_GRAPH_HEIGHT = 1024 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that a graph may be further truncated if the graph's +# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH +# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), +# the graph is not depth-constrained. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = YES diff --git a/cpp/options.mk b/cpp/options.mk new file mode 100644 index 0000000000..a1dfd695a7 --- /dev/null +++ b/cpp/options.mk @@ -0,0 +1,54 @@ + #l + # Copyright (c) 2006 The Apache Software Foundation + # + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. + # + +# Directories +SPEC_DIR = ${QPID_HOME}/specs +QPID_CPP_HOME = ${QPID_HOME}/cpp +COMMON_HOME = ${QPID_CPP_HOME}/common +TOOLS_DIR = ${QPID_CPP_HOME}/tools +LIB_DIR = ${QPID_CPP_HOME}/lib +BIN_DIR = ${QPID_CPP_HOME}/bin +APR_HOME= /usr +BOOST_HOME= /usr +CPPUNIT_HOME= /usr + +# Compile flags +DEBUG = -g +# _USE_APR_IO_ set when APR IO build is desired. +OPT = -D _USE_APR_IO_ #-O3 +APR_INCLUDES=-I ${APR_HOME}/include/apr-1/ +BOOST_INCLUDES=-I ${BOOST_HOME}/include/boost-1_33_1 +CPPUNIT_INCLUDES=-I ${CPPUNIT_HOME}/include +COMMON_INCLUDES = -I ${COMMON_HOME}/framing/inc -I ${COMMON_HOME}/framing/generated -I ${COMMON_HOME}/concurrent/inc -I ${COMMON_HOME}/io/inc -I ${COMMON_HOME}/error/inc -I $(COMMON_HOME)/utils/inc ${APR_INCLUDES} ${BOOST_INCLUDES} ${CPPUNIT_INCLUDES} +SRC_INCLUDES = $(COMMON_INCLUDES) -I inc +TEST_INCLUDES = $(COMMON_INCLUDES) -I ../inc +INCLUDES=$(SRC_INCLUDES) # Default to src +CXXFLAGS = $(DEBUG) $(OPT) -MMD -fpic $(INCLUDES) + +# TODO aconway 2006-09-12: This is not something we want in a release +# but it's useful for development. +RPATH= -Wl,-rpath,$(CURDIR)/$(LIB_DIR) + +# General link flags +LDFLAGS= -L $(LIB_DIR) -L ${APR_HOME}/lib -L ${BOOST_HOME}/lib -L ${CPPUNIT_HOME}/lib $(RPATH) + +# Libraries and executables. Use absolute paths so exes can find +# libs wherever they are run. TODO: Proper library management. +BROKER=$(BIN_DIR)/qpidd +BROKER_LIB=$(CURDIR)/$(LIB_DIR)/libqpid_broker.so.1.0 +COMMON_LIB=$(CURDIR)/$(LIB_DIR)/libqpid_common.so.1.0 +CLIENT_LIB=$(CURDIR)/$(LIB_DIR)/libqpid_client.so.1.0 + diff --git a/cpp/test_plugins.mk b/cpp/test_plugins.mk new file mode 100644 index 0000000000..abac186954 --- /dev/null +++ b/cpp/test_plugins.mk @@ -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. +# + + +# +# Standard make fragment for building test plugins in a directory. +# + +include ${QPID_HOME}/cpp/options.mk + +SOURCES := $(wildcard *.cpp) +TESTS := $(SOURCES:.cpp=.so) +DEPS= $(SOURCES:.cpp=.d) + +INCLUDES = $(TEST_INCLUDES) + +.PHONY: all clean + +all: $(TESTS) + +clean: + -@rm -f $(TESTS) $(DEPS) + +# Rule to build test plugins from .cpp files. +%.so: %.cpp + $(CXX) -shared -o $@ $< $(CXXFLAGS) $(LDFLAGS) $(LDLIBS) + +# Dependencies +-include $(DEPS) diff --git a/cpp/tools/saxon8.jar b/cpp/tools/saxon8.jar new file mode 100644 index 0000000000..197ce75c5b Binary files /dev/null and b/cpp/tools/saxon8.jar differ 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 = + "://[:][?