From 559e9702d5a7c26dddee708e267f2f685d4de89e Mon Sep 17 00:00:00 2001 From: Aidan Skinner Date: Thu, 24 Apr 2008 01:54:20 +0000 Subject: QPID-832 merge M2.x git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@651133 13f79535-47bb-0310-9956-ffa450edef68 --- java/broker/pom.xml | 4 +- java/broker/src/main/grammar/SelectorParser.jj | 2 +- .../server/exchange/DefaultExchangeFactory.java | 2 +- .../server/handler/ExchangeDeclareHandler.java | 2 +- .../server/security/access/plugins/AllowAll.java | 4 +- java/client/build.xml | 8 +- .../transport/ExistingSocketConnectorDemo.java | 171 +++ java/client/pom.xml | 24 +- java/client/src/main/grammar/SelectorParser.jj | 17 + .../socket/nio/ExistingSocketConnector.java | 478 ++++++++ .../qpid/client/AMQAuthenticationException.java | 5 + .../org/apache/qpid/client/AMQBrokerDetails.java | 19 +- .../java/org/apache/qpid/client/AMQConnection.java | 519 ++++++--- .../qpid/client/AMQConnectionDelegate_0_8.java | 24 +- .../apache/qpid/client/AMQConnectionFactory.java | 14 +- .../org/apache/qpid/client/AMQConnectionURL.java | 1 + .../org/apache/qpid/client/AMQDestination.java | 23 +- .../org/apache/qpid/client/AMQHeadersExchange.java | 2 +- .../org/apache/qpid/client/AMQQueueBrowser.java | 48 +- .../java/org/apache/qpid/client/AMQSession.java | 580 +++++++--- .../qpid/client/AMQSessionDirtyException.java | 42 + .../org/apache/qpid/client/AMQSession_0_10.java | 16 +- .../org/apache/qpid/client/AMQSession_0_8.java | 323 +++--- .../org/apache/qpid/client/AMQTemporaryTopic.java | 3 +- .../main/java/org/apache/qpid/client/AMQTopic.java | 16 +- .../apache/qpid/client/BasicMessageConsumer.java | 440 +++++--- .../qpid/client/BasicMessageConsumer_0_10.java | 22 +- .../qpid/client/BasicMessageConsumer_0_8.java | 27 +- .../apache/qpid/client/BasicMessageProducer.java | 80 +- .../qpid/client/BasicMessageProducer_0_10.java | 8 +- .../qpid/client/BasicMessageProducer_0_8.java | 77 +- .../org/apache/qpid/client/CustomJMSXProperty.java | 7 +- .../org/apache/qpid/client/QueueSenderAdapter.java | 8 +- .../apache/qpid/client/TopicSubscriberAdaptor.java | 4 +- .../qpid/client/failover/FailoverHandler.java | 9 +- .../qpid/client/failover/FailoverRetrySupport.java | 7 + .../handler/AccessRequestOkMethodHandler.java | 36 + .../client/handler/BasicCancelOkMethodHandler.java | 13 +- .../client/handler/BasicDeliverMethodHandler.java | 22 +- .../client/handler/BasicReturnMethodHandler.java | 29 +- .../client/handler/ChannelCloseMethodHandler.java | 27 +- .../handler/ChannelCloseOkMethodHandler.java | 8 +- .../client/handler/ChannelFlowMethodHandler.java | 54 + .../client/handler/ChannelFlowOkMethodHandler.java | 12 +- .../client/handler/ClientMethodDispatcherImpl.java | 528 +++++++++ .../handler/ClientMethodDispatcherImpl_0_9.java | 155 +++ .../handler/ClientMethodDispatcherImpl_8_0.java | 85 ++ .../handler/ConnectionCloseMethodHandler.java | 31 +- .../handler/ConnectionOpenOkMethodHandler.java | 7 +- .../handler/ConnectionRedirectMethodHandler.java | 13 +- .../handler/ConnectionSecureMethodHandler.java | 25 +- .../handler/ConnectionStartMethodHandler.java | 47 +- .../handler/ConnectionTuneMethodHandler.java | 55 +- .../handler/ExchangeBoundOkMethodHandler.java | 12 +- .../client/handler/QueueDeleteOkMethodHandler.java | 13 +- .../qpid/client/message/AbstractJMSMessage.java | 65 +- .../client/message/AbstractJMSMessageFactory.java | 13 +- .../apache/qpid/client/message/JMSMapMessage.java | 8 +- .../apache/qpid/client/message/ReturnMessage.java | 2 +- .../qpid/client/message/UnprocessedMessage.java | 27 +- .../client/message/UnprocessedMessage_0_10.java | 2 +- .../client/message/UnprocessedMessage_0_8.java | 18 +- .../qpid/client/protocol/AMQProtocolHandler.java | 272 +++-- .../qpid/client/protocol/AMQProtocolSession.java | 165 ++- .../state/AMQMethodNotImplementedException.java | 32 + .../org/apache/qpid/client/state/AMQState.java | 26 +- .../apache/qpid/client/state/AMQStateManager.java | 193 +--- .../client/state/StateAwareMethodListener.java | 8 +- .../transport/SocketTransportConnection.java | 34 +- .../qpid/client/transport/TransportConnection.java | 92 +- .../transport/VmPipeTransportConnection.java | 9 +- .../java/org/apache/qpid/jms/BrokerDetails.java | 2 + .../java/org/apache/qpid/jms/ConnectionURL.java | 4 +- .../java/org/apache/qpid/jms/FailoverPolicy.java | 12 +- .../java/org/apache/qpid/jms/MessageProducer.java | 4 + .../jms/failover/FailoverRoundRobinServers.java | 52 +- .../qpid/jms/failover/FailoverSingleServer.java | 48 +- .../jndi/PropertiesFileInitialContextFactory.java | 2 - .../qpid/fragmentation/TestLargePublisher.java | 2 +- .../org/apache/qpid/pubsub1/TestPublisher.java | 4 +- .../qpid/client/AMQQueueDeferredOrderingTest.java | 2 +- .../client/MessageListenerMultiConsumerTest.java | 6 + .../apache/qpid/client/MessageListenerTest.java | 7 + .../qpid/client/ResetMessageListenerTest.java | 24 +- .../qpid/test/unit/basic/PropertyValueTest.java | 12 +- .../apache/qpid/test/unit/basic/SelectorTest.java | 187 +++- .../qpid/test/unit/basic/close/CloseTest.java | 74 ++ .../qpid/test/unit/basic/close/CloseTests.java | 74 -- .../qpid/test/unit/client/AMQSessionTest.java | 15 +- .../ChannelCloseMethodHandlerNoCloseOk.java | 13 +- .../unit/client/channelclose/ChannelCloseTest.java | 123 ++- .../client/channelclose/NoCloseOKStateManager.java | 44 +- .../unit/client/connection/ConnectionTest.java | 104 +- .../client/connectionurl/ConnectionURLTest.java | 19 + .../client/protocol/AMQProtocolSessionTest.java | 7 +- .../qpid/test/unit/close/CloseBeforeAckTest.java | 4 +- .../qpid/test/unit/ct/DurableSubscriberTest.java | 167 +++ .../qpid/test/unit/ct/DurableSubscriberTests.java | 167 --- .../qpid/test/unit/message/JMSPropertiesTest.java | 31 + .../test/unit/topic/DurableSubscriptionTest.java | 132 ++- .../test/unit/transacted/CommitRollbackTest.java | 12 +- java/common/build.xml | 6 +- java/common/pom.xml | 12 - java/common/protocol-version.xml | 22 +- .../mina/common/FixedSizeByteBufferAllocator.java | 528 +++++++++ .../mina/common/support/DefaultIoFuture.java | 228 ++++ .../mina/filter/WriteBufferFullExeception.java | 48 + .../mina/filter/WriteBufferLimitFilterBuilder.java | 272 +++++ .../filter/codec/OurCumulativeProtocolDecoder.java | 197 ++++ .../mina/filter/codec/QpidProtocolCodecFilter.java | 440 ++++++++ .../socket/nio/MultiThreadSocketAcceptor.java | 547 +++++++++ .../socket/nio/MultiThreadSocketConnector.java | 486 ++++++++ .../socket/nio/MultiThreadSocketFilterChain.java | 67 ++ .../socket/nio/MultiThreadSocketIoProcessor.java | 1026 +++++++++++++++++ .../nio/MultiThreadSocketSessionConfigImpl.java | 240 ++++ .../socket/nio/MultiThreadSocketSessionImpl.java | 488 ++++++++ .../mina/transport/vmpipe/QpidVmPipeConnector.java | 151 +++ .../java/org/apache/qpid/AMQChannelException.java | 8 +- .../org/apache/qpid/AMQConnectionException.java | 13 +- .../apache/qpid/AMQConnectionFailureException.java | 87 +- .../main/java/org/apache/qpid/AMQException.java | 31 +- .../apache/qpid/AMQInvalidArgumentException.java | 6 + .../org/apache/qpid/AMQUndeliveredException.java | 6 + .../java/org/apache/qpid/codec/AMQDecoder.java | 114 +- .../main/java/org/apache/qpid/framing/AMQBody.java | 79 +- .../java/org/apache/qpid/framing/AMQFrame.java | 58 +- .../qpid/framing/AMQFrameDecodingException.java | 6 + .../org/apache/qpid/framing/AMQMethodBody.java | 215 ++-- .../apache/qpid/framing/AMQMethodBodyFactory.java | 3 +- .../org/apache/qpid/framing/AMQMethodBodyImpl.java | 96 ++ .../qpid/framing/AMQMethodBodyInstanceFactory.java | 3 +- .../org/apache/qpid/framing/AMQShortString.java | 1165 ++++++++++++-------- .../qpid/framing/AMQShortStringTokenizer.java | 31 + .../main/java/org/apache/qpid/framing/AMQType.java | 262 +++-- .../org/apache/qpid/framing/AMQTypedValue.java | 32 +- .../apache/qpid/framing/CompositeAMQDataBlock.java | 27 +- .../java/org/apache/qpid/framing/ContentBody.java | 21 +- .../org/apache/qpid/framing/ContentHeaderBody.java | 10 +- .../framing/ContentHeaderPropertiesFactory.java | 4 +- .../org/apache/qpid/framing/DeferredDataBlock.java | 50 + .../org/apache/qpid/framing/EncodingUtils.java | 19 +- .../java/org/apache/qpid/framing/FieldTable.java | 108 +- .../org/apache/qpid/framing/HeartbeatBody.java | 14 +- .../apache/qpid/framing/ProtocolInitiation.java | 3 +- .../qpid/framing/SmallCompositeAMQDataBlock.java | 22 +- .../qpid/framing/VersionSpecificRegistry.java | 2 +- .../framing/abstraction/MessagePublishInfo.java | 2 + .../qpid/framing/amqp_0_9/AMQMethodBody_0_9.java | 209 ++++ .../qpid/framing/amqp_0_9/MethodConverter_0_9.java | 172 +++ .../qpid/framing/amqp_8_0/AMQMethodBody_8_0.java | 209 ++++ .../qpid/framing/amqp_8_0/MethodConverter_8_0.java | 151 +++ .../src/main/java/org/apache/qpid/pool/Job.java | 22 +- .../java/org/apache/qpid/pool/PoolingFilter.java | 105 +- .../apache/qpid/protocol/AMQMethodListener.java | 3 +- .../protocol/AMQVersionAwareProtocolSession.java | 15 +- .../apache/qpid/protocol/ProtocolVersionAware.java | 6 + .../ConcurrentLinkedMessageQueueAtomicSize.java | 8 + .../apache/mina/SocketIOTest/IOWriterClient.java | 396 +++++++ .../apache/mina/SocketIOTest/IOWriterServer.java | 157 +++ .../apache/qpid/framing/AMQShortStringTest.java | 109 ++ .../qpid/framing/PropertyFieldTableTest.java | 82 +- java/common/templates/method/MethodBodyClass.tmpl | 183 --- .../common/templates/method/MethodBodyInterface.vm | 62 ++ .../templates/method/version/MethodBodyClass.vm | 209 ++++ .../common/templates/model/AmqpConstantsClass.tmpl | 37 - .../model/ClientMethodDispatcherInterface.vm | 56 + .../templates/model/MethodDispatcherInterface.vm | 39 + .../templates/model/MethodRegistryClass.tmpl | 159 --- java/common/templates/model/MethodRegistryClass.vm | 104 ++ .../templates/model/ProtocolVersionListClass.tmpl | 127 --- .../templates/model/ProtocolVersionListClass.vm | 178 +++ .../model/ServerMethodDispatcherInterface.vm | 56 + .../templates/model/version/AmqpConstantsClass.vm | 37 + .../version/ClientMethodDispatcherInterface.vm | 55 + .../model/version/MethodDispatcherInterface.vm | 43 + .../templates/model/version/MethodRegistryClass.vm | 193 ++++ .../version/ServerMethodDispatcherInterface.vm | 55 + java/pom.xml | 122 +- java/systests/build.xml | 2 +- java/systests/etc/bin/fail.py | 88 ++ java/systests/pom.xml | 133 ++- .../java/org/apache/qpid/server/ack/TxAckTest.java | 15 +- .../exchange/AbstractHeadersExchangeTestBase.java | 9 +- .../exchange/MessagingTestConfigProperties.java | 3 +- .../qpid/server/failover/FailoverMethodTest.java | 136 +++ .../org/apache/qpid/server/plugins/PluginTest.java | 54 + .../protocol/AMQProtocolSessionMBeanTest.java | 36 +- .../qpid/server/protocol/MaxChannelsTest.java | 4 +- .../java/org/apache/qpid/server/queue/AckTest.java | 25 +- .../qpid/server/queue/ConcurrencyTestDisabled.java | 8 +- .../qpid/server/queue/DeliveryManagerTest.java | 8 +- .../qpid/server/queue/MessageTestHelper.java | 18 +- .../qpid/server/queue/MockProtocolSession.java | 46 +- .../qpid/server/queue/SubscriptionTestHelper.java | 35 +- .../apache/qpid/server/queue/TimeToLiveTest.java | 135 ++- .../qpid/server/security/acl/SimpleACLTest.java | 607 ++++++++++ .../qpid/server/store/TestReferenceCounting.java | 15 +- .../org/apache/qpid/server/txn/TxnBufferTest.java | 11 +- .../qpid/server/util/TestApplicationRegistry.java | 20 +- .../org/apache/qpid/test/FailoverBaseCase.java | 76 ++ .../main/java/org/apache/qpid/test/VMTestCase.java | 70 +- .../org/apache/qpid/test/client/CancelTest.java | 111 ++ .../org/apache/qpid/test/client/DupsOkTest.java | 160 +++ .../qpid/test/client/QueueBrowserAutoAckTest.java | 510 +++++++++ .../test/client/QueueBrowserClientAckTest.java | 49 + .../qpid/test/client/QueueBrowserDupsOkTest.java | 49 + .../qpid/test/client/QueueBrowserNoAckTest.java | 50 + .../qpid/test/client/QueueBrowserPreAckTest.java | 53 + .../apache/qpid/test/client/QueueBrowserTest.java | 152 --- .../test/client/QueueBrowserTransactedTest.java | 51 + .../qpid/test/client/failover/FailoverTest.java | 257 +++++ .../apache/qpid/test/framework/AMQPPublisher.java | 54 + .../qpid/test/framework/BrokerLifecycleAware.java | 70 ++ .../apache/qpid/test/framework/CauseFailure.java | 42 + .../test/framework/CauseFailureUserPrompt.java | 65 ++ .../apache/qpid/test/framework/CircuitEndBase.java | 11 +- .../qpid/test/framework/ExceptionMonitor.java | 79 +- .../qpid/test/framework/FrameworkBaseCase.java | 204 ++-- .../test/framework/FrameworkClientBaseCase.java | 20 + .../qpid/test/framework/FrameworkTestContext.java | 48 + .../test/framework/LocalAMQPCircuitFactory.java | 168 +++ .../qpid/test/framework/LocalCircuitFactory.java | 316 ++++++ .../qpid/test/framework/MessageIdentityVector.java | 167 +++ .../framework/MessagingTestConfigProperties.java | 268 ++++- .../test/framework/NotApplicableAssertion.java | 112 ++ .../org/apache/qpid/test/framework/Publisher.java | 40 +- .../org/apache/qpid/test/framework/Receiver.java | 48 +- .../apache/qpid/test/framework/TestCaseVector.java | 88 ++ .../org/apache/qpid/test/framework/TestUtils.java | 11 +- .../clocksynch/ClockSynchFailureException.java | 4 +- .../framework/clocksynch/ClockSynchThread.java | 5 +- .../framework/clocksynch/ClockSynchronizer.java | 2 +- .../framework/clocksynch/UDPClockReference.java | 7 +- .../framework/clocksynch/UDPClockSynchronizer.java | 7 +- .../distributedcircuit/DistributedCircuitImpl.java | 8 +- .../DistributedPublisherImpl.java | 36 +- .../DistributedReceiverImpl.java | 48 +- .../distributedcircuit/TestClientCircuitEnd.java | 21 +- .../framework/distributedtesting/Coordinator.java | 87 +- .../DistributedTestDecorator.java | 2 +- .../distributedtesting/FanOutTestDecorator.java | 3 +- .../distributedtesting/InteropTestDecorator.java | 5 +- .../framework/distributedtesting/TestClient.java | 497 +++++++++ .../test/framework/listeners/XMLTestListener.java | 4 +- .../localcircuit/LocalAMQPPublisherImpl.java | 133 +++ .../framework/localcircuit/LocalCircuitImpl.java | 192 +--- .../framework/localcircuit/LocalPublisherImpl.java | 60 +- .../framework/localcircuit/LocalReceiverImpl.java | 54 +- .../test/framework/qpid/AMQPFeatureDecorator.java | 96 ++ .../test/framework/qpid/CauseFailureDecorator.java | 95 ++ .../qpid/test/framework/qpid/CauseFailureInVM.java | 70 ++ .../test/framework/qpid/InVMBrokerDecorator.java | 135 +++ .../framework/sequencers/BaseCircuitFactory.java | 10 +- .../test/framework/sequencers/CircuitFactory.java | 14 +- .../framework/sequencers/FanOutCircuitFactory.java | 6 +- .../sequencers/InteropCircuitFactory.java | 14 +- .../apache/qpid/test/testcases/FailoverTest.java | 119 ++ .../qpid/test/testcases/ImmediateMessageTest.java | 303 +++++ .../qpid/test/testcases/MandatoryMessageTest.java | 321 ++++++ .../apache/qpid/test/testcases/RollbackTest.java | 132 +++ .../org/apache/qpid/test/testcases/TTLTest.java | 151 +++ .../apache/qpid/test/unit/ack/AcknowledgeTest.java | 174 +++ .../org/apache/qpid/util/ConversationFactory.java | 2 +- 263 files changed, 20753 insertions(+), 4013 deletions(-) create mode 100644 java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java create mode 100644 java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java create mode 100644 java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java create mode 100644 java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTest.java delete mode 100644 java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java create mode 100644 java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java delete mode 100644 java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTests.java create mode 100644 java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java create mode 100644 java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java create mode 100644 java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java create mode 100644 java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java create mode 100644 java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java create mode 100644 java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java create mode 100644 java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java create mode 100644 java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java create mode 100644 java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java create mode 100644 java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java create mode 100644 java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java delete mode 100644 java/common/templates/method/MethodBodyClass.tmpl create mode 100644 java/common/templates/method/MethodBodyInterface.vm create mode 100644 java/common/templates/method/version/MethodBodyClass.vm delete mode 100644 java/common/templates/model/AmqpConstantsClass.tmpl create mode 100644 java/common/templates/model/ClientMethodDispatcherInterface.vm create mode 100644 java/common/templates/model/MethodDispatcherInterface.vm delete mode 100644 java/common/templates/model/MethodRegistryClass.tmpl create mode 100644 java/common/templates/model/MethodRegistryClass.vm delete mode 100644 java/common/templates/model/ProtocolVersionListClass.tmpl create mode 100644 java/common/templates/model/ProtocolVersionListClass.vm create mode 100644 java/common/templates/model/ServerMethodDispatcherInterface.vm create mode 100644 java/common/templates/model/version/AmqpConstantsClass.vm create mode 100644 java/common/templates/model/version/ClientMethodDispatcherInterface.vm create mode 100644 java/common/templates/model/version/MethodDispatcherInterface.vm create mode 100644 java/common/templates/model/version/MethodRegistryClass.vm create mode 100644 java/common/templates/model/version/ServerMethodDispatcherInterface.vm create mode 100644 java/systests/etc/bin/fail.py create mode 100644 java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java delete mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java create mode 100644 java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java (limited to 'java') diff --git a/java/broker/pom.xml b/java/broker/pom.xml index 153e186a26..53913f161f 100644 --- a/java/broker/pom.xml +++ b/java/broker/pom.xml @@ -22,14 +22,14 @@ org.apache.qpid qpid-broker jar - 1.0-incubating-M2.1-SNAPSHOT + 1.0-incubating-M3-SNAPSHOT Qpid Broker http://cwiki.apache.org/confluence/display/qpid org.apache.qpid qpid - 1.0-incubating-M2.1-SNAPSHOT + 1.0-incubating-M3-SNAPSHOT ../pom.xml diff --git a/java/broker/src/main/grammar/SelectorParser.jj b/java/broker/src/main/grammar/SelectorParser.jj index f6a843e080..eb4467f1cc 100644 --- a/java/broker/src/main/grammar/SelectorParser.jj +++ b/java/broker/src/main/grammar/SelectorParser.jj @@ -94,7 +94,7 @@ public class SelectorParser { return this.JmsSelector(); } catch (Throwable e) { - throw (AMQInvalidArgumentException)new AMQInvalidArgumentException(sql).initCause(e); + throw (AMQInvalidArgumentException)new AMQInvalidArgumentException(sql, e); } } diff --git a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java index 1a9dc6673a..636aa7eb03 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java +++ b/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -67,7 +67,7 @@ public class DefaultExchangeFactory implements ExchangeFactory if (exchType == null) { - throw new AMQUnknownExchangeType("Unknown exchange type: " + type); + throw new AMQUnknownExchangeType("Unknown exchange type: " + type, null); } Exchange e = exchType.newInstance(_host, exchange, durable, ticket, autoDelete); return e; diff --git a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java index 9a98bc9659..97d25f650d 100644 --- a/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java +++ b/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java @@ -101,7 +101,7 @@ public class ExchangeDeclareHandler implements StateAwareMethodListener - + @@ -29,9 +29,9 @@ - - - + diff --git a/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java new file mode 100644 index 0000000000..d7eb138523 --- /dev/null +++ b/java/client/example/src/main/java/org/apache/qpid/example/transport/ExistingSocketConnectorDemo.java @@ -0,0 +1,171 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.example.transport; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.url.URLSyntaxException; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.channels.SocketChannel; +import java.util.UUID; + +/** + * This is a simple application that demonstrates how you can use the Qpid AMQP interfaces to use existing sockets as + * the transport for the Client API. + * + * The Demo here runs twice: + * 1. Just to show a simple publish and receive. + * 2. To demonstrate how to use existing sockets and utilise the underlying client failover mechnaism. + */ +public class ExistingSocketConnectorDemo implements ConnectionListener +{ + private static boolean DEMO_FAILOVER = false; + + public static void main(String[] args) throws IOException, URLSyntaxException, AMQException, JMSException + { + System.out.println("Testing socket connection to localhost:5672."); + + new ExistingSocketConnectorDemo(); + + System.out.println("Testing socket connection failover between localhost:5672 and localhost:5673."); + + DEMO_FAILOVER = true; + + new ExistingSocketConnectorDemo(); + } + + Connection _connection; + MessageProducer _producer; + Session _session; + + String Socket1_ID = UUID.randomUUID().toString(); + String Socket2_ID = UUID.randomUUID().toString(); + + + + /** Here we can see the broker we are connecting to is set to be 'socket:///' signifying we will provide the socket. */ + public final String CONNECTION = "amqp://guest:guest@id/test?brokerlist='socket://" + Socket1_ID + ";socket://" + Socket2_ID + "'"; + + + public ExistingSocketConnectorDemo() throws IOException, URLSyntaxException, AMQException, JMSException + { + + Socket socket = SocketChannel.open().socket(); + socket.connect(new InetSocketAddress("localhost", 5672)); + + TransportConnection.registerOpenSocket(Socket1_ID, socket); + + + _connection = new AMQConnection(CONNECTION); + + _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + MessageConsumer consumer = _session.createConsumer(_session.createQueue("Queue")); + + _producer = _session.createProducer(_session.createQueue("Queue")); + + _connection.start(); + + if (!DEMO_FAILOVER) + { + _producer.send(_session.createTextMessage("Simple Test")); + } + else + { + // Using the Qpid interfaces we can set a listener that allows us to demonstrate failover + ((AMQConnection) _connection).setConnectionListener(this); + + System.out.println("Testing failover: Please ensure second broker running on localhost:5673 and shutdown broker on 5672."); + } + + //We do a blocking receive here so that we can demonstrate failover. + Message message = consumer.receive(); + + System.out.println("Recevied :" + message); + + _connection.close(); + } + + // ConnectionListener Interface + + public void bytesSent(long count) + { + //not used in this example + } + public void bytesReceived(long count) + { + //not used in this example + } + + public boolean preFailover(boolean redirect) + { + /** + * This method is called before the underlying client library starts to reconnect. This gives us the opportunity + * to set a new socket for the failover to occur on. + */ + try + { + Socket socket = SocketChannel.open().socket(); + + socket.connect(new InetSocketAddress("localhost", 5673)); + + // This is the new method to pass in an open socket for the connection to use. + TransportConnection.registerOpenSocket(Socket2_ID, socket); + } + catch (IOException e) + { + e.printStackTrace(); + return false; + } + return true; + } + + public boolean preResubscribe() + { + //not used in this example - but must return true to allow the resubscription of existing clients. + return true; + } + + public void failoverComplete() + { + // Now that failover has completed we can send a message that the receiving thread will pick up + try + { + _producer.send(_session.createTextMessage("Simple Failover Test")); + } + catch (JMSException e) + { + e.printStackTrace(); + } + } +} diff --git a/java/client/pom.xml b/java/client/pom.xml index 07a3520f58..bcce3e1d3b 100644 --- a/java/client/pom.xml +++ b/java/client/pom.xml @@ -69,18 +69,8 @@ commons-lang - - org.apache.mina - mina-filter-ssl - - - org.slf4j - slf4j-log4j12 - 1.4.0 - test - org.apache.qpid @@ -89,19 +79,7 @@ - junit - junit - test - - - - org.easymock - easymockclassextension - test - - - - uk.co.thebadgerset + org.apache.qpid junit-toolkit test diff --git a/java/client/src/main/grammar/SelectorParser.jj b/java/client/src/main/grammar/SelectorParser.jj index 2dca11748e..a72da526ae 100644 --- a/java/client/src/main/grammar/SelectorParser.jj +++ b/java/client/src/main/grammar/SelectorParser.jj @@ -170,6 +170,8 @@ TOKEN [IGNORE_CASE] : TOKEN [IGNORE_CASE] : { < ID : ["a"-"z", "_", "$"] (["a"-"z","0"-"9","_", "$"])* > + | < QUOTED_ID : "\"" ( ("\"\"") | ~["\""] )* "\"" > + } // ---------------------------------------------------------------------------- @@ -577,6 +579,7 @@ String stringLitteral() : PropertyExpression variable() : { Token t; + StringBuffer rc = new StringBuffer(); PropertyExpression left=null; } { @@ -585,6 +588,20 @@ PropertyExpression variable() : { left = new PropertyExpression(t.image); } + | + t = + { + // Decode the sting value. + String image = t.image; + for( int i=1; i < image.length()-1; i++ ) { + char c = image.charAt(i); + if( c == '"' ) + i++; + rc.append(c); + } + return new PropertyExpression(rc.toString()); + } + ) { return left; diff --git a/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java b/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java new file mode 100644 index 0000000000..98716c0c3c --- /dev/null +++ b/java/client/src/main/java/org/apache/mina/transport/socket/nio/ExistingSocketConnector.java @@ -0,0 +1,478 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.BaseIoConnector; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.Queue; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; + +/** + * {@link IoConnector} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 627427 $, $Date: 2008-02-13 14:39:10 +0000 (Wed, 13 Feb 2008) $ + */ +public class ExistingSocketConnector extends BaseIoConnector +{ + /** @noinspection StaticNonFinalField */ + private static volatile int nextId = 0; + + private final Object lock = new Object(); + private final int id = nextId++; + private final String threadName = "SocketConnector-" + id; + private SocketConnectorConfig defaultConfig = new SocketConnectorConfig(); + private final Queue connectQueue = new Queue(); + private final SocketIoProcessor[] ioProcessors; + private final int processorCount; + private final Executor executor; + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + private int workerTimeout = 60; // 1 min. + private Socket _openSocket = null; + + /** Create a connector with a single processing thread using a NewThreadExecutor */ + public ExistingSocketConnector() + { + this(1, new NewThreadExecutor()); + } + + /** + * Create a connector with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public ExistingSocketConnector(int processorCount, Executor executor) + { + if (processorCount < 1) + { + throw new IllegalArgumentException("Must have at least one processor"); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new SocketIoProcessor[processorCount]; + + for (int i = 0; i < processorCount; i++) + { + ioProcessors[i] = new SocketIoProcessor("SocketConnectorIoProcessor-" + id + "." + i, executor); + } + } + + /** + * How many seconds to keep the connection thread alive between connection requests + * + * @return Number of seconds to keep connection thread alive + */ + public int getWorkerTimeout() + { + return workerTimeout; + } + + /** + * Set how many seconds the connection worker thread should remain alive once idle before terminating itself. + * + * @param workerTimeout Number of seconds to keep thread alive. Must be >=0 + */ + public void setWorkerTimeout(int workerTimeout) + { + if (workerTimeout < 0) + { + throw new IllegalArgumentException("Must be >= 0"); + } + this.workerTimeout = workerTimeout; + } + + public ConnectFuture connect(SocketAddress address, IoHandler handler, IoServiceConfig config) + { + return connect(address, null, handler, config); + } + + public ConnectFuture connect(SocketAddress address, SocketAddress localAddress, + IoHandler handler, IoServiceConfig config) + { + /** Changes here from the Mina OpenSocketConnector. + * Ignoreing all address as they are not needed */ + + if (handler == null) + { + throw new NullPointerException("handler"); + } + + + if (config == null) + { + config = getDefaultConfig(); + } + + if (_openSocket == null) + { + throw new IllegalArgumentException("Specifed Socket not active"); + } + + boolean success = false; + + try + { + DefaultConnectFuture future = new DefaultConnectFuture(); + newSession(_openSocket, handler, config, future); + success = true; + return future; + } + catch (IOException e) + { + return DefaultConnectFuture.newFailedFuture(e); + } + finally + { + if (!success && _openSocket != null) + { + try + { + _openSocket.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + public IoServiceConfig getDefaultConfig() + { + return defaultConfig; + } + + /** + * Sets the config this connector will use by default. + * + * @param defaultConfig the default config. + * + * @throws NullPointerException if the specified value is null. + */ + public void setDefaultConfig(SocketConnectorConfig defaultConfig) + { + if (defaultConfig == null) + { + throw new NullPointerException("defaultConfig"); + } + this.defaultConfig = defaultConfig; + } + + private synchronized void startupWorker() throws IOException + { + if (worker == null) + { + selector = Selector.open(); + worker = new Worker(); + executor.execute(new NamePreservingRunnable(worker)); + } + } + + private void registerNew() + { + if (connectQueue.isEmpty()) + { + return; + } + + for (; ;) + { + ConnectionRequest req; + synchronized (connectQueue) + { + req = (ConnectionRequest) connectQueue.pop(); + } + + if (req == null) + { + break; + } + + SocketChannel ch = req.channel; + try + { + ch.register(selector, SelectionKey.OP_CONNECT, req); + } + catch (IOException e) + { + req.setException(e); + } + } + } + + private void processSessions(Set keys) + { + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isConnectable()) + { + continue; + } + + SocketChannel ch = (SocketChannel) key.channel(); + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + boolean success = false; + try + { + ch.finishConnect(); + newSession(ch, entry.handler, entry.config, entry); + success = true; + } + catch (Throwable e) + { + entry.setException(e); + } + finally + { + key.cancel(); + if (!success) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + keys.clear(); + } + + private void processTimedOutSessions(Set keys) + { + long currentTime = System.currentTimeMillis(); + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isValid()) + { + continue; + } + + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + if (currentTime >= entry.deadline) + { + entry.setException(new ConnectException()); + try + { + key.channel().close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + key.cancel(); + } + } + } + } + + private void newSession(Socket socket, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + SocketSessionImpl session = new SocketSessionImpl(this, + nextProcessor(), + getListeners(), + config, + socket.getChannel(), + handler, + socket.getRemoteSocketAddress()); + + newSession(session, config, connectFuture); + } + + private void newSession(SocketChannel ch, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + + { + SocketSessionImpl session = new SocketSessionImpl(this, + nextProcessor(), + getListeners(), + config, + ch, + handler, + ch.socket().getRemoteSocketAddress()); + + newSession(session, config, connectFuture); + } + + private void newSession(SocketSessionImpl session, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + try + { + getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getThreadModel().buildFilterChain(session.getFilterChain()); + } + catch (Throwable e) + { + throw (IOException) new IOException("Failed to create a session.").initCause(e); + } + session.getIoProcessor().addNew(session); + connectFuture.setSession(session); + } + + private SocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + public void setOpenSocket(Socket openSocket) + { + _openSocket = openSocket; + } + + private class Worker implements Runnable + { + private long lastActive = System.currentTimeMillis(); + + public void run() + { + Thread.currentThread().setName(ExistingSocketConnector.this.threadName); + + for (; ;) + { + try + { + int nKeys = selector.select(1000); + + registerNew(); + + if (nKeys > 0) + { + processSessions(selector.selectedKeys()); + } + + processTimedOutSessions(selector.keys()); + + if (selector.keys().isEmpty()) + { + if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) + { + synchronized (lock) + { + if (selector.keys().isEmpty() && + connectQueue.isEmpty()) + { + worker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + break; + } + } + } + } + else + { + lastActive = System.currentTimeMillis(); + } + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + } + } + + private class ConnectionRequest extends DefaultConnectFuture + { + private final SocketChannel channel; + private final long deadline; + private final IoHandler handler; + private final IoServiceConfig config; + + private ConnectionRequest(SocketChannel channel, IoHandler handler, IoServiceConfig config) + { + this.channel = channel; + long timeout; + if (config instanceof IoConnectorConfig) + { + timeout = ((IoConnectorConfig) config).getConnectTimeoutMillis(); + } + else + { + timeout = ((IoConnectorConfig) getDefaultConfig()).getConnectTimeoutMillis(); + } + this.deadline = System.currentTimeMillis() + timeout; + this.handler = handler; + this.config = config; + } + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java b/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java index 6bae0166d1..05ac3dca9e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java @@ -39,4 +39,9 @@ public class AMQAuthenticationException extends AMQException { super(error, msg, cause); } + public boolean isHardError() + { + return true; + } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java index b35f73d84a..f051450260 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java @@ -58,8 +58,9 @@ public class AMQBrokerDetails implements BrokerDetails if (transport != null) { //todo this list of valid transports should be enumerated somewhere - if ((!(transport.equalsIgnoreCase("vm") || - transport.equalsIgnoreCase("tcp")))) + if ((!(transport.equalsIgnoreCase(BrokerDetails.VM) || + transport.equalsIgnoreCase(BrokerDetails.TCP) || + transport.equalsIgnoreCase(BrokerDetails.SOCKET)))) { if (transport.equalsIgnoreCase("localhost")) { @@ -164,7 +165,10 @@ public class AMQBrokerDetails implements BrokerDetails } else { - setPort(port); + if (!_transport.equalsIgnoreCase(SOCKET)) + { + setPort(port); + } } String queryString = connection.getQuery(); @@ -271,13 +275,16 @@ public class AMQBrokerDetails implements BrokerDetails sb.append(_transport); sb.append("://"); - if (!(_transport.equalsIgnoreCase("vm"))) + if (!(_transport.equalsIgnoreCase(VM))) { sb.append(_host); } - sb.append(':'); - sb.append(_port); + if (!(_transport.equalsIgnoreCase(SOCKET))) + { + sb.append(':'); + sb.append(_port); + } sb.append(printOptionsURL()); diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java index 8ab16c65c0..3969eef8a9 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java @@ -1,5 +1,5 @@ /* - * +* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -20,17 +20,31 @@ */ package org.apache.qpid.client; -import java.io.IOException; -import java.net.ConnectException; -import java.nio.channels.UnresolvedAddressException; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import org.apache.qpid.AMQConnectionFailureException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQProtocolException; +import org.apache.qpid.AMQUndeliveredException; +import org.apache.qpid.AMQUnresolvedAddressException; +import org.apache.qpid.client.failover.FailoverException; +import org.apache.qpid.client.failover.FailoverProtectedOperation; +import org.apache.qpid.client.failover.FailoverRetrySupport; +import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.state.AMQState; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.*; +import org.apache.qpid.jms.BrokerDetails; +import org.apache.qpid.jms.ChannelLimitReachedException; +import org.apache.qpid.jms.Connection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.jms.ConnectionURL; +import org.apache.qpid.jms.FailoverPolicy; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpidity.transport.TransportConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.jms.ConnectionConsumer; import javax.jms.ConnectionMetaData; @@ -49,25 +63,117 @@ import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.Referenceable; import javax.naming.StringRefAddr; - -import org.apache.qpid.*; -import org.apache.qpid.client.failover.FailoverException; -import org.apache.qpid.client.protocol.AMQProtocolHandler; -import org.apache.qpid.exchange.ExchangeDefaults; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.jms.BrokerDetails; -import org.apache.qpid.jms.Connection; -import org.apache.qpid.jms.ConnectionListener; -import org.apache.qpid.jms.ConnectionURL; -import org.apache.qpid.jms.FailoverPolicy; -import org.apache.qpid.protocol.AMQConstant; -import org.apache.qpid.url.URLSyntaxException; -import org.apache.qpidity.transport.TransportConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.ConnectException; +import java.nio.channels.UnresolvedAddressException; +import java.text.MessageFormat; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class AMQConnection extends Closeable implements Connection, QueueConnection, TopicConnection, Referenceable { + public static final class ChannelToSessionMap + { + private final AMQSession[] _fastAccessSessions = new AMQSession[16]; + private final LinkedHashMap _slowAccessSessions = new LinkedHashMap(); + private int _size = 0; + private static final int FAST_CHANNEL_ACCESS_MASK = 0xFFFFFFF0; + + public AMQSession get(int channelId) + { + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + return _fastAccessSessions[channelId]; + } + else + { + return _slowAccessSessions.get(channelId); + } + } + + public AMQSession put(int channelId, AMQSession session) + { + AMQSession oldVal; + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + oldVal = _fastAccessSessions[channelId]; + _fastAccessSessions[channelId] = session; + } + else + { + oldVal = _slowAccessSessions.put(channelId, session); + } + if((oldVal != null) && (session == null)) + { + _size--; + } + else if((oldVal == null) && (session != null)) + { + _size++; + } + + return session; + + } + + + public AMQSession remove(int channelId) + { + AMQSession session; + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + session = _fastAccessSessions[channelId]; + _fastAccessSessions[channelId] = null; + } + else + { + session = _slowAccessSessions.remove(channelId); + } + + if(session != null) + { + _size--; + } + return session; + + } + + public Collection values() + { + ArrayList values = new ArrayList(size()); + + for(int i = 0; i < 16; i++) + { + if(_fastAccessSessions[i] != null) + { + values.add(_fastAccessSessions[i]); + } + } + values.addAll(_slowAccessSessions.values()); + + return values; + } + + public int size() + { + return _size; + } + + public void clear() + { + _size = 0; + _slowAccessSessions.clear(); + for(int i = 0; i<16; i++) + { + _fastAccessSessions[i] = null; + } + } + } + + private static final Logger _logger = LoggerFactory.getLogger(AMQConnection.class); protected AtomicInteger _idFactory = new AtomicInteger(0); @@ -76,7 +182,9 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect * This is the "root" mutex that must be held when doing anything that could be impacted by failover. This must be * held by any child objects of this connection such as the session, producers and consumers. */ - protected final Object _failoverMutex = new Object(); + private final Object _failoverMutex = new Object(); + + private final Object _sessionCreationLock = new Object(); /** * A channel is roughly analogous to a session. The server can negotiate the maximum number of channels per session @@ -85,7 +193,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect protected long _maximumChannelCount; /** The maximum size of frame supported by the server */ - protected long _maximumFrameSize; + private long _maximumFrameSize; /** * The protocol handler dispatches protocol events for this connection. For example, when the connection is dropped @@ -95,30 +203,31 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect protected AMQProtocolHandler _protocolHandler; /** Maps from session id (Integer) to AMQSession instance */ - protected final Map _sessions = new LinkedHashMap(); + private final ChannelToSessionMap _sessions = new ChannelToSessionMap(); - protected String _clientName; + private String _clientName; /** The user name to use for authentication */ - protected String _username; + private String _username; /** The password to use for authentication */ - protected String _password; + private String _password; /** The virtual path to connect to on the AMQ server */ - protected String _virtualHost; + private String _virtualHost; + protected ExceptionListener _exceptionListener; - protected ConnectionListener _connectionListener; + private ConnectionListener _connectionListener; - protected ConnectionURL _connectionURL; + private ConnectionURL _connectionURL; /** * Whether this connection is started, i.e. whether messages are flowing to consumers. It has no meaning for message * publication. */ - protected boolean _started; + protected volatile boolean _started; /** Policy dictating how to failover */ protected FailoverPolicy _failoverPolicy; @@ -136,22 +245,23 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect /* * The connection meta data */ - protected QpidConnectionMetaData _connectionMetaData; + private QpidConnectionMetaData _connectionMetaData; /** Configuration info for SSL */ - protected SSLConfiguration _sslConfiguration; + private SSLConfiguration _sslConfiguration; - protected AMQShortString _defaultTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; - protected AMQShortString _defaultQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; - protected AMQShortString _temporaryTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; - protected AMQShortString _temporaryQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; + private AMQShortString _defaultTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; + private AMQShortString _defaultQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; + private AMQShortString _temporaryTopicExchangeName = ExchangeDefaults.TOPIC_EXCHANGE_NAME; + private AMQShortString _temporaryQueueExchangeName = ExchangeDefaults.DIRECT_EXCHANGE_NAME; /** Thread Pool for executing connection level processes. Such as returning bounced messages. */ - protected final ExecutorService _taskPool = Executors.newCachedThreadPool(); - protected static final long DEFAULT_TIMEOUT = 1000 * 30; + private final ExecutorService _taskPool = Executors.newCachedThreadPool(); + private static final long DEFAULT_TIMEOUT = 1000 * 30; + private ProtocolVersion _protocolVersion = ProtocolVersion.v0_9; // FIXME TGM, shouldn't need this protected AMQConnectionDelegate _delegate; - + // this connection maximum number of prefetched messages private long _maxPrefetch; @@ -259,7 +369,8 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } _failoverPolicy = new FailoverPolicy(connectionURL); - if (_failoverPolicy.getCurrentBrokerDetails().getTransport().equals(BrokerDetails.VM)) + BrokerDetails brokerDetails = _failoverPolicy.getNextBrokerDetails(); + if (brokerDetails.getTransport().equals(BrokerDetails.VM)) { _delegate = new AMQConnectionDelegate_0_8(this); } @@ -272,6 +383,26 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _delegate = new AMQConnectionDelegate_0_10(this); } + final ArrayList exceptions = new ArrayList(); + + class Listener implements ExceptionListener + { + public void onException(JMSException e) + { + exceptions.add(e); + } + } + + try + { + setExceptionListener(new Listener()); + } + catch (JMSException e) + { + // Shouldn't happen + throw new AMQException(null, null, e); + } + if (_logger.isInfoEnabled()) { _logger.info("Connection:" + connectionURL); @@ -288,6 +419,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _clientName = connectionURL.getClientName(); _username = connectionURL.getUsername(); _password = connectionURL.getPassword(); + setVirtualHost(connectionURL.getVirtualHost()); if (connectionURL.getDefaultQueueExchangeName() != null) @@ -311,7 +443,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } - _protocolHandler = new AMQProtocolHandler(this); + _protocolHandler = new AMQProtocolHandler(this); // We are not currently connected _connected = false; @@ -319,11 +451,13 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect Exception lastException = new Exception(); lastException.initCause(new ConnectException()); - while (!_connected && _failoverPolicy.failoverAllowed()) + // TMG FIXME this seems... wrong... + boolean retryAllowed = true; + while (!_connected && retryAllowed ) { try { - makeBrokerConnection(_failoverPolicy.getNextBrokerDetails()); + makeBrokerConnection(brokerDetails); lastException = null; _connected = true; } @@ -346,8 +480,10 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (_logger.isInfoEnabled()) { _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), - e.getCause()); + e.getCause()); } + retryAllowed = _failoverPolicy.failoverAllowed(); + brokerDetails = _failoverPolicy.getNextBrokerDetails(); } } @@ -359,8 +495,31 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (!_connected) { String message = null; + try + { + Thread.sleep(150); + } + catch (InterruptedException e) + { + // Eat it, we've hopefully got all the exceptions if this happened + } + if (exceptions.size() > 0) + { + JMSException e = exceptions.get(0); + int code = -1; + try + { + code = new Integer(e.getErrorCode()).intValue(); + } + catch (NumberFormatException nfe) + { + // Ignore this, we have some error codes and messages swapped around + } - if (lastException != null) + throw new AMQConnectionFailureException(AMQConstant.getConstant(code), + e.getMessage(), e); + } + else if (lastException != null) { if (lastException.getCause() != null) { @@ -397,7 +556,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (e.getCause() != null) { e.initCause(lastException); - } + } } throw e; @@ -406,6 +565,18 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _connectionMetaData = new QpidConnectionMetaData(this); } + protected boolean checkException(Throwable thrown) + { + Throwable cause = thrown.getCause(); + + if (cause == null) + { + cause = thrown; + } + + return ((cause instanceof ConnectException) || (cause instanceof UnresolvedAddressException)); + } + private void getDelegate() throws AMQProtocolException { try @@ -444,20 +615,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _virtualHost = virtualHost; } - protected boolean checkException(Throwable thrown) - { - Throwable cause = thrown.getCause(); - - if (cause == null) - { - cause = thrown; - } - - return ((cause instanceof ConnectException) || (cause instanceof UnresolvedAddressException)); - } - - - public boolean attemptReconnection(String host, int port) { BrokerDetails bd = new AMQBrokerDetails(host, port, _sslConfiguration); @@ -559,8 +716,55 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode, final int prefetchHigh, final int prefetchLow) throws JMSException { + synchronized (_sessionCreationLock) + { + checkNotClosed(); + return _delegate.createSession(transacted, acknowledgeMode, prefetchHigh, prefetchLow); + } + } + + private void createChannelOverWire(int channelId, int prefetchHigh, int prefetchLow, boolean transacted) + throws AMQException, FailoverException + { + + ChannelOpenBody channelOpenBody = getProtocolHandler().getMethodRegistry().createChannelOpenBody(null); + + // TODO: Be aware of possible changes to parameter order as versions change. + + _protocolHandler.syncWrite(channelOpenBody.generateFrame(channelId), ChannelOpenOkBody.class); + + BasicQosBody basicQosBody = getProtocolHandler().getMethodRegistry().createBasicQosBody(0, prefetchHigh, false); + + // todo send low water mark when protocol allows. + // todo Be aware of possible changes to parameter order as versions change. + _protocolHandler.syncWrite(basicQosBody.generateFrame(channelId), BasicQosOkBody.class); + + if (transacted) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Issuing TxSelect for " + channelId); + } - return _delegate.createSession(transacted, acknowledgeMode, prefetchHigh, prefetchLow); + TxSelectBody body = getProtocolHandler().getMethodRegistry().createTxSelectBody(); + + // TODO: Be aware of possible changes to parameter order as versions change. + _protocolHandler.syncWrite(body.generateFrame(channelId), TxSelectOkBody.class); + } + } + + private void reopenChannel(int channelId, int prefetchHigh, int prefetchLow, boolean transacted) + throws AMQException, FailoverException + { + try + { + createChannelOverWire(channelId, prefetchHigh, prefetchLow, transacted); + } + catch (AMQException e) + { + deregisterSession(channelId); + throw new AMQException(null, "Error reopening channel " + channelId + " after failover: " + e, e); + } } public void setFailoverPolicy(FailoverPolicy policy) @@ -659,10 +863,10 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect if (!_started) { _started = true; - final Iterator it = _sessions.entrySet().iterator(); + final Iterator it = _sessions.values().iterator(); while (it.hasNext()) { - final AMQSession s = (AMQSession) ((Map.Entry) it.next()).getValue(); + final AMQSession s = (AMQSession) (it.next()); try { s.start(); @@ -673,6 +877,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } } + } } @@ -704,49 +909,89 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect public void close(long timeout) throws JMSException { - synchronized (getFailoverMutex()) + close(new ArrayList(_sessions.values()),timeout); + } + + public void close(List sessions, long timeout) throws JMSException + { + synchronized(_sessionCreationLock) { - if (!_closed.getAndSet(true)) + if(!sessions.isEmpty()) { - try + AMQSession session = sessions.remove(0); + synchronized(session.getMessageDeliveryLock()) { - long startCloseTime = System.currentTimeMillis(); - - _taskPool.shutdown(); - closeAllSessions(null, timeout, startCloseTime); - - if (!_taskPool.isTerminated()) + close(sessions, timeout); + } + } + else + { + if (!_closed.getAndSet(true)) + { + synchronized (getFailoverMutex()) { try { - // adjust timeout - long taskPoolTimeout = adjustTimeout(timeout, startCloseTime); + long startCloseTime = System.currentTimeMillis(); + + closeAllSessions(null, timeout, startCloseTime); + + //This MUST occur after we have successfully closed all Channels/Sessions + _taskPool.shutdown(); - _taskPool.awaitTermination(taskPoolTimeout, TimeUnit.MILLISECONDS); + if (!_taskPool.isTerminated()) + { + try + { + // adjust timeout + long taskPoolTimeout = adjustTimeout(timeout, startCloseTime); + + _taskPool.awaitTermination(taskPoolTimeout, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) + { + _logger.info("Interrupted while shutting down connection thread pool."); + } + } + + // adjust timeout + timeout = adjustTimeout(timeout, startCloseTime); + _delegate.closeConneciton(timeout); + + //If the taskpool hasn't shutdown by now then give it shutdownNow. + // This will interupt any running tasks. + if (!_taskPool.isTerminated()) + { + List tasks = _taskPool.shutdownNow(); + for (Runnable r : tasks) + { + _logger.warn("Connection close forced taskpool to prevent execution:" + r); + } + } } - catch (InterruptedException e) + catch (AMQException e) { - _logger.info("Interrupted while shutting down connection thread pool."); + JMSException jmse = new JMSException("Error closing connection: " + e); + jmse.setLinkedException(e); + throw jmse; } } - - // adjust timeout - timeout = adjustTimeout(timeout, startCloseTime); - _delegate.closeConneciton(timeout); - //_protocolHandler.closeConnection(timeout); - - } - catch (AMQException e) - { - JMSException jmse = new JMSException("Error closing connection: " + e); - jmse.setLinkedException(e); - throw jmse; } } } } + private long adjustTimeout(long timeout, long startTime) + { + long now = System.currentTimeMillis(); + timeout -= now - startTime; + if (timeout < 0) + { + timeout = 0; + } + return timeout; + } /** * Marks all sessions and their children as closed without sending any protocol messages. Useful when you need to @@ -811,19 +1056,6 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } } - - private long adjustTimeout(long timeout, long startTime) - { - long now = System.currentTimeMillis(); - timeout -= now - startTime; - if (timeout < 0) - { - timeout = 0; - } - - return timeout; - } - public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector, ServerSessionPool sessionPool, int maxMessages) throws JMSException { @@ -889,11 +1121,11 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect return _maximumFrameSize; } - public Map getSessions() + public ChannelToSessionMap getSessions() { return _sessions; } - + public String getUsername() { return _username; @@ -1017,18 +1249,7 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect /** * Invoked by the AMQProtocolSession when a protocol session exception has occurred. This method sends the exception - * to a JMS exception liste - { - ArrayList sessions = new ArrayList(_sessions.values()); - _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: removeKey? - for (Iterator it = sessions.iterator(); it.hasNext();) - { - AMQSession s = (AMQSession) it.next(); - // _protocolHandler.addSessionByChannel(s.getChannelId(), s); - reopenChannel(s.getChannelId(), s.getDefaultPrefetchHigh(), s.getDefaultPrefetchLow(), s.getTransacted()); - s.resubscribe(); - } - }ner, if configured, and propagates the exception to sessions, which in turn will + * to a JMS exception listener, if configured, and propagates the exception to sessions, which in turn will * propagate to consumers. This allows synchronous consumers to have exceptions thrown to them. * * @param cause the exception @@ -1085,8 +1306,12 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect { _exceptionListener.onException(je); } + else + { + _logger.error("Throwable Received but no listener set: " + cause.getMessage()); + } - if (!(cause instanceof AMQUndeliveredException) && !(cause instanceof AMQAuthenticationException)) + if (hardError(cause)) { try { @@ -1110,6 +1335,16 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect } } + private boolean hardError(Throwable cause) + { + if (cause instanceof AMQException) + { + return ((AMQException)cause).isHardError(); + } + + return true; + } + void registerSession(int channelId, AMQSession session) { _sessions.put(channelId, session); @@ -1120,6 +1355,24 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect _sessions.remove(channelId); } + /** + * For all sessions, and for all consumers in those sessions, resubscribe. This is called during failover handling. + * The caller must hold the failover mutex before calling this method. + */ + public void resubscribeSesssions() throws JMSException, AMQException, FailoverException + { + ArrayList sessions = new ArrayList(_sessions.values()); + _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: removeKey? + for (Iterator it = sessions.iterator(); it.hasNext();) + { + AMQSession s = (AMQSession) it.next(); + // _protocolHandler.addSessionByChannel(s.getChannelId(), s); + reopenChannel(s.getChannelId(), s.getDefaultPrefetchHigh(), s.getDefaultPrefetchLow(), s.getTransacted()); + s.resubscribe(); + s.setFlowControl(true); + } + } + public String toString() { StringBuffer buf = new StringBuffer("AMQConnection:\n"); @@ -1207,6 +1460,22 @@ public class AMQConnection extends Closeable implements Connection, QueueConnect return _sessions.get(channelId); } + public ProtocolVersion getProtocolVersion() + { + return _protocolVersion; + } + + public void setProtocolVersion(ProtocolVersion protocolVersion) + { + _protocolVersion = protocolVersion; + _protocolHandler.getProtocolSession().setProtocolVersion(protocolVersion); + } + + public boolean isFailingOver() + { + return (_protocolHandler.getFailoverLatch() != null); + } + /** * Get the maximum number of messages that this connection can pre-fetch. * diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java index c22fcc3c7a..637cff5b7e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_8.java @@ -178,31 +178,25 @@ public class AMQConnectionDelegate_0_8 implements AMQConnectionDelegate private void createChannelOverWire(int channelId, int prefetchHigh, int prefetchLow, boolean transacted) throws AMQException, FailoverException { - + ChannelOpenBody channelOpenBody = _conn.getProtocolHandler().getMethodRegistry().createChannelOpenBody(null); // TODO: Be aware of possible changes to parameter order as versions change. - - _conn._protocolHandler.syncWrite(ChannelOpenBody.createAMQFrame(channelId, _conn._protocolHandler.getProtocolMajorVersion(), - _conn._protocolHandler.getProtocolMinorVersion(), null), // outOfBand - ChannelOpenOkBody.class); + _conn._protocolHandler.syncWrite(channelOpenBody.generateFrame(channelId), ChannelOpenOkBody.class); // todo send low water mark when protocol allows. // todo Be aware of possible changes to parameter order as versions change. - _conn._protocolHandler.syncWrite(BasicQosBody.createAMQFrame(channelId, _conn._protocolHandler.getProtocolMajorVersion(), - _conn._protocolHandler.getProtocolMinorVersion(), false, // global - prefetchHigh, // prefetchCount - 0), // prefetchSize - BasicQosOkBody.class); - + BasicQosBody basicQosBody = _conn.getProtocolHandler().getMethodRegistry().createBasicQosBody(0,prefetchHigh,false); + _conn._protocolHandler.syncWrite(basicQosBody.generateFrame(channelId),BasicQosOkBody.class); + if (transacted) { if (_logger.isDebugEnabled()) { _logger.debug("Issuing TxSelect for " + channelId); } - + TxSelectBody body = _conn.getProtocolHandler().getMethodRegistry().createTxSelectBody(); + // TODO: Be aware of possible changes to parameter order as versions change. - _conn._protocolHandler.syncWrite(TxSelectBody.createAMQFrame(channelId, _conn._protocolHandler.getProtocolMajorVersion(), - _conn._protocolHandler.getProtocolMinorVersion()), TxSelectOkBody.class); + _conn._protocolHandler.syncWrite(body.generateFrame(channelId), TxSelectOkBody.class); } } @@ -212,7 +206,7 @@ public class AMQConnectionDelegate_0_8 implements AMQConnectionDelegate */ public void resubscribeSessions() throws JMSException, AMQException, FailoverException { - ArrayList sessions = new ArrayList(_conn._sessions.values()); + ArrayList sessions = new ArrayList(_conn.getSessions().values()); _logger.info(MessageFormat.format("Resubscribing sessions = {0} sessions.size={1}", sessions, sessions.size())); // FIXME: removeKey? for (Iterator it = sessions.iterator(); it.hasNext();) { diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java index 3fcec67fe1..fd8063e99b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java @@ -7,9 +7,9 @@ * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @@ -23,6 +23,7 @@ package org.apache.qpid.client; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Hashtable; +import java.util.UUID; import javax.jms.*; import javax.naming.Context; @@ -259,7 +260,7 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF } catch (UnknownHostException e) { - return null; + return "UnknownHost" + UUID.randomUUID(); } } @@ -352,7 +353,9 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF * @param name * @param ctx * @param env + * * @return AMQConnection,AMQTopic,AMQQueue, or AMQConnectionFactory. + * * @throws Exception */ public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env) throws Exception @@ -408,8 +411,9 @@ public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionF public Reference getReference() throws NamingException { - return new Reference(AMQConnectionFactory.class.getName(), - new StringRefAddr(AMQConnectionFactory.class.getName(), _connectionDetails.getURL()), + return new Reference( + AMQConnectionFactory.class.getName(), + new StringRefAddr(AMQConnectionFactory.class.getName(), _connectionDetails.getURL()), AMQConnectionFactory.class.getName(), null); // factory location } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java index 770fab7a81..02959aff3b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java @@ -27,6 +27,7 @@ import java.util.Map; import org.apache.qpid.client.url.URLParser; import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ProtocolVersion; import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.jms.ConnectionURL; import org.apache.qpid.url.URLHelper; diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java b/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java index e02b3d6643..52080112c9 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java @@ -56,7 +56,9 @@ public abstract class AMQDestination implements Destination, Referenceable private String _url; private AMQShortString _urlAsShortString; - private boolean _validated; + private boolean _checkedForQueueBinding; + + private boolean _exchangeExistsChecked; private byte[] _byteEncoding; private static final int IS_DURABLE_MASK = 0x1; @@ -234,14 +236,25 @@ public abstract class AMQDestination implements Destination, Referenceable } - public boolean isValidated() + public boolean isCheckedForQueueBinding() + { + return _checkedForQueueBinding; + } + + public void setCheckedForQueueBinding(boolean checkedForQueueBinding) + { + _checkedForQueueBinding = checkedForQueueBinding; + } + + + public boolean isExchangeExistsChecked() { - return _validated; + return _exchangeExistsChecked; } - public void setValidated(boolean validated) + public void setExchangeExistsChecked(final boolean exchangeExistsChecked) { - _validated = validated; + _exchangeExistsChecked = exchangeExistsChecked; } public String toURL() diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java b/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java index b27dfc6dcb..b9e9a33cd6 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java @@ -49,6 +49,6 @@ public class AMQHeadersExchange extends AMQDestination //Not sure what the best approach is here, probably to treat this like a topic //and allow server to generate names. As it is AMQ specific it doesn't need to //fit the JMS API expectations so this is not as yet critical. - return false; + return getAMQQueueName() == null; } } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java b/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java index f56477c0fc..08fd49286b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java @@ -22,6 +22,7 @@ package org.apache.qpid.client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.qpid.AMQException; import javax.jms.IllegalStateException; import javax.jms.JMSException; @@ -50,7 +51,9 @@ public class AMQQueueBrowser implements QueueBrowser _messageSelector = ((messageSelector == null) || (messageSelector.trim().length() == 0)) ? null : messageSelector; // Create Consumer to verify message selector. BasicMessageConsumer consumer = - (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false); + (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false); + // Close this consumer as we are not looking to consume only to establish that, at least for now, + // the QB can be created consumer.close(); } @@ -88,36 +91,37 @@ public class AMQQueueBrowser implements QueueBrowser checkState(); final BasicMessageConsumer consumer = (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false); + _consumers.add(consumer); return new Enumeration() + { + + Message _nextMessage = consumer == null ? null : consumer.receive(1000); + + public boolean hasMoreElements() { - Message _nextMessage = consumer.receive(1000); + _logger.info("QB:hasMoreElements:" + (_nextMessage != null)); + return (_nextMessage != null); + } - public boolean hasMoreElements() + public Object nextElement() + { + Message msg = _nextMessage; + try { - _logger.info("QB:hasMoreElements:" + (_nextMessage != null)); - return (_nextMessage != null); + _logger.info("QB:nextElement about to receive"); + _nextMessage = consumer.receive(1000); + _logger.info("QB:nextElement received:" + _nextMessage); } - - public Object nextElement() + catch (JMSException e) { - Message msg = _nextMessage; - try - { - _logger.info("QB:nextElement about to receive"); - _nextMessage = consumer.receive(1000); - _logger.info("QB:nextElement received:" + _nextMessage); - } - catch (JMSException e) - { - _logger.warn("Exception caught while queue browsing", e); - _nextMessage = null; - } - - return msg; + _logger.warn("Exception caught while queue browsing", e); + _nextMessage = null; } - }; + return msg; + } + }; } public void close() throws JMSException diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java index ee55743d0e..404c0cd381 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQSession.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession.java @@ -20,14 +20,15 @@ */ package org.apache.qpid.client; - import java.io.Serializable; import java.net.URISyntaxException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -57,7 +58,9 @@ import javax.jms.Topic; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.jms.TopicSubscriber; +import javax.jms.TransactionRolledBackException; +import org.apache.qpid.AMQDisconnectedException; import org.apache.qpid.AMQException; import org.apache.qpid.AMQInvalidArgumentException; import org.apache.qpid.AMQInvalidRoutingKeyException; @@ -77,11 +80,12 @@ import org.apache.qpid.client.message.ReturnMessage; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.util.FlowControllingBlockingQueue; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; import org.apache.qpid.jms.Session; import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQMethodEvent; import org.apache.qpid.url.AMQBindingURL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,6 +110,86 @@ import org.slf4j.LoggerFactory; */ public abstract class AMQSession extends Closeable implements Session, QueueSession, TopicSession { + public static final class IdToConsumerMap + { + private final BasicMessageConsumer[] _fastAccessConsumers = new BasicMessageConsumer[16]; + private final ConcurrentHashMap _slowAccessConsumers = new ConcurrentHashMap(); + + + public BasicMessageConsumer get(int id) + { + if((id & 0xFFFFFFF0) == 0) + { + return _fastAccessConsumers[id]; + } + else + { + return _slowAccessConsumers.get(id); + } + } + + public BasicMessageConsumer put(int id, BasicMessageConsumer consumer) + { + BasicMessageConsumer oldVal; + if((id & 0xFFFFFFF0) == 0) + { + oldVal = _fastAccessConsumers[id]; + _fastAccessConsumers[id] = consumer; + } + else + { + oldVal = _slowAccessConsumers.put(id, consumer); + } + + return consumer; + + } + + + public BasicMessageConsumer remove(int id) + { + BasicMessageConsumer consumer; + if((id & 0xFFFFFFF0) == 0) + { + consumer = _fastAccessConsumers[id]; + _fastAccessConsumers[id] = null; + } + else + { + consumer = _slowAccessConsumers.remove(id); + } + + return consumer; + + } + + public Collection values() + { + ArrayList values = new ArrayList(); + + for(int i = 0; i < 16; i++) + { + if(_fastAccessConsumers[i] != null) + { + values.add(_fastAccessConsumers[i]); + } + } + values.addAll(_slowAccessConsumers.values()); + + return values; + } + + + public void clear() + { + _slowAccessConsumers.clear(); + for(int i = 0; i<16; i++) + { + _fastAccessConsumers[i] = null; + } + } + } + /** Used for debugging. */ private static final Logger _logger = LoggerFactory.getLogger(AMQSession.class); @@ -155,7 +239,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess protected boolean _transacted; /** Holds the sessions acknowledgement mode. */ - protected int _acknowledgeMode; + protected final int _acknowledgeMode; /** Holds this session unique identifier, used to distinguish it from other sessions. */ protected int _channelId; @@ -231,8 +315,16 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess * Maps from identifying tags to message consumers, in order to pass dispatch incoming messages to the right * consumer. */ - protected Map _consumers = - new ConcurrentHashMap(); + protected final IdToConsumerMap _consumers = new IdToConsumerMap(); + + //Map _consumers = + //new ConcurrentHashMap(); + + /** + * Contains a list of consumers which have been removed but which might still have + * messages to acknowledge, eg in client ack or transacted modes + */ + private CopyOnWriteArrayList _removedConsumers = new CopyOnWriteArrayList(); /** Provides a count of consumers on destinations, in order to be able to know if a destination has consumers. */ private ConcurrentHashMap _destinationConsumerCount = @@ -284,6 +376,32 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess protected final boolean _strictAMQPFATAL; private final Object _messageDeliveryLock = new Object(); + /** Session state : used to detect if commit is a) required b) allowed , i.e. does the tx span failover. */ + private boolean _dirty; + /** Has failover occured on this session */ + private boolean _failedOver; + + + + private static final class FlowControlIndicator + { + private volatile boolean _flowControl = true; + + public synchronized void setFlowControl(boolean flowControl) + { + _flowControl= flowControl; + notify(); + } + + public boolean getFlowControl() + { + return _flowControl; + } + } + + /** Flow control */ + private FlowControlIndicator _flowControl = new FlowControlIndicator(); + /** * Creates a new session on a connection. * @@ -330,24 +448,20 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { public void aboveThreshold(int currentValue) { - if (_acknowledgeMode == NO_ACKNOWLEDGE) - { _logger.debug( "Above threshold(" + _defaultPrefetchHighMark + ") so suspending channel. Current value is " + currentValue); new Thread(new SuspenderRunner(true)).start(); - } + } public void underThreshold(int currentValue) { - if (_acknowledgeMode == NO_ACKNOWLEDGE) - { _logger.debug( "Below threshold(" + _defaultPrefetchLowMark + ") so unsuspending channel. Current value is " + currentValue); new Thread(new SuspenderRunner(false)).start(); - } + } }); } @@ -357,7 +471,22 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } - + /** + * Creates a new session on a connection with the default message factory factory. + * + * @param con The connection on which to create the session. + * @param channelId The unique identifier for the session. + * @param transacted Indicates whether or not the session is transactional. + * @param acknowledgeMode The acknoledgement mode for the session. + * @param defaultPrefetchHigh The maximum number of messages to prefetched before suspending the session. + * @param defaultPrefetchLow The number of prefetched messages at which to resume the session. + */ + AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetchHigh, + int defaultPrefetchLow) + { + this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetchHigh, + defaultPrefetchLow); + } // ===== JMS Session methods. @@ -371,6 +500,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess close(-1); } + public BytesMessage createBytesMessage() throws JMSException + { + checkNotClosed(); + return new JMSBytesMessage(); + } + /** * Acknowledges all unacknowledged messages on the session, for all message consumers on the session. * @@ -381,6 +516,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess if (isClosed()) { throw new IllegalStateException("Session is already closed"); + } + else if (hasFailedOver()) + { + throw new IllegalStateException("has failed over"); } while (true) @@ -405,6 +544,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public abstract void acknowledgeMessage(long deliveryTag, boolean multiple); + public MethodRegistry getMethodRegistry() + { + MethodRegistry methodRegistry = getProtocolHandler().getMethodRegistry(); + return methodRegistry; + } + /** * Binds the named queue, with the specified routing key, to the named exchange. * @@ -471,30 +616,26 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { if (_logger.isInfoEnabled()) { - _logger.info("Closing session: " + this );//+ ":" - // + Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 6)); + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + _logger.info("Closing session: " + this); // + ":" + // + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); } - if( _dispatcher != null ) - { - _dispatcher.setConnectionStopped(true); - } - synchronized (_messageDeliveryLock) + // Ensure we only try and close an open session. + if (!_closed.getAndSet(true)) { - - // We must close down all producers and consumers in an orderly fashion. This is the only method - // that can be called from a different thread of control from the one controlling the session. synchronized (_connection.getFailoverMutex()) { - // Ensure we only try and close an open session. - if (!_closed.getAndSet(true)) + // We must close down all producers and consumers in an orderly fashion. This is the only method + // that can be called from a different thread of control from the one controlling the session. + synchronized (_messageDeliveryLock) { // we pass null since this is not an error case closeProducersAndConsumers(null); try { - sendClose(timeout); + sendClose(timeout); } catch (AMQException e) { @@ -527,25 +668,44 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public void closed(Throwable e) throws JMSException { - synchronized (_messageDeliveryLock) + // This method needs to be improved. Throwables only arrive here from the mina : exceptionRecived + // calls through connection.closeAllSessions which is also called by the public connection.close() + // with a null cause + // When we are closing the Session due to a protocol session error we simply create a new AMQException + // with the correct error code and text this is cleary WRONG as the instanceof check below will fail. + // We need to determin here if the connection should be + + if (e instanceof AMQDisconnectedException) + { + if (_dispatcher != null) + { + // Failover failed and ain't coming back. Knife the dispatcher. + _dispatcher.interrupt(); + } + } + + if (!_closed.getAndSet(true)) { synchronized (_connection.getFailoverMutex()) { - // An AMQException has an error code and message already and will be passed in when closure occurs as a - // result of a channel close request - _closed.set(true); - AMQException amqe; - if (e instanceof AMQException) - { - amqe = (AMQException) e; - } - else + synchronized (_messageDeliveryLock) { - amqe = new AMQException(null, "Closing session forcibly", e); - } + // An AMQException has an error code and message already and will be passed in when closure occurs as a + // result of a channel close request + AMQException amqe; + if (e instanceof AMQException) + { + amqe = (AMQException) e; + } + else + { + amqe = new AMQException("Closing session forcibly", e); + } + - _connection.deregisterSession(_channelId); - closeProducersAndConsumers(amqe); + _connection.deregisterSession(_channelId); + closeProducersAndConsumers(amqe); + } } } } @@ -565,10 +725,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public void commit() throws JMSException { - checkTransacted(); + checkTransacted(); try { + + // TGM FIXME: what about failover? // Acknowledge all delivered messages while (true) { @@ -580,7 +742,6 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess acknowledgeMessage(tag, false); } - // Commits outstanding messages and acknowledgments sendCommit(); } @@ -600,16 +761,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { // Remove the consumer from the map - BasicMessageConsumer consumer = (BasicMessageConsumer) _consumers.get(consumerTag); + BasicMessageConsumer consumer = _consumers.get(consumerTag.toIntValue()); if (consumer != null) { - // fixme this isn't right.. needs to check if _queue contains data for this consumer - if (consumer.isAutoClose()) // && _queue.isEmpty()) - { - consumer.closeWhenNoMessages(true); - } - - if (!consumer.isNoConsume()) + if (!consumer.isNoConsume()) // Normal Consumer { // Clean the Maps up first // Flush any pending messages for this consumerTag @@ -620,13 +775,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess else { _logger.info("Dispatcher is null so created stopped dispatcher"); - - startDistpatcherIfNecessary(true); + startDispatcherIfNecessary(true); } _dispatcher.rejectPending(consumer); } - else + else // Queue Browser { // Just close the consumer // fixme the CancelOK is being processed before the arriving messages.. @@ -634,13 +788,24 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // has yet to receive before the close comes in. // consumer.markClosed(); + + + + if (consumer.isAutoClose()) + { + // There is a small window where the message is between the two queues in the dispatcher. + if (consumer.isClosed()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing consumer:" + consumer.debugIdentity()); + } + + deregisterConsumer(consumer); + } + } } } - else - { - _logger.warn("Unable to confirm cancellation of consumer (" + consumerTag + "). Not found in consumer map."); - } - } public QueueBrowser createBrowser(Queue queue) throws JMSException @@ -675,21 +840,11 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess messageSelector, null, true, true); } - public BytesMessage createBytesMessage() throws JMSException - { - synchronized (_connection.getFailoverMutex()) - { - checkNotClosed(); - - return new JMSBytesMessage(); - } - } - public MessageConsumer createConsumer(Destination destination) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, false, null, null, + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, (destination instanceof Topic), null, null, false, false); } @@ -701,11 +856,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess false, false); } + public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, false, + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, false, (destination instanceof Topic), messageSelector, null, false, false); } @@ -714,16 +870,27 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { checkValidDestination(destination); - return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, false, + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, (destination instanceof Topic), messageSelector, null, false, false); } + + public MessageConsumer createExclusiveConsumer(Destination destination, String messageSelector, boolean noLocal) + throws JMSException + { + checkValidDestination(destination); + + return createConsumerImpl(destination, _defaultPrefetchHighMark, _defaultPrefetchLowMark, noLocal, true, + messageSelector, null, false, false); + } + + public MessageConsumer createConsumer(Destination destination, int prefetch, boolean noLocal, boolean exclusive, String selector) throws JMSException { checkValidDestination(destination); - return createConsumerImpl(destination, prefetch, prefetch, noLocal, exclusive, selector, null, false, false); + return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, null, false, false); } public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal, @@ -739,7 +906,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { checkValidDestination(destination); - return createConsumerImpl(destination, prefetch, prefetch, noLocal, exclusive, selector, rawSelector, false, false); + return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, rawSelector, false, false); } public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal, @@ -770,12 +937,8 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess public MapMessage createMapMessage() throws JMSException { - synchronized (_connection.getFailoverMutex()) - { - checkNotClosed(); - - return new JMSMapMessage(); - } + checkNotClosed(); + return new JMSMapMessage(); } public javax.jms.Message createMessage() throws JMSException @@ -785,12 +948,8 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess public ObjectMessage createObjectMessage() throws JMSException { - synchronized (_connection.getFailoverMutex()) - { - checkNotClosed(); - - return (ObjectMessage) new JMSObjectMessage(); - } + checkNotClosed(); + return (ObjectMessage) new JMSObjectMessage(); } public ObjectMessage createObjectMessage(Serializable object) throws JMSException @@ -827,7 +986,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { checkNotClosed(); - return new TopicPublisherAdapter((BasicMessageProducer) createProducer(topic), topic); + return new TopicPublisherAdapter((BasicMessageProducer) createProducer(topic,false,false), topic); } public Queue createQueue(String queueName) throws JMSException @@ -874,7 +1033,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { public Object execute() throws AMQException, FailoverException { - sendCreateQueue(name, autoDelete,durable,exclusive); + sendCreateQueue(name, autoDelete, durable, exclusive); return null; } }, _connection).execute(); @@ -989,9 +1148,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess AMQTopic dest = checkValidTopic(topic); // AMQTopic dest = new AMQTopic(topic.getTopicName()); - return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); + return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createExclusiveConsumer(dest)); } + /** * Creates a non-durable subscriber with a message selector * @@ -1009,7 +1169,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess AMQTopic dest = checkValidTopic(topic); // AMQTopic dest = new AMQTopic(topic.getTopicName()); - return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest, messageSelector, noLocal)); + return new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createExclusiveConsumer(dest, messageSelector, noLocal)); } public abstract TemporaryQueue createTemporaryQueue() throws JMSException; @@ -1267,14 +1427,14 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } - public abstract void sendRecover() throws AMQException, FailoverException; + abstract void sendRecover() throws AMQException, FailoverException; public void rejectMessage(UnprocessedMessage message, boolean requeue) { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Rejecting Unacked message:" + message.getDeliveryTag()); + _logger.debug("Rejecting Unacked message:" + message.getDeliveryTag()); } rejectMessage(message.getDeliveryTag(), requeue); @@ -1282,9 +1442,9 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess public void rejectMessage(AbstractJMSMessage message, boolean requeue) { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Rejecting Abstract message:" + message.getDeliveryTag()); + _logger.debug("Rejecting Abstract message:" + message.getDeliveryTag()); } rejectMessage(message.getDeliveryTag(), requeue); @@ -1325,6 +1485,8 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess sendRollback(); + markClean(); + if (!isSuspended) { suspendChannel(false); @@ -1460,6 +1622,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess AMQDestination amqd = (AMQDestination) destination; + final AMQProtocolHandler protocolHandler = getProtocolHandler(); // TODO: Define selectors in AMQP // TODO: construct the rawSelector from the selector string if rawSelector == null final FieldTable ft = FieldTableFactory.newFieldTable(); @@ -1499,11 +1662,6 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess { JMSException ex = new JMSException("Error registering consumer: " + e); - if (_logger.isDebugEnabled()) - { - e.printStackTrace(); - } - ex.setLinkedException(e); throw ex; } @@ -1531,7 +1689,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ void deregisterConsumer(BasicMessageConsumer consumer) { - if (_consumers.remove(consumer.getConsumerTag()) != null) + if (_consumers.remove(consumer.getConsumerTag().toIntValue()) != null) { String subscriptionName = _reverseSubscriptionMap.remove(consumer); if (subscriptionName != null) @@ -1547,6 +1705,13 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess _destinationConsumerCount.remove(dest); } } + + // Consumers that are closed in a transaction must be stored + // so that messages they have received can be acknowledged on commit + if (_transacted) + { + _removedConsumers.add(consumer); + } } } @@ -1582,8 +1747,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ public abstract boolean isQueueBound(final AMQShortString exchangeName, final AMQShortString queueName, final AMQShortString routingKey) throws JMSException; - - + public abstract boolean isQueueBound(final AMQDestination destination) throws JMSException; /** @@ -1605,6 +1769,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess */ void resubscribe() throws AMQException { + _failedOver = true; resubscribeProducers(); resubscribeConsumers(); } @@ -1639,13 +1804,20 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // If the event dispatcher is not running then start it too. if (hasMessageListeners()) { - startDistpatcherIfNecessary(); + startDispatcherIfNecessary(); } } - synchronized void startDistpatcherIfNecessary() + void startDispatcherIfNecessary() { + //If we are the dispatcher then we don't need to check we are started + if (Thread.currentThread() == _dispatcher) + { + return; + } + // If IMMEDIATE_PREFETCH is not set then we need to start fetching + // This is final per session so will be multi-thread safe. if (!_immediatePrefetch) { // We do this now if this is the first call on a started connection @@ -1662,10 +1834,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } - startDistpatcherIfNecessary(false); + startDispatcherIfNecessary(false); } - synchronized void startDistpatcherIfNecessary(boolean initiallyStopped) + synchronized void startDispatcherIfNecessary(boolean initiallyStopped) { if (_dispatcher == null) { @@ -1816,7 +1988,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } else { - con.close(); + con.close(false); } } // at this point the _consumers map will be empty @@ -1891,20 +2063,22 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess private void consumeFromQueue(BasicMessageConsumer consumer, AMQShortString queueName, AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector) throws AMQException, FailoverException { - //need to generate a consumer tag on the client so we can exploit the nowait flag - AMQShortString tag = new AMQShortString(Integer.toString(_nextTag++)); + int tagId = _nextTag++; + // need to generate a consumer tag on the client so we can exploit the nowait flag + AMQShortString tag = new AMQShortString(Integer.toString(tagId)); + consumer.setConsumerTag(tag); // we must register the consumer in the map before we actually start listening - _consumers.put(tag, consumer); + _consumers.put(tagId, consumer); try { - sendConsume(consumer,queueName,protocolHandler,nowait,messageSelector,tag); + sendConsume(consumer, queueName, protocolHandler, nowait, messageSelector, tag); } catch (AMQException e) { // clean-up the map in the event of an error - _consumers.remove(tag); + _consumers.remove(tagId); throw e; } } @@ -1945,6 +2119,34 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler, nowait); } + + /** + * Returns the number of messages currently queued for the given destination. + * + *

Note that this operation automatically retries in the event of fail-over. + * + * @param amqd The destination to be checked + * + * @return the number of queued messages. + * + * @throws AMQException If the queue cannot be declared for any reason. + */ + public long getQueueDepth(final AMQDestination amqd) + throws AMQException + { + return new FailoverNoopSupport( + new FailoverProtectedOperation() + { + public Long execute() throws AMQException, FailoverException + { + return requestQueueDepth(amqd); + } + }, _connection).execute(); + + } + + abstract Long requestQueueDepth(AMQDestination amqd) throws AMQException, FailoverException; + /** * Declares the named exchange and type of exchange. * @@ -1960,7 +2162,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess * @todo Be aware of possible changes to parameter order as versions change. */ private void declareExchange(final AMQShortString name, final AMQShortString type, - final AMQProtocolHandler protocolHandler, final boolean nowait) throws AMQException + final AMQProtocolHandler protocolHandler, final boolean nowait) throws AMQException { new FailoverNoopSupport(new FailoverProtectedOperation() { @@ -2013,7 +2215,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess amqd.setQueueName(protocolHandler.generateQueueName()); } - sendQueueDeclare(amqd,protocolHandler); + sendQueueDeclare(amqd, protocolHandler); return amqd.getAMQQueueName(); } @@ -2064,12 +2266,12 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess return _connection.getProtocolHandler(); } - protected byte getProtocolMajorVersion() + public byte getProtocolMajorVersion() { return getProtocolHandler().getProtocolMajorVersion(); } - protected byte getProtocolMinorVersion() + public byte getProtocolMinorVersion() { return getProtocolHandler().getProtocolMinorVersion(); } @@ -2227,7 +2429,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess if (_logger.isDebugEnabled()) { _logger.debug("Removing message(" + System.identityHashCode(message) + ") from _queue DT:" - + message.getDeliveryTag()); + + message.getDeliveryTag()); } messages.remove(); @@ -2250,6 +2452,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess for (Iterator it = consumers.iterator(); it.hasNext();) { BasicMessageConsumer consumer = (BasicMessageConsumer) it.next(); + consumer.failedOver(); registerConsumer(consumer, true); } } @@ -2276,11 +2479,10 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess // Bounced message is processed here, away from the mina thread AbstractJMSMessage bouncedMessage = _messageFactoryRegistry.createMessage(0, false, msg.getExchange(), - msg.getExchange(), msg.getContentHeader(), msg.getBodies()); - - AMQConstant errorCode = AMQConstant.getConstant(msg.getReplyCode()); - AMQShortString reason = msg.getReplyText(); - _logger.debug("Message returned with error code " + errorCode + " (" + reason + ")"); + msg.getRoutingKey(), msg.getContentHeader(), msg.getBodies()); + AMQConstant errorCode = AMQConstant.getConstant(msg.getReplyCode()); + AMQShortString reason = msg.getReplyText(); + _logger.debug("Message returned with error code " + errorCode + " (" + reason + ")"); // @TODO should this be moved to an exception handler of sorts. Somewhere errors are converted to correct execeptions. if (errorCode == AMQConstant.NO_CONSUMERS) @@ -2318,7 +2520,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess * * @todo Be aware of possible changes to parameter order as versions change. */ - protected void suspendChannel(boolean suspend) throws AMQException // , FailoverException + protected void suspendChannel(boolean suspend) throws AMQException // , FailoverException { synchronized (_suspensionLock) { @@ -2339,6 +2541,13 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } } + public abstract void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException; + + Object getMessageDeliveryLock() + { + return _messageDeliveryLock; + } + /** * Indicates whether this session consumers pre-fetche messages * @@ -2350,7 +2559,62 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } - public abstract void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException; + /** Signifies that the session has pending sends to commit. */ + public void markDirty() + { + _dirty = true; + } + + /** Signifies that the session has no pending sends to commit. */ + public void markClean() + { + _dirty = false; + _failedOver = false; + } + + /** + * Check to see if failover has occured since the last call to markClean(commit or rollback). + * + * @return boolean true if failover has occured. + */ + public boolean hasFailedOver() + { + return _failedOver; + } + + /** + * Check to see if any message have been sent in this transaction and have not been commited. + * + * @return boolean true if a message has been sent but not commited + */ + public boolean isDirty() + { + return _dirty; + } + + public void setTicket(int ticket) + { + _ticket = ticket; + } + + public void setFlowControl(final boolean active) + { + _flowControl.setFlowControl(active); + } + + + public void checkFlowControl() throws InterruptedException + { + synchronized(_flowControl) + { + while(!_flowControl.getFlowControl()) + { + _flowControl.wait(); + } + } + + } + /** Responsible for decoding a message fragment and passing it to the appropriate message consumer. */ class Dispatcher extends Thread @@ -2361,6 +2625,7 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess private final Object _lock = new Object(); private final AtomicLong _rollbackMark = new AtomicLong(-1); + private String dispatcherID = "" + System.identityHashCode(this); public Dispatcher() { @@ -2392,10 +2657,11 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } // Reject messages on pre-receive queue - consumer.rollback(); + consumer.rollbackPendingMessages(); // Reject messages on pre-dispatch queue rejectMessagesForConsumerTag(consumer.getConsumerTag(), true); + //Let the dispatcher deal with this when it gets to them. // closeConsumer consumer.markClosed(); @@ -2436,6 +2702,13 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } + for (int i = 0; i < _removedConsumers.size(); i++) + { + // Sends acknowledgement to server + _removedConsumers.get(i).rollback(); + _removedConsumers.remove(i); + } + setConnectionStopped(isStopped); } @@ -2484,9 +2757,16 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess } else { - synchronized (_messageDeliveryLock) + if (message.getDeliveryTag() <= _rollbackMark.get()) { - dispatchMessage(message); + rejectMessage(message, true); + } + else + { + synchronized (_messageDeliveryLock) + { + dispatchMessage(message); + } } } @@ -2535,38 +2815,38 @@ public abstract class AMQSession extends Closeable implements Session, QueueSess //This if block is not needed anymore as bounce messages are handled separately //if (message.getDeliverBody() != null) //{ - final BasicMessageConsumer consumer = - (BasicMessageConsumer) _consumers.get(new AMQShortString(message.getConsumerTag())); + final BasicMessageConsumer consumer = + _consumers.get(message.getConsumerTag().toIntValue()); - if ((consumer == null) || consumer.isClosed()) + if ((consumer == null) || consumer.isClosed()) + { + if (_dispatcherLogger.isInfoEnabled()) { - if (_dispatcherLogger.isInfoEnabled()) + if (consumer == null) { - if (consumer == null) - { - _dispatcherLogger.info("Received a message(" + System.identityHashCode(message) + ")" + "[" - + message.getDeliveryTag() + "] from queue " - + message.getConsumerTag() + " )without a handler - rejecting(requeue)..."); - } - else - { - _dispatcherLogger.info("Received a message(" + System.identityHashCode(message) + ")" + "[" - + message.getDeliveryTag() + "] from queue " + " consumer(" - + consumer.debugIdentity() + ") is closed rejecting(requeue)..."); - } + _dispatcherLogger.info("Dispatcher(" + dispatcherID + ")Received a message(" + System.identityHashCode(message) + ")" + "[" + + message.getDeliveryTag() + "] from queue " + + message.getConsumerTag() + " )without a handler - rejecting(requeue)..."); } - // Don't reject if we're already closing - if (!_closed.get()) + else { - rejectMessage(message, true); + _dispatcherLogger.info("Received a message(" + System.identityHashCode(message) + ")" + "[" + + message.getDeliveryTag() + "] from queue " + " consumer(" + + message.getConsumerTag() + ") is closed rejecting(requeue)..."); } } - else + // Don't reject if we're already closing + if (!_closed.get()) { - consumer.notifyMessage(message, _channelId); + rejectMessage(message, true); } } - //} + else + { + consumer.notifyMessage(message); + } + + } } /*public void requestAccess(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write, diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java b/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java new file mode 100644 index 0000000000..a1b240ed54 --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQSessionDirtyException represents all failures to send data on a transacted session that is + * no longer in a state that the client expects. i.e. failover has occured so previously sent messages + * will not be part of the transaction. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Represent attempt to perform additional sends on a dirty session. + *
+ */ +public class AMQSessionDirtyException extends AMQException +{ + public AMQSessionDirtyException(String msg) + { + super(AMQConstant.RESOURCE_ERROR, msg, null); + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java index d72668bb53..6089c54314 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java @@ -395,16 +395,16 @@ public class AMQSession_0_10 extends AMQSession try { preAcquire = ( ! consumer.isNoConsume() && consumer.getMessageSelector() == null) || !(consumer.getDestination() instanceof AMQQueue); + getQpidSession().messageSubscribe(queueName.toString(), tag.toString(), + getAcknowledgeMode() == NO_ACKNOWLEDGE ? Session.TRANSFER_CONFIRM_MODE_NOT_REQUIRED:Session.TRANSFER_CONFIRM_MODE_REQUIRED, + preAcquire ? Session.TRANSFER_ACQUIRE_MODE_PRE_ACQUIRE : Session.TRANSFER_ACQUIRE_MODE_NO_ACQUIRE, + new MessagePartListenerAdapter((BasicMessageConsumer_0_10) consumer), null, + consumer.isExclusive() ? Option.EXCLUSIVE : Option.NO_OPTION); } catch (JMSException e) { throw new AMQException(AMQConstant.INTERNAL_ERROR, "problem when registering consumer", e); } - getQpidSession().messageSubscribe(queueName.toString(), tag.toString(), - (Boolean.getBoolean("noAck") ?Session.TRANSFER_CONFIRM_MODE_NOT_REQUIRED:Session.TRANSFER_CONFIRM_MODE_REQUIRED), - preAcquire ? Session.TRANSFER_ACQUIRE_MODE_PRE_ACQUIRE : Session.TRANSFER_ACQUIRE_MODE_NO_ACQUIRE, - new MessagePartListenerAdapter((BasicMessageConsumer_0_10) consumer), null, - consumer.isExclusive() ? Option.EXCLUSIVE : Option.NO_OPTION); if (! prefetch()) { @@ -746,4 +746,10 @@ public class AMQSession_0_10 extends AMQSession return subscriber; } + + Long requestQueueDepth(AMQDestination amqd) + { + return getQpidSession().queueQuery(amqd.getQueueName()).get().getMessageCount(); + } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java index dd0fd9c457..740bd5dace 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java @@ -30,8 +30,10 @@ import org.apache.qpid.client.failover.FailoverProtectedOperation; import org.apache.qpid.client.failover.FailoverRetrySupport; import org.apache.qpid.client.message.MessageFactoryRegistry; import org.apache.qpid.client.protocol.AMQProtocolHandler; +import org.apache.qpid.client.state.listener.SpecificMethodFrameListener; import org.apache.qpid.common.AMQPFilterTypes; import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; import org.apache.qpid.jms.Session; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQMethodEvent; @@ -79,11 +81,16 @@ public class AMQSession_0_8 extends AMQSession defaultPrefetchLow); } + private ProtocolVersion getProtocolVersion() + { + return getProtocolHandler().getProtocolVersion(); + } + public void acknowledgeMessage(long deliveryTag, boolean multiple) { - final AMQFrame ackFrame = - BasicAckBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), deliveryTag, - multiple); + BasicAckBody body = getMethodRegistry().createBasicAckBody(deliveryTag, multiple); + + final AMQFrame ackFrame = body.generateFrame(_channelId); if (_logger.isDebugEnabled()) { @@ -96,28 +103,17 @@ public class AMQSession_0_8 extends AMQSession public void sendQueueBind(final AMQShortString queueName, final AMQShortString routingKey, final FieldTable arguments, final AMQShortString exchangeName, final AMQDestination dest) throws AMQException, FailoverException { - AMQFrame queueBind = QueueBindBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), arguments, // arguments - exchangeName, // exchange - false, // nowait - queueName, // queue - routingKey, // routingKey - getTicket()); // ticket - - getProtocolHandler().syncWrite(queueBind, QueueBindOkBody.class); + getProtocolHandler().syncWrite(getProtocolHandler().getMethodRegistry().createQueueBindBody + (getTicket(),queueName,exchangeName,routingKey,false,arguments). + generateFrame(_channelId), QueueBindOkBody.class); } public void sendClose(long timeout) throws AMQException, FailoverException { getProtocolHandler().closeSession(this); - - final AMQFrame frame = ChannelCloseBody.createAMQFrame - (getChannelId(), getProtocolMajorVersion(), getProtocolMinorVersion(), - 0, // classId - 0, // methodId - AMQConstant.REPLY_SUCCESS.getCode(), // replyCode - new AMQShortString("JMS client closing channel")); // replyText - - getProtocolHandler().syncWrite(frame, ChannelCloseOkBody.class, timeout); + getProtocolHandler().syncWrite(getProtocolHandler().getMethodRegistry().createChannelCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), + new AMQShortString("JMS client closing channel"), 0, 0).generateFrame(_channelId), + ChannelCloseOkBody.class, timeout); // When control resumes at this point, a reply will have been received that // indicates the broker has closed the channel successfully. } @@ -126,21 +122,14 @@ public class AMQSession_0_8 extends AMQSession { final AMQProtocolHandler handler = getProtocolHandler(); - handler.syncWrite(TxCommitBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion()), TxCommitOkBody.class); + handler.syncWrite(getProtocolHandler().getMethodRegistry().createTxCommitBody().generateFrame(_channelId), TxCommitOkBody.class); } public void sendCreateQueue(AMQShortString name, final boolean autoDelete, final boolean durable, final boolean exclusive) throws AMQException, FailoverException { - AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), null, // arguments - autoDelete, // autoDelete - durable, // durable - exclusive, // exclusive - false, // nowait - false, // passive - name, // queue - getTicket()); // ticket - + QueueDeclareBody body = getMethodRegistry().createQueueDeclareBody(getTicket(),name,false,durable,exclusive,autoDelete,false,null); + AMQFrame queueDeclare = body.generateFrame(_channelId); getProtocolHandler().syncWrite(queueDeclare, QueueDeclareOkBody.class); } @@ -151,16 +140,29 @@ public class AMQSession_0_8 extends AMQSession if (isStrictAMQP()) { // We can't use the BasicRecoverBody-OK method as it isn't part of the spec. - _connection.getProtocolHandler().writeFrame( - BasicRecoverBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), false)); // requeue + + BasicRecoverBody body = getMethodRegistry().createBasicRecoverBody(false); + _connection.getProtocolHandler().writeFrame(body.generateFrame(_channelId)); _logger.warn("Session Recover cannot be guaranteed with STRICT_AMQP. Messages may arrive out of order."); } else { - - _connection.getProtocolHandler().syncWrite( - BasicRecoverBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), false) // requeue - , BasicRecoverOkBody.class); + // in Qpid the 0-8 spec was hacked to have a recover-ok method... this is bad + // in 0-9 we used the cleaner addition of a new sync recover method with its own ok + if(getProtocolHandler().getProtocolVersion().equals(ProtocolVersion.v8_0)) + { + BasicRecoverBody body = getMethodRegistry().createBasicRecoverBody(false); + _connection.getProtocolHandler().syncWrite(body.generateFrame(_channelId), BasicRecoverOkBody.class); + } + else if(getProtocolVersion().equals(ProtocolVersion.v0_9)) + { + BasicRecoverSyncBody body = ((MethodRegistry_0_9)getMethodRegistry()).createBasicRecoverSyncBody(false); + _connection.getProtocolHandler().syncWrite(body.generateFrame(_channelId), BasicRecoverSyncOkBody.class); + } + else + { + throw new RuntimeException("Unsupported version of the AMQP Protocol: " + getProtocolVersion()); + } } } @@ -189,13 +191,13 @@ public class AMQSession_0_8 extends AMQSession { if (_logger.isDebugEnabled()) { - _logger.debug("Rejecting delivery tag:" + deliveryTag); + _logger.debug("Rejecting delivery tag:" + deliveryTag + ":SessionHC:" + this.hashCode()); } - AMQFrame basicRejectBody = BasicRejectBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), deliveryTag, - requeue); + BasicRejectBody body = getMethodRegistry().createBasicRejectBody(deliveryTag, requeue); + AMQFrame frame = body.generateFrame(_channelId); - _connection.getProtocolHandler().writeFrame(basicRejectBody); + _connection.getProtocolHandler().writeFrame(frame); } } @@ -214,10 +216,8 @@ public class AMQSession_0_8 extends AMQSession { public AMQMethodEvent execute() throws AMQException, FailoverException { - AMQFrame boundFrame = ExchangeBoundBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), - exchangeName, // exchange - queueName, // queue - routingKey); // routingKey + AMQFrame boundFrame = getProtocolHandler().getMethodRegistry().createExchangeBoundBody + (exchangeName, routingKey, queueName).generateFrame(_channelId); return getProtocolHandler().syncWrite(boundFrame, ExchangeBoundOkBody.class); @@ -227,7 +227,7 @@ public class AMQSession_0_8 extends AMQSession // Extract and return the response code from the query. ExchangeBoundOkBody responseBody = (ExchangeBoundOkBody) response.getMethod(); - return (responseBody.replyCode == 0); + return (responseBody.getReplyCode() == 0); } catch (AMQException e) { @@ -238,7 +238,6 @@ public class AMQSession_0_8 extends AMQSession public void sendConsume(BasicMessageConsumer consumer, AMQShortString queueName, AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector, AMQShortString tag) throws AMQException, FailoverException { - FieldTable arguments = FieldTableFactory.newFieldTable(); if ((messageSelector != null) && !messageSelector.equals("")) { @@ -255,18 +254,17 @@ public class AMQSession_0_8 extends AMQSession arguments.put(AMQPFilterTypes.NO_CONSUME.getValue(), Boolean.TRUE); } - consumer.setConsumerTag(tag); - // we must register the consumer in the map before we actually start listening - _consumers.put(tag, consumer); - // TODO: Be aware of possible changes to parameter order as versions change. - AMQFrame jmsConsume = BasicConsumeBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), arguments, // arguments - tag, // consumerTag - consumer.isExclusive(), // exclusive - consumer.getAcknowledgeMode() == Session.NO_ACKNOWLEDGE, // noAck - consumer.isNoLocal(), // noLocal - nowait, // nowait - queueName, // queue - getTicket()); // ticket + BasicConsumeBody body = getMethodRegistry().createBasicConsumeBody(getTicket(), + queueName, + tag, + consumer.isNoLocal(), + consumer.getAcknowledgeMode() == Session.NO_ACKNOWLEDGE, + consumer.isExclusive(), + nowait, + arguments); + + + AMQFrame jmsConsume = body.generateFrame(_channelId); if (nowait) { @@ -281,48 +279,37 @@ public class AMQSession_0_8 extends AMQSession public void sendExchangeDeclare(final AMQShortString name, final AMQShortString type, final AMQProtocolHandler protocolHandler, final boolean nowait) throws AMQException, FailoverException { - AMQFrame exchangeDeclare = ExchangeDeclareBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), null, // arguments - false, // autoDelete - false, // durable - name, // exchange - false, // internal - nowait, // nowait - false, // passive - getTicket(), // ticket - type); // type + ExchangeDeclareBody body = getMethodRegistry().createExchangeDeclareBody(getTicket(),name,type,false,false,false,false,nowait,null); + AMQFrame exchangeDeclare = body.generateFrame(_channelId); protocolHandler.syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class); } public void sendQueueDeclare(final AMQDestination amqd, final AMQProtocolHandler protocolHandler) throws AMQException, FailoverException { - AMQFrame queueDeclare = QueueDeclareBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), null, // arguments - amqd.isAutoDelete(), // autoDelete - amqd.isDurable(), // durable - amqd.isExclusive(), // exclusive - false, // nowait - false, // passive - amqd.getAMQQueueName(), // queue - getTicket()); // ticket + QueueDeclareBody body = getMethodRegistry().createQueueDeclareBody(getTicket(),amqd.getAMQQueueName(),false,amqd.isDurable(),amqd.isExclusive(),amqd.isAutoDelete(),false,null); + + AMQFrame queueDeclare = body.generateFrame(_channelId); protocolHandler.syncWrite(queueDeclare, QueueDeclareOkBody.class); } public void sendQueueDelete(final AMQShortString queueName) throws AMQException, FailoverException { - AMQFrame queueDeleteFrame = QueueDeleteBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), false, // ifEmpty - false, // ifUnused - true, // nowait - queueName, // queue - getTicket()); // ticket + QueueDeleteBody body = getMethodRegistry().createQueueDeleteBody(getTicket(), + queueName, + false, + false, + true); + AMQFrame queueDeleteFrame = body.generateFrame(_channelId); getProtocolHandler().syncWrite(queueDeleteFrame, QueueDeleteOkBody.class); } public void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException { - AMQFrame channelFlowFrame = ChannelFlowBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), !suspend); - + ChannelFlowBody body = getMethodRegistry().createChannelFlowBody(!suspend); + AMQFrame channelFlowFrame = body.generateFrame(_channelId); _connection.getProtocolHandler().syncWrite(channelFlowFrame, ChannelFlowOkBody.class); } @@ -348,8 +335,9 @@ public class AMQSession_0_8 extends AMQSession public void sendRollback() throws AMQException, FailoverException { - _connection.getProtocolHandler().syncWrite(TxRollbackBody.createAMQFrame(_channelId, - getProtocolMajorVersion(), getProtocolMinorVersion()), TxRollbackOkBody.class); + TxRollbackBody body = getMethodRegistry().createTxRollbackBody(); + AMQFrame frame = body.generateFrame(getChannelId()); + getProtocolHandler().syncWrite(frame, TxRollbackOkBody.class); } public TemporaryQueue createTemporaryQueue() throws JMSException @@ -360,68 +348,109 @@ public class AMQSession_0_8 extends AMQSession } public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException - { - - checkNotClosed(); - AMQTopic origTopic = checkValidTopic(topic); - AMQTopic dest = AMQTopic.createDurableTopic(origTopic, name, _connection); - TopicSubscriberAdaptor subscriber = _subscriptions.get(name); - if (subscriber != null) - { - if (subscriber.getTopic().equals(topic)) - { - throw new IllegalStateException("Already subscribed to topic " + topic + " with subscription exchange " - + name); - } - else - { - unsubscribe(name); - } - } - else - { - AMQShortString topicName; - if (topic instanceof AMQTopic) - { - topicName = ((AMQTopic) topic).getRoutingKey(); - } - else - { - topicName = new AMQShortString(topic.getTopicName()); - } - - if (_strictAMQP) - { - if (_strictAMQPFATAL) - { - throw new UnsupportedOperationException("JMS Durable not currently supported by AMQP."); - } - else - { - _logger.warn("Unable to determine if subscription already exists for '" + topicName + "' " - + "for creation durableSubscriber. Requesting queue deletion regardless."); - } - - deleteQueue(dest.getAMQQueueName()); - } - else - { - // if the queue is bound to the exchange but NOT for this topic, then the JMS spec - // says we must trash the subscription. - if (isQueueBound(dest.getExchangeName(), dest.getAMQQueueName()) - && !isQueueBound(dest.getExchangeName(), dest.getAMQQueueName(), topicName)) - { - deleteQueue(dest.getAMQQueueName()); - } - } - } - - subscriber = new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); - - _subscriptions.put(name, subscriber); - _reverseSubscriptionMap.put(subscriber.getMessageConsumer(), name); - - return subscriber; - } + { + + checkNotClosed(); + AMQTopic origTopic = checkValidTopic(topic); + AMQTopic dest = AMQTopic.createDurableTopic(origTopic, name, _connection); + TopicSubscriberAdaptor subscriber = _subscriptions.get(name); + if (subscriber != null) + { + if (subscriber.getTopic().equals(topic)) + { + throw new IllegalStateException("Already subscribed to topic " + topic + " with subscription exchange " + + name); + } + else + { + unsubscribe(name); + } + } + else + { + AMQShortString topicName; + if (topic instanceof AMQTopic) + { + topicName = ((AMQTopic) topic).getRoutingKey(); + } + else + { + topicName = new AMQShortString(topic.getTopicName()); + } + + if (_strictAMQP) + { + if (_strictAMQPFATAL) + { + throw new UnsupportedOperationException("JMS Durable not currently supported by AMQP."); + } + else + { + _logger.warn("Unable to determine if subscription already exists for '" + topicName + "' " + + "for creation durableSubscriber. Requesting queue deletion regardless."); + } + + deleteQueue(dest.getAMQQueueName()); + } + else + { + // if the queue is bound to the exchange but NOT for this topic, then the JMS spec + // says we must trash the subscription. + if (isQueueBound(dest.getExchangeName(), dest.getAMQQueueName()) + && !isQueueBound(dest.getExchangeName(), dest.getAMQQueueName(), topicName)) + { + deleteQueue(dest.getAMQQueueName()); + } + } + } + + subscriber = new TopicSubscriberAdaptor(dest, (BasicMessageConsumer) createConsumer(dest)); + + _subscriptions.put(name, subscriber); + _reverseSubscriptionMap.put(subscriber.getMessageConsumer(), name); + + return subscriber; + } + + class QueueDeclareOkHandler extends SpecificMethodFrameListener + { + + private long _messageCount; + private long _consumerCount; + + public QueueDeclareOkHandler() + { + super(getChannelId(), QueueDeclareOkBody.class); + } + + public boolean processMethod(int channelId, AMQMethodBody frame) //throws AMQException + { + boolean matches = super.processMethod(channelId, frame); + if (matches) + { + QueueDeclareOkBody declareOk = (QueueDeclareOkBody) frame; + _messageCount = declareOk.getMessageCount(); + _consumerCount = declareOk.getConsumerCount(); + } + return matches; + } + + } + + Long requestQueueDepth(AMQDestination amqd) throws AMQException, FailoverException + { + AMQFrame queueDeclare = + getMethodRegistry().createQueueDeclareBody(getTicket(), + amqd.getAMQQueueName(), + true, + amqd.isDurable(), + amqd.isExclusive(), + amqd.isAutoDelete(), + false, + null).generateFrame(_channelId); + QueueDeclareOkHandler okHandler = new QueueDeclareOkHandler(); + getProtocolHandler().writeCommandFrameAndWaitForReply(queueDeclare, okHandler); + return okHandler._messageCount; + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java b/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java index 6c954ec3df..7b5781530b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java @@ -24,6 +24,7 @@ import org.apache.qpid.framing.AMQShortString; import javax.jms.JMSException; import javax.jms.TemporaryTopic; +import java.util.UUID; /** * AMQ implementation of TemporaryTopic. @@ -38,7 +39,7 @@ class AMQTemporaryTopic extends AMQTopic implements TemporaryTopic, TemporaryDes */ public AMQTemporaryTopic(AMQSession session) { - super(session.getTemporaryTopicExchangeName(),new AMQShortString("TempQueue" + Long.toString(System.currentTimeMillis()))); + super(session.getTemporaryTopicExchangeName(),new AMQShortString("tmp_" + UUID.randomUUID())); _session = session; } diff --git a/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java b/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java index fcc36cd3fd..40041afdc6 100644 --- a/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java +++ b/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java @@ -120,8 +120,7 @@ public class AMQTopic extends AMQDestination implements Topic public boolean isNameRequired() { - // Topics always rely on a server generated queue name. - return false; + return !isDurable(); } /** @@ -136,4 +135,17 @@ public class AMQTopic extends AMQDestination implements Topic public void setQueueName(String queueName) { } + + public boolean equals(Object o) + { + return (o instanceof AMQTopic) + && ((AMQTopic)o).getExchangeName().equals(getExchangeName()) + && ((AMQTopic)o).getRoutingKey().equals(getRoutingKey()); + + } + + public int hashCode() + { + return getExchangeName().hashCode() + getRoutingKey().hashCode(); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java index 015a2ccc57..1741903bb8 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java @@ -20,45 +20,47 @@ */ package org.apache.qpid.client; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageListener; - +import org.apache.qpid.AMQException; +import org.apache.qpid.client.failover.FailoverException; import org.apache.qpid.client.message.AbstractJMSMessage; import org.apache.qpid.client.message.MessageFactoryRegistry; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.protocol.AMQProtocolHandler; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.*; import org.apache.qpid.jms.MessageConsumer; import org.apache.qpid.jms.Session; -import org.apache.qpid.AMQException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.TreeSet; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + public abstract class BasicMessageConsumer extends Closeable implements MessageConsumer { private static final Logger _logger = LoggerFactory.getLogger(BasicMessageConsumer.class); - /** - * The connection being used by this consumer - */ - protected AMQConnection _connection; + /** The connection being used by this consumer */ + protected final AMQConnection _connection; - protected String _messageSelector; + protected final String _messageSelector; - private boolean _noLocal; + private final boolean _noLocal; - private AMQDestination _destination; + private final AMQDestination _destination; /** * When true indicates that a blocking receive call is in progress @@ -69,15 +71,11 @@ public abstract class BasicMessageConsumer extends Closeable implements Me */ private final AtomicReference _messageListener = new AtomicReference(); - /** - * The consumer tag allows us to close the consumer by sending a jmsCancel method to the broker - */ + /** The consumer tag allows us to close the consumer by sending a jmsCancel method to the broker */ protected AMQShortString _consumerTag; - /** - * We need to know the channel id when constructing frames - */ - protected int _channelId; + /** We need to know the channel id when constructing frames */ + protected final int _channelId; /** * Used in the blocking receive methods to receive a message from the Session thread.

Or to notify of errors @@ -85,40 +83,40 @@ public abstract class BasicMessageConsumer extends Closeable implements Me */ protected final ArrayBlockingQueue _synchronousQueue; - protected MessageFactoryRegistry _messageFactory; + protected final MessageFactoryRegistry _messageFactory; protected final AMQSession _session; - protected AMQProtocolHandler _protocolHandler; + protected final AMQProtocolHandler _protocolHandler; /** * We need to store the "raw" field table so that we can resubscribe in the event of failover being required */ - private FieldTable _rawSelectorFieldTable; + private final FieldTable _rawSelectorFieldTable; /** * We store the high water prefetch field in order to be able to reuse it when resubscribing in the event of * failover */ - private int _prefetchHigh; + private final int _prefetchHigh; /** * We store the low water prefetch field in order to be able to reuse it when resubscribing in the event of * failover */ - private int _prefetchLow; + private final int _prefetchLow; /** * We store the exclusive field in order to be able to reuse it when resubscribing in the event of failover */ - private boolean _exclusive; + private final boolean _exclusive; /** * The acknowledge mode in force for this consumer. Note that the AMQP protocol allows different ack modes per * consumer whereas JMS defines this at the session level, hence why we associate it with the consumer in our * implementation. */ - private int _acknowledgeMode; + private final int _acknowledgeMode; /** * Number of messages unacknowledged in DUPS_OK_ACKNOWLEDGE mode @@ -131,6 +129,19 @@ public abstract class BasicMessageConsumer extends Closeable implements Me */ private boolean _dups_ok_acknowledge_send; + /** + * List of tags delievered, The last of which which should be acknowledged on commit in transaction mode. + */ + private ConcurrentLinkedQueue _receivedDeliveryTags = new ConcurrentLinkedQueue(); + + /** The last tag that was "multiple" acknowledged on this session (if transacted) */ + private long _lastAcked; + + /** set of tags which have previously been acked; but not part of the multiple ack (transacted mode only) */ + private final SortedSet _previouslyAcked = new TreeSet(); + + private final Object _commitLock = new Object(); + /** * The thread that was used to call receive(). This is important for being able to interrupt that thread if a * receive() is in progress. @@ -148,12 +159,13 @@ public abstract class BasicMessageConsumer extends Closeable implements Me * autoClose denotes that the consumer will automatically cancel itself when there are no more messages to receive * on the queue. This is used for queue browsing. */ - private boolean _autoClose; - private boolean _closeWhenNoMessages; + private final boolean _autoClose; - private boolean _noConsume; + private final boolean _noConsume; private List _closedStack = null; + + protected BasicMessageConsumer(int channelId, AMQConnection connection, AMQDestination destination, String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory, AMQSession session, AMQProtocolHandler protocolHandler, @@ -172,7 +184,7 @@ public abstract class BasicMessageConsumer extends Closeable implements Me _prefetchHigh = prefetchHigh; _prefetchLow = prefetchLow; _exclusive = exclusive; - _acknowledgeMode = acknowledgeMode; + _synchronousQueue = new ArrayBlockingQueue(prefetchHigh, true); _autoClose = autoClose; _noConsume = noConsume; @@ -182,6 +194,10 @@ public abstract class BasicMessageConsumer extends Closeable implements Me { _acknowledgeMode = Session.NO_ACKNOWLEDGE; } + else + { + _acknowledgeMode = acknowledgeMode; + } } public AMQDestination getDestination() @@ -254,14 +270,14 @@ public abstract class BasicMessageConsumer extends Closeable implements Me if (messageListener != null) { - // handle case where connection has already been started, and the dispatcher has alreaded started + //todo: handle case where connection has already been started, and the dispatcher has alreaded started // putting values on the _synchronousQueue synchronized (_session) { _messageListener.set(messageListener); _session.setHasMessageListeners(); - _session.startDistpatcherIfNecessary(); + _session.startDispatcherIfNecessary(); } } } @@ -273,12 +289,32 @@ public abstract class BasicMessageConsumer extends Closeable implements Me { _session.addUnacknowledgedMessage(jmsMsg.getDeliveryTag()); } - + _session.setInRecovery(false); } - private void acquireReceiving() throws JMSException + /** + * @param immediate if true then return immediately if the connection is failing over + * + * @return boolean if the acquisition was successful + * + * @throws JMSException if a listener has already been set or another thread is receiving + * @throws InterruptedException if interrupted + */ + private boolean acquireReceiving(boolean immediate) throws JMSException, InterruptedException { + if (_connection.isFailingOver()) + { + if (immediate) + { + return false; + } + else + { + _connection.blockUntilNotFailingOver(); + } + } + if (!_receiving.compareAndSet(false, true)) { throw new javax.jms.IllegalStateException("Another thread is already receiving."); @@ -290,6 +326,7 @@ public abstract class BasicMessageConsumer extends Closeable implements Me } _receivingThread = Thread.currentThread(); + return true; } private void releaseReceiving() @@ -343,26 +380,30 @@ public abstract class BasicMessageConsumer extends Closeable implements Me checkPreConditions(); - acquireReceiving(); - - _session.startDistpatcherIfNecessary(); - try { - if (closeOnAutoClose()) + acquireReceiving(false); + } + catch (InterruptedException e) + { + _logger.warn("Interrupted acquire: " + e); + if (isClosed()) { return null; } + } - Object o = getMessageFromQueue(l); + _session.startDispatcherIfNecessary(); + try + { + Object o = getMessageFromQueue(l); final AbstractJMSMessage m = returnMessageOrThrow(o); if (m != null) { preApplicationProcessing(m); postDeliver(m); } - return m; } catch (InterruptedException e) @@ -395,35 +436,34 @@ public abstract class BasicMessageConsumer extends Closeable implements Me return o; } - private boolean closeOnAutoClose() throws JMSException - { - if (isAutoClose() && _closeWhenNoMessages && _synchronousQueue.isEmpty()) - { - close(false); - - return true; - } - else - { - return false; - } - } - public Message receiveNoWait() throws JMSException { checkPreConditions(); - acquireReceiving(); - - _session.startDistpatcherIfNecessary(); - try { - if (closeOnAutoClose()) + if (!acquireReceiving(true)) { + //If we couldn't acquire the receiving thread then return null. + // This will occur if failing over. return null; } + } + catch (InterruptedException e) + { + /* + * This seems slightly shoddy but should never actually be executed + * since we told acquireReceiving to return immediately and it shouldn't + * block on anything. + */ + + return null; + } + + _session.startDispatcherIfNecessary(); + try + { Object o = getMessageFromQueue(-1); final AbstractJMSMessage m = returnMessageOrThrow(o); if (m != null) @@ -450,7 +490,7 @@ public abstract class BasicMessageConsumer extends Closeable implements Me * We can get back either a Message or an exception from the queue. This method examines the argument and deals with * it by throwing it (if an exception) or returning it (in any other case). * - * @param o + * @param o the object to return or throw * @return a message only if o is a Message * @throws JMSException if the argument is a throwable. If it is a JMSException it is rethrown as is, but if not a * JMSException is created with the linked exception set appropriately @@ -481,61 +521,69 @@ public abstract class BasicMessageConsumer extends Closeable implements Me public void close(boolean sendClose) throws JMSException { - // synchronized (_closed) - if (_logger.isInfoEnabled()) { _logger.info("Closing consumer:" + debugIdentity()); } - synchronized (_connection.getFailoverMutex()) + if (!_closed.getAndSet(true)) { - if (!_closed.getAndSet(true)) + if (_logger.isDebugEnabled()) { - if (_logger.isTraceEnabled()) - { - if (_closedStack != null) - { - _logger.trace(_consumerTag + " close():" + Arrays.asList(Thread.currentThread().getStackTrace()) - .subList(3, 6)); - _logger.trace(_consumerTag + " previously:" + _closedStack.toString()); - } - else - { - _closedStack = Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 6); - } - } - - if (sendClose) + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (_closedStack != null) { - // TODO: Be aware of possible changes to parameter order as versions change. - sendCancel(); + _logger.debug(_consumerTag + " previously:" + _closedStack.toString()); } else { - // //fixme this probably is not right - // if (!isNoConsume()) - //{ // done in BasicCancelOK Handler but not sending one so just deregister. - // deregisterConsumer(); - //} + _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1); } + } - deregisterConsumer(); - - if (_messageListener != null && _receiving.get() && _receivingThread != null) + if (sendClose) + { + // The Synchronized block only needs to protect network traffic. + synchronized (_connection.getFailoverMutex()) { - if (_logger.isInfoEnabled()) + try { - _logger.info("Interrupting thread: " + _receivingThread); + sendCancel(); } + catch (AMQException e) + { + throw new JMSAMQException("Error closing consumer: " + e, e); + } + catch (FailoverException e) + { + throw new JMSAMQException("FailoverException interrupted basic cancel.", e); + } + } + } + else + { + // //fixme this probably is not right + // if (!isNoConsume()) + { // done in BasicCancelOK Handler but not sending one so just deregister. + deregisterConsumer(); + } + } - _receivingThread.interrupt(); + // This will occur if session.close is called closing all consumers we may be blocked waiting for a receive + // so we need to let it know it is time to close. + if ((_messageListener != null) && _receiving.get()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Interrupting thread: " + _receivingThread); } + + _receivingThread.interrupt(); } } } - public abstract void sendCancel() throws JMSAMQException; + abstract void sendCancel() throws AMQException, FailoverException; /** * Called when you need to invalidate a consumer. Used for example when failover has occurred and the client has @@ -547,13 +595,14 @@ public abstract class BasicMessageConsumer extends Closeable implements Me { _closed.set(true); - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { if (_closedStack != null) { - _logger.trace(_consumerTag + " markClosed():" + Arrays - .asList(Thread.currentThread().getStackTrace()).subList(3, 8)); - _logger.trace(_consumerTag + " previously:" + _closedStack.toString()); + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + _logger.debug(_consumerTag + " markClosed():" + + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); + _logger.debug(_consumerTag + " previously:" + _closedStack.toString()); } else { @@ -570,9 +619,8 @@ public abstract class BasicMessageConsumer extends Closeable implements Me * message listener or a synchronous receive() caller. * * @param messageFrame the raw unprocessed mesage - * @param channelId channel on which this message was sent */ - void notifyMessage(UnprocessedMessage messageFrame, int channelId) + void notifyMessage(UnprocessedMessage messageFrame) { final boolean debug = _logger.isDebugEnabled(); @@ -584,6 +632,7 @@ public abstract class BasicMessageConsumer extends Closeable implements Me try { AbstractJMSMessage jmsMessage = createJMSMessageFromUnprocessedMessage(messageFrame); + if (debug) { _logger.debug("Message is of type: " + jmsMessage.getClass().getName()); @@ -594,11 +643,9 @@ public abstract class BasicMessageConsumer extends Closeable implements Me // if (!_closed.get()) { - jmsMessage.setConsumer(this); - preDeliver(jmsMessage); - notifyMessage(jmsMessage, channelId); + notifyMessage(jmsMessage); } // else // { @@ -624,12 +671,8 @@ public abstract class BasicMessageConsumer extends Closeable implements Me public abstract AbstractJMSMessage createJMSMessageFromUnprocessedMessage(UnprocessedMessage messageFrame) throws Exception; - - /** - * @param jmsMessage this message has already been processed so can't redo preDeliver - * @param channelId - */ - public void notifyMessage(AbstractJMSMessage jmsMessage, int channelId) + /** @param jmsMessage this message has already been processed so can't redo preDeliver */ + public void notifyMessage(AbstractJMSMessage jmsMessage) { try { @@ -700,26 +743,6 @@ public abstract class BasicMessageConsumer extends Closeable implements Me break; case Session.DUPS_OK_ACKNOWLEDGE: - if (++_outstanding >= _prefetchHigh) - { - _dups_ok_acknowledge_send = true; - } - - if (_outstanding <= _prefetchLow) - { - _dups_ok_acknowledge_send = false; - } - - if (_dups_ok_acknowledge_send) - { - if (!_session.isInRecovery()) - { - _session.acknowledgeMessage(msg.getDeliveryTag(), true); - } - } - - break; - case Session.AUTO_ACKNOWLEDGE: // we do not auto ack a message if the application code called recover() if (!_session.isInRecovery()) @@ -728,7 +751,6 @@ public abstract class BasicMessageConsumer extends Closeable implements Me } break; - case Session.SESSION_TRANSACTED: if (isNoConsume()) { @@ -743,22 +765,105 @@ public abstract class BasicMessageConsumer extends Closeable implements Me } } + + /** + * Acknowledge up to last message delivered (if any). Used when commiting. + * + * @return the lastDeliveryTag to acknowledge + */ + Long getLastDelivered() + { + if (!_receivedDeliveryTags.isEmpty()) + { + Long lastDeliveryTag = _receivedDeliveryTags.poll(); + + while (!_receivedDeliveryTags.isEmpty()) + { + lastDeliveryTag = _receivedDeliveryTags.poll(); + } + + assert _receivedDeliveryTags.isEmpty(); + + return lastDeliveryTag; + } + + return null; + } + + /** + * Acknowledge up to last message delivered (if any). Used when commiting. + */ + void acknowledgeDelivered() + { + synchronized(_commitLock) + { + ArrayList tagsToAck = new ArrayList(); + + while (!_receivedDeliveryTags.isEmpty()) + { + tagsToAck.add(_receivedDeliveryTags.poll()); + } + + Collections.sort(tagsToAck); + + long prevAcked = _lastAcked; + long oldAckPoint = -1; + + while(oldAckPoint != prevAcked) + { + oldAckPoint = prevAcked; + + Iterator tagsToAckIterator = tagsToAck.iterator(); + + while(tagsToAckIterator.hasNext() && tagsToAckIterator.next() == prevAcked+1) + { + tagsToAckIterator.remove(); + prevAcked++; + } + + Iterator previousAckIterator = _previouslyAcked.iterator(); + while(previousAckIterator.hasNext() && previousAckIterator.next() == prevAcked+1) + { + previousAckIterator.remove(); + prevAcked++; + } + + } + if(prevAcked != _lastAcked) + { + _session.acknowledgeMessage(prevAcked, true); + _lastAcked = prevAcked; + } + + Iterator tagsToAckIterator = tagsToAck.iterator(); + + while(tagsToAckIterator.hasNext()) + { + Long tag = tagsToAckIterator.next(); + _session.acknowledgeMessage(tag, false); + _previouslyAcked.add(tag); + } + } + } + + void notifyError(Throwable cause) { // synchronized (_closed) { _closed.set(true); - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); if (_closedStack != null) { - _logger.trace(_consumerTag + " notifyError():" + Arrays - .asList(Thread.currentThread().getStackTrace()).subList(3, 8)); - _logger.trace(_consumerTag + " previously" + _closedStack.toString()); + _logger.debug(_consumerTag + " notifyError():" + + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1)); + _logger.debug(_consumerTag + " previously" + _closedStack.toString()); } else { - _closedStack = Arrays.asList(Thread.currentThread().getStackTrace()).subList(3, 8); + _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1); } } } @@ -823,20 +928,13 @@ public abstract class BasicMessageConsumer extends Closeable implements Me return _noConsume; } - public void closeWhenNoMessages(boolean b) + public void rollback() { - _closeWhenNoMessages = b; - - if (_closeWhenNoMessages && _synchronousQueue.isEmpty() && _receiving.get() && (_messageListener != null)) - { - _receivingThread.interrupt(); - } - + rollbackPendingMessages(); } - public void rollback() + public void rollbackPendingMessages() { - // rollback pending messages if (_synchronousQueue.size() > 0) { if (_logger.isDebugEnabled()) @@ -847,6 +945,9 @@ public abstract class BasicMessageConsumer extends Closeable implements Me Iterator iterator = _synchronousQueue.iterator(); + int initialSize = _synchronousQueue.size(); + + boolean removed = false; while (iterator.hasNext()) { @@ -855,22 +956,30 @@ public abstract class BasicMessageConsumer extends Closeable implements Me { _session.rejectMessage(((AbstractJMSMessage) o), true); - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Rejected message:" + ((AbstractJMSMessage) o).getDeliveryTag()); + _logger.debug("Rejected message:" + ((AbstractJMSMessage) o).getDeliveryTag()); } iterator.remove(); + removed = true; } else { - _logger.error("Queue contained a :" + o - .getClass() + " unable to reject as it is not an AbstractJMSMessage. Will be cleared"); + _logger.error("Queue contained a :" + o.getClass() + + " unable to reject as it is not an AbstractJMSMessage. Will be cleared"); iterator.remove(); + removed = true; + } } + + if (removed && (initialSize == _synchronousQueue.size())) + { + _logger.error("Queue had content removed but didn't change in size." + initialSize); } + if (_synchronousQueue.size() != 0) { _logger.warn("Queue was not empty after rejecting all messages Remaining:" + _synchronousQueue.size()); @@ -883,7 +992,7 @@ public abstract class BasicMessageConsumer extends Closeable implements Me public String debugIdentity() { - return String.valueOf(_consumerTag); + return String.valueOf(_consumerTag) + "[" + System.identityHashCode(this) + "]"; } public void clearReceiveQueue() @@ -891,7 +1000,6 @@ public abstract class BasicMessageConsumer extends Closeable implements Me _synchronousQueue.clear(); } - public void start() { // do nothing as this is a 0_10 feature @@ -923,4 +1031,12 @@ public abstract class BasicMessageConsumer extends Closeable implements Me { _session.addBindingKey(this,amqd,routingKey); } + + /** to be called when a failover has occured */ + public void failedOver() + { + clearReceiveQueue(); + // TGM FIXME: think this should just be removed + // clearUnackedMessages(); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java index 4f2d5d8c34..d92899087b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java @@ -117,7 +117,7 @@ public class BasicMessageConsumer_0_10 extends BasicMessageConsumer implements FailoverSup { _log.debug("Failover exception caught during operation: " + e, e); } + catch (IllegalStateException e) + { + if (!(e.getMessage().startsWith("Fail-over interupted no-op failover support"))) + { + throw e; + } + } } } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java new file mode 100644 index 0000000000..a150d1446a --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java @@ -0,0 +1,36 @@ +package org.apache.qpid.client.handler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.framing.*; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.protocol.AMQProtocolSession; +import org.apache.qpid.client.AMQNoConsumersException; +import org.apache.qpid.client.AMQNoRouteException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInvalidRoutingKeyException; +import org.apache.qpid.AMQChannelClosedException; +import org.apache.qpid.protocol.AMQConstant; + +public class AccessRequestOkMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = LoggerFactory.getLogger(AccessRequestOkMethodHandler.class); + + private static AccessRequestOkMethodHandler _handler = new AccessRequestOkMethodHandler(); + + public static AccessRequestOkMethodHandler getInstance() + { + return _handler; + } + + public void methodReceived(AMQStateManager stateManager, AccessRequestOkBody method, int channelId) + throws AMQException + { + _logger.debug("AccessRequestOk method received"); + final AMQProtocolSession session = stateManager.getProtocolSession(); + session.setTicket(method.getTicket(), channelId); + + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java index 8f0ee05b3e..e3e08667d8 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java @@ -25,12 +25,13 @@ import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.AMQMethodBody; import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicCancelOkMethodHandler implements StateAwareMethodListener +public class BasicCancelOkMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(BasicCancelOkMethodHandler.class); @@ -44,16 +45,18 @@ public class BasicCancelOkMethodHandler implements StateAwareMethodListener private BasicCancelOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, BasicCancelOkBody body, int channelId) throws AMQException { - BasicCancelOkBody body = (BasicCancelOkBody) evt.getMethod(); + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) { - _logger.info("New BasicCancelOk method received for consumer:" + body.consumerTag); + _logger.info("New BasicCancelOk method received for consumer:" + body.getConsumerTag()); } - protocolSession.confirmConsumerCancelled(evt.getChannelId(), body.consumerTag); + session.confirmConsumerCancelled(channelId, body.getConsumerTag()); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java index 92ba6fd136..4deaa314ec 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java @@ -21,16 +21,16 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; +import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.framing.BasicDeliverBody; -import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicDeliverMethodHandler implements StateAwareMethodListener +public class BasicDeliverMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(BasicDeliverMethodHandler.class); @@ -41,18 +41,18 @@ public class BasicDeliverMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, BasicDeliverBody body, int channelId) throws AMQException { - BasicDeliverBody deliveryBody = (BasicDeliverBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); final UnprocessedMessage_0_8 msg = new UnprocessedMessage_0_8( - evt.getChannelId(), - deliveryBody.deliveryTag, - deliveryBody.consumerTag.asString(), - deliveryBody.getExchange(), - deliveryBody.getRoutingKey(), - deliveryBody.getRedelivered()); + channelId, + body.getDeliveryTag(), + body.getConsumerTag(), + body.getExchange(), + body.getRoutingKey(), + body.getRedelivered()); _logger.debug("New JmsDeliver method received"); - protocolSession.unprocessedMessageReceived(msg); + session.unprocessedMessageReceived(msg); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java index bb5a56e5bd..682c3ac2c1 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java @@ -22,15 +22,18 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; import org.apache.qpid.client.message.ReturnMessage; +import org.apache.qpid.client.message.UnprocessedMessage; +import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.framing.BasicReturnBody; import org.apache.qpid.protocol.AMQMethodEvent; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasicReturnMethodHandler implements StateAwareMethodListener +public class BasicReturnMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(BasicReturnMethodHandler.class); @@ -41,18 +44,20 @@ public class BasicReturnMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + + public void methodReceived(AMQStateManager stateManager, BasicReturnBody body, int channelId) + throws AMQException { - BasicReturnBody returnBody = (BasicReturnBody)evt.getMethod(); _logger.debug("New JmsBounce method received"); - final ReturnMessage msg = new ReturnMessage(evt.getChannelId(), - returnBody.getExchange(), - returnBody.getRoutingKey(), - returnBody.getReplyText(), - returnBody.getReplyCode() - ); - - protocolSession.unprocessedMessageReceived(msg); + final AMQProtocolSession session = stateManager.getProtocolSession(); + final ReturnMessage msg = new ReturnMessage(channelId, + body.getExchange(), + body.getRoutingKey(), + body.getReplyText(), + body.getReplyCode() + ); + + session.unprocessedMessageReceived(msg); } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java index 9ed3ef7a60..ee4cf14d58 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java @@ -38,7 +38,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelCloseMethodHandler implements StateAwareMethodListener +public class ChannelCloseMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseMethodHandler.class); @@ -49,22 +49,26 @@ public class ChannelCloseMethodHandler implements StateAwareMethodListener return _handler; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, ChannelCloseBody method, int channelId) throws AMQException { _logger.debug("ChannelClose method received"); - ChannelCloseBody method = (ChannelCloseBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); - AMQConstant errorCode = AMQConstant.getConstant(method.replyCode); - AMQShortString reason = method.replyText; + + AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode()); + AMQShortString reason = method.getReplyText(); if (_logger.isDebugEnabled()) { _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason); } - // TODO: Be aware of possible changes to parameter order as versions change. - AMQFrame frame = ChannelCloseOkBody.createAMQFrame(evt.getChannelId(), method.getMajor(), method.getMinor()); - protocolSession.writeFrame(frame); + + + ChannelCloseOkBody body = session.getMethodRegistry().createChannelCloseOkBody(); + AMQFrame frame = body.generateFrame(channelId); + session.writeFrame(frame); + if (errorCode != AMQConstant.REPLY_SUCCESS) { if (_logger.isDebugEnabled()) @@ -100,6 +104,11 @@ public class ChannelCloseMethodHandler implements StateAwareMethodListener } // fixme why is this only done when the close is expected... // should the above forced closes not also cause a close? - protocolSession.channelClosed(evt.getChannelId(), errorCode, String.valueOf(reason)); + // ---------- + // Closing the session only when it is expected allows the errors to be processed + // Calling this here will prevent failover. So we should do this for all exceptions + // that should never cause failover. Such as authentication errors. + + session.channelClosed(channelId, errorCode, String.valueOf(reason)); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java index e1fe2697e5..8d3277d4de 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java @@ -21,6 +21,7 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; @@ -29,7 +30,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelCloseOkMethodHandler implements StateAwareMethodListener +public class ChannelCloseOkMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseOkMethodHandler.class); @@ -40,11 +41,12 @@ public class ChannelCloseOkMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, ChannelCloseOkBody method, int channelId) throws AMQException { - _logger.info("Received channel-close-ok for channel-id " + evt.getChannelId()); + _logger.info("Received channel-close-ok for channel-id " + channelId); + final AMQProtocolSession session = stateManager.getProtocolSession(); // todo this should do the local closure } } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java new file mode 100644 index 0000000000..b47fe751d6 --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java @@ -0,0 +1,54 @@ +package org.apache.qpid.client.handler; + +import org.apache.qpid.framing.ChannelFlowBody; +import org.apache.qpid.client.state.StateAwareMethodListener; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.protocol.AMQProtocolSession; +import org.apache.qpid.AMQException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ + +public class ChannelFlowMethodHandler implements StateAwareMethodListener +{ + private static final Logger _logger = LoggerFactory.getLogger(ChannelFlowMethodHandler.class); + private static final ChannelFlowMethodHandler _instance = new ChannelFlowMethodHandler(); + + public static ChannelFlowMethodHandler getInstance() + { + return _instance; + } + + private ChannelFlowMethodHandler() + { } + + public void methodReceived(AMQStateManager stateManager, ChannelFlowBody body, int channelId) + throws AMQException + { + + final AMQProtocolSession session = stateManager.getProtocolSession(); + session.setFlowControl(channelId, body.getActive()); + } + + +} diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java index ca3f46d08b..96de8af54b 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java @@ -30,7 +30,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelFlowOkMethodHandler implements StateAwareMethodListener +public class ChannelFlowOkMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ChannelFlowOkMethodHandler.class); private static final ChannelFlowOkMethodHandler _instance = new ChannelFlowOkMethodHandler(); @@ -43,10 +43,12 @@ public class ChannelFlowOkMethodHandler implements StateAwareMethodListener private ChannelFlowOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ChannelFlowOkBody body, int channelId) + throws AMQException { - ChannelFlowOkBody method = (ChannelFlowOkBody) evt.getMethod(); - _logger.debug("Received Channel.Flow-Ok message, active = " + method.active); + + _logger.debug("Received Channel.Flow-Ok message, active = " + body.getActive()); } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java new file mode 100644 index 0000000000..de976b05bd --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java @@ -0,0 +1,528 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.client.handler; + +import java.util.Map; +import java.util.HashMap; + +import org.apache.qpid.framing.*; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.AMQMethodNotImplementedException; + + +public class ClientMethodDispatcherImpl implements MethodDispatcher +{ + + + private static final BasicCancelOkMethodHandler _basicCancelOkMethodHandler = BasicCancelOkMethodHandler.getInstance(); + private static final BasicDeliverMethodHandler _basicDeliverMethodHandler = BasicDeliverMethodHandler.getInstance(); + private static final BasicReturnMethodHandler _basicReturnMethodHandler = BasicReturnMethodHandler.getInstance(); + private static final ChannelCloseMethodHandler _channelCloseMethodHandler = ChannelCloseMethodHandler.getInstance(); + private static final ChannelFlowOkMethodHandler _channelFlowOkMethodHandler = ChannelFlowOkMethodHandler.getInstance(); + private static final ConnectionCloseMethodHandler _connectionCloseMethodHandler = ConnectionCloseMethodHandler.getInstance(); + private static final ConnectionOpenOkMethodHandler _connectionOpenOkMethodHandler = ConnectionOpenOkMethodHandler.getInstance(); + private static final ConnectionRedirectMethodHandler _connectionRedirectMethodHandler = ConnectionRedirectMethodHandler.getInstance(); + private static final ConnectionSecureMethodHandler _connectionSecureMethodHandler = ConnectionSecureMethodHandler.getInstance(); + private static final ConnectionStartMethodHandler _connectionStartMethodHandler = ConnectionStartMethodHandler.getInstance(); + private static final ConnectionTuneMethodHandler _connectionTuneMethodHandler = ConnectionTuneMethodHandler.getInstance(); + private static final ExchangeBoundOkMethodHandler _exchangeBoundOkMethodHandler = ExchangeBoundOkMethodHandler.getInstance(); + private static final QueueDeleteOkMethodHandler _queueDeleteOkMethodHandler = QueueDeleteOkMethodHandler.getInstance(); + + + + private static interface DispatcherFactory + { + public ClientMethodDispatcherImpl createMethodDispatcher(AMQStateManager stateManager); + } + + private static final Map _dispatcherFactories = + new HashMap(); + + static + { + _dispatcherFactories.put(ProtocolVersion.v8_0, + new DispatcherFactory() + { + public ClientMethodDispatcherImpl createMethodDispatcher(AMQStateManager stateManager) + { + return new ClientMethodDispatcherImpl_8_0(stateManager); + } + }); + + _dispatcherFactories.put(ProtocolVersion.v0_9, + new DispatcherFactory() + { + public ClientMethodDispatcherImpl createMethodDispatcher(AMQStateManager stateManager) + { + return new ClientMethodDispatcherImpl_0_9(stateManager); + } + }); + + } + + + public static ClientMethodDispatcherImpl newMethodDispatcher(ProtocolVersion version, AMQStateManager stateManager) + { + DispatcherFactory factory = _dispatcherFactories.get(version); + return factory.createMethodDispatcher(stateManager); + } + + + + + private AMQStateManager _stateManager; + + public ClientMethodDispatcherImpl(AMQStateManager stateManager) + { + _stateManager = stateManager; + } + + + public AMQStateManager getStateManager() + { + return _stateManager; + } + + public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException + { + _basicCancelOkMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchBasicConsumeOk(BasicConsumeOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchBasicDeliver(BasicDeliverBody body, int channelId) throws AMQException + { + _basicDeliverMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchBasicGetEmpty(BasicGetEmptyBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchBasicGetOk(BasicGetOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchBasicQosOk(BasicQosOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchBasicReturn(BasicReturnBody body, int channelId) throws AMQException + { + _basicReturnMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException + { + _channelCloseMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException + { + _channelFlowOkMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchChannelOpenOk(ChannelOpenOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchConnectionClose(ConnectionCloseBody body, int channelId) throws AMQException + { + _connectionCloseMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchConnectionCloseOk(ConnectionCloseOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchConnectionOpenOk(ConnectionOpenOkBody body, int channelId) throws AMQException + { + _connectionOpenOkMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException + { + _connectionRedirectMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException + { + _connectionSecureMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException + { + _connectionStartMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException + { + _connectionTuneMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException + { + _queueDeleteOkMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchQueuePurgeOk(QueuePurgeOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchStreamCancelOk(StreamCancelOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchStreamConsumeOk(StreamConsumeOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchAccessRequest(AccessRequestBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicAck(BasicAckBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicCancel(BasicCancelBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicConsume(BasicConsumeBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicGet(BasicGetBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicPublish(BasicPublishBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicQos(BasicQosBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicRecover(BasicRecoverBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchBasicReject(BasicRejectBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchChannelOpen(ChannelOpenBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchConnectionOpen(ConnectionOpenBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchConnectionSecureOk(ConnectionSecureOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchConnectionStartOk(ConnectionStartOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchConnectionTuneOk(ConnectionTuneOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchDtxSelect(DtxSelectBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchDtxStart(DtxStartBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchExchangeBound(ExchangeBoundBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchExchangeDeclare(ExchangeDeclareBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchExchangeDelete(ExchangeDeleteBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileAck(FileAckBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileCancel(FileCancelBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileConsume(FileConsumeBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFilePublish(FilePublishBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileQos(FileQosBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileReject(FileRejectBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchQueueBind(QueueBindBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchQueueDeclare(QueueDeclareBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchQueueDelete(QueueDeleteBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchQueuePurge(QueuePurgeBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchStreamCancel(StreamCancelBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchStreamConsume(StreamConsumeBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchStreamPublish(StreamPublishBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchStreamQos(StreamQosBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchTunnelRequest(TunnelRequestBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchTxCommit(TxCommitBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchTxRollback(TxRollbackBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchTxSelect(TxSelectBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchDtxSelectOk(DtxSelectOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchDtxStartOk(DtxStartOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchExchangeBoundOk(ExchangeBoundOkBody body, int channelId) throws AMQException + { + _exchangeBoundOkMethodHandler.methodReceived(_stateManager,body,channelId); + return true; + } + + public boolean dispatchExchangeDeclareOk(ExchangeDeclareOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchExchangeDeleteOk(ExchangeDeleteOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchFileCancelOk(FileCancelOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileConsumeOk(FileConsumeOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileDeliver(FileDeliverBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileOpen(FileOpenBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileOpenOk(FileOpenOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileQosOk(FileQosOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileReturn(FileReturnBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchFileStage(FileStageBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchQueueBindOk(QueueBindOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchQueueDeclareOk(QueueDeclareOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchStreamDeliver(StreamDeliverBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchStreamQosOk(StreamQosOkBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchStreamReturn(StreamReturnBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchTxCommitOk(TxCommitOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTxRollbackOk(TxRollbackOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTxSelectOk(TxSelectOkBody body, int channelId) throws AMQException + { + return false; + } + +} diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java new file mode 100644 index 0000000000..ae6d5e8283 --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java @@ -0,0 +1,155 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.client.handler; + +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.MethodDispatcher_0_9; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.state.AMQStateManager; +import org.apache.qpid.client.state.AMQMethodNotImplementedException; + + +public class ClientMethodDispatcherImpl_0_9 extends ClientMethodDispatcherImpl implements MethodDispatcher_0_9 +{ + public ClientMethodDispatcherImpl_0_9(AMQStateManager stateManager) + { + super(stateManager); + } + + + public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException + { + throw new AMQMethodNotImplementedException(body); + } + + public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException + { + return false; + } + + +} diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java new file mode 100644 index 0000000000..6bd6874cde --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + package org.apache.qpid.client.handler; + +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_8_0.MethodDispatcher_8_0; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.state.AMQStateManager; + +public class ClientMethodDispatcherImpl_8_0 extends ClientMethodDispatcherImpl implements MethodDispatcher_8_0 +{ + public ClientMethodDispatcherImpl_8_0(AMQStateManager stateManager) + { + super(stateManager); + } + + public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchChannelAlert(ChannelAlertBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestContent(TestContentBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestContentOk(TestContentOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestInteger(TestIntegerBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestIntegerOk(TestIntegerOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestString(TestStringBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestStringOk(TestStringOkBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestTable(TestTableBody body, int channelId) throws AMQException + { + return false; + } + + public boolean dispatchTestTableOk(TestTableOkBody body, int channelId) throws AMQException + { + return false; + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java index 752f44237d..950a3288fc 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java @@ -36,7 +36,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ConnectionCloseMethodHandler implements StateAwareMethodListener +public class ConnectionCloseMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ConnectionCloseMethodHandler.class); @@ -50,32 +50,35 @@ public class ConnectionCloseMethodHandler implements StateAwareMethodListener private ConnectionCloseMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionCloseBody method, int channelId) + throws AMQException { _logger.info("ConnectionClose frame received"); - ConnectionCloseBody method = (ConnectionCloseBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + // does it matter // stateManager.changeState(AMQState.CONNECTION_CLOSING); - AMQConstant errorCode = AMQConstant.getConstant(method.replyCode); - AMQShortString reason = method.replyText; + AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode()); + AMQShortString reason = method.getReplyText(); try { + + ConnectionCloseOkBody closeOkBody = session.getMethodRegistry().createConnectionCloseOkBody(); // TODO: check whether channel id of zero is appropriate // Be aware of possible changes to parameter order as versions change. - protocolSession.writeFrame(ConnectionCloseOkBody.createAMQFrame((short) 0, method.getMajor(), - method.getMinor())); + session.writeFrame(closeOkBody.generateFrame(0)); if (errorCode != AMQConstant.REPLY_SUCCESS) { - if (errorCode == AMQConstant.NOT_ALLOWED) + if (errorCode == AMQConstant.NOT_ALLOWED || (errorCode == AMQConstant.ACCESS_REFUSED)) { - _logger.info("Authentication Error:" + Thread.currentThread().getName()); + _logger.info("Error :" + errorCode +":"+ Thread.currentThread().getName()); - protocolSession.closeProtocolSession(); + // todo ritchiem : Why do this here when it is going to be done in the finally block? + session.closeProtocolSession(); // todo this is a bit of a fudge (could be conssidered such as each new connection needs a new state manager or at least a fresh state. stateManager.changeState(AMQState.CONNECTION_NOT_STARTED); @@ -94,9 +97,13 @@ public class ConnectionCloseMethodHandler implements StateAwareMethodListener { // this actually closes the connection in the case where it is not an error. - protocolSession.closeProtocolSession(); + session.closeProtocolSession(); + // ritchiem: Doing this though will cause any waiting connection start to be released without being able to + // see what the cause was. stateManager.changeState(AMQState.CONNECTION_CLOSED); } } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java index 2e0f273c32..fd7acac84f 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java @@ -21,13 +21,14 @@ package org.apache.qpid.client.handler; import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionOpenOkBody; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQState; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; import org.apache.qpid.protocol.AMQMethodEvent; -public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener +public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener { private static final ConnectionOpenOkMethodHandler _instance = new ConnectionOpenOkMethodHandler(); @@ -40,9 +41,11 @@ public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionOpenOkBody body, int channelId) + throws AMQException { stateManager.changeState(AMQState.CONNECTION_OPEN); } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java index 213c0eba6e..cac68c9467 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java @@ -30,7 +30,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ConnectionRedirectMethodHandler implements StateAwareMethodListener +public class ConnectionRedirectMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ConnectionRedirectMethodHandler.class); @@ -46,13 +46,13 @@ public class ConnectionRedirectMethodHandler implements StateAwareMethodListener private ConnectionRedirectMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionRedirectBody method, int channelId) + throws AMQException { _logger.info("ConnectionRedirect frame received"); - ConnectionRedirectBody method = (ConnectionRedirectBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); - String host = method.host.toString(); + String host = method.getHost().toString(); // the host is in the form hostname:port with the port being optional int portIndex = host.indexOf(':'); @@ -68,6 +68,7 @@ public class ConnectionRedirectMethodHandler implements StateAwareMethodListener } - protocolSession.failover(host, port); + session.failover(host, port); } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java index b7776705fe..900aa2abac 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java @@ -32,7 +32,7 @@ import org.apache.qpid.framing.ConnectionSecureBody; import org.apache.qpid.framing.ConnectionSecureOkBody; import org.apache.qpid.protocol.AMQMethodEvent; -public class ConnectionSecureMethodHandler implements StateAwareMethodListener +public class ConnectionSecureMethodHandler implements StateAwareMethodListener { private static final ConnectionSecureMethodHandler _instance = new ConnectionSecureMethodHandler(); @@ -41,27 +41,26 @@ public class ConnectionSecureMethodHandler implements StateAwareMethodListener return _instance; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionSecureBody body, int channelId) + throws AMQException { - SaslClient client = protocolSession.getSaslClient(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + SaslClient client = session.getSaslClient(); if (client == null) { throw new AMQException(null, "No SASL client set up - cannot proceed with authentication", null); } - ConnectionSecureBody body = (ConnectionSecureBody) evt.getMethod(); + try { // Evaluate server challenge - byte[] response = client.evaluateChallenge(body.challenge); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - // Be aware of possible changes to parameter order as versions change. - AMQFrame responseFrame = ConnectionSecureOkBody.createAMQFrame(evt.getChannelId(), - body.getMajor(), body.getMinor(), - response); // response - protocolSession.writeFrame(responseFrame); + byte[] response = client.evaluateChallenge(body.getChallenge()); + + ConnectionSecureOkBody secureOkBody = session.getMethodRegistry().createConnectionSecureOkBody(response); + + session.writeFrame(secureOkBody.generateFrame(channelId)); } catch (SaslException e) { @@ -70,4 +69,6 @@ public class ConnectionSecureMethodHandler implements StateAwareMethodListener } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java index 59b493a6f7..d3746f137e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java @@ -48,7 +48,7 @@ import java.io.UnsupportedEncodingException; import java.util.HashSet; import java.util.StringTokenizer; -public class ConnectionStartMethodHandler implements StateAwareMethodListener +public class ConnectionStartMethodHandler implements StateAwareMethodListener { private static final Logger _log = LoggerFactory.getLogger(ConnectionStartMethodHandler.class); @@ -62,15 +62,16 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener private ConnectionStartMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionStartBody body, int channelId) + throws AMQException { _log.debug("public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, " + "AMQMethodEvent evt): called"); - ConnectionStartBody body = (ConnectionStartBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + - ProtocolVersion pv = new ProtocolVersion((byte) body.versionMajor, (byte) body.versionMinor); + ProtocolVersion pv = new ProtocolVersion((byte) body.getVersionMajor(), (byte) body.getVersionMinor()); // For the purposes of interop, we can make the client accept the broker's version string. // If it does, it then internally records the version as being the latest one that it understands. @@ -83,26 +84,26 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener if (pv.isSupported()) { - protocolSession.setProtocolVersion(pv.getMajorVersion(), pv.getMinorVersion()); + session.setProtocolVersion(pv); try { // Used to hold the SASL mechanism to authenticate with. String mechanism; - if (body.mechanisms == null) + if (body.getMechanisms()== null) { throw new AMQException(null, "mechanism not specified in ConnectionStart method frame", null); } else { - mechanism = chooseMechanism(body.mechanisms); + mechanism = chooseMechanism(body.getMechanisms()); _log.debug("mechanism = " + mechanism); } if (mechanism == null) { - throw new AMQException(null, "No supported security mechanism found, passed: " + new String(body.mechanisms), null); + throw new AMQException(null, "No supported security mechanism found, passed: " + new String(body.getMechanisms()), null); } byte[] saslResponse; @@ -110,7 +111,7 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener { SaslClient sc = Sasl.createSaslClient(new String[] { mechanism }, null, "AMQP", "localhost", null, - createCallbackHandler(mechanism, protocolSession)); + createCallbackHandler(mechanism, session)); if (sc == null) { throw new AMQException(null, "Client SASL configuration error: no SaslClient could be created for mechanism " + mechanism @@ -118,21 +119,21 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener + " details of how to register non-standard SASL client providers.", null); } - protocolSession.setSaslClient(sc); + session.setSaslClient(sc); saslResponse = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null); } catch (SaslException e) { - protocolSession.setSaslClient(null); + session.setSaslClient(null); throw new AMQException(null, "Unable to create SASL client: " + e, e); } - if (body.locales == null) + if (body.getLocales() == null) { throw new AMQException(null, "Locales is not defined in Connection Start method", null); } - final String locales = new String(body.locales, "utf8"); + final String locales = new String(body.getLocales(), "utf8"); final StringTokenizer tokenizer = new StringTokenizer(locales, " "); String selectedLocale = null; if (tokenizer.hasMoreTokens()) @@ -148,23 +149,20 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener FieldTable clientProperties = FieldTableFactory.newFieldTable(); clientProperties.setString(new AMQShortString(ClientProperties.instance.toString()), - protocolSession.getClientID()); + session.getClientID()); clientProperties.setString(new AMQShortString(ClientProperties.product.toString()), QpidProperties.getProductName()); clientProperties.setString(new AMQShortString(ClientProperties.version.toString()), QpidProperties.getReleaseVersion()); clientProperties.setString(new AMQShortString(ClientProperties.platform.toString()), getFullSystemInfo()); + + ConnectionStartOkBody connectionStartOkBody = session.getMethodRegistry().createConnectionStartOkBody(clientProperties,new AMQShortString(mechanism),saslResponse,new AMQShortString(locales)); // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. // Be aware of possible changes to parameter order as versions change. - protocolSession.writeFrame(ConnectionStartOkBody.createAMQFrame(evt.getChannelId(), - protocolSession.getProtocolMajorVersion(), protocolSession.getProtocolMinorVersion(), - clientProperties, // clientProperties - new AMQShortString(selectedLocale), // locale - new AMQShortString(mechanism), // mechanism - saslResponse)); // response - + session.writeFrame(connectionStartOkBody.generateFrame(channelId)); + } catch (UnsupportedEncodingException e) { @@ -173,10 +171,10 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener } else { - _log.error("Broker requested Protocol [" + body.versionMajor + "-" + body.versionMinor + _log.error("Broker requested Protocol [" + body.getVersionMajor() + "-" + body.getVersionMinor() + "] which is not supported by this version of the client library"); - protocolSession.closeProtocolSession(); + session.closeProtocolSession(); } } @@ -235,4 +233,5 @@ public class ConnectionStartMethodHandler implements StateAwareMethodListener throw new AMQException(null, "Unable to create callback handler: " + e, e); } } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java index 68556991d7..fc0e40b745 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java @@ -26,17 +26,13 @@ import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQState; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.state.StateAwareMethodListener; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ConnectionOpenBody; -import org.apache.qpid.framing.ConnectionTuneBody; -import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ConnectionTuneMethodHandler implements StateAwareMethodListener +public class ConnectionTuneMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ConnectionTuneMethodHandler.class); @@ -50,48 +46,41 @@ public class ConnectionTuneMethodHandler implements StateAwareMethodListener protected ConnectionTuneMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ConnectionTuneBody frame, int channelId) + throws AMQException { _logger.debug("ConnectionTune frame received"); - ConnectionTuneBody frame = (ConnectionTuneBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); + final MethodRegistry methodRegistry = session.getMethodRegistry(); - ConnectionTuneParameters params = protocolSession.getConnectionTuneParameters(); + + ConnectionTuneParameters params = session.getConnectionTuneParameters(); if (params == null) { params = new ConnectionTuneParameters(); } - params.setFrameMax(frame.frameMax); - params.setChannelMax(frame.channelMax); - params.setHeartbeat(Integer.getInteger("amqj.heartbeat.delay", frame.heartbeat)); - protocolSession.setConnectionTuneParameters(params); + params.setFrameMax(frame.getFrameMax()); + params.setChannelMax(frame.getChannelMax()); + params.setHeartbeat(Integer.getInteger("amqj.heartbeat.delay", frame.getHeartbeat())); + session.setConnectionTuneParameters(params); stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); - protocolSession.writeFrame(createTuneOkFrame(evt.getChannelId(), params, frame.getMajor(), frame.getMinor())); - String host = protocolSession.getAMQConnection().getVirtualHost(); + ConnectionTuneOkBody tuneOkBody = methodRegistry.createConnectionTuneOkBody(params.getChannelMax(), + params.getFrameMax(), + params.getHeartbeat()); + // Be aware of possible changes to parameter order as versions change. + session.writeFrame(tuneOkBody.generateFrame(channelId)); + + String host = session.getAMQConnection().getVirtualHost(); AMQShortString virtualHost = new AMQShortString("/" + host); - protocolSession.writeFrame(createConnectionOpenFrame(evt.getChannelId(), virtualHost, null, true, frame.getMajor(), - frame.getMinor())); - } + ConnectionOpenBody openBody = methodRegistry.createConnectionOpenBody(virtualHost,null,true); - protected AMQFrame createConnectionOpenFrame(int channel, AMQShortString path, AMQShortString capabilities, - boolean insist, byte major, byte minor) - { // Be aware of possible changes to parameter order as versions change. - return ConnectionOpenBody.createAMQFrame(channel, major, minor, // AMQP version (major, minor) - capabilities, // capabilities - insist, // insist - path); // virtualHost + session.writeFrame(openBody.generateFrame(channelId)); } - protected AMQFrame createTuneOkFrame(int channel, ConnectionTuneParameters params, byte major, byte minor) - { - // Be aware of possible changes to parameter order as versions change. - return ConnectionTuneOkBody.createAMQFrame(channel, major, minor, params.getChannelMax(), // channelMax - params.getFrameMax(), // frameMax - params.getHeartbeat()); // heartbeat - } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java index 862a9be8d4..8de40beb10 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; /** * @author Apache Software Foundation */ -public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener +public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ExchangeBoundOkMethodHandler.class); private static final ExchangeBoundOkMethodHandler _instance = new ExchangeBoundOkMethodHandler(); @@ -46,14 +46,14 @@ public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener private ExchangeBoundOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException + public void methodReceived(AMQStateManager stateManager, ExchangeBoundOkBody body, int channelId) + throws AMQException { if (_logger.isDebugEnabled()) { - ExchangeBoundOkBody body = (ExchangeBoundOkBody) evt.getMethod(); - _logger.debug("Received Exchange.Bound-Ok message, response code: " + body.replyCode + " text: " - + body.replyText); + _logger.debug("Received Exchange.Bound-Ok message, response code: " + body.getReplyCode() + " text: " + + body.getReplyText()); } } + } diff --git a/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java b/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java index 65060d44d2..41225c0569 100644 --- a/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java +++ b/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java @@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory; /** * @author Apache Software Foundation */ -public class QueueDeleteOkMethodHandler implements StateAwareMethodListener +public class QueueDeleteOkMethodHandler implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(QueueDeleteOkMethodHandler.class); private static final QueueDeleteOkMethodHandler _instance = new QueueDeleteOkMethodHandler(); @@ -46,13 +46,14 @@ public class QueueDeleteOkMethodHandler implements StateAwareMethodListener private QueueDeleteOkMethodHandler() { } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) - throws AMQException - { + public void methodReceived(AMQStateManager stateManager, QueueDeleteOkBody body, int channelId) + throws AMQException + { if (_logger.isDebugEnabled()) { - QueueDeleteOkBody body = (QueueDeleteOkBody) evt.getMethod(); - _logger.debug("Received Queue.Delete-Ok message, message count: " + body.messageCount); + _logger.debug("Received Queue.Delete-Ok message, message count: " + body.getMessageCount()); } } + + } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java index cfbf687401..f57f0ff252 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java @@ -63,8 +63,8 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach protected boolean _changedData = true; private Destination _destination; private JMSHeaderAdapter _headerAdapter; - private BasicMessageConsumer _consumer; - private boolean _strictAMQP; + private static final boolean STRICT_AMQP_COMPLIANCE = + Boolean.parseBoolean(System.getProperties().getProperty(AMQSession.STRICT_AMQP, AMQSession.STRICT_AMQP_DEFAULT)); /** * This is 0_10 specific @@ -108,6 +108,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach return _010message; } + protected AbstractJMSMessage(ByteBuffer data) { super(new BasicContentHeaderProperties()); @@ -122,8 +123,6 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach _changedData = (data == null); _headerAdapter = new JMSHeaderAdapter(((BasicContentHeaderProperties) _contentHeaderProperties).getHeaders()); - _strictAMQP = - Boolean.parseBoolean(System.getProperties().getProperty(AMQSession.STRICT_AMQP, AMQSession.STRICT_AMQP_DEFAULT)); } protected AbstractJMSMessage(long deliveryTag, BasicContentHeaderProperties contentHeader, AMQShortString exchange, @@ -171,7 +170,10 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach { if (getContentHeaderProperties().getMessageIdAsString() == null) { - getContentHeaderProperties().setMessageId("ID:" + UUID.randomUUID()); + StringBuilder b = new StringBuilder(39); + b.append("ID:"); + b.append(UUID.randomUUID()); + getContentHeaderProperties().setMessageId(b.toString()); } return getContentHeaderProperties().getMessageIdAsString(); @@ -351,7 +353,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public boolean getBooleanProperty(AMQShortString propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -361,7 +363,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public boolean getBooleanProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -371,7 +373,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public byte getByteProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -381,7 +383,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public byte[] getBytesProperty(AMQShortString propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -391,7 +393,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public short getShortProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -401,7 +403,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public int getIntProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -411,7 +413,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public long getLongProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -421,7 +423,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public float getFloatProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -431,7 +433,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public double getDoubleProperty(String propertyName) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -441,12 +443,20 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public String getStringProperty(String propertyName) throws JMSException { - if (_strictAMQP) + //NOTE: if the JMSX Property is a non AMQP property then we must check _strictAMQP and throw as below. + if (propertyName.equals(CustomJMSXProperty.JMSXUserID.toString())) { - throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); + return ((BasicContentHeaderProperties) _contentHeaderProperties).getUserIdAsString(); } + else + { + if (STRICT_AMQP_COMPLIANCE) + { + throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); + } - return getJmsHeaders().getString(propertyName); + return getJmsHeaders().getString(propertyName); + } } public Object getObjectProperty(String propertyName) throws JMSException @@ -461,7 +471,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setBooleanProperty(AMQShortString propertyName, boolean b) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -472,7 +482,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setBooleanProperty(String propertyName, boolean b) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -483,7 +493,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setByteProperty(String propertyName, byte b) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -494,7 +504,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setBytesProperty(AMQShortString propertyName, byte[] bytes) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -505,7 +515,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setShortProperty(String propertyName, short i) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -523,7 +533,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setLongProperty(String propertyName, long l) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -534,7 +544,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setFloatProperty(String propertyName, float f) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -545,7 +555,7 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach public void setDoubleProperty(String propertyName, double v) throws JMSException { - if (_strictAMQP) + if (STRICT_AMQP_COMPLIANCE) { throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP"); } @@ -741,11 +751,6 @@ public abstract class AbstractJMSMessage extends AMQMessage implements org.apach } } - public void setConsumer(BasicMessageConsumer basicMessageConsumer) - { - _consumer = basicMessageConsumer; - } - public void receivedFromServer() { _changedData = false; diff --git a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java index 48992659f6..0dbad3726c 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java @@ -79,8 +79,17 @@ public abstract class AbstractJMSMessageFactory implements MessageFactory while (it.hasNext()) { ContentBody cb = (ContentBody) it.next(); - data.put(cb.payload); - cb.payload.release(); + final ByteBuffer payload = cb.payload; + if(payload.isDirect() || payload.isReadOnly()) + { + data.put(payload); + } + else + { + data.put(payload.array(), payload.arrayOffset(), payload.limit()); + } + + payload.release(); } data.flip(); diff --git a/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java index 1fb5e637c9..fed1f1c609 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java @@ -56,7 +56,11 @@ public class JMSMapMessage extends AbstractBytesTypedMessage implements javax.jm JMSMapMessage(ByteBuffer data) throws JMSException { super(data); // this instantiates a content header - populateMapFromData(); + if(data != null) + { + populateMapFromData(); + } + } JMSMapMessage(long messageNbr, BasicContentHeaderProperties contentHeader, AMQShortString exchange, AMQShortString routingKey, @@ -77,7 +81,7 @@ public class JMSMapMessage extends AbstractBytesTypedMessage implements javax.jm public String toBodyString() throws JMSException { - return _map.toString(); + return _map == null ? "" : _map.toString(); } public AMQShortString getMimeTypeAsShortString() diff --git a/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java index 593c7795b0..c866a5028e 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java @@ -9,7 +9,7 @@ public class ReturnMessage extends UnprocessedMessage_0_8 public ReturnMessage(int channelId,AMQShortString exchange,AMQShortString routingKey,AMQShortString replyText,int replyCode) { - super(channelId,-1,"",exchange,routingKey,false); + super(channelId,-1,null,exchange,routingKey,false); _replyText = replyText; _replyCode = replyCode; } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java index 5cb943cd33..17efd7e24f 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java @@ -23,6 +23,20 @@ package org.apache.qpid.client.message; import java.util.List; import org.apache.qpid.framing.AMQShortString; +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.BasicMessageConsumer; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicDeliverBody; +import org.apache.qpid.framing.BasicReturnBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; /** @@ -36,12 +50,12 @@ public abstract class UnprocessedMessage { private final int _channelId; private final long _deliveryId; - private final String _consumerTag; + private final AMQShortString _consumerTag; protected AMQShortString _exchange; protected AMQShortString _routingKey; protected boolean _redelivered; - public UnprocessedMessage(int channelId,long deliveryId,String consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) + public UnprocessedMessage(int channelId,long deliveryId,AMQShortString consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) { _channelId = channelId; _deliveryId = deliveryId; @@ -65,7 +79,7 @@ public abstract class UnprocessedMessage return _deliveryId; } - public String getConsumerTag() + public AMQShortString getConsumerTag() { return _consumerTag; } @@ -84,14 +98,13 @@ public abstract class UnprocessedMessage { return _redelivered; } - public abstract List getBodies(); - + public abstract H getContentHeader(); - + // specific to 0_10 public String getReplyToURL() { return ""; - } + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java index 79d829ca39..09f41a9ba6 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java @@ -43,7 +43,7 @@ public class UnprocessedMessage_0_10 extends UnprocessedMessage _bodies = new ArrayList(); - public UnprocessedMessage_0_10(int channelId,long deliveryId,String consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) + public UnprocessedMessage_0_10(int channelId,long deliveryId,AMQShortString consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) { super(channelId,deliveryId,consumerTag,exchange,routingKey,redelivered); } diff --git a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java index 8e32de382b..78da8cdca2 100644 --- a/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java +++ b/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.BasicDeliverBody; +import org.apache.qpid.framing.BasicReturnBody; import org.apache.qpid.framing.ContentBody; import org.apache.qpid.framing.ContentHeaderBody; @@ -46,11 +47,22 @@ public class UnprocessedMessage_0_8 extends UnprocessedMessage _bodies; - public UnprocessedMessage_0_8(int channelId,long deliveryId,String consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) + public UnprocessedMessage_0_8(int channelId,long deliveryId,AMQShortString consumerTag,AMQShortString exchange,AMQShortString routingKey,boolean redelivered) { super(channelId,deliveryId,consumerTag,exchange,routingKey,redelivered); } + public UnprocessedMessage_0_8(int channelId, BasicReturnBody body) + { + //FIXME: TGM, SRSLY 4RL + super(channelId, 0, null, body.getExchange(), body.getRoutingKey(), false); + } + + public UnprocessedMessage_0_8(int channelId, BasicDeliverBody body) + { + super(channelId, body.getDeliveryTag(), body.getConsumerTag(), body.getExchange(), body.getRoutingKey(), false); + } + public void receiveBody(ContentBody body) { @@ -119,8 +131,8 @@ public class UnprocessedMessage_0_8 extends UnprocessedMessage evt = - new AMQMethodEvent(frame.getChannel(), (AMQMethodBody) bodyFrame); + AMQFrame frame = (AMQFrame) message; - try - { + final AMQBody bodyFrame = frame.getBodyFrame(); - boolean wasAnyoneInterested = getStateManager().methodReceived(evt); - if (!_frameListeners.isEmpty()) - { - Iterator it = _frameListeners.iterator(); - while (it.hasNext()) - { - final AMQMethodListener listener = (AMQMethodListener) it.next(); - wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; - } - } - - if (!wasAnyoneInterested) - { - throw new AMQException(null, "AMQMethodEvent " + evt + " was not processed by any listener. Listeners:" - + _frameListeners, null); - } - } - catch (AMQException e) - { - getStateManager().error(e); - if (!_frameListeners.isEmpty()) - { - Iterator it = _frameListeners.iterator(); - while (it.hasNext()) - { - final AMQMethodListener listener = (AMQMethodListener) it.next(); - listener.error(e); - } - } - - exceptionCaught(session, e); - } + HeartbeatDiagnostics.received(bodyFrame instanceof HeartbeatBody); - break; + bodyFrame.handle(frame.getChannel(),_protocolSession); - case ContentHeaderBody.TYPE: + _connection.bytesReceived(_protocolSession.getIoSession().getReadBytes()); + } + else if (message instanceof ProtocolInitiation) + { + // We get here if the server sends a response to our initial protocol header + // suggesting an alternate ProtocolVersion; the server will then close the + // connection. + ProtocolInitiation protocolInit = (ProtocolInitiation) message; + ProtocolVersion pv = protocolInit.checkVersion(); + getConnection().setProtocolVersion(pv); + + // get round a bug in old versions of qpid whereby the connection is not closed + _stateManager.changeState(AMQState.CONNECTION_CLOSED); + } + } - _protocolSession.messageContentHeaderReceived(frame.getChannel(), (ContentHeaderBody) bodyFrame); - break; + public void methodBodyReceived(final int channelId, final AMQBody bodyFrame, IoSession session)//, final IoSession session) + throws AMQException + { - case ContentBody.TYPE: + if (_logger.isDebugEnabled()) + { + _logger.debug("(" + System.identityHashCode(this) + ")Method frame received: " + bodyFrame); + } - _protocolSession.messageContentBodyReceived(frame.getChannel(), (ContentBody) bodyFrame); - break; + final AMQMethodEvent evt = + new AMQMethodEvent(channelId, (AMQMethodBody) bodyFrame); - case HeartbeatBody.TYPE: + try + { - if (debug) + boolean wasAnyoneInterested = getStateManager().methodReceived(evt); + if (!_frameListeners.isEmpty()) + { + //This iterator is safe from the error state as the frame listeners always add before they send so their + // will be ready and waiting for this response. + Iterator it = _frameListeners.iterator(); + while (it.hasNext()) { - _logger.debug("Received heartbeat"); + final AMQMethodListener listener = (AMQMethodListener) it.next(); + wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; } + } - break; - - default: + if (!wasAnyoneInterested) + { + throw new AMQException(null, "AMQMethodEvent " + evt + " was not processed by any listener. Listeners:" + + _frameListeners, null); + } + } + catch (AMQException e) + { + if (!_frameListeners.isEmpty()) + { + Iterator it = _frameListeners.iterator(); + while (it.hasNext()) + { + final AMQMethodListener listener = (AMQMethodListener) it.next(); + listener.error(e); + } + } + exceptionCaught(session, e); } - _connection.bytesReceived(_protocolSession.getIoSession().getReadBytes()); } private static int _messagesOut; @@ -506,6 +566,12 @@ public class AMQProtocolHandler extends IoHandlerAdapter getStateManager().attainState(s); } + public AMQState attainState(Set states) throws AMQException + { + return getStateManager().attainState(states); + } + + /** * Convenience method that writes a frame to the protocol session. Equivalent to calling * getProtocolSession().write(). @@ -547,7 +613,15 @@ public class AMQProtocolHandler extends IoHandlerAdapter { try { - _frameListeners.add(listener); + synchronized (_frameListeners) + { + if (_lastFailoverException != null) + { + throw _lastFailoverException; + } + + _frameListeners.add(listener); + } _protocolSession.writeFrame(frame); AMQMethodEvent e = listener.blockForFrame(timeout); @@ -556,10 +630,6 @@ public class AMQProtocolHandler extends IoHandlerAdapter // When control resumes before this line, a reply will have been received // that matches the criteria defined in the blocking listener } - catch (AMQException e) - { - throw e; - } finally { // If we don't removeKey the listener then no-one will @@ -600,16 +670,11 @@ public class AMQProtocolHandler extends IoHandlerAdapter { getStateManager().changeState(AMQState.CONNECTION_CLOSING); - // AMQP version change: Hardwire the version to 0-8 (major=8, minor=0) - // TODO: Connect this to the session version obtained from ProtocolInitiation for this session. - // Be aware of possible changes to parameter order as versions change. - final AMQFrame frame = - ConnectionCloseBody.createAMQFrame(0, _protocolSession.getProtocolMajorVersion(), - _protocolSession.getProtocolMinorVersion(), // AMQP version (major, minor) - 0, // classId - 0, // methodId - AMQConstant.REPLY_SUCCESS.getCode(), // replyCode - new AMQShortString("JMS client is closing the connection.")); // replyText + ConnectionCloseBody body = _protocolSession.getMethodRegistry().createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), // replyCode + new AMQShortString("JMS client is closing the connection."),0,0); + + + final AMQFrame frame = body.generateFrame(0); try { @@ -682,7 +747,10 @@ public class AMQProtocolHandler extends IoHandlerAdapter public void setStateManager(AMQStateManager stateManager) { _stateManager = stateManager; - _protocolSession.setStateManager(stateManager); + if (_protocolSession != null) + { + _protocolSession.setStateManager(stateManager); + } } public AMQProtocolSession getProtocolSession() @@ -709,4 +777,14 @@ public class AMQProtocolHandler extends IoHandlerAdapter { return _protocolSession.getProtocolMinorVersion(); } + + public MethodRegistry getMethodRegistry() + { + return getStateManager().getMethodRegistry(); + } + + public ProtocolVersion getProtocolVersion() + { + return _protocolSession.getProtocolVersion(); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java index f5008496d9..6e782e0bfc 100644 --- a/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java +++ b/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java @@ -21,40 +21,31 @@ package org.apache.qpid.client.protocol; import org.apache.commons.lang.StringUtils; - import org.apache.mina.common.CloseFuture; import org.apache.mina.common.IdleStatus; import org.apache.mina.common.IoSession; import org.apache.mina.common.WriteFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jms.JMSException; +import javax.security.sasl.SaslClient; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.apache.qpid.AMQException; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.ConnectionTuneParameters; -// import org.apache.qpid.client.message.UnexpectedBodyReceivedException; import org.apache.qpid.client.message.ReturnMessage; import org.apache.qpid.client.message.UnprocessedMessage; import org.apache.qpid.client.message.UnprocessedMessage_0_8; import org.apache.qpid.client.state.AMQStateManager; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ContentBody; -import org.apache.qpid.framing.ContentHeaderBody; -import org.apache.qpid.framing.MainRegistry; -import org.apache.qpid.framing.ProtocolInitiation; -import org.apache.qpid.framing.ProtocolVersion; -import org.apache.qpid.framing.VersionSpecificRegistry; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.jms.JMSException; -import javax.security.sasl.SaslClient; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import org.apache.qpid.client.handler.ClientMethodDispatcherImpl; /** * Wrapper for protocol session that provides type-safe access to session attributes.

The underlying protocol @@ -95,18 +86,27 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession * Maps from a channel id to an unprocessed message. This is used to tie together the JmsDeliverBody (which arrives * first) with the subsequent content header and content bodies. */ - protected ConcurrentMap _channelId2UnprocessedMsgMap = new ConcurrentHashMap(); + private final ConcurrentMap _channelId2UnprocessedMsgMap = new ConcurrentHashMap(); + private final UnprocessedMessage[] _channelId2UnprocessedMsgArray = new UnprocessedMessage[16]; /** Counter to ensure unique queue names */ protected int _queueId = 1; protected final Object _queueIdLock = new Object(); - private byte _protocolMinorVersion; - private byte _protocolMajorVersion; - private VersionSpecificRegistry _registry = - MainRegistry.getVersionSpecificRegistry(ProtocolVersion.getLatestSupportedVersion()); + private ProtocolVersion _protocolVersion; +// private VersionSpecificRegistry _registry = +// MainRegistry.getVersionSpecificRegistry(ProtocolVersion.getLatestSupportedVersion()); + + + private MethodRegistry _methodRegistry = + MethodRegistry.getMethodRegistry(ProtocolVersion.getLatestSupportedVersion()); + + + private MethodDispatcher _methodDispatcher; + private final AMQConnection _connection; + private static final int FAST_CHANNEL_ACCESS_MASK = 0xFFFFFFF0; public AMQProtocolSession(AMQProtocolHandler protocolHandler, IoSession protocolSession, AMQConnection connection) { @@ -126,6 +126,9 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession _minaProtocolSession.setWriteTimeout(LAST_WRITE_FUTURE_JOIN_TIMEOUT); _stateManager = stateManager; _stateManager.setProtocolSession(this); + _protocolVersion = connection.getProtocolVersion(); + _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(ProtocolVersion.getLatestSupportedVersion(), + stateManager); _connection = connection; } @@ -135,7 +138,7 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession // start the process of setting up the connection. This is the first place that // data is written to the server. - _minaProtocolSession.write(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); + _minaProtocolSession.write(new ProtocolInitiation(_connection.getProtocolVersion())); } public String getClientID() @@ -164,6 +167,8 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession public void setStateManager(AMQStateManager stateManager) { _stateManager = stateManager; + _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(_protocolVersion, + stateManager); } public String getVirtualHost() @@ -230,14 +235,25 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession * * @throws AMQException if this was not expected */ - public void unprocessedMessageReceived(UnprocessedMessage_0_8 message) throws AMQException + public void unprocessedMessageReceived(UnprocessedMessage message) throws AMQException { - _channelId2UnprocessedMsgMap.put(message.getChannelId(), message); + final int channelId = message.getChannelId(); + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + _channelId2UnprocessedMsgArray[channelId] = message; + } + else + { + _channelId2UnprocessedMsgMap.put(channelId, message); + } } - public void messageContentHeaderReceived(int channelId, ContentHeaderBody contentHeader) throws AMQException + public void contentHeaderReceived(int channelId, ContentHeaderBody contentHeader) throws AMQException { - UnprocessedMessage_0_8 msg = (UnprocessedMessage_0_8) _channelId2UnprocessedMsgMap.get(channelId); + final UnprocessedMessage msg = (channelId & FAST_CHANNEL_ACCESS_MASK) == 0 ? _channelId2UnprocessedMsgArray[channelId] + : _channelId2UnprocessedMsgMap.get(channelId); + + if (msg == null) { throw new AMQException(null, "Error: received content header without having received a BasicDeliver frame first", null); @@ -255,9 +271,19 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession } } - public void messageContentBodyReceived(int channelId, ContentBody contentBody) throws AMQException + public void contentBodyReceived(final int channelId, ContentBody contentBody) throws AMQException { - UnprocessedMessage_0_8 msg = _channelId2UnprocessedMsgMap.get(channelId); + UnprocessedMessage_0_8 msg; + final boolean fastAccess = (channelId & FAST_CHANNEL_ACCESS_MASK) == 0; + if(fastAccess) + { + msg = (UnprocessedMessage_0_8) _channelId2UnprocessedMsgArray[channelId]; + } + else + { + msg = (UnprocessedMessage_0_8) _channelId2UnprocessedMsgMap.get(channelId); + } + if (msg == null) { throw new AMQException(null, "Error: received content body without having received a JMSDeliver frame first", null); @@ -265,7 +291,14 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession if (msg.getContentHeader() == null) { - _channelId2UnprocessedMsgMap.remove(channelId); + if(fastAccess) + { + _channelId2UnprocessedMsgArray[channelId] = null; + } + else + { + _channelId2UnprocessedMsgMap.remove(channelId); + } throw new AMQException(null, "Error: received content body without having received a ContentHeader frame first", null); } @@ -285,6 +318,11 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession } } + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException + { + + } + /** * Deliver a message to the appropriate session, removing the unprocessed message from our map * @@ -295,7 +333,14 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession { AMQSession session = getSession(channelId); session.messageReceived(msg); - _channelId2UnprocessedMsgMap.remove(channelId); + if((channelId & FAST_CHANNEL_ACCESS_MASK) == 0) + { + _channelId2UnprocessedMsgArray[channelId] = null; + } + else + { + _channelId2UnprocessedMsgMap.remove(channelId); + } } protected AMQSession getSession(int channelId) @@ -440,26 +485,64 @@ public class AMQProtocolSession implements AMQVersionAwareProtocolSession session.confirmConsumerCancelled(consumerTag); } - public void setProtocolVersion(final byte versionMajor, final byte versionMinor) + public void setProtocolVersion(final ProtocolVersion pv) { - _protocolMajorVersion = versionMajor; - _protocolMinorVersion = versionMinor; - _registry = MainRegistry.getVersionSpecificRegistry(versionMajor, versionMinor); + _protocolVersion = pv; + _methodRegistry = MethodRegistry.getMethodRegistry(pv); + _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(pv, _stateManager); + + // _registry = MainRegistry.getVersionSpecificRegistry(versionMajor, versionMinor); } public byte getProtocolMinorVersion() { - return _protocolMinorVersion; + return _protocolVersion.getMinorVersion(); } public byte getProtocolMajorVersion() { - return _protocolMajorVersion; + return _protocolVersion.getMajorVersion(); } - public VersionSpecificRegistry getRegistry() + public ProtocolVersion getProtocolVersion() { - return _registry; + return _protocolVersion; } +// public VersionSpecificRegistry getRegistry() +// { +// return _registry; +// } + + public MethodRegistry getMethodRegistry() + { + return _methodRegistry; + } + + public MethodDispatcher getMethodDispatcher() + { + return _methodDispatcher; + } + + + public void setTicket(int ticket, int channelId) + { + final AMQSession session = getSession(channelId); + session.setTicket(ticket); + } + public void setMethodDispatcher(MethodDispatcher methodDispatcher) + { + _methodDispatcher = methodDispatcher; + } + + public void setFlowControl(final int channelId, final boolean active) + { + final AMQSession session = getSession(channelId); + session.setFlowControl(active); + } + + public void methodFrameReceived(final int channel, final AMQMethodBody amqMethodBody) throws AMQException + { + _protocolHandler.methodBodyReceived(channel, amqMethodBody, _minaProtocolSession); + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java new file mode 100644 index 0000000000..5185278eef --- /dev/null +++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.client.state; + +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.AMQException; + +public class AMQMethodNotImplementedException extends AMQException +{ + public AMQMethodNotImplementedException(AMQMethodBody body) + { + super(null, "Unexpected Method Received: " + body.getClass().getName(), null); + } +} diff --git a/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java index 4996f59345..d32d10542f 100644 --- a/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java +++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java @@ -24,8 +24,22 @@ package org.apache.qpid.client.state; * States used in the AMQ protocol. Used by the finite state machine to determine * valid responses. */ -public class AMQState +public enum AMQState { + + CONNECTION_NOT_STARTED(1, "CONNECTION_NOT_STARTED"), + + CONNECTION_NOT_TUNED(2, "CONNECTION_NOT_TUNED"), + + CONNECTION_NOT_OPENED(3, "CONNECTION_NOT_OPENED"), + + CONNECTION_OPEN(4, "CONNECTION_OPEN"), + + CONNECTION_CLOSING(5, "CONNECTION_CLOSING"), + + CONNECTION_CLOSED(6, "CONNECTION_CLOSED"); + + private final int _id; private final String _name; @@ -41,16 +55,6 @@ public class AMQState return "AMQState: id = " + _id + " name: " + _name; } - public static final AMQState CONNECTION_NOT_STARTED = new AMQState(1, "CONNECTION_NOT_STARTED"); - - public static final AMQState CONNECTION_NOT_TUNED = new AMQState(2, "CONNECTION_NOT_TUNED"); - - public static final AMQState CONNECTION_NOT_OPENED = new AMQState(3, "CONNECTION_NOT_OPENED"); - public static final AMQState CONNECTION_OPEN = new AMQState(4, "CONNECTION_OPEN"); - public static final AMQState CONNECTION_CLOSING = new AMQState(5, "CONNECTION_CLOSING"); - - public static final AMQState CONNECTION_CLOSED = new AMQState(6, "CONNECTION_CLOSED"); - } diff --git a/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java b/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java index 9f430d76a7..eda1a1f5fd 100644 --- a/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java +++ b/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java @@ -21,49 +21,22 @@ package org.apache.qpid.client.state; import org.apache.qpid.AMQException; -import org.apache.qpid.client.handler.BasicCancelOkMethodHandler; -import org.apache.qpid.client.handler.BasicDeliverMethodHandler; -import org.apache.qpid.client.handler.BasicReturnMethodHandler; -import org.apache.qpid.client.handler.ChannelCloseMethodHandler; -import org.apache.qpid.client.handler.ChannelCloseOkMethodHandler; -import org.apache.qpid.client.handler.ChannelFlowOkMethodHandler; -import org.apache.qpid.client.handler.ConnectionCloseMethodHandler; -import org.apache.qpid.client.handler.ConnectionOpenOkMethodHandler; -import org.apache.qpid.client.handler.ConnectionSecureMethodHandler; -import org.apache.qpid.client.handler.ConnectionStartMethodHandler; -import org.apache.qpid.client.handler.ConnectionTuneMethodHandler; -import org.apache.qpid.client.handler.ExchangeBoundOkMethodHandler; -import org.apache.qpid.client.handler.QueueDeleteOkMethodHandler; import org.apache.qpid.client.protocol.AMQProtocolSession; -import org.apache.qpid.framing.AMQMethodBody; -import org.apache.qpid.framing.BasicCancelOkBody; -import org.apache.qpid.framing.BasicDeliverBody; -import org.apache.qpid.framing.BasicReturnBody; -import org.apache.qpid.framing.ChannelCloseBody; -import org.apache.qpid.framing.ChannelCloseOkBody; -import org.apache.qpid.framing.ChannelFlowOkBody; -import org.apache.qpid.framing.ConnectionCloseBody; -import org.apache.qpid.framing.ConnectionOpenOkBody; -import org.apache.qpid.framing.ConnectionSecureBody; -import org.apache.qpid.framing.ConnectionStartBody; -import org.apache.qpid.framing.ConnectionTuneBody; -import org.apache.qpid.framing.ExchangeBoundOkBody; -import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQMethodEvent; import org.apache.qpid.protocol.AMQMethodListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.HashMap; import java.util.Iterator; -import java.util.Map; +import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; /** * The state manager is responsible for managing the state of the protocol session.

For each AMQProtocolHandler * there is a separate state manager. */ -public class AMQStateManager implements AMQMethodListener +public class AMQStateManager { private static final Logger _logger = LoggerFactory.getLogger(AMQStateManager.class); @@ -72,15 +45,15 @@ public class AMQStateManager implements AMQMethodListener /** The current state */ private AMQState _currentState; + /** * Maps from an AMQState instance to a Map from Class to StateTransitionHandler. The class must be a subclass of * AMQFrame. */ - protected final Map _state2HandlersMap = new HashMap(); - private final CopyOnWriteArraySet _stateListeners = new CopyOnWriteArraySet(); + private final Object _stateLock = new Object(); - private static final long MAXIMUM_STATE_WAIT_TIME = 30000L; + private static final long MAXIMUM_STATE_WAIT_TIME = Long.parseLong(System.getProperty("amqj.MaximumStateWait", "30000")); public AMQStateManager() { @@ -96,54 +69,11 @@ public class AMQStateManager implements AMQMethodListener { _protocolSession = protocolSession; _currentState = state; - if (register) - { - registerListeners(); - } - } - protected void registerListeners() - { - Map frame2handlerMap = new HashMap(); - - // we need to register a map for the null (i.e. all state) handlers otherwise you get - // a stack overflow in the handler searching code when you present it with a frame for which - // no handlers are registered - // - _state2HandlersMap.put(null, frame2handlerMap); - - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ConnectionStartBody.class, ConnectionStartMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); - - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ConnectionTuneBody.class, ConnectionTuneMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionSecureBody.class, ConnectionSecureMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); - - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ConnectionOpenOkBody.class, ConnectionOpenOkMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); - - // - // ConnectionOpen handlers - // - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseMethodHandler.getInstance()); - frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - frame2handlerMap.put(BasicDeliverBody.class, BasicDeliverMethodHandler.getInstance()); - frame2handlerMap.put(BasicReturnBody.class, BasicReturnMethodHandler.getInstance()); - frame2handlerMap.put(BasicCancelOkBody.class, BasicCancelOkMethodHandler.getInstance()); - frame2handlerMap.put(ChannelFlowOkBody.class, ChannelFlowOkMethodHandler.getInstance()); - frame2handlerMap.put(QueueDeleteOkBody.class, QueueDeleteOkMethodHandler.getInstance()); - frame2handlerMap.put(ExchangeBoundOkBody.class, ExchangeBoundOkMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); } + + public AMQState getCurrentState() { return _currentState; @@ -160,72 +90,17 @@ public class AMQStateManager implements AMQMethodListener } } - public void error(Exception e) - { - _logger.debug("State manager receive error notification: " + e); - synchronized (_stateListeners) - { - final Iterator it = _stateListeners.iterator(); - while (it.hasNext()) - { - final StateListener l = (StateListener) it.next(); - l.error(e); - } - } - } public boolean methodReceived(AMQMethodEvent evt) throws AMQException { - StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod()); - if (handler != null) - { - handler.methodReceived(this, _protocolSession, evt); - - return true; - } - return false; + B method = evt.getMethod(); + + // StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod()); + method.execute(_protocolSession.getMethodDispatcher(), evt.getChannelId()); + return true; } - protected StateAwareMethodListener findStateTransitionHandler(AMQState currentState, AMQMethodBody frame) - // throws IllegalStateTransitionException - { - final Class clazz = frame.getClass(); - if (_logger.isDebugEnabled()) - { - _logger.debug("Looking for state[" + currentState + "] transition handler for frame " + clazz); - } - - final Map classToHandlerMap = (Map) _state2HandlersMap.get(currentState); - - if (classToHandlerMap == null) - { - // if no specialised per state handler is registered look for a - // handler registered for "all" states - return findStateTransitionHandler(null, frame); - } - - final StateAwareMethodListener handler = (StateAwareMethodListener) classToHandlerMap.get(clazz); - if (handler == null) - { - if (currentState == null) - { - _logger.debug("No state transition handler defined for receiving frame " + frame); - - return null; - } - else - { - // if no specialised per state handler is registered look for a - // handler registered for "all" states - return findStateTransitionHandler(null, frame); - } - } - else - { - return handler; - } - } public void attainState(final AMQState s) throws AMQException { @@ -272,4 +147,46 @@ public class AMQStateManager implements AMQMethodListener { _protocolSession = session; } + + public MethodRegistry getMethodRegistry() + { + return getProtocolSession().getMethodRegistry(); + } + + public AMQState attainState(Set stateSet) throws AMQException + { + synchronized (_stateLock) + { + final long waitUntilTime = System.currentTimeMillis() + MAXIMUM_STATE_WAIT_TIME; + long waitTime = MAXIMUM_STATE_WAIT_TIME; + + while (!stateSet.contains(_currentState) && (waitTime > 0)) + { + try + { + _stateLock.wait(MAXIMUM_STATE_WAIT_TIME); + } + catch (InterruptedException e) + { + _logger.warn("Thread interrupted"); + } + + if (!stateSet.contains(_currentState)) + { + waitTime = waitUntilTime - System.currentTimeMillis(); + } + } + + if (!stateSet.contains(_currentState)) + { + _logger.warn("State not achieved within permitted time. Current state " + _currentState + + ", desired state: " + stateSet); + throw new AMQException(null, "State not achieved within permitted time. Current state " + _currentState + + ", desired state: " + stateSet, null); + } + return _currentState; + } + + + } } diff --git a/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java b/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java index b3932533ce..8c65f56af3 100644 --- a/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java +++ b/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java @@ -21,6 +21,7 @@ package org.apache.qpid.client.state; import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.protocol.AMQMethodEvent; @@ -29,8 +30,9 @@ import org.apache.qpid.protocol.AMQMethodEvent; * the opportunity to update state. * */ -public interface StateAwareMethodListener +public interface StateAwareMethodListener { - void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, - AMQMethodEvent evt) throws AMQException; + + void methodReceived(AMQStateManager stateManager, B body, int channelId) throws AMQException; + } diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java index 5482e48699..b2f7ae8395 100644 --- a/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java @@ -24,6 +24,7 @@ import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.ConnectFuture; import org.apache.mina.common.IoConnector; import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.transport.socket.nio.ExistingSocketConnector; import org.apache.mina.transport.socket.nio.SocketConnectorConfig; import org.apache.mina.transport.socket.nio.SocketSessionConfig; @@ -36,6 +37,9 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class SocketTransportConnection implements ITransportConnection { @@ -83,8 +87,34 @@ public class SocketTransportConnection implements ITransportConnection _logger.info("send-buffer-size = " + scfg.getSendBufferSize()); scfg.setReceiveBufferSize(Integer.getInteger("amqj.receiveBufferSize", DEFAULT_BUFFER_SIZE)); _logger.info("recv-buffer-size = " + scfg.getReceiveBufferSize()); - final InetSocketAddress address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort()); - _logger.info("Attempting connection to " + address); + + final InetSocketAddress address; + + if (brokerDetail.getTransport().equals(BrokerDetails.SOCKET)) + { + address = null; + + Socket socket = TransportConnection.removeOpenSocket(brokerDetail.getHost()); + + if (socket != null) + { + _logger.info("Using existing Socket:" + socket); + + ((ExistingSocketConnector) ioConnector).setOpenSocket(socket); + } + else + { + throw new IllegalArgumentException("Active Socket must be provided for broker " + + "with 'socket://' transport:" + brokerDetail); + } + } + else + { + address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort()); + _logger.info("Attempting connection to " + address); + } + + ConnectFuture future = ioConnector.connect(address, protocolHandler); // wait for connection to complete diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java index 883cb2340a..a871c754b5 100644 --- a/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java @@ -23,6 +23,8 @@ package org.apache.qpid.client.transport; import org.apache.mina.common.IoConnector; import org.apache.mina.common.IoHandlerAdapter; import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.transport.socket.nio.ExistingSocketConnector; +import org.apache.mina.transport.socket.nio.MultiThreadSocketConnector; import org.apache.mina.transport.socket.nio.SocketConnector; import org.apache.mina.transport.vmpipe.VmPipeAcceptor; import org.apache.mina.transport.vmpipe.VmPipeAddress; @@ -34,8 +36,10 @@ import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.net.Socket; + /** * The TransportConnection is a helper class responsible for connecting to an AMQ server. It sets up the underlying @@ -54,12 +58,25 @@ public class TransportConnection private static final int TCP = 0; private static final int VM = 1; + private static final int SOCKET = 2; private static Logger _logger = LoggerFactory.getLogger(TransportConnection.class); private static final String DEFAULT_QPID_SERVER = "org.apache.qpid.server.protocol.AMQPFastProtocolHandler"; - public static ITransportConnection getInstance(BrokerDetails details) throws AMQTransportConnectionException + private static Map _openSocketRegister = new ConcurrentHashMap(); + + public static void registerOpenSocket(String socketID, Socket openSocket) + { + _openSocketRegister.put(socketID, openSocket); + } + + public static Socket removeOpenSocket(String socketID) + { + return _openSocketRegister.remove(socketID); + } + + public static synchronized ITransportConnection getInstance(BrokerDetails details) throws AMQTransportConnectionException { int transport = getTransport(details.getTransport()); @@ -68,7 +85,7 @@ public class TransportConnection throw new AMQNoTransportForProtocolException(details, null, null); } - if (transport == _currentInstance) + /* if (transport == _currentInstance) { if (transport == VM) { @@ -83,13 +100,23 @@ public class TransportConnection } } - _currentInstance = transport; + _currentInstance = transport;*/ + ITransportConnection instance; switch (transport) { - + case SOCKET: + instance = + new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() + { + public IoConnector newSocketConnector() + { + return new ExistingSocketConnector(); + } + }); + break; case TCP: - _instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() + instance = new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory() { public IoConnector newSocketConnector() { @@ -97,38 +124,44 @@ public class TransportConnection // FIXME - this needs to be sorted to use the new Mina MultiThread SA. if (Boolean.getBoolean("qpidnio")) { - _logger.error("Using Qpid NIO - sysproperty 'qpidnio' is set."); - // result = new org.apache.qpid.nio.SocketConnector(); // non-blocking connector + _logger.warn("Using Qpid MultiThreaded NIO - " + (System.getProperties().containsKey("qpidnio") + ? "Qpid NIO is new default" + : "Sysproperty 'qpidnio' is set")); + result = new MultiThreadSocketConnector(); } - // else - + else { _logger.info("Using Mina NIO"); result = new SocketConnector(); // non-blocking connector } - // Don't have the connector's worker thread wait around for other connections (we only use // one SocketConnector per connection at the moment anyway). This allows short-running // clients (like unit tests) to complete quickly. result.setWorkerTimeout(0); - return result; } }); break; - case VM: { - _instance = getVMTransport(details, Boolean.getBoolean("amqj.AutoCreateVMBroker")); + instance = getVMTransport(details, Boolean.getBoolean("amqj.AutoCreateVMBroker")); break; } + default: + // FIXME: TGM + throw new AMQNoTransportForProtocolException(details, null, null); } - return _instance; + return instance; } private static int getTransport(String transport) { + if (transport.equals(BrokerDetails.SOCKET)) + { + return SOCKET; + } + if (transport.equals(BrokerDetails.TCP)) { return TCP; @@ -151,7 +184,15 @@ public class TransportConnection { if (AutoCreate) { - createVMBroker(port); + if (AutoCreate) + { + createVMBroker(port); + } + else + { + throw new AMQVMBrokerCreationException(null, port, "VM Broker on port " + port + + " does not exist. Auto create disabled.", null); + } } else { @@ -281,16 +322,17 @@ public class TransportConnection public static void killAllVMBrokers() { _logger.info("Killing all VM Brokers"); - _acceptor.unbindAll(); - - Iterator keys = _inVmPipeAddress.keySet().iterator(); - - while (keys.hasNext()) + if (_acceptor != null) { - int id = (Integer) keys.next(); - _inVmPipeAddress.remove(id); + _acceptor.unbindAll(); } - + synchronized (_inVmPipeAddress) + { + _inVmPipeAddress.clear(); + } + _acceptor = null; + _currentInstance = -1; + _currentVMPort = -1; } public static void killVMBroker(int port) @@ -300,6 +342,8 @@ public class TransportConnection { _logger.info("Killing VM Broker:" + port); _inVmPipeAddress.remove(port); + // This does need to be sychronized as otherwise mina can hang + // if a new connection is made _acceptor.unbind(pipe); } } diff --git a/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java b/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java index d9137dc8b1..dca6efba67 100644 --- a/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java +++ b/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java @@ -22,15 +22,12 @@ package org.apache.qpid.client.transport; import org.apache.mina.common.ConnectFuture; import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.transport.vmpipe.QpidVmPipeConnector; import org.apache.mina.transport.vmpipe.VmPipeAddress; import org.apache.mina.transport.vmpipe.VmPipeConnector; - import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.jms.BrokerDetails; -import org.apache.qpid.pool.PoolingFilter; -import org.apache.qpid.pool.ReferenceCountingExecutorService; import org.apache.qpid.pool.ReadWriteThreadModel; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,10 +46,10 @@ public class VmPipeTransportConnection implements ITransportConnection public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) throws IOException { - final VmPipeConnector ioConnector = new VmPipeConnector(); + final VmPipeConnector ioConnector = new QpidVmPipeConnector(); final IoServiceConfig cfg = ioConnector.getDefaultConfig(); - cfg.setThreadModel(ReadWriteThreadModel.getInstance()); + cfg.setThreadModel(ReadWriteThreadModel.getInstance()); final VmPipeAddress address = new VmPipeAddress(_port); _logger.info("Attempting connection to " + address); diff --git a/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java b/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java index 133285fe87..72ba16086d 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java +++ b/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java @@ -33,8 +33,10 @@ public interface BrokerDetails */ public static final String OPTIONS_RETRY = "retries"; public static final String OPTIONS_CONNECT_TIMEOUT = "connecttimeout"; + public static final String OPTIONS_CONNECT_DELAY = "connectdelay"; public static final int DEFAULT_PORT = 5672; + public static final String SOCKET = "socket"; public static final String TCP = "tcp"; public static final String VM = "vm"; diff --git a/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java b/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java index 11376467d7..da8cd4f750 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java +++ b/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java @@ -21,6 +21,7 @@ package org.apache.qpid.jms; import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ProtocolVersion; import java.util.List; @@ -45,7 +46,7 @@ public interface ConnectionURL public static final String OPTIONS_TEMPORARY_QUEUE_EXCHANGE = "temporaryQueueExchange"; public static final byte URL_0_8 = 1; public static final byte URL_0_10 = 2; - + String getURL(); String getFailoverMethod(); @@ -89,4 +90,5 @@ public interface ConnectionURL AMQShortString getTemporaryQueueExchangeName(); AMQShortString getTemporaryTopicExchangeName(); + } diff --git a/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java b/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java index 6ec883ff0b..8e3ccc3b02 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java +++ b/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java @@ -34,7 +34,6 @@ public class FailoverPolicy private static final long MINUTE = 60000L; private static final long DEFAULT_METHOD_TIMEOUT = 1 * MINUTE; - private static final long DEFAULT_FAILOVER_TIMEOUT = 4 * MINUTE; private FailoverMethod[] _methods = new FailoverMethod[1]; @@ -161,16 +160,7 @@ public class FailoverPolicy } else { - if ((now - _lastFailTime) >= DEFAULT_FAILOVER_TIMEOUT) - { - _logger.info("Failover timeout"); - - return false; - } - else - { - _lastMethodTime = now; - } + _lastMethodTime = now; } } else diff --git a/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java b/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java index b91fc2d960..b830c377b8 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java +++ b/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java @@ -50,4 +50,8 @@ public interface MessageProducer extends javax.jms.MessageProducer void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, boolean mandatory, boolean immediate) throws JMSException; + + void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive, + boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException; + } diff --git a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java index 74a46de63b..9c172da6a2 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java +++ b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java @@ -22,7 +22,6 @@ package org.apache.qpid.jms.failover; import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.jms.ConnectionURL; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,34 +34,22 @@ public class FailoverRoundRobinServers implements FailoverMethod /** The default number of times to retry each server */ public static final int DEFAULT_SERVER_RETRIES = 0; - /** - * The index into the hostDetails array of the broker to which we are connected - */ + /** The index into the hostDetails array of the broker to which we are connected */ private int _currentBrokerIndex = -1; - /** - * The number of times to retry connecting for each server - */ + /** The number of times to retry connecting for each server */ private int _serverRetries; - /** - * The current number of retry attempts made - */ + /** The current number of retry attempts made */ private int _currentServerRetry; - /** - * The number of times to cycle through the servers - */ + /** The number of times to cycle through the servers */ private int _cycleRetries; - /** - * The current number of cycles performed. - */ + /** The current number of cycles performed. */ private int _currentCycleRetries; - /** - * Array of BrokerDetail used to make connections. - */ + /** Array of BrokerDetail used to make connections. */ private ConnectionURL _connectionDetails; public FailoverRoundRobinServers(ConnectionURL connectionDetails) @@ -128,6 +115,8 @@ public class FailoverRoundRobinServers implements FailoverMethod public BrokerDetails getNextBrokerDetails() { + boolean doDelay = false; + if (_currentBrokerIndex == (_connectionDetails.getBrokerCount() - 1)) { if (_currentServerRetry < _serverRetries) @@ -143,6 +132,7 @@ public class FailoverRoundRobinServers implements FailoverMethod else { _logger.info("Retrying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex)); + doDelay=true; } _currentServerRetry++; @@ -175,6 +165,7 @@ public class FailoverRoundRobinServers implements FailoverMethod else { _logger.info("Retrying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex)); + doDelay=true; } _currentServerRetry++; @@ -189,7 +180,28 @@ public class FailoverRoundRobinServers implements FailoverMethod } } - return _connectionDetails.getBrokerDetails(_currentBrokerIndex); + BrokerDetails broker = _connectionDetails.getBrokerDetails(_currentBrokerIndex); + + String delayStr = broker.getProperty(BrokerDetails.OPTIONS_CONNECT_DELAY); + if (delayStr != null && doDelay) + { + Long delay = Long.parseLong(delayStr); + _logger.info("Delay between connect retries:" + delay); + try + { + Thread.sleep(delay); + } + catch (InterruptedException ie) + { + return null; + } + } + else + { + _logger.info("No delay between connect retries, use tcp://host:port?connectdelay='value' to enable."); + } + + return broker; } public void setBroker(BrokerDetails broker) diff --git a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java index d59c1fb98d..b1bc3cee96 100644 --- a/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java +++ b/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java @@ -22,25 +22,23 @@ package org.apache.qpid.jms.failover; import org.apache.qpid.jms.BrokerDetails; import org.apache.qpid.jms.ConnectionURL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FailoverSingleServer implements FailoverMethod { + private static final Logger _logger = LoggerFactory.getLogger(FailoverSingleServer.class); + /** The default number of times to rety a conection to this server */ public static final int DEFAULT_SERVER_RETRIES = 1; - /** - * The details of the Single Server - */ + /** The details of the Single Server */ private BrokerDetails _brokerDetail; - /** - * The number of times to retry connecting to the sever - */ + /** The number of times to retry connecting to the sever */ private int _retries; - /** - * The current number of attempts made to the server - */ + /** The current number of attempts made to the server */ private int _currentRetries; @@ -78,7 +76,7 @@ public class FailoverSingleServer implements FailoverMethod public BrokerDetails getCurrentBrokerDetails() { - return _brokerDetail; + return _brokerDetail; } public BrokerDetails getNextBrokerDetails() @@ -91,11 +89,29 @@ public class FailoverSingleServer implements FailoverMethod { if (_currentRetries < _retries) { - _currentRetries ++; + _currentRetries++; } + } + - return _brokerDetail; + String delayStr = _brokerDetail.getProperty(BrokerDetails.OPTIONS_CONNECT_DELAY); + if (delayStr != null && _currentRetries != 1) + { + Long delay = Long.parseLong(delayStr); + _logger.info("Delay between connect retries:" + delay); + try + { + + Thread.sleep(delay); + } + catch (InterruptedException ie) + { + _logger.info("No delay between connect retries, use tcp://host:port?connectdelay='value' to enable."); + return null; + } } + + return _brokerDetail; } public void setBroker(BrokerDetails broker) @@ -138,10 +154,10 @@ public class FailoverSingleServer implements FailoverMethod public String toString() { - return "SingleServer:\n"+ - "Max Retries:"+_retries+ - "\nCurrent Retry:"+_currentRetries+ - "\n"+_brokerDetail+"\n"; + return "SingleServer:\n" + + "Max Retries:" + _retries + + "\nCurrent Retry:" + _currentRetries + + "\n" + _brokerDetail + "\n"; } } diff --git a/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java b/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java index 42a168bad8..43ac56dee9 100644 --- a/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java +++ b/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java @@ -99,8 +99,6 @@ public class PropertiesFileInitialContextFactory implements InitialContextFactor _logger.warn("Unable to load property file specified in Provider_URL:" + environment.get(Context.PROVIDER_URL)); } - - createConnectionFactories(data, environment); createDestinations(data, environment); diff --git a/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java b/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java index a246352d8b..2fe01fc126 100644 --- a/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java +++ b/java/client/src/old_test/java/org/apache/qpid/fragmentation/TestLargePublisher.java @@ -183,7 +183,7 @@ public class TestLargePublisher } catch (UnknownHostException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } catch (AMQException e) { diff --git a/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java b/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java index 33891142b5..37b4ff1498 100644 --- a/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java +++ b/java/client/src/old_test/java/org/apache/qpid/pubsub1/TestPublisher.java @@ -133,7 +133,7 @@ public class TestPublisher } catch (JMSException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } } @@ -163,7 +163,7 @@ public class TestPublisher } catch (UnknownHostException e) { - e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + e.printStackTrace(); } catch (AMQException e) { diff --git a/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java b/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java index 8724c65b61..fe418535d6 100644 --- a/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java @@ -126,7 +126,7 @@ public class AMQQueueDeferredOrderingTest extends TestCase _logger.info("Consuming messages"); for (int i = 0; i < NUM_MESSAGES; i++) { - Message msg = consumer.receive(1500); + Message msg = consumer.receive(3000); assertNotNull("Message should not be null", msg); assertTrue("Message should be a text message", msg instanceof TextMessage); assertEquals("Message content does not match expected", Integer.toString(i), ((TextMessage) msg).getText()); diff --git a/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java b/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java index 75e50ee09b..136b9b94b6 100644 --- a/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java @@ -20,13 +20,17 @@ */ package org.apache.qpid.client; +import junit.framework.TestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.Connection; +import javax.jms.ConnectionFactory; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; @@ -34,7 +38,9 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.naming.Context; +import javax.naming.spi.InitialContextFactory; +import java.util.Hashtable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java b/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java index 2eb511f8cd..12b84b1495 100644 --- a/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/MessageListenerTest.java @@ -20,13 +20,17 @@ */ package org.apache.qpid.client; +import junit.framework.TestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jms.Connection; +import javax.jms.ConnectionFactory; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageListener; @@ -34,6 +38,9 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.naming.Context; +import javax.naming.spi.InitialContextFactory; + +import java.util.Hashtable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java b/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java index 882915fb8f..c920499a07 100644 --- a/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java +++ b/java/client/src/test/java/org/apache/qpid/client/ResetMessageListenerTest.java @@ -29,7 +29,16 @@ import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.jms.*; +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; import javax.naming.Context; import javax.naming.spi.InitialContextFactory; @@ -65,12 +74,15 @@ public class ResetMessageListenerTest extends QpidTestCase private final CountDownLatch _allFirstMessagesSent = new CountDownLatch(2); // all messages Sent Lock private final CountDownLatch _allSecondMessagesSent = new CountDownLatch(2); // all messages Sent Lock private final CountDownLatch _allFirstMessagesSent010 = new CountDownLatch(MSG_COUNT); // all messages Sent Lock - private final CountDownLatch _allSecondMessagesSent010 = new CountDownLatch(MSG_COUNT); // all messages Sent Lock + private final CountDownLatch _allSecondMessagesSent010 = new CountDownLatch(MSG_COUNT); // all messages Sent Lock + + private String oldImmediatePrefetch; protected void setUp() throws Exception { super.setUp(); + oldImmediatePrefetch = System.getProperty(AMQSession.IMMEDIATE_PREFETCH); System.setProperty(AMQSession.IMMEDIATE_PREFETCH, "true"); _clientConnection = getConnection("guest", "guest"); @@ -109,8 +121,12 @@ public class ResetMessageListenerTest extends QpidTestCase { _clientConnection.close(); - _producerConnection.close(); super.tearDown(); + if (oldImmediatePrefetch == null) + { + oldImmediatePrefetch = AMQSession.IMMEDIATE_PREFETCH_DEFAULT; + } + System.setProperty(AMQSession.IMMEDIATE_PREFETCH, oldImmediatePrefetch); } public void testAsynchronousRecieve() @@ -238,7 +254,7 @@ public class ResetMessageListenerTest extends QpidTestCase try { - _allSecondMessagesSent.await(1000, TimeUnit.MILLISECONDS); + _allSecondMessagesSent.await(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java index 737daeb350..61ba3aad3a 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java @@ -60,6 +60,7 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener private final List messages = new ArrayList(); private int _count = 1; public String _connectionString = "vm://:1"; + private static final String USERNAME = "guest"; protected void setUp() throws Exception { @@ -171,7 +172,7 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener m.setJMSReplyTo(q); m.setStringProperty("TempQueue", q.toString()); - _logger.trace("Message:" + m); + _logger.debug("Message:" + m); Assert.assertEquals("Check temp queue has been set correctly", m.getJMSReplyTo().toString(), m.getStringProperty("TempQueue")); @@ -287,7 +288,14 @@ public class PropertyValueTest extends QpidTestCase implements MessageListener ((AMQMessage) m).setVoidProperty(new AMQShortString("void")); Assert.assertTrue("Check void properties are correctly transported", - ((AMQMessage) m).getPropertyHeaders().containsKey("void")); + ((AMQMessage) m).getPropertyHeaders().containsKey("void")); + + //JMSXUserID + if (m.getStringProperty("JMSXUserID") != null) + { + Assert.assertEquals("Check 'JMSXUserID' is supported ", USERNAME, + m.getStringProperty("JMSXUserID")); + } } received.clear(); diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java index ed4f6041df..987b30ce28 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/SelectorTest.java @@ -21,7 +21,7 @@ package org.apache.qpid.test.unit.basic; import junit.framework.TestCase; - +import org.apache.qpid.AMQException; import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.AMQDestination; import org.apache.qpid.client.AMQQueue; @@ -29,11 +29,14 @@ import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.BasicMessageProducer; import org.apache.qpid.client.transport.TransportConnection; import org.apache.qpid.testutil.QpidTestCase; +import org.apache.qpid.url.URLSyntaxException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jms.Connection; import javax.jms.DeliveryMode; +import javax.jms.InvalidSelectorException; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; @@ -47,11 +50,12 @@ public class SelectorTest extends QpidTestCase implements MessageListener private AMQSession _session; private int count; public String _connectionString = "vm://:1"; + private static final String INVALID_SELECTOR = "Cost LIKE 5"; protected void setUp() throws Exception { super.setUp(); - init((AMQConnection) getConnection("guest", "guest")); + init((AMQConnection) getConnection("guest", "guest")); } protected void tearDown() throws Exception @@ -59,19 +63,19 @@ public class SelectorTest extends QpidTestCase implements MessageListener super.tearDown(); } - private void init(AMQConnection connection) throws Exception + private void init(AMQConnection connection) throws JMSException { init(connection, new AMQQueue(connection, randomize("SessionStartTest"), true)); } - private void init(AMQConnection connection, AMQDestination destination) throws Exception + private void init(AMQConnection connection, AMQDestination destination) throws JMSException { _connection = connection; _destination = destination; connection.start(); String selector = null; - // selector = "Cost = 2 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT; + selector = "Cost = 2 AND \"property-with-hyphen\" = 'wibble'"; // selector = "JMSType = Special AND Cost = 2 AND AMQMessageID > 0 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT; _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE); @@ -79,13 +83,17 @@ public class SelectorTest extends QpidTestCase implements MessageListener _session.createConsumer(destination, selector).setMessageListener(this); } - public synchronized void test() throws JMSException, InterruptedException + public synchronized void test() throws Exception { try { + + init((AMQConnection) getConnection("guest", "guest", randomize("Client"))); + Message msg = _session.createTextMessage("Message"); msg.setJMSPriority(1); msg.setIntProperty("Cost", 2); + msg.setStringProperty("property-with-hyphen", "wibble"); msg.setJMSType("Special"); _logger.info("Sending Message:" + msg); @@ -105,10 +113,147 @@ public class SelectorTest extends QpidTestCase implements MessageListener // throw new RuntimeException("Did not get message!"); } } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + System.out.println("SUCCESS!!"); + } + } + catch (InterruptedException e) + { + _logger.debug("IE :" + e.getClass().getSimpleName() + ":" + e.getMessage()); + } + catch (URLSyntaxException e) + { + _logger.debug("URL:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + fail("Wrong exception"); + } + catch (AMQException e) + { + _logger.debug("AMQ:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + fail("Wrong exception"); + } + finally { - _session.close(); - _connection.close(); + if (_session != null) + { + _session.close(); + } + if (_connection != null) + { + _connection.close(); + } + } + } + + + public void testInvalidSelectors() throws Exception + { + Connection connection = null; + + try + { + connection = getConnection("guest", "guest", randomize("Client")); + _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE); + } + catch (JMSException e) + { + fail(e.getMessage()); + } + catch (AMQException e) + { + fail(e.getMessage()); + } + catch (URLSyntaxException e) + { + fail("Error:" + e.getMessage()); + } + + //Try Creating a Browser + try + { + _session.createBrowser(_session.createQueue("Ping"), INVALID_SELECTOR); + } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + _logger.debug("SUCCESS!!"); + } + } + + //Try Creating a Consumer + try + { + _session.createConsumer(_session.createQueue("Ping"), INVALID_SELECTOR); + } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + _logger.debug("SUCCESS!!"); + } + } + + //Try Creating a Receiever + try + { + _session.createReceiver(_session.createQueue("Ping"), INVALID_SELECTOR); + } + catch (JMSException e) + { + _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage()); + if (!(e instanceof InvalidSelectorException)) + { + fail("Wrong exception:" + e.getMessage()); + } + else + { + _logger.debug("SUCCESS!!"); + } + } + + finally + { + if (_session != null) + { + try + { + _session.close(); + } + catch (JMSException e) + { + fail("Error cleaning up:" + e.getMessage()); + } + } + if (_connection != null) + { + try + { + _connection.close(); + } + catch (JMSException e) + { + fail("Error cleaning up:" + e.getMessage()); + } + } } } @@ -127,9 +272,29 @@ public class SelectorTest extends QpidTestCase implements MessageListener public static void main(String[] argv) throws Exception { SelectorTest test = new SelectorTest(); - test._connectionString = (argv.length == 0) ? "localhost:5672" : argv[0]; - test.setUp(); - test.test(); + test._connectionString = (argv.length == 0) ? "localhost:3000" : argv[0]; + + try + { + while (true) + { + if (test._connectionString.contains("vm://:1")) + { + test.setUp(); + } + test.test(); + + if (test._connectionString.contains("vm://:1")) + { + test.tearDown(); + } + } + } + catch (Exception e) + { + System.err.println(e.getMessage()); + e.printStackTrace(); + } } public static junit.framework.Test suite() diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTest.java new file mode 100644 index 0000000000..6f1ddebb0c --- /dev/null +++ b/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTest.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.test.unit.basic.close; +import org.apache.qpid.AMQException; +import org.apache.qpid.testutil.QpidTestCase; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.url.AMQBindingURL; +import org.apache.qpid.url.URLSyntaxException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jms.JMSException; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; + +public class CloseTest extends QpidTestCase +{ + private static final Logger _logger = LoggerFactory.getLogger(CloseTest.class); + + private static final String BROKER = "vm://:1"; + + protected void setUp() throws Exception + { + super.setUp(); + } + + protected void tearDown() throws Exception + { + super.setUp(); + } + + public void testCloseQueueReceiver() throws Exception + { + AMQConnection connection = (AMQConnection) getConnection("guest", "guest"); + + Session session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); + + AMQQueue queue = new AMQQueue(new AMQBindingURL("test-queue")); + MessageConsumer consumer = session.createConsumer(queue); + + MessageProducer producer_not_used_but_created_for_testing = session.createProducer(queue); + + connection.start(); + + _logger.info("About to close consumer"); + + consumer.close(); + + _logger.info("Closed Consumer"); + connection.close(); + } +} diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java b/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java deleted file mode 100644 index 10c054a863..0000000000 --- a/java/client/src/test/java/org/apache/qpid/test/unit/basic/close/CloseTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - * - */ -package org.apache.qpid.test.unit.basic.close; -import org.apache.qpid.AMQException; -import org.apache.qpid.testutil.QpidTestCase; -import org.apache.qpid.client.AMQConnection; -import org.apache.qpid.client.AMQQueue; -import org.apache.qpid.client.transport.TransportConnection; -import org.apache.qpid.url.AMQBindingURL; -import org.apache.qpid.url.URLSyntaxException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.jms.JMSException; -import javax.jms.MessageConsumer; -import javax.jms.MessageProducer; -import javax.jms.Session; - -public class CloseTests extends QpidTestCase -{ - private static final Logger _logger = LoggerFactory.getLogger(CloseTests.class); - - private static final String BROKER = "vm://:1"; - - protected void setUp() throws Exception - { - super.setUp(); - } - - protected void tearDown() throws Exception - { - super.setUp(); - } - - public void testCloseQueueReceiver() throws Exception - { - AMQConnection connection = (AMQConnection) getConnection("guest", "guest"); - - Session session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); - - AMQQueue queue = new AMQQueue(new AMQBindingURL("test-queue")); - MessageConsumer consumer = session.createConsumer(queue); - - MessageProducer producer_not_used_but_created_for_testing = session.createProducer(queue); - - connection.start(); - - _logger.info("About to close consumer"); - - consumer.close(); - - _logger.info("Closed Consumer"); - connection.close(); - } -} diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java index 6a4e01affd..965c22af4a 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/AMQSessionTest.java @@ -75,18 +75,11 @@ public class AMQSessionTest extends QpidTestCase public void testCreateDurableSubscriber() throws JMSException { - try - { - TopicSubscriber subscriber = _session.createDurableSubscriber(_topic, "mysubname"); - assertEquals("Topic names should match from durable TopicSubscriber", _topic.getTopicName(), subscriber.getTopic().getTopicName()); + TopicSubscriber subscriber = _session.createDurableSubscriber(_topic, "mysubname"); + assertEquals("Topic names should match from durable TopicSubscriber", _topic.getTopicName(), subscriber.getTopic().getTopicName()); - subscriber = _session.createDurableSubscriber(_topic, "mysubname", "abc", false); - assertEquals("Topic names should match from durable TopicSubscriber with selector", _topic.getTopicName(), subscriber.getTopic().getTopicName()); - } - catch (Throwable e) - { - e.printStackTrace(); - } + subscriber = _session.createDurableSubscriber(_topic, "mysubname2", "abc", false); + assertEquals("Topic names should match from durable TopicSubscriber with selector", _topic.getTopicName(), subscriber.getTopic().getTopicName()); } public void testCreateQueueReceiver() throws JMSException diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java index 85fcf6d95a..b6776a1a44 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseMethodHandlerNoCloseOk.java @@ -37,7 +37,7 @@ import org.apache.qpid.protocol.AMQMethodEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListener +public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListener { private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseMethodHandlerNoCloseOk.class); @@ -48,14 +48,15 @@ public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListe return _handler; } - public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, AMQMethodEvent evt) + public void methodReceived(AMQStateManager stateManager, ChannelCloseBody method, int channelId) throws AMQException { _logger.debug("ChannelClose method received"); - ChannelCloseBody method = (ChannelCloseBody) evt.getMethod(); + final AMQProtocolSession session = stateManager.getProtocolSession(); - AMQConstant errorCode = AMQConstant.getConstant(method.replyCode); - AMQShortString reason = method.replyText; + + AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode()); + AMQShortString reason = method.getReplyText(); if (_logger.isDebugEnabled()) { _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason); @@ -95,6 +96,6 @@ public class ChannelCloseMethodHandlerNoCloseOk implements StateAwareMethodListe } - protocolSession.channelClosed(evt.getChannelId(), errorCode, String.valueOf(reason)); + session.channelClosed(channelId, errorCode, String.valueOf(reason)); } } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java index 08d6b0bcab..45a9ca1dd6 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java @@ -26,17 +26,13 @@ import org.apache.qpid.AMQException; import org.apache.qpid.AMQTimeoutException; import org.apache.qpid.testutil.QpidTestCase; import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.handler.ClientMethodDispatcherImpl; import org.apache.qpid.client.failover.FailoverException; +import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.client.state.AMQStateManager; import org.apache.qpid.client.transport.TransportConnection; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ChannelCloseOkBody; -import org.apache.qpid.framing.ChannelOpenBody; -import org.apache.qpid.framing.ChannelOpenOkBody; -import org.apache.qpid.framing.ExchangeDeclareBody; -import org.apache.qpid.framing.ExchangeDeclareOkBody; +import org.apache.qpid.framing.*; import org.apache.qpid.jms.ConnectionListener; import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.url.URLSyntaxException; @@ -53,6 +49,9 @@ import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, ConnectionListener { @@ -140,8 +139,11 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, /* close channel and send guff then send ok no errors + REMOVE TEST - The behaviour after server has sent close is undefined. + the server should be free to fail as it may wish to reclaim its resources + immediately after close. */ - public void testSendingMethodsAfterClose() throws Exception + /*public void testSendingMethodsAfterClose() throws Exception { // this is testing an 0.8 connection if(isBroker08()) @@ -167,7 +169,19 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, // Set StateManager to manager that ignores Close-oks AMQProtocolSession protocolSession= ((AMQConnection) _connection).getProtocolHandler().getProtocolSession(); - AMQStateManager newStateManager=new NoCloseOKStateManager(protocolSession); + + MethodDispatcher d = protocolSession.getMethodDispatcher(); + + MethodDispatcher wrappedDispatcher = (MethodDispatcher) + Proxy.newProxyInstance(d.getClass().getClassLoader(), + d.getClass().getInterfaces(), + new MethodDispatcherProxyHandler( + (ClientMethodDispatcherImpl) d)); + + protocolSession.setMethodDispatcher(wrappedDispatcher); + + + AMQStateManager newStateManager=new NoCloseOKStateManager(protocolSession); newStateManager.changeState(oldStateManager.getCurrentState()); ((AMQConnection) _connection).getProtocolHandler().setStateManager(newStateManager); @@ -257,7 +271,7 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, } } } - +*/ private void createChannelAndTest(int channel) throws FailoverException { // Create A channel @@ -284,10 +298,9 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, private void sendClose(int channel) { - AMQFrame frame = - ChannelCloseOkBody.createAMQFrame(channel, - ((AMQConnection) _connection).getProtocolHandler().getProtocolMajorVersion(), - ((AMQConnection) _connection).getProtocolHandler().getProtocolMinorVersion()); + ChannelCloseOkBody body = + ((AMQConnection) _connection).getProtocolHandler().getMethodRegistry().createChannelCloseOkBody(); + AMQFrame frame = body.generateFrame(channel); ((AMQConnection) _connection).getProtocolHandler().writeFrame(frame); } @@ -345,35 +358,43 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, private void declareExchange(int channelId, String _type, String _name, boolean nowait) throws AMQException, FailoverException { - AMQFrame exchangeDeclare = - ExchangeDeclareBody.createAMQFrame(channelId, - ((AMQConnection) _connection).getProtocolHandler().getProtocolMajorVersion(), - ((AMQConnection) _connection).getProtocolHandler().getProtocolMinorVersion(), null, // arguments - false, // autoDelete - false, // durable - new AMQShortString(_name), // exchange - false, // internal - nowait, // nowait - true, // passive - 0, // ticket - new AMQShortString(_type)); // type - - if (nowait) - { - ((AMQConnection) _connection).getProtocolHandler().writeFrame(exchangeDeclare); - } - else - { - ((AMQConnection) _connection).getProtocolHandler().syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class, - SYNC_TIMEOUT); - } + ExchangeDeclareBody body = + ((AMQConnection) _connection).getProtocolHandler() + .getMethodRegistry() + .createExchangeDeclareBody(0, + new AMQShortString(_name), + new AMQShortString(_type), + true, + false, + false, + false, + nowait, + null); + AMQFrame exchangeDeclare = body.generateFrame(channelId); + AMQProtocolHandler protocolHandler = ((AMQConnection) _connection).getProtocolHandler(); + + + if (nowait) + { + protocolHandler.writeFrame(exchangeDeclare); + } + else + { + protocolHandler.syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class, SYNC_TIMEOUT); + } + +// return null; +// } +// }, (AMQConnection)_connection).execute(); + } private void createChannel(int channelId) throws AMQException, FailoverException { - ((AMQConnection) _connection).getProtocolHandler().syncWrite(ChannelOpenBody.createAMQFrame(channelId, - ((AMQConnection) _connection).getProtocolHandler().getProtocolMajorVersion(), - ((AMQConnection) _connection).getProtocolHandler().getProtocolMinorVersion(), null), // outOfBand + ChannelOpenBody body = + ((AMQConnection) _connection).getProtocolHandler().getMethodRegistry().createChannelOpenBody(null); + + ((AMQConnection) _connection).getProtocolHandler().syncWrite(body.generateFrame(channelId), // outOfBand ChannelOpenOkBody.class); } @@ -402,4 +423,28 @@ public class ChannelCloseTest extends QpidTestCase implements ExceptionListener, public void failoverComplete() { } + + private static final class MethodDispatcherProxyHandler implements InvocationHandler + { + private final ClientMethodDispatcherImpl _underlyingDispatcher; + private final ChannelCloseMethodHandlerNoCloseOk _handler = ChannelCloseMethodHandlerNoCloseOk.getInstance(); + + + public MethodDispatcherProxyHandler(ClientMethodDispatcherImpl dispatcher) + { + _underlyingDispatcher = dispatcher; + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + if(method.getName().equals("dispatchChannelClose")) + { + _handler.methodReceived(_underlyingDispatcher.getStateManager(), + (ChannelCloseBody) args[0], (Integer)args[1]); + } + Method dispatcherMethod = _underlyingDispatcher.getClass().getMethod(method.getName(), method.getParameterTypes()); + return dispatcherMethod.invoke(_underlyingDispatcher, args); + + } + } } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java index d128f30727..c7eb745566 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/channelclose/NoCloseOKStateManager.java @@ -59,49 +59,7 @@ public class NoCloseOKStateManager extends AMQStateManager super(protocolSession); } - protected void registerListeners() - { - Map frame2handlerMap = new HashMap(); - - // we need to register a map for the null (i.e. all state) handlers otherwise you get - // a stack overflow in the handler searching code when you present it with a frame for which - // no handlers are registered - // - _state2HandlersMap.put(null, frame2handlerMap); - - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ConnectionStartBody.class, ConnectionStartMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); - - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ConnectionTuneBody.class, ConnectionTuneMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionSecureBody.class, ConnectionSecureMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); - - frame2handlerMap = new HashMap(); - frame2handlerMap.put(ConnectionOpenOkBody.class, ConnectionOpenOkMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); - - // - // ConnectionOpen handlers - // - frame2handlerMap = new HashMap(); - // Use Test Handler for Close methods to not send Close-OKs - frame2handlerMap.put(ChannelCloseBody.class, ChannelCloseMethodHandlerNoCloseOk.getInstance()); - - frame2handlerMap.put(ChannelCloseOkBody.class, ChannelCloseOkMethodHandler.getInstance()); - frame2handlerMap.put(ConnectionCloseBody.class, ConnectionCloseMethodHandler.getInstance()); - frame2handlerMap.put(BasicDeliverBody.class, BasicDeliverMethodHandler.getInstance()); - frame2handlerMap.put(BasicReturnBody.class, BasicReturnMethodHandler.getInstance()); - frame2handlerMap.put(BasicCancelOkBody.class, BasicCancelOkMethodHandler.getInstance()); - frame2handlerMap.put(ChannelFlowOkBody.class, ChannelFlowOkMethodHandler.getInstance()); - frame2handlerMap.put(QueueDeleteOkBody.class, QueueDeleteOkMethodHandler.getInstance()); - frame2handlerMap.put(ExchangeBoundOkBody.class, ExchangeBoundOkMethodHandler.getInstance()); - _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); - } + } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java index 7eb74e2492..f856e8c20b 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java @@ -33,6 +33,7 @@ import org.apache.qpid.jms.Session; import junit.framework.TestCase; import javax.jms.Connection; +import javax.jms.JMSException; import javax.jms.QueueSession; import javax.jms.TopicSession; @@ -55,25 +56,30 @@ public class ConnectionTest extends TestCase TransportConnection.killVMBroker(1); } - public void testSimpleConnection() + public void testSimpleConnection() throws Exception { + AMQConnection conn = null; try { - AMQConnection conn = new AMQConnection(_broker, "guest", "guest", "fred", "test"); - conn.close(); + conn = new AMQConnection(_broker, "guest", "guest", "fred", "test"); } catch (Exception e) { fail("Connection to " + _broker + " should succeed. Reason: " + e); } + finally + { + conn.close(); + } } - public void testDefaultExchanges() + public void testDefaultExchanges() throws Exception { + AMQConnection conn = null; try { - AMQConnection conn = new AMQConnection("amqp://guest:guest@clientid/test?brokerlist='" + conn = new AMQConnection("amqp://guest:guest@clientid/test?brokerlist='" + _broker + "?retries='1''&defaultQueueExchange='test.direct'" + "&defaultTopicExchange='test.topic'" @@ -106,37 +112,53 @@ public class ConnectionTest extends TestCase topicSession.close(); - - conn.close(); } catch (Exception e) { fail("Connection to " + _broker + " should succeed. Reason: " + e); } + finally + { + conn.close(); + } } - //fixme AMQAuthenticationException is not propogaged - public void PasswordFailureConnection() throws Exception + //See QPID-771 + public void testPasswordFailureConnection() throws Exception { + AMQConnection conn = null; try { - new AMQConnection("amqp://guest:rubbishpassword@clientid/test?brokerlist='" + _broker + "?retries='1''"); + conn = new AMQConnection("amqp://guest:rubbishpassword@clientid/test?brokerlist='" + _broker + "?retries='1''"); fail("Connection should not be established password is wrong."); } catch (AMQException amqe) { - if (!(amqe instanceof AMQAuthenticationException)) + if (amqe.getCause().getClass() == Exception.class) + { + System.err.println("QPID-594 : WARNING RACE CONDITION. Unable to determine cause of Connection Failure."); + return; + } + + assertEquals("Exception was wrong type", JMSException.class, amqe.getCause().getClass()); + Exception linked = ((JMSException) amqe.getCause()).getLinkedException(); + assertEquals("Exception was wrong type", AMQAuthenticationException.class, linked.getClass()); + } + finally + { + if (conn != null) { - fail("Correct exception not thrown. Excpected 'AMQAuthenticationException' got: " + amqe); + conn.close(); } } } public void testConnectionFailure() throws Exception { + AMQConnection conn = null; try { - new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_NotRunning + "?retries='0''"); + conn = new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_NotRunning + "?retries='0''"); fail("Connection should not be established"); } catch (AMQException amqe) @@ -146,14 +168,22 @@ public class ConnectionTest extends TestCase fail("Correct exception not thrown. Excpected 'AMQConnectionException' got: " + amqe); } } + finally + { + if (conn != null) + { + conn.close(); + } + } } public void testUnresolvedHostFailure() throws Exception { + AMQConnection conn = null; try { - new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_BadDNS + "?retries='0''"); + conn = new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_BadDNS + "?retries='0''"); fail("Connection should not be established"); } catch (AMQException amqe) @@ -163,6 +193,38 @@ public class ConnectionTest extends TestCase fail("Correct exception not thrown. Excpected 'AMQUnresolvedAddressException' got: " + amqe); } } + finally + { + if (conn != null) + { + conn.close(); + } + } + + } + + public void testUnresolvedVirtualHostFailure() throws Exception + { + AMQConnection conn = null; + try + { + conn = new AMQConnection("amqp://guest:guest@clientid/rubbishhost?brokerlist='" + _broker + "?retries='0''"); + fail("Connection should not be established"); + } + catch (AMQException amqe) + { + if (!(amqe instanceof AMQConnectionFailureException)) + { + fail("Correct exception not thrown. Excpected 'AMQConnectionFailureException' got: " + amqe); + } + } + finally + { + if (conn != null) + { + conn.close(); + } + } } public void testClientIdCannotBeChanged() throws Exception @@ -180,7 +242,10 @@ public class ConnectionTest extends TestCase } finally { - connection.close(); + if (connection != null) + { + connection.close(); + } } } @@ -188,7 +253,14 @@ public class ConnectionTest extends TestCase { Connection connection = new AMQConnection(_broker, "guest", "guest", null, "test"); - assertNotNull(connection.getClientID()); + try + { + assertNotNull(connection.getClientID()); + } + finally + { + connection.close(); + } connection.close(); } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java index bfbba61913..27adc4dd77 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/connectionurl/ConnectionURLTest.java @@ -473,6 +473,25 @@ public class ConnectionURLTest extends TestCase } + public void testSocketProtocol() throws URLSyntaxException + { + String url = "amqp://guest:guest@id/test" + "?brokerlist='socket://VM-Unique-socketID'"; + + try + { + AMQConnectionURL curl = new AMQConnectionURL(url); + assertNotNull(curl); + assertEquals(1, curl.getBrokerCount()); + assertNotNull(curl.getBrokerDetails(0)); + assertEquals(BrokerDetails.SOCKET, curl.getBrokerDetails(0).getTransport()); + assertEquals("VM-Unique-socketID", curl.getBrokerDetails(0).getHost()); + assertEquals("URL does not toString as expected", url, curl.toString()); + } + catch (URLSyntaxException e) + { + fail(e.getMessage()); + } + } public static junit.framework.Test suite() { diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java index 3776ff767f..4cdd7dd7e8 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java @@ -27,8 +27,9 @@ import org.apache.qpid.client.AMQConnection; import org.apache.qpid.client.protocol.AMQProtocolHandler; import org.apache.qpid.client.protocol.AMQProtocolSession; import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.testutil.QpidTestCase; -public class AMQProtocolSessionTest extends TestCase +public class AMQProtocolSessionTest extends QpidTestCase { private static class AMQProtSession extends AMQProtocolSession { @@ -50,7 +51,7 @@ public class AMQProtocolSessionTest extends TestCase } //private Strings for test values and expected results - private String _brokenAddress; + private String _brokenAddress = "tcp://myAddress;:";; private String _generatedAddress; private String _emptyAddress; private String _generatedAddress_2; @@ -64,7 +65,7 @@ public class AMQProtocolSessionTest extends TestCase super.setUp(); //don't care about the values set here apart from the dummy IoSession - _testSession = new AMQProtSession(null,new TestIoSession(),null); + _testSession = new AMQProtSession(null,new TestIoSession(), (AMQConnection) getConnection("guest", "guest")); //initialise addresses for test and expected results _port = 123; diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java index e78c295ce5..d25986d991 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java @@ -29,8 +29,8 @@ import org.apache.qpid.testutil.QpidTestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import uk.co.thebadgerset.junit.concurrency.TestRunnable; -import uk.co.thebadgerset.junit.concurrency.ThreadTestCoordinator; +import org.apache.qpid.junit.concurrency.TestRunnable; +import org.apache.qpid.junit.concurrency.ThreadTestCoordinator; import javax.jms.Connection; import javax.jms.Message; diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java new file mode 100644 index 0000000000..9caba63fe4 --- /dev/null +++ b/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java @@ -0,0 +1,167 @@ +/* Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.test.unit.ct; + +import javax.jms.*; + +import org.apache.qpid.testutil.QpidTestCase; + +/** + * Crash Recovery tests for durable subscription + * + */ +public class DurableSubscriberTest extends QpidTestCase +{ + private final String _topicName = "durableSubscriberTopic"; + + /** + * test strategy: + * create and register a durable subscriber then close it + * create a publisher and send a persistant message followed by a non persistant message + * crash and restart the broker + * recreate the durable subscriber and check that only the first message is received + */ + public void testDurSubRestoredAfterNonPersistentMessageSent() throws Exception + { + if (!isBroker08()) + { + TopicConnectionFactory factory = getConnectionFactory(); + Topic topic = (Topic) getInitialContext().lookup(_topicName); + //create and register a durable subscriber then close it + TopicConnection durConnection = factory.createTopicConnection("guest", "guest"); + TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, "dursub"); + durConnection.start(); + durSub1.close(); + durSession.close(); + durConnection.stop(); + + //create a publisher and send a persistant message followed by a non persistant message + TopicConnection pubConnection = factory.createTopicConnection("guest", "guest"); + TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicPublisher publisher = pubSession.createPublisher(topic); + Message message = pubSession.createMessage(); + message.setIntProperty("count", 1); + publisher.publish(message, javax.jms.DeliveryMode.PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY, + javax.jms.Message.DEFAULT_TIME_TO_LIVE); + message.setIntProperty("count", 2); + publisher.publish(message, javax.jms.DeliveryMode.NON_PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY, + javax.jms.Message.DEFAULT_TIME_TO_LIVE); + publisher.close(); + pubSession.close(); + //now stop the server + try + { + shutdownServer(); + } + catch (Exception e) + { + System.out.println("problems shutting down arjuna-ms"); + throw e; + } + //now recreate the durable subscriber and check the received messages + factory = getConnectionFactory(); + topic = (Topic) getInitialContext().lookup(_topicName); + TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest"); + TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, "dursub"); + durConnection2.start(); + Message m1 = durSub2.receive(1000); + if (m1 == null) + { + assertTrue("testDurSubRestoredAfterNonPersistentMessageSent test failed. no message was returned", + false); + } + assertTrue("testDurSubRestoredAfterNonPersistentMessageSent test failed. Wrong message was returned.", + m1.getIntProperty("count") == 1); + durSession2.unsubscribe("dursub"); + durConnection2.close(); + } + } + + /** + * create and register a durable subscriber with a message selector and then close it + * crash the broker + * create a publisher and send 5 right messages and 5 wrong messages + * recreate the durable subscriber and check the received the 5 expected messages + */ + public void testDurSubRestoresMessageSelector() throws Exception + { + if (!isBroker08()) + { + TopicConnectionFactory factory = getConnectionFactory(); + Topic topic = (Topic) getInitialContext().lookup(_topicName); + //create and register a durable subscriber with a message selector and then close it + TopicConnection durConnection = factory.createTopicConnection("guest", "guest"); + TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, "dursub", "testprop='true'", false); + durConnection.start(); + durSub1.close(); + durSession.close(); + durConnection.stop(); + //now stop the server + try + { + shutdownServer(); + } + catch (Exception e) + { + System.out.println("problems shutting down arjuna-ms"); + throw e; + } + topic = (Topic) getInitialContext().lookup(_topicName); + factory = getConnectionFactory(); + TopicConnection pubConnection = factory.createTopicConnection("guest", "guest"); + TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicPublisher publisher = pubSession.createPublisher(topic); + for (int i = 0; i < 5; i++) + { + Message message = pubSession.createMessage(); + message.setStringProperty("testprop", "true"); + publisher.publish(message); + message = pubSession.createMessage(); + message.setStringProperty("testprop", "false"); + publisher.publish(message); + } + publisher.close(); + pubSession.close(); + + //now recreate the durable subscriber and check the received messages + TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest"); + TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); + TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, "dursub"); + durConnection2.start(); + for (int i = 0; i < 5; i++) + { + Message message = durSub2.receive(1000); + if (message == null) + { + assertTrue("testDurSubRestoresMessageSelector test failed. no message was returned", false); + } + else + { + assertTrue("testDurSubRestoresMessageSelector test failed. message selector not reset", + message.getStringProperty("testprop").equals("true")); + } + } + durSession2.unsubscribe("dursub"); + durConnection2.close(); + } + } +} + diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTests.java b/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTests.java deleted file mode 100644 index af19db5128..0000000000 --- a/java/client/src/test/java/org/apache/qpid/test/unit/ct/DurableSubscriberTests.java +++ /dev/null @@ -1,167 +0,0 @@ -/* Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.qpid.test.unit.ct; - -import javax.jms.*; - -import org.apache.qpid.testutil.QpidTestCase; - -/** - * Crash Recovery tests for durable subscription - * - */ -public class DurableSubscriberTests extends QpidTestCase -{ - private final String _topicName = "durableSubscriberTopic"; - - /** - * test strategy: - * create and register a durable subscriber then close it - * create a publisher and send a persistant message followed by a non persistant message - * crash and restart the broker - * recreate the durable subscriber and check that only the first message is received - */ - public void testDurSubRestoredAfterNonPersistentMessageSent() throws Exception - { - if (!isBroker08()) - { - TopicConnectionFactory factory = getConnectionFactory(); - Topic topic = (Topic) getInitialContext().lookup(_topicName); - //create and register a durable subscriber then close it - TopicConnection durConnection = factory.createTopicConnection("guest", "guest"); - TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, "dursub"); - durConnection.start(); - durSub1.close(); - durSession.close(); - durConnection.stop(); - - //create a publisher and send a persistant message followed by a non persistant message - TopicConnection pubConnection = factory.createTopicConnection("guest", "guest"); - TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - TopicPublisher publisher = pubSession.createPublisher(topic); - Message message = pubSession.createMessage(); - message.setIntProperty("count", 1); - publisher.publish(message, javax.jms.DeliveryMode.PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY, - javax.jms.Message.DEFAULT_TIME_TO_LIVE); - message.setIntProperty("count", 2); - publisher.publish(message, javax.jms.DeliveryMode.NON_PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY, - javax.jms.Message.DEFAULT_TIME_TO_LIVE); - publisher.close(); - pubSession.close(); - //now stop the server - try - { - shutdownServer(); - } - catch (Exception e) - { - System.out.println("problems shutting down arjuna-ms"); - throw e; - } - //now recreate the durable subscriber and check the received messages - factory = getConnectionFactory(); - topic = (Topic) getInitialContext().lookup(_topicName); - TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest"); - TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, "dursub"); - durConnection2.start(); - Message m1 = durSub2.receive(1000); - if (m1 == null) - { - assertTrue("testDurSubRestoredAfterNonPersistentMessageSent test failed. no message was returned", - false); - } - assertTrue("testDurSubRestoredAfterNonPersistentMessageSent test failed. Wrong message was returned.", - m1.getIntProperty("count") == 1); - durSession2.unsubscribe("dursub"); - durConnection2.close(); - } - } - - /** - * create and register a durable subscriber with a message selector and then close it - * crash the broker - * create a publisher and send 5 right messages and 5 wrong messages - * recreate the durable subscriber and check the received the 5 expected messages - */ - public void testDurSubRestoresMessageSelector() throws Exception - { - if (!isBroker08()) - { - TopicConnectionFactory factory = getConnectionFactory(); - Topic topic = (Topic) getInitialContext().lookup(_topicName); - //create and register a durable subscriber with a message selector and then close it - TopicConnection durConnection = factory.createTopicConnection("guest", "guest"); - TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, "dursub", "testprop='true'", false); - durConnection.start(); - durSub1.close(); - durSession.close(); - durConnection.stop(); - //now stop the server - try - { - shutdownServer(); - } - catch (Exception e) - { - System.out.println("problems shutting down arjuna-ms"); - throw e; - } - topic = (Topic) getInitialContext().lookup(_topicName); - factory = getConnectionFactory(); - TopicConnection pubConnection = factory.createTopicConnection("guest", "guest"); - TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - TopicPublisher publisher = pubSession.createPublisher(topic); - for (int i = 0; i < 5; i++) - { - Message message = pubSession.createMessage(); - message.setStringProperty("testprop", "true"); - publisher.publish(message); - message = pubSession.createMessage(); - message.setStringProperty("testprop", "false"); - publisher.publish(message); - } - publisher.close(); - pubSession.close(); - - //now recreate the durable subscriber and check the received messages - TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest"); - TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); - TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, "dursub"); - durConnection2.start(); - for (int i = 0; i < 5; i++) - { - Message message = durSub2.receive(1000); - if (message == null) - { - assertTrue("testDurSubRestoresMessageSelector test failed. no message was returned", false); - } - else - { - assertTrue("testDurSubRestoresMessageSelector test failed. message selector not reset", - message.getStringProperty("testprop").equals("true")); - } - } - durSession2.unsubscribe("dursub"); - durConnection2.close(); - } - } -} - diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java index c4b94a6f36..6883a09f1b 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java @@ -39,6 +39,7 @@ import javax.jms.MessageProducer; import javax.jms.ObjectMessage; import javax.jms.Queue; import javax.jms.Session; +import java.util.Enumeration; /** * @author Apache Software Foundation @@ -84,6 +85,12 @@ public class JMSPropertiesTest extends QpidTestCase sentMsg.setJMSType(JMS_TYPE); sentMsg.setJMSReplyTo(JMS_REPLY_TO); + String JMSXGroupID_VALUE = "group"; + sentMsg.setStringProperty("JMSXGroupID", JMSXGroupID_VALUE); + + int JMSXGroupSeq_VALUE = 1; + sentMsg.setIntProperty("JMSXGroupSeq", JMSXGroupSeq_VALUE); + // send it producer.send(sentMsg); @@ -100,6 +107,30 @@ public class JMSPropertiesTest extends QpidTestCase // assertEquals("JMS Delivery Mode mismatch",sentMsg.getJMSDeliveryMode(),rm.getJMSDeliveryMode()); assertEquals("JMS Type mismatch", sentMsg.getJMSType(), rm.getJMSType()); assertEquals("JMS Reply To mismatch", sentMsg.getJMSReplyTo(), rm.getJMSReplyTo()); + assertTrue("JMSMessageID Does not start ID:", rm.getJMSMessageID().startsWith("ID:")); + + //Validate that the JMSX values are correct + assertEquals("JMSXGroupID is not as expected:", JMSXGroupID_VALUE, rm.getStringProperty("JMSXGroupID")); + assertEquals("JMSXGroupSeq is not as expected:", JMSXGroupSeq_VALUE, rm.getIntProperty("JMSXGroupSeq")); + + boolean JMSXGroupID_Available = false; + boolean JMSXGroupSeq_Available = false; + Enumeration props = con.getMetaData().getJMSXPropertyNames(); + while (props.hasMoreElements()) + { + String name = (String) props.nextElement(); + if (name.equals("JMSXGroupID")) + { + JMSXGroupID_Available = true; + } + if (name.equals("JMSXGroupSeq")) + { + JMSXGroupSeq_Available = true; + } + } + + assertTrue("JMSXGroupID not available.",JMSXGroupID_Available); + assertTrue("JMSXGroupSeq not available.",JMSXGroupSeq_Available); con.close(); } diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java index 23bd2a960a..50ed1dee9e 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java @@ -41,6 +41,14 @@ import javax.jms.Session; import javax.jms.TextMessage; import javax.jms.TopicSubscriber; +/** + * @todo Code to check that a consumer gets only one particular method could be factored into a re-usable method (as + * a static on a base test helper class, e.g. TestUtils. + * + * @todo Code to create test end-points using session per connection, or all sessions on one connection, to be factored + * out to make creating this test variation simpler. Want to make this variation available through LocalCircuit, + * driven by the test model. + */ public class DurableSubscriptionTest extends QpidTestCase { private static final Logger _logger = LoggerFactory.getLogger(DurableSubscriptionTest.class); @@ -113,12 +121,26 @@ public class DurableSubscriptionTest extends QpidTestCase con.close(); } - public void testDurability() throws Exception + public void testDurabilityAUTOACK() throws Exception { + durabilityImpl(Session.AUTO_ACKNOWLEDGE); + } + public void testDurabilityNOACKSessionPerConnection() throws Exception + { + durabilityImplSessionPerConnection(AMQSession.NO_ACKNOWLEDGE); + } + + public void testDurabilityAUTOACKSessionPerConnection() throws Exception + { + durabilityImplSessionPerConnection(Session.AUTO_ACKNOWLEDGE); + } + + private void durabilityImpl(int ackMode) throws Exception + { AMQConnection con = (AMQConnection) getConnection("guest", "guest"); - AMQTopic topic = new AMQTopic(con, "MyDurableSubscriptionTestTopic"); - Session session1 = con.createSession(false, AMQSession.NO_ACKNOWLEDGE); + AMQTopic topic = new AMQTopic(con, "MyTopic"); + Session session1 = con.createSession(false, ackMode); MessageConsumer consumer1 = session1.createConsumer(topic); Session sessionProd = con.createSession(false, AMQSession.NO_ACKNOWLEDGE); @@ -144,10 +166,82 @@ public class DurableSubscriptionTest extends QpidTestCase consumer2.close(); - Session session3 = con.createSession(false, AMQSession.NO_ACKNOWLEDGE); + producer.send(session1.createTextMessage("B")); + + _logger.info("Receive message on consumer 1 :expecting B"); + msg = consumer1.receive(500); + assertNotNull("Consumer 1 should get message 'B'.", msg); + assertEquals("Incorrect Message recevied on consumer1.", "B", ((TextMessage) msg).getText()); + _logger.info("Receive message on consumer 1 :expecting null"); + msg = consumer1.receive(500); + assertNull("There should be no more messages for consumption on consumer1.", msg); + + Session session3 = con.createSession(false, ackMode); MessageConsumer consumer3 = session3.createDurableSubscriber(topic, "MySubscription"); - producer.send(session1.createTextMessage("B")); + _logger.info("Receive message on consumer 3 :expecting B"); + msg = consumer3.receive(500); + assertNotNull("Consumer 3 should get message 'B'.", msg); + assertEquals("Incorrect Message recevied on consumer4.", "B", ((TextMessage) msg).getText()); + _logger.info("Receive message on consumer 3 :expecting null"); + msg = consumer3.receive(500); + assertNull("There should be no more messages for consumption on consumer3.", msg); + + consumer1.close(); + consumer3.close(); + + con.close(); + } + + private void durabilityImplSessionPerConnection(int ackMode) throws Exception + { + Message msg; + // Create producer. + AMQConnection con0 = (AMQConnection) getConnection("guest", "guest"); + con0.start(); + Session session0 = con0.createSession(false, ackMode); + + AMQTopic topic = new AMQTopic(con0, "MyTopic"); + + Session sessionProd = con0.createSession(false, ackMode); + MessageProducer producer = sessionProd.createProducer(topic); + + // Create consumer 1. + AMQConnection con1 = (AMQConnection) getConnection("guest", "guest"); + con1.start(); + Session session1 = con1.createSession(false, ackMode); + + MessageConsumer consumer1 = session0.createConsumer(topic); + + // Create consumer 2. + AMQConnection con2 = (AMQConnection) getConnection("guest", "guest"); + con2.start(); + Session session2 = con2.createSession(false, ackMode); + + TopicSubscriber consumer2 = session2.createDurableSubscriber(topic, "MySubscription"); + + // Send message and check that both consumers get it and only it. + producer.send(session0.createTextMessage("A")); + + msg = consumer1.receive(500); + assertNotNull("Message should be available", msg); + assertEquals("Message Text doesn't match", "A", ((TextMessage) msg).getText()); + msg = consumer1.receive(500); + assertNull("There should be no more messages for consumption on consumer1.", msg); + + msg = consumer2.receive(); + assertNotNull(msg); + assertEquals("Consumer 2 should also received the first msg.", "A", ((TextMessage) msg).getText()); + msg = consumer2.receive(500); + assertNull("There should be no more messages for consumption on consumer2.", msg); + + // Detach the durable subscriber. + consumer2.close(); + session2.close(); + con2.close(); + + // Send message and receive on open consumer. + producer.send(session0.createTextMessage("B")); _logger.info("Receive message on consumer 1 :expecting B"); msg = consumer1.receive(1000); @@ -156,17 +250,27 @@ public class DurableSubscriptionTest extends QpidTestCase msg = consumer1.receive(1000); assertEquals(null, msg); + // Re-attach a new consumer to the durable subscription, and check that it gets the message that it missed. + AMQConnection con3 = (AMQConnection) getConnection("guest", "guest"); + con3.start(); + Session session3 = con3.createSession(false, ackMode); + + TopicSubscriber consumer3 = session3.createDurableSubscriber(topic, "MySubscription"); + _logger.info("Receive message on consumer 3 :expecting B"); - msg = consumer3.receive(1000); - assertEquals("B", ((TextMessage) msg).getText()); + msg = consumer3.receive(500); + assertNotNull("Consumer 3 should get message 'B'.", msg); + assertEquals("Incorrect Message recevied on consumer4.", "B", ((TextMessage) msg).getText()); _logger.info("Receive message on consumer 3 :expecting null"); - msg = consumer3.receive(1000); - assertEquals(null, msg); - // we need to unsubscribe as the session is NO_ACKNOWLEDGE - // messages for the durable subscriber are not deleted so the test cannot - // be run twice in a row - session2.unsubscribe("MySubscription"); - con.close(); + msg = consumer3.receive(500); + assertNull("There should be no more messages for consumption on consumer3.", msg); + + consumer1.close(); + consumer3.close(); + + con0.close(); + con1.close(); + con3.close(); } public static junit.framework.Test suite() diff --git a/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java b/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java index 495cc98f31..f2f35644c6 100644 --- a/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java +++ b/java/client/src/test/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java @@ -409,9 +409,9 @@ public class CommitRollbackTest extends QpidTestCase } else { - _logger.warn("Got 2, message prefetched wasn't cleared or messages was in transit when rollback occured"); + _logger.warn("Got 2, message prefetched wasn't cleared or messages was in transit when rollback occured"); assertFalse("Already received message two", _gottwo); - + assertFalse("Already received message redelivered two", _gottwoRedelivered); _gottwo = true; } } @@ -419,6 +419,13 @@ public class CommitRollbackTest extends QpidTestCase verifyMessages(_consumer.receive(1000)); } + /** + * This test sends two messages receives on of them but doesn't ack it. + * The consumer is then closed + * the first message should be returned as redelivered. + * the second message should be delivered normally. + * @throws Exception + */ public void testSend2ThenCloseAfter1andTryAgain() throws Exception { assertTrue("session is not transacted", _session.getTransacted()); @@ -437,6 +444,7 @@ public class CommitRollbackTest extends QpidTestCase assertTrue("Messasge is marked as redelivered" + result, !result.getJMSRedelivered()); _logger.info("Closing Consumer"); + _consumer.close(); _logger.info("Creating New consumer"); diff --git a/java/common/build.xml b/java/common/build.xml index af27eb8d8f..6172a680ec 100644 --- a/java/common/build.xml +++ b/java/common/build.xml @@ -26,8 +26,8 @@ - - + + @@ -72,6 +72,8 @@ + + diff --git a/java/common/pom.xml b/java/common/pom.xml index ee3ae1b0a7..714087d843 100644 --- a/java/common/pom.xml +++ b/java/common/pom.xml @@ -138,18 +138,6 @@ - - commons-configuration - commons-configuration - - - - commons-cli - commons-cli - - - - org.slf4j slf4j-api diff --git a/java/common/protocol-version.xml b/java/common/protocol-version.xml index 2ed7dbff23..ee9db6049b 100644 --- a/java/common/protocol-version.xml +++ b/java/common/protocol-version.xml @@ -27,18 +27,23 @@ - - + + + + - + + - + - - - + + + + + @@ -47,7 +52,8 @@ - + + diff --git a/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java b/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java new file mode 100644 index 0000000000..bed80d5954 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/FixedSizeByteBufferAllocator.java @@ -0,0 +1,528 @@ +package org.apache.mina.common; + +import org.apache.mina.common.ByteBuffer; + +import java.nio.*; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +public class FixedSizeByteBufferAllocator implements ByteBufferAllocator +{ + + + private static final int MINIMUM_CAPACITY = 1; + + public FixedSizeByteBufferAllocator () + { + } + + public ByteBuffer allocate( int capacity, boolean direct ) + { + java.nio.ByteBuffer nioBuffer; + if( direct ) + { + nioBuffer = java.nio.ByteBuffer.allocateDirect( capacity ); + } + else + { + nioBuffer = java.nio.ByteBuffer.allocate( capacity ); + } + return new FixedSizeByteBuffer( nioBuffer ); + } + + public ByteBuffer wrap( java.nio.ByteBuffer nioBuffer ) + { + return new FixedSizeByteBuffer( nioBuffer ); + } + + public void dispose() + { + } + + + + private static final class FixedSizeByteBuffer extends ByteBuffer + { + private java.nio.ByteBuffer buf; + private int refCount = 1; + private int mark = -1; + + + protected FixedSizeByteBuffer( java.nio.ByteBuffer buf ) + { + this.buf = buf; + buf.order( ByteOrder.BIG_ENDIAN ); + refCount = 1; + } + + public synchronized void acquire() + { + if( refCount <= 0 ) + { + throw new IllegalStateException( "Already released buffer." ); + } + + refCount ++; + } + + public void release() + { + synchronized( this ) + { + if( refCount <= 0 ) + { + refCount = 0; + throw new IllegalStateException( + "Already released buffer. You released the buffer too many times." ); + } + + refCount --; + if( refCount > 0) + { + return; + } + } + } + + public java.nio.ByteBuffer buf() + { + return buf; + } + + public boolean isPooled() + { + return false; + } + + public void setPooled( boolean pooled ) + { + } + + public ByteBuffer duplicate() { + return new FixedSizeByteBuffer( this.buf.duplicate() ); + } + + public ByteBuffer slice() { + return new FixedSizeByteBuffer( this.buf.slice() ); + } + + public ByteBuffer asReadOnlyBuffer() { + return new FixedSizeByteBuffer( this.buf.asReadOnlyBuffer() ); + } + + public byte[] array() + { + return buf.array(); + } + + public int arrayOffset() + { + return buf.arrayOffset(); + } + + public boolean isDirect() + { + return buf.isDirect(); + } + + public boolean isReadOnly() + { + return buf.isReadOnly(); + } + + public int capacity() + { + return buf.capacity(); + } + + public ByteBuffer capacity( int newCapacity ) + { + if( newCapacity > capacity() ) + { + // Allocate a new buffer and transfer all settings to it. + int pos = position(); + int limit = limit(); + ByteOrder bo = order(); + + capacity0( newCapacity ); + buf.limit( limit ); + if( mark >= 0 ) + { + buf.position( mark ); + buf.mark(); + } + buf.position( pos ); + buf.order( bo ); + } + + return this; + } + + protected void capacity0( int requestedCapacity ) + { + int newCapacity = MINIMUM_CAPACITY; + while( newCapacity < requestedCapacity ) + { + newCapacity <<= 1; + } + + java.nio.ByteBuffer oldBuf = this.buf; + java.nio.ByteBuffer newBuf; + if( isDirect() ) + { + newBuf = java.nio.ByteBuffer.allocateDirect( newCapacity ); + } + else + { + newBuf = java.nio.ByteBuffer.allocate( newCapacity ); + } + + newBuf.clear(); + oldBuf.clear(); + newBuf.put( oldBuf ); + this.buf = newBuf; + } + + + + public boolean isAutoExpand() + { + return false; + } + + public ByteBuffer setAutoExpand( boolean autoExpand ) + { + if(autoExpand) throw new IllegalArgumentException(); + else return this; + } + + public ByteBuffer expand( int pos, int expectedRemaining ) + { + int end = pos + expectedRemaining; + if( end > capacity() ) + { + // The buffer needs expansion. + capacity( end ); + } + + if( end > limit() ) + { + // We call limit() directly to prevent StackOverflowError + buf.limit( end ); + } + return this; + } + + public int position() + { + return buf.position(); + } + + public ByteBuffer position( int newPosition ) + { + + buf.position( newPosition ); + if( mark > newPosition ) + { + mark = -1; + } + return this; + } + + public int limit() + { + return buf.limit(); + } + + public ByteBuffer limit( int newLimit ) + { + buf.limit( newLimit ); + if( mark > newLimit ) + { + mark = -1; + } + return this; + } + + public ByteBuffer mark() + { + buf.mark(); + mark = position(); + return this; + } + + public int markValue() + { + return mark; + } + + public ByteBuffer reset() + { + buf.reset(); + return this; + } + + public ByteBuffer clear() + { + buf.clear(); + mark = -1; + return this; + } + + public ByteBuffer flip() + { + buf.flip(); + mark = -1; + return this; + } + + public ByteBuffer rewind() + { + buf.rewind(); + mark = -1; + return this; + } + + public byte get() + { + return buf.get(); + } + + public ByteBuffer put( byte b ) + { + buf.put( b ); + return this; + } + + public byte get( int index ) + { + return buf.get( index ); + } + + public ByteBuffer put( int index, byte b ) + { + buf.put( index, b ); + return this; + } + + public ByteBuffer get( byte[] dst, int offset, int length ) + { + buf.get( dst, offset, length ); + return this; + } + + public ByteBuffer put( java.nio.ByteBuffer src ) + { + buf.put( src ); + return this; + } + + public ByteBuffer put( byte[] src, int offset, int length ) + { + buf.put( src, offset, length ); + return this; + } + + public ByteBuffer compact() + { + buf.compact(); + mark = -1; + return this; + } + + public ByteOrder order() + { + return buf.order(); + } + + public ByteBuffer order( ByteOrder bo ) + { + buf.order( bo ); + return this; + } + + public char getChar() + { + return buf.getChar(); + } + + public ByteBuffer putChar( char value ) + { + buf.putChar( value ); + return this; + } + + public char getChar( int index ) + { + return buf.getChar( index ); + } + + public ByteBuffer putChar( int index, char value ) + { + buf.putChar( index, value ); + return this; + } + + public CharBuffer asCharBuffer() + { + return buf.asCharBuffer(); + } + + public short getShort() + { + return buf.getShort(); + } + + public ByteBuffer putShort( short value ) + { + buf.putShort( value ); + return this; + } + + public short getShort( int index ) + { + return buf.getShort( index ); + } + + public ByteBuffer putShort( int index, short value ) + { + buf.putShort( index, value ); + return this; + } + + public ShortBuffer asShortBuffer() + { + return buf.asShortBuffer(); + } + + public int getInt() + { + return buf.getInt(); + } + + public ByteBuffer putInt( int value ) + { + buf.putInt( value ); + return this; + } + + public int getInt( int index ) + { + return buf.getInt( index ); + } + + public ByteBuffer putInt( int index, int value ) + { + buf.putInt( index, value ); + return this; + } + + public IntBuffer asIntBuffer() + { + return buf.asIntBuffer(); + } + + public long getLong() + { + return buf.getLong(); + } + + public ByteBuffer putLong( long value ) + { + buf.putLong( value ); + return this; + } + + public long getLong( int index ) + { + return buf.getLong( index ); + } + + public ByteBuffer putLong( int index, long value ) + { + buf.putLong( index, value ); + return this; + } + + public LongBuffer asLongBuffer() + { + return buf.asLongBuffer(); + } + + public float getFloat() + { + return buf.getFloat(); + } + + public ByteBuffer putFloat( float value ) + { + buf.putFloat( value ); + return this; + } + + public float getFloat( int index ) + { + return buf.getFloat( index ); + } + + public ByteBuffer putFloat( int index, float value ) + { + buf.putFloat( index, value ); + return this; + } + + public FloatBuffer asFloatBuffer() + { + return buf.asFloatBuffer(); + } + + public double getDouble() + { + return buf.getDouble(); + } + + public ByteBuffer putDouble( double value ) + { + buf.putDouble( value ); + return this; + } + + public double getDouble( int index ) + { + return buf.getDouble( index ); + } + + public ByteBuffer putDouble( int index, double value ) + { + buf.putDouble( index, value ); + return this; + } + + public DoubleBuffer asDoubleBuffer() + { + return buf.asDoubleBuffer(); + } + + + } + + +} diff --git a/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java b/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java new file mode 100644 index 0000000000..c515263317 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/common/support/DefaultIoFuture.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.common.support; + +import org.apache.mina.common.IoFuture; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoFutureListener; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * A default implementation of {@link org.apache.mina.common.IoFuture}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 440259 $, $Date: 2006-09-05 14:01:47 +0900 (í™”, 05 9ì›” 2006) $ + */ +public class DefaultIoFuture implements IoFuture +{ + private final IoSession session; + private final Object lock; + private List listeners; + private Object result; + private boolean ready; + + + /** + * Creates a new instance. + * + * @param session an {@link IoSession} which is associated with this future + */ + public DefaultIoFuture( IoSession session ) + { + this.session = session; + this.lock = this; + } + + /** + * Creates a new instance which uses the specified object as a lock. + */ + public DefaultIoFuture( IoSession session, Object lock ) + { + if( lock == null ) + { + throw new NullPointerException( "lock" ); + } + this.session = session; + this.lock = lock; + } + + public IoSession getSession() + { + return session; + } + + public Object getLock() + { + return lock; + } + + public void join() + { + synchronized( lock ) + { + while( !ready ) + { + try + { + lock.wait(); + } + catch( InterruptedException e ) + { + } + } + } + } + + public boolean join( long timeoutInMillis ) + { + long startTime = ( timeoutInMillis <= 0 ) ? 0 : System + .currentTimeMillis(); + long waitTime = timeoutInMillis; + + synchronized( lock ) + { + if( ready ) + { + return ready; + } + else if( waitTime <= 0 ) + { + return ready; + } + + for( ;; ) + { + try + { + lock.wait( waitTime ); + } + catch( InterruptedException e ) + { + } + + if( ready ) + return true; + else + { + waitTime = timeoutInMillis - ( System.currentTimeMillis() - startTime ); + if( waitTime <= 0 ) + { + return ready; + } + } + } + } + } + + public boolean isReady() + { + synchronized( lock ) + { + return ready; + } + } + + /** + * Sets the result of the asynchronous operation, and mark it as finished. + */ + protected void setValue( Object newValue ) + { + synchronized( lock ) + { + // Allow only once. + if( ready ) + { + return; + } + + result = newValue; + ready = true; + lock.notifyAll(); + + notifyListeners(); + } + } + + /** + * Returns the result of the asynchronous operation. + */ + protected Object getValue() + { + synchronized( lock ) + { + return result; + } + } + + public void addListener( IoFutureListener listener ) + { + if( listener == null ) + { + throw new NullPointerException( "listener" ); + } + + synchronized( lock ) + { + if(listeners == null) + { + listeners = new ArrayList(); + } + listeners.add( listener ); + if( ready ) + { + listener.operationComplete( this ); + } + } + } + + public void removeListener( IoFutureListener listener ) + { + if( listener == null ) + { + throw new NullPointerException( "listener" ); + } + + synchronized( lock ) + { + listeners.remove( listener ); + } + } + + private void notifyListeners() + { + synchronized( lock ) + { + + if(listeners != null) + { + + for( Iterator i = listeners.iterator(); i.hasNext(); ) { + ( ( IoFutureListener ) i.next() ).operationComplete( this ); + } + } + } + } +} + + + diff --git a/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java b/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java new file mode 100644 index 0000000000..47f19aa76d --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/WriteBufferFullExeception.java @@ -0,0 +1,48 @@ +package org.apache.mina.filter; + +import org.apache.mina.common.IoFilter;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +public class WriteBufferFullExeception extends RuntimeException +{ + private IoFilter.WriteRequest _writeRequest; + + public WriteBufferFullExeception() + { + this(null); + } + + public WriteBufferFullExeception(IoFilter.WriteRequest writeRequest) + { + _writeRequest = writeRequest; + } + + + public void setWriteRequest(IoFilter.WriteRequest writeRequest) + { + _writeRequest = writeRequest; + } + + public IoFilter.WriteRequest getWriteRequest() + { + return _writeRequest; + } +} diff --git a/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java b/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java new file mode 100644 index 0000000000..4e9db9071a --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/WriteBufferLimitFilterBuilder.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.filter; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.DefaultIoFilterChainBuilder; +import org.apache.mina.common.IoFilterAdapter; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoSession; +import org.apache.mina.filter.executor.ExecutorFilter; + +import java.util.Iterator; +import java.util.List; + +/** + * This filter will turn the asynchronous filterWrite method in to a blocking send when there are more than + * the prescribed number of messages awaiting filterWrite. It should be used in conjunction with the + * {@link ReadThrottleFilterBuilder} on a server as the blocking writes will allow the read thread to + * cause an Out of Memory exception due to a back log of unprocessed messages. + * + * This is should only be viewed as a temporary work around for DIRMINA-302. + * + * A true solution should not be implemented as a filter as this issue will always occur. On a machine + * where the network is slower than the local producer. + * + * Suggested improvement is to allow implementation of policices on what to do when buffer is full. + * + * They could be: + * Block - As this does + * Wait on a given Future - to drain more of the queue.. in essence this filter with high/low watermarks + * Throw Exception - through the client filterWrite() method to allow them to get immediate feedback on buffer state + * + *

+ *

Usage: + *

+ *


+ * DefaultFilterChainBuilder builder = ...
+ * WriteBufferLimitFilterBuilder filter = new WriteBufferLimitFilterBuilder();
+ * filter.attach( builder );
+ * 
+ *

+ * or + *

+ *


+ * IoFilterChain chain = ...
+ * WriteBufferLimitFilterBuilder filter = new WriteBufferLimitFilterBuilder();
+ * filter.attach( chain );
+ * 
+ * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class WriteBufferLimitFilterBuilder +{ + public static final String PENDING_SIZE = WriteBufferLimitFilterBuilder.class.getName() + ".pendingSize"; + + private static int DEFAULT_CONNECTION_BUFFER_MESSAGE_COUNT = 5000; + + private volatile boolean throwNotBlock = false; + + private volatile int maximumConnectionBufferCount; + private volatile long maximumConnectionBufferSize; + + private final Object _blockLock = new Object(); + + private int _blockWaiters = 0; + + + public WriteBufferLimitFilterBuilder() + { + this(DEFAULT_CONNECTION_BUFFER_MESSAGE_COUNT); + } + + public WriteBufferLimitFilterBuilder(int maxWriteBufferSize) + { + setMaximumConnectionBufferCount(maxWriteBufferSize); + } + + + /** + * Set the maximum amount pending items in the writeQueue for a given session. + * Changing the value will only take effect when new data is received for a + * connection, including existing connections. Default value is 5000 msgs. + * + * @param maximumConnectionBufferCount New buffer size. Must be > 0 + */ + public void setMaximumConnectionBufferCount(int maximumConnectionBufferCount) + { + this.maximumConnectionBufferCount = maximumConnectionBufferCount; + this.maximumConnectionBufferSize = 0; + } + + public void setMaximumConnectionBufferSize(long maximumConnectionBufferSize) + { + this.maximumConnectionBufferSize = maximumConnectionBufferSize; + this.maximumConnectionBufferCount = 0; + } + + /** + * Attach this filter to the specified filter chain. It will search for the ThreadPoolFilter, and attach itself + * before and after that filter. + * + * @param chain {@link IoFilterChain} to attach self to. + */ + public void attach(IoFilterChain chain) + { + String name = getThreadPoolFilterEntryName(chain.getAll()); + + chain.addBefore(name, getClass().getName() + ".sendlimit", new SendLimit()); + } + + /** + * Attach this filter to the specified builder. It will search for the + * {@link ExecutorFilter}, and attach itself before and after that filter. + * + * @param builder {@link DefaultIoFilterChainBuilder} to attach self to. + */ + public void attach(DefaultIoFilterChainBuilder builder) + { + String name = getThreadPoolFilterEntryName(builder.getAll()); + + builder.addBefore(name, getClass().getName() + ".sendlimit", new SendLimit()); + } + + private String getThreadPoolFilterEntryName(List entries) + { + Iterator i = entries.iterator(); + + while (i.hasNext()) + { + IoFilterChain.Entry entry = (IoFilterChain.Entry) i.next(); + + if (entry.getFilter().getClass().isAssignableFrom(ExecutorFilter.class)) + { + return entry.getName(); + } + } + + throw new IllegalStateException("Chain does not contain a ExecutorFilter"); + } + + + public class SendLimit extends IoFilterAdapter + { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception + { + try + { + waitTillSendAllowed(session); + } + catch (WriteBufferFullExeception wbfe) + { + nextFilter.exceptionCaught(session, wbfe); + } + + if (writeRequest.getMessage() instanceof ByteBuffer) + { + increasePendingWriteSize(session, (ByteBuffer) writeRequest.getMessage()); + } + + nextFilter.filterWrite(session, writeRequest); + } + + private void increasePendingWriteSize(IoSession session, ByteBuffer message) + { + synchronized (session) + { + Long pendingSize = getScheduledWriteBytes(session) + message.remaining(); + session.setAttribute(PENDING_SIZE, pendingSize); + } + } + + private boolean sendAllowed(IoSession session) + { + if (session.isClosing()) + { + return true; + } + + int lmswm = maximumConnectionBufferCount; + long lmswb = maximumConnectionBufferSize; + + return (lmswm == 0 || session.getScheduledWriteRequests() < lmswm) + && (lmswb == 0 || getScheduledWriteBytes(session) < lmswb); + } + + private long getScheduledWriteBytes(IoSession session) + { + synchronized (session) + { + Long i = (Long) session.getAttribute(PENDING_SIZE); + return null == i ? 0 : i; + } + } + + private void waitTillSendAllowed(IoSession session) + { + synchronized (_blockLock) + { + if (throwNotBlock) + { + throw new WriteBufferFullExeception(); + } + + _blockWaiters++; + + while (!sendAllowed(session)) + { + try + { + _blockLock.wait(); + } + catch (InterruptedException e) + { + // Ignore. + } + } + _blockWaiters--; + } + } + + public void messageSent(NextFilter nextFilter, IoSession session, Object message) throws Exception + { + if (message instanceof ByteBuffer) + { + decrementPendingWriteSize(session, (ByteBuffer) message); + } + notifyWaitingWriters(); + nextFilter.messageSent(session, message); + } + + private void decrementPendingWriteSize(IoSession session, ByteBuffer message) + { + synchronized (session) + { + session.setAttribute(PENDING_SIZE, getScheduledWriteBytes(session) - message.remaining()); + } + } + + private void notifyWaitingWriters() + { + synchronized (_blockLock) + { + if (_blockWaiters != 0) + { + _blockLock.notifyAll(); + } + } + + } + + }//SentLimit + + +} diff --git a/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java b/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java new file mode 100644 index 0000000000..3f7e206cb4 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/codec/OurCumulativeProtocolDecoder.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.filter.codec; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoSession; + +/** + * A {@link ProtocolDecoder} that cumulates the content of received + * buffers to a cumulative buffer to help users implement decoders. + *

+ * If the received {@link ByteBuffer} is only a part of a message. + * decoders should cumulate received buffers to make a message complete or + * to postpone decoding until more buffers arrive. + *

+ * Here is an example decoder that decodes CRLF terminated lines into + * Command objects: + *

+ * public class CRLFTerminatedCommandLineDecoder
+ *         extends CumulativeProtocolDecoder {
+ *
+ *     private Command parseCommand(ByteBuffer in) {
+ *         // Convert the bytes in the specified buffer to a
+ *         // Command object.
+ *         ...
+ *     }
+ *
+ *     protected boolean doDecode(IoSession session, ByteBuffer in,
+ *                                ProtocolDecoderOutput out)
+ *             throws Exception {
+ *
+ *         // Remember the initial position.
+ *         int start = in.position();
+ *
+ *         // Now find the first CRLF in the buffer.
+ *         byte previous = 0;
+ *         while (in.hasRemaining()) {
+ *             byte current = in.get();
+ *
+ *             if (previous == '\r' && current == '\n') {
+ *                 // Remember the current position and limit.
+ *                 int position = in.position();
+ *                 int limit = in.limit();
+ *                 try {
+ *                     in.position(start);
+ *                     in.limit(position);
+ *                     // The bytes between in.position() and in.limit()
+ *                     // now contain a full CRLF terminated line.
+ *                     out.write(parseCommand(in.slice()));
+ *                 } finally {
+ *                     // Set the position to point right after the
+ *                     // detected line and set the limit to the old
+ *                     // one.
+ *                     in.position(position);
+ *                     in.limit(limit);
+ *                 }
+ *                 // Decoded one line; CumulativeProtocolDecoder will
+ *                 // call me again until I return false. So just
+ *                 // return true until there are no more lines in the
+ *                 // buffer.
+ *                 return true;
+ *             }
+ *
+ *             previous = current;
+ *         }
+ *
+ *         // Could not find CRLF in the buffer. Reset the initial
+ *         // position to the one we recorded above.
+ *         in.position(start);
+ *
+ *         return false;
+ *     }
+ * }
+ * 
+ * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public abstract class OurCumulativeProtocolDecoder extends ProtocolDecoderAdapter { + + private static final String BUFFER = OurCumulativeProtocolDecoder.class + .getName() + + ".Buffer"; + + /** + * Creates a new instance. + */ + protected OurCumulativeProtocolDecoder() { + } + + /** + * Cumulates content of in into internal buffer and forwards + * decoding request to {@link #doDecode(IoSession, ByteBuffer, ProtocolDecoderOutput)}. + * doDecode() is invoked repeatedly until it returns false + * and the cumulative buffer is NOT compacted after decoding ends. + * + * @throws IllegalStateException if your doDecode() returned + * true not consuming the cumulative buffer. + */ + public void decode(IoSession session, ByteBuffer in, + ProtocolDecoderOutput out) throws Exception { + boolean usingSessionBuffer = true; + ByteBuffer buf = (ByteBuffer) session.getAttribute(BUFFER); + // If we have a session buffer, append data to that; otherwise + // use the buffer read from the network directly. + if (buf != null) { + buf.put(in); + buf.flip(); + } else { + buf = in; + usingSessionBuffer = false; + } + + for (;;) { + int oldPos = buf.position(); + boolean decoded = doDecode(session, buf, out); + if (decoded) { + if (buf.position() == oldPos) { + throw new IllegalStateException( + "doDecode() can't return true when buffer is not consumed."); + } + + if (!buf.hasRemaining()) { + break; + } + } else { + break; + } + } + + + // if there is any data left that cannot be decoded, we store + // it in a buffer in the session and next time this decoder is + // invoked the session buffer gets appended to + if (buf.hasRemaining()) { + storeRemainingInSession(buf, session); + } else { + if (usingSessionBuffer) + removeSessionBuffer(session); + } + } + + /** + * Implement this method to consume the specified cumulative buffer and + * decode its content into message(s). + * + * @param in the cumulative buffer + * @return true if and only if there's more to decode in the buffer + * and you want to have doDecode method invoked again. + * Return false if remaining data is not enough to decode, + * then this method will be invoked again when more data is cumulated. + * @throws Exception if cannot decode in. + */ + protected abstract boolean doDecode(IoSession session, ByteBuffer in, + ProtocolDecoderOutput out) throws Exception; + + /** + * Releases the cumulative buffer used by the specified session. + * Please don't forget to call super.dispose( session ) when + * you override this method. + */ + public void dispose(IoSession session) throws Exception { + removeSessionBuffer(session); + } + + private void removeSessionBuffer(IoSession session) { + ByteBuffer buf = (ByteBuffer) session.removeAttribute(BUFFER); + if (buf != null) { + buf.release(); + } + } + + private void storeRemainingInSession(ByteBuffer buf, IoSession session) { + ByteBuffer remainingBuf = ByteBuffer.allocate(buf.capacity()); + remainingBuf.setAutoExpand(true); + remainingBuf.order(buf.order()); + remainingBuf.put(buf); + session.setAttribute(BUFFER, remainingBuf); + } +} diff --git a/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java b/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java new file mode 100644 index 0000000000..b8c6f29720 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/filter/codec/QpidProtocolCodecFilter.java @@ -0,0 +1,440 @@ +package org.apache.mina.filter.codec; + + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ + +import org.apache.mina.common.*; +import org.apache.mina.common.support.DefaultWriteFuture; +import org.apache.mina.filter.codec.support.SimpleProtocolDecoderOutput; +import org.apache.mina.util.SessionLog; +import org.apache.mina.util.Queue; + + +public class QpidProtocolCodecFilter extends IoFilterAdapter +{ + public static final String ENCODER = QpidProtocolCodecFilter.class.getName() + ".encoder"; + public static final String DECODER = QpidProtocolCodecFilter.class.getName() + ".decoder"; + + private static final Class[] EMPTY_PARAMS = new Class[0]; + private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap( new byte[0] ); + + private final ProtocolCodecFactory factory; + + public QpidProtocolCodecFilter( ProtocolCodecFactory factory ) + { + if( factory == null ) + { + throw new NullPointerException( "factory" ); + } + this.factory = factory; + } + + public QpidProtocolCodecFilter( final ProtocolEncoder encoder, final ProtocolDecoder decoder ) + { + if( encoder == null ) + { + throw new NullPointerException( "encoder" ); + } + if( decoder == null ) + { + throw new NullPointerException( "decoder" ); + } + + this.factory = new ProtocolCodecFactory() + { + public ProtocolEncoder getEncoder() + { + return encoder; + } + + public ProtocolDecoder getDecoder() + { + return decoder; + } + }; + } + + public QpidProtocolCodecFilter( final Class encoderClass, final Class decoderClass ) + { + if( encoderClass == null ) + { + throw new NullPointerException( "encoderClass" ); + } + if( decoderClass == null ) + { + throw new NullPointerException( "decoderClass" ); + } + if( !ProtocolEncoder.class.isAssignableFrom( encoderClass ) ) + { + throw new IllegalArgumentException( "encoderClass: " + encoderClass.getName() ); + } + if( !ProtocolDecoder.class.isAssignableFrom( decoderClass ) ) + { + throw new IllegalArgumentException( "decoderClass: " + decoderClass.getName() ); + } + try + { + encoderClass.getConstructor( EMPTY_PARAMS ); + } + catch( NoSuchMethodException e ) + { + throw new IllegalArgumentException( "encoderClass doesn't have a public default constructor." ); + } + try + { + decoderClass.getConstructor( EMPTY_PARAMS ); + } + catch( NoSuchMethodException e ) + { + throw new IllegalArgumentException( "decoderClass doesn't have a public default constructor." ); + } + + this.factory = new ProtocolCodecFactory() + { + public ProtocolEncoder getEncoder() throws Exception + { + return ( ProtocolEncoder ) encoderClass.newInstance(); + } + + public ProtocolDecoder getDecoder() throws Exception + { + return ( ProtocolDecoder ) decoderClass.newInstance(); + } + }; + } + + public void onPreAdd( IoFilterChain parent, String name, IoFilter.NextFilter nextFilter ) throws Exception + { + if( parent.contains( ProtocolCodecFilter.class ) ) + { + throw new IllegalStateException( "A filter chain cannot contain more than one QpidProtocolCodecFilter." ); + } + } + + public void messageReceived( IoFilter.NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + if( !( message instanceof ByteBuffer ) ) + { + nextFilter.messageReceived( session, message ); + return; + } + + ByteBuffer in = ( ByteBuffer ) message; + ProtocolDecoder decoder = getDecoder( session ); + ProtocolDecoderOutput decoderOut = getDecoderOut( session, nextFilter ); + + try + { + decoder.decode( session, in, decoderOut ); + } + catch( Throwable t ) + { + ProtocolDecoderException pde; + if( t instanceof ProtocolDecoderException ) + { + pde = ( ProtocolDecoderException ) t; + } + else + { + pde = new ProtocolDecoderException( t ); + } + pde.setHexdump( in.getHexDump() ); + throw pde; + } + finally + { + // Dispose the decoder if this session is connectionless. + if( session.getTransportType().isConnectionless() ) + { + disposeDecoder( session ); + } + + // Release the read buffer. + in.release(); + + decoderOut.flush(); + } + } + + public void messageSent( IoFilter.NextFilter nextFilter, IoSession session, Object message ) throws Exception + { + if( message instanceof HiddenByteBuffer ) + { + return; + } + + if( !( message instanceof MessageByteBuffer ) ) + { + nextFilter.messageSent( session, message ); + return; + } + + nextFilter.messageSent( session, ( ( MessageByteBuffer ) message ).message ); + } + + public void filterWrite( IoFilter.NextFilter nextFilter, IoSession session, IoFilter.WriteRequest writeRequest ) throws Exception + { + Object message = writeRequest.getMessage(); + if( message instanceof ByteBuffer ) + { + nextFilter.filterWrite( session, writeRequest ); + return; + } + + ProtocolEncoder encoder = getEncoder( session ); + ProtocolEncoderOutputImpl encoderOut = getEncoderOut( session, nextFilter, writeRequest ); + + try + { + encoder.encode( session, message, encoderOut ); + encoderOut.flush(); + nextFilter.filterWrite( + session, + new IoFilter.WriteRequest( + new MessageByteBuffer( writeRequest.getMessage() ), + writeRequest.getFuture(), writeRequest.getDestination() ) ); + } + catch( Throwable t ) + { + ProtocolEncoderException pee; + if( t instanceof ProtocolEncoderException ) + { + pee = ( ProtocolEncoderException ) t; + } + else + { + pee = new ProtocolEncoderException( t ); + } + throw pee; + } + finally + { + // Dispose the encoder if this session is connectionless. + if( session.getTransportType().isConnectionless() ) + { + disposeEncoder( session ); + } + } + } + + public void sessionClosed( IoFilter.NextFilter nextFilter, IoSession session ) throws Exception + { + // Call finishDecode() first when a connection is closed. + ProtocolDecoder decoder = getDecoder( session ); + ProtocolDecoderOutput decoderOut = getDecoderOut( session, nextFilter ); + try + { + decoder.finishDecode( session, decoderOut ); + } + catch( Throwable t ) + { + ProtocolDecoderException pde; + if( t instanceof ProtocolDecoderException ) + { + pde = ( ProtocolDecoderException ) t; + } + else + { + pde = new ProtocolDecoderException( t ); + } + throw pde; + } + finally + { + // Dispose all. + disposeEncoder( session ); + disposeDecoder( session ); + + decoderOut.flush(); + } + + nextFilter.sessionClosed( session ); + } + + private ProtocolEncoder getEncoder( IoSession session ) throws Exception + { + ProtocolEncoder encoder = ( ProtocolEncoder ) session.getAttribute( ENCODER ); + if( encoder == null ) + { + encoder = factory.getEncoder(); + session.setAttribute( ENCODER, encoder ); + } + return encoder; + } + + private ProtocolEncoderOutputImpl getEncoderOut( IoSession session, IoFilter.NextFilter nextFilter, IoFilter.WriteRequest writeRequest ) + { + return new ProtocolEncoderOutputImpl( session, nextFilter, writeRequest ); + } + + private ProtocolDecoder getDecoder( IoSession session ) throws Exception + { + ProtocolDecoder decoder = ( ProtocolDecoder ) session.getAttribute( DECODER ); + if( decoder == null ) + { + decoder = factory.getDecoder(); + session.setAttribute( DECODER, decoder ); + } + return decoder; + } + + private ProtocolDecoderOutput getDecoderOut( IoSession session, IoFilter.NextFilter nextFilter ) + { + return new SimpleProtocolDecoderOutput( session, nextFilter ); + } + + private void disposeEncoder( IoSession session ) + { + ProtocolEncoder encoder = ( ProtocolEncoder ) session.removeAttribute( ENCODER ); + if( encoder == null ) + { + return; + } + + try + { + encoder.dispose( session ); + } + catch( Throwable t ) + { + SessionLog.warn( + session, + "Failed to dispose: " + encoder.getClass().getName() + + " (" + encoder + ')' ); + } + } + + private void disposeDecoder( IoSession session ) + { + ProtocolDecoder decoder = ( ProtocolDecoder ) session.removeAttribute( DECODER ); + if( decoder == null ) + { + return; + } + + try + { + decoder.dispose( session ); + } + catch( Throwable t ) + { + SessionLog.warn( + session, + "Falied to dispose: " + decoder.getClass().getName() + + " (" + decoder + ')' ); + } + } + + private static class HiddenByteBuffer extends ByteBufferProxy + { + private HiddenByteBuffer( ByteBuffer buf ) + { + super( buf ); + } + } + + private static class MessageByteBuffer extends ByteBufferProxy + { + private final Object message; + + private MessageByteBuffer( Object message ) + { + super( EMPTY_BUFFER ); + this.message = message; + } + + public void acquire() + { + // no-op since we are wraping a zero-byte buffer, this instance is to just curry the message + } + + public void release() + { + // no-op since we are wraping a zero-byte buffer, this instance is to just curry the message + } + } + + private static class ProtocolEncoderOutputImpl implements ProtocolEncoderOutput + { + private ByteBuffer buffer; + + private final IoSession session; + private final IoFilter.NextFilter nextFilter; + private final IoFilter.WriteRequest writeRequest; + + public ProtocolEncoderOutputImpl( IoSession session, IoFilter.NextFilter nextFilter, IoFilter.WriteRequest writeRequest ) + { + this.session = session; + this.nextFilter = nextFilter; + this.writeRequest = writeRequest; + } + + + + public void write( ByteBuffer buf ) + { + if(buffer != null) + { + flush(); + } + buffer = buf; + } + + public void mergeAll() + { + } + + public WriteFuture flush() + { + WriteFuture future = null; + if( buffer == null ) + { + return null; + } + else + { + ByteBuffer buf = buffer; + // Flush only when the buffer has remaining. + if( buf.hasRemaining() ) + { + future = doFlush( buf ); + } + + } + + return future; + } + + + protected WriteFuture doFlush( ByteBuffer buf ) + { + WriteFuture future = new DefaultWriteFuture( session ); + nextFilter.filterWrite( + session, + new IoFilter.WriteRequest( + buf, + future, writeRequest.getDestination() ) ); + return future; + } + } +} + diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java new file mode 100644 index 0000000000..e5360d32e0 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketAcceptor.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.BaseIoAcceptor; +import org.apache.mina.util.Queue; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.NamePreservingRunnable; +import edu.emory.mathcs.backport.java.util.concurrent.Executor; + +/** + * {@link IoAcceptor} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketAcceptor extends SocketAcceptor +{ + /** + * @noinspection StaticNonFinalField + */ + private static volatile int nextId = 0; + + private final Executor executor; + private final Object lock = new Object(); + private final int id = nextId ++; + private final String threadName = "SocketAcceptor-" + id; + private final Map channels = new HashMap(); + + private final Queue registerQueue = new Queue(); + private final Queue cancelQueue = new Queue(); + + private final MultiThreadSocketIoProcessor[] ioProcessors; + private final int processorCount; + + /** + * @noinspection FieldAccessedSynchronizedAndUnsynchronized + */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + + /** + * Create an acceptor with a single processing thread using a NewThreadExecutor + */ + public MultiThreadSocketAcceptor() + { + this( 1, new NewThreadExecutor() ); + } + + /** + * Create an acceptor with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public MultiThreadSocketAcceptor( int processorCount, Executor executor ) + { + if( processorCount < 1 ) + { + throw new IllegalArgumentException( "Must have at least one processor" ); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new MultiThreadSocketIoProcessor[processorCount]; + + for( int i = 0; i < processorCount; i++ ) + { + ioProcessors[i] = new MultiThreadSocketIoProcessor( "SocketAcceptorIoProcessor-" + id + "." + i, executor ); + } + } + + + /** + * Binds to the specified address and handles incoming connections with the specified + * handler. Backlog value is configured to the value of backlog property. + * + * @throws IOException if failed to bind + */ + public void bind( SocketAddress address, IoHandler handler, IoServiceConfig config ) throws IOException + { + if( handler == null ) + { + throw new NullPointerException( "handler" ); + } + + if( address != null && !( address instanceof InetSocketAddress ) ) + { + throw new IllegalArgumentException( "Unexpected address type: " + address.getClass() ); + } + + if( config == null ) + { + config = getDefaultConfig(); + } + + RegistrationRequest request = new RegistrationRequest( address, handler, config ); + + synchronized( registerQueue ) + { + registerQueue.push( request ); + } + + startupWorker(); + + selector.wakeup(); + + synchronized( request ) + { + while( !request.done ) + { + try + { + request.wait(); + } + catch( InterruptedException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + + if( request.exception != null ) + { + throw request.exception; + } + } + + + private synchronized void startupWorker() throws IOException + { + synchronized( lock ) + { + if( worker == null ) + { + selector = Selector.open(); + worker = new Worker(); + + executor.execute( new NamePreservingRunnable( worker ) ); + } + } + } + + public void unbind( SocketAddress address ) + { + if( address == null ) + { + throw new NullPointerException( "address" ); + } + + CancellationRequest request = new CancellationRequest( address ); + + try + { + startupWorker(); + } + catch( IOException e ) + { + // IOException is thrown only when Worker thread is not + // running and failed to open a selector. We simply throw + // IllegalArgumentException here because we can simply + // conclude that nothing is bound to the selector. + throw new IllegalArgumentException( "Address not bound: " + address ); + } + + synchronized( cancelQueue ) + { + cancelQueue.push( request ); + } + + selector.wakeup(); + + synchronized( request ) + { + while( !request.done ) + { + try + { + request.wait(); + } + catch( InterruptedException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + + if( request.exception != null ) + { + request.exception.fillInStackTrace(); + + throw request.exception; + } + } + + + private class Worker implements Runnable + { + public void run() + { + Thread.currentThread().setName(MultiThreadSocketAcceptor.this.threadName ); + + for( ; ; ) + { + try + { + int nKeys = selector.select(); + + registerNew(); + + if( nKeys > 0 ) + { + processSessions( selector.selectedKeys() ); + } + + cancelKeys(); + + if( selector.keys().isEmpty() ) + { + synchronized( lock ) + { + if( selector.keys().isEmpty() && + registerQueue.isEmpty() && + cancelQueue.isEmpty() ) + { + worker = null; + try + { + selector.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + finally + { + selector = null; + } + break; + } + } + } + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + + try + { + Thread.sleep( 1000 ); + } + catch( InterruptedException e1 ) + { + ExceptionMonitor.getInstance().exceptionCaught( e1 ); + } + } + } + } + + private void processSessions( Set keys ) throws IOException + { + Iterator it = keys.iterator(); + while( it.hasNext() ) + { + SelectionKey key = ( SelectionKey ) it.next(); + + it.remove(); + + if( !key.isAcceptable() ) + { + continue; + } + + ServerSocketChannel ssc = ( ServerSocketChannel ) key.channel(); + + SocketChannel ch = ssc.accept(); + + if( ch == null ) + { + continue; + } + + boolean success = false; + try + { + + RegistrationRequest req = ( RegistrationRequest ) key.attachment(); + + MultiThreadSocketSessionImpl session = new MultiThreadSocketSessionImpl( + MultiThreadSocketAcceptor.this, nextProcessor(), getListeners(), + req.config, ch, req.handler, req.address ); + + // New Interface +// SocketSessionImpl session = new SocketSessionImpl( +// SocketAcceptor.this, nextProcessor(), getListeners(), +// req.config, ch, req.handler, req.address ); + + + getFilterChainBuilder().buildFilterChain( session.getFilterChain() ); + req.config.getFilterChainBuilder().buildFilterChain( session.getFilterChain() ); + req.config.getThreadModel().buildFilterChain( session.getFilterChain() ); + session.getIoProcessor().addNew( session ); + success = true; + } + catch( Throwable t ) + { + ExceptionMonitor.getInstance().exceptionCaught( t ); + } + finally + { + if( !success ) + { + ch.close(); + } + } + } + } + } + + private MultiThreadSocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + + private void registerNew() + { + if( registerQueue.isEmpty() ) + { + return; + } + + for( ; ; ) + { + RegistrationRequest req; + + synchronized( registerQueue ) + { + req = ( RegistrationRequest ) registerQueue.pop(); + } + + if( req == null ) + { + break; + } + + ServerSocketChannel ssc = null; + + try + { + ssc = ServerSocketChannel.open(); + ssc.configureBlocking( false ); + + // Configure the server socket, + SocketAcceptorConfig cfg; + if( req.config instanceof SocketAcceptorConfig ) + { + cfg = ( SocketAcceptorConfig ) req.config; + } + else + { + cfg = ( SocketAcceptorConfig ) getDefaultConfig(); + } + + ssc.socket().setReuseAddress( cfg.isReuseAddress() ); + ssc.socket().setReceiveBufferSize( + ( ( SocketSessionConfig ) cfg.getSessionConfig() ).getReceiveBufferSize() ); + + // and bind. + ssc.socket().bind( req.address, cfg.getBacklog() ); + if( req.address == null || req.address.getPort() == 0 ) + { + req.address = ( InetSocketAddress ) ssc.socket().getLocalSocketAddress(); + } + ssc.register( selector, SelectionKey.OP_ACCEPT, req ); + + synchronized( channels ) + { + channels.put( req.address, ssc ); + } + + getListeners().fireServiceActivated( + this, req.address, req.handler, req.config ); + } + catch( IOException e ) + { + req.exception = e; + } + finally + { + synchronized( req ) + { + req.done = true; + + req.notifyAll(); + } + + if( ssc != null && req.exception != null ) + { + try + { + ssc.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + } + } + } + } + + + private void cancelKeys() + { + if( cancelQueue.isEmpty() ) + { + return; + } + + for( ; ; ) + { + CancellationRequest request; + + synchronized( cancelQueue ) + { + request = ( CancellationRequest ) cancelQueue.pop(); + } + + if( request == null ) + { + break; + } + + ServerSocketChannel ssc; + synchronized( channels ) + { + ssc = ( ServerSocketChannel ) channels.remove( request.address ); + } + + // close the channel + try + { + if( ssc == null ) + { + request.exception = new IllegalArgumentException( "Address not bound: " + request.address ); + } + else + { + SelectionKey key = ssc.keyFor( selector ); + request.registrationRequest = ( RegistrationRequest ) key.attachment(); + key.cancel(); + + selector.wakeup(); // wake up again to trigger thread death + + ssc.close(); + } + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught( e ); + } + finally + { + synchronized( request ) + { + request.done = true; + request.notifyAll(); + } + + if( request.exception == null ) + { + getListeners().fireServiceDeactivated( + this, request.address, + request.registrationRequest.handler, + request.registrationRequest.config ); + } + } + } + } + + private static class RegistrationRequest + { + private InetSocketAddress address; + private final IoHandler handler; + private final IoServiceConfig config; + private IOException exception; + private boolean done; + + private RegistrationRequest( SocketAddress address, IoHandler handler, IoServiceConfig config ) + { + this.address = ( InetSocketAddress ) address; + this.handler = handler; + this.config = config; + } + } + + + private static class CancellationRequest + { + private final SocketAddress address; + private boolean done; + private RegistrationRequest registrationRequest; + private RuntimeException exception; + + private CancellationRequest( SocketAddress address ) + { + this.address = address; + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java new file mode 100644 index 0000000000..b1612840db --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketConnector.java @@ -0,0 +1,486 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.NewThreadExecutor; +import org.apache.mina.util.Queue; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; + +/** + * {@link IoConnector} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketConnector extends SocketConnector +{ + /** @noinspection StaticNonFinalField */ + private static volatile int nextId = 0; + + private final Object lock = new Object(); + private final int id = nextId++; + private final String threadName = "SocketConnector-" + id; + private SocketConnectorConfig defaultConfig = new SocketConnectorConfig(); + private final Queue connectQueue = new Queue(); + private final MultiThreadSocketIoProcessor[] ioProcessors; + private final int processorCount; + private final Executor executor; + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private Selector selector; + private Worker worker; + private int processorDistributor = 0; + private int workerTimeout = 60; // 1 min. + + /** Create a connector with a single processing thread using a NewThreadExecutor */ + public MultiThreadSocketConnector() + { + this(1, new NewThreadExecutor()); + } + + /** + * Create a connector with the desired number of processing threads + * + * @param processorCount Number of processing threads + * @param executor Executor to use for launching threads + */ + public MultiThreadSocketConnector(int processorCount, Executor executor) + { + if (processorCount < 1) + { + throw new IllegalArgumentException("Must have at least one processor"); + } + + this.executor = executor; + this.processorCount = processorCount; + ioProcessors = new MultiThreadSocketIoProcessor[processorCount]; + + for (int i = 0; i < processorCount; i++) + { + ioProcessors[i] = new MultiThreadSocketIoProcessor("SocketConnectorIoProcessor-" + id + "." + i, executor); + } + } + + /** + * How many seconds to keep the connection thread alive between connection requests + * + * @return Number of seconds to keep connection thread alive + */ + public int getWorkerTimeout() + { + return workerTimeout; + } + + /** + * Set how many seconds the connection worker thread should remain alive once idle before terminating itself. + * + * @param workerTimeout Number of seconds to keep thread alive. Must be >=0 + */ + public void setWorkerTimeout(int workerTimeout) + { + if (workerTimeout < 0) + { + throw new IllegalArgumentException("Must be >= 0"); + } + this.workerTimeout = workerTimeout; + } + + public ConnectFuture connect(SocketAddress address, IoHandler handler, IoServiceConfig config) + { + return connect(address, null, handler, config); + } + + public ConnectFuture connect(SocketAddress address, SocketAddress localAddress, + IoHandler handler, IoServiceConfig config) + { + if (address == null) + { + throw new NullPointerException("address"); + } + if (handler == null) + { + throw new NullPointerException("handler"); + } + + if (!(address instanceof InetSocketAddress)) + { + throw new IllegalArgumentException("Unexpected address type: " + + address.getClass()); + } + + if (localAddress != null && !(localAddress instanceof InetSocketAddress)) + { + throw new IllegalArgumentException("Unexpected local address type: " + + localAddress.getClass()); + } + + if (config == null) + { + config = getDefaultConfig(); + } + + SocketChannel ch = null; + boolean success = false; + try + { + ch = SocketChannel.open(); + ch.socket().setReuseAddress(true); + if (localAddress != null) + { + ch.socket().bind(localAddress); + } + + ch.configureBlocking(false); + + if (ch.connect(address)) + { + DefaultConnectFuture future = new DefaultConnectFuture(); + newSession(ch, handler, config, future); + success = true; + return future; + } + + success = true; + } + catch (IOException e) + { + return DefaultConnectFuture.newFailedFuture(e); + } + finally + { + if (!success && ch != null) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + + ConnectionRequest request = new ConnectionRequest(ch, handler, config); + synchronized (lock) + { + try + { + startupWorker(); + } + catch (IOException e) + { + try + { + ch.close(); + } + catch (IOException e2) + { + ExceptionMonitor.getInstance().exceptionCaught(e2); + } + + return DefaultConnectFuture.newFailedFuture(e); + } + } + + synchronized (connectQueue) + { + connectQueue.push(request); + } + selector.wakeup(); + + return request; + } + + private synchronized void startupWorker() throws IOException + { + if (worker == null) + { + selector = Selector.open(); + worker = new Worker(); + executor.execute(new NamePreservingRunnable(worker)); + } + } + + private void registerNew() + { + if (connectQueue.isEmpty()) + { + return; + } + + for (; ;) + { + ConnectionRequest req; + synchronized (connectQueue) + { + req = (ConnectionRequest) connectQueue.pop(); + } + + if (req == null) + { + break; + } + + SocketChannel ch = req.channel; + try + { + ch.register(selector, SelectionKey.OP_CONNECT, req); + } + catch (IOException e) + { + req.setException(e); + } + } + } + + private void processSessions(Set keys) + { + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isConnectable()) + { + continue; + } + + SocketChannel ch = (SocketChannel) key.channel(); + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + boolean success = false; + try + { + ch.finishConnect(); + newSession(ch, entry.handler, entry.config, entry); + success = true; + } + catch (Throwable e) + { + entry.setException(e); + } + finally + { + key.cancel(); + if (!success) + { + try + { + ch.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + keys.clear(); + } + + private void processTimedOutSessions(Set keys) + { + long currentTime = System.currentTimeMillis(); + Iterator it = keys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + + if (!key.isValid()) + { + continue; + } + + ConnectionRequest entry = (ConnectionRequest) key.attachment(); + + if (currentTime >= entry.deadline) + { + entry.setException(new ConnectException()); + try + { + key.channel().close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + key.cancel(); + } + } + } + } + + private void newSession(SocketChannel ch, IoHandler handler, IoServiceConfig config, ConnectFuture connectFuture) + throws IOException + { + MultiThreadSocketSessionImpl session = + new MultiThreadSocketSessionImpl(this, nextProcessor(), getListeners(), + config, ch, handler, ch.socket().getRemoteSocketAddress()); + + //new interface +// SocketSessionImpl session = new SocketSessionImpl( +// this, nextProcessor(), getListeners(), +// config, ch, handler, ch.socket().getRemoteSocketAddress() ); + try + { + getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + config.getThreadModel().buildFilterChain(session.getFilterChain()); + } + catch (Throwable e) + { + throw (IOException) new IOException("Failed to create a session.").initCause(e); + } + + // Set the ConnectFuture of the specified session, which will be + // removed and notified by AbstractIoFilterChain eventually. + session.setAttribute( AbstractIoFilterChain.CONNECT_FUTURE, connectFuture ); + + // Forward the remaining process to the SocketIoProcessor. + session.getIoProcessor().addNew(session); + } + + private MultiThreadSocketIoProcessor nextProcessor() + { + return ioProcessors[processorDistributor++ % processorCount]; + } + + private class Worker implements Runnable + { + private long lastActive = System.currentTimeMillis(); + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketConnector.this.threadName); + + for (; ;) + { + try + { + int nKeys = selector.select(1000); + + registerNew(); + + if (nKeys > 0) + { + processSessions(selector.selectedKeys()); + } + + processTimedOutSessions(selector.keys()); + + if (selector.keys().isEmpty()) + { + if (System.currentTimeMillis() - lastActive > workerTimeout * 1000L) + { + synchronized (lock) + { + if (selector.keys().isEmpty() && + connectQueue.isEmpty()) + { + worker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + break; + } + } + } + } + else + { + lastActive = System.currentTimeMillis(); + } + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + } + } + + private class ConnectionRequest extends DefaultConnectFuture + { + private final SocketChannel channel; + private final long deadline; + private final IoHandler handler; + private final IoServiceConfig config; + + private ConnectionRequest(SocketChannel channel, IoHandler handler, IoServiceConfig config) + { + this.channel = channel; + long timeout; + if (config instanceof IoConnectorConfig) + { + timeout = ((IoConnectorConfig) config).getConnectTimeoutMillis(); + } + else + { + timeout = ((IoConnectorConfig) getDefaultConfig()).getConnectTimeoutMillis(); + } + this.deadline = System.currentTimeMillis() + timeout; + this.handler = handler; + this.config = config; + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java new file mode 100644 index 0000000000..67b8c8d820 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketFilterChain.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import java.io.IOException; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.util.Queue; + +/** + * An {@link IoFilterChain} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + */ +class MultiThreadSocketFilterChain extends AbstractIoFilterChain { + + MultiThreadSocketFilterChain( IoSession parent ) + { + super( parent ); + } + + protected void doWrite( IoSession session, WriteRequest writeRequest ) + { + MultiThreadSocketSessionImpl s = (MultiThreadSocketSessionImpl) session; + Queue writeRequestQueue = s.getWriteRequestQueue(); + + // SocketIoProcessor.doFlush() will reset it after write is finished + // because the buffer will be passed with messageSent event. + ( ( ByteBuffer ) writeRequest.getMessage() ).mark(); + synchronized( writeRequestQueue ) + { + writeRequestQueue.push( writeRequest ); + if( writeRequestQueue.size() == 1 && session.getTrafficMask().isWritable() ) + { + // Notify SocketIoProcessor only when writeRequestQueue was empty. + s.getIoProcessor().flush( s ); + } + } + } + + protected void doClose( IoSession session ) throws IOException + { + MultiThreadSocketSessionImpl s = (MultiThreadSocketSessionImpl) session; + s.getIoProcessor().remove( s ); + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java new file mode 100644 index 0000000000..c23ad8686f --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketIoProcessor.java @@ -0,0 +1,1026 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import edu.emory.mathcs.backport.java.util.concurrent.Executor; +import edu.emory.mathcs.backport.java.util.concurrent.locks.ReentrantLock; +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IdleStatus; +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.WriteTimeoutException; +import org.apache.mina.util.IdentityHashSet; +import org.apache.mina.util.NamePreservingRunnable; +import org.apache.mina.util.Queue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Performs all I/O operations for sockets which is connected or bound. This class is used by MINA internally. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $, + */ +class MultiThreadSocketIoProcessor extends SocketIoProcessor +{ + Logger _logger = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class); + Logger _loggerRead = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class + ".Reader"); + Logger _loggerWrite = LoggerFactory.getLogger(MultiThreadSocketIoProcessor.class + ".Writer"); + + private static final long SELECTOR_TIMEOUT = 1000L; + + private int MAX_READ_BYTES_PER_SESSION = 524288; //512K + private int MAX_FLUSH_BYTES_PER_SESSION = 524288; //512K + + private final Object readLock = new Object(); + private final Object writeLock = new Object(); + + private final String threadName; + private final Executor executor; + + private ReentrantLock trafficMaskUpdateLock = new ReentrantLock(); + + /** @noinspection FieldAccessedSynchronizedAndUnsynchronized */ + private volatile Selector selector, writeSelector; + + private final Queue newSessions = new Queue(); + private final Queue removingSessions = new Queue(); + private final BlockingQueue flushingSessions = new LinkedBlockingQueue(); + private final IdentityHashSet flushingSessionsSet = new IdentityHashSet(); + + private final Queue trafficControllingSessions = new Queue(); + + private ReadWorker readWorker; + private WriteWorker writeWorker; + private long lastIdleReadCheckTime = System.currentTimeMillis(); + private long lastIdleWriteCheckTime = System.currentTimeMillis(); + + MultiThreadSocketIoProcessor(String threadName, Executor executor) + { + super(threadName, executor); + this.threadName = threadName; + this.executor = executor; + } + + void addNew(SocketSessionImpl session) throws IOException + { + synchronized (newSessions) + { + newSessions.push(session); + } + + startupWorker(); + + selector.wakeup(); + writeSelector.wakeup(); + } + + void remove(SocketSessionImpl session) throws IOException + { + scheduleRemove(session); + startupWorker(); + selector.wakeup(); + } + + private void startupWorker() throws IOException + { + synchronized (readLock) + { + if (readWorker == null) + { + selector = Selector.open(); + readWorker = new ReadWorker(); + executor.execute(new NamePreservingRunnable(readWorker)); + } + } + + synchronized (writeLock) + { + if (writeWorker == null) + { + writeSelector = Selector.open(); + writeWorker = new WriteWorker(); + executor.execute(new NamePreservingRunnable(writeWorker)); + } + } + + } + + void flush(SocketSessionImpl session) + { + scheduleFlush(session); + Selector selector = this.writeSelector; + + if (selector != null) + { + selector.wakeup(); + } + } + + void updateTrafficMask(SocketSessionImpl session) + { + scheduleTrafficControl(session); + Selector selector = this.selector; + if (selector != null) + { + selector.wakeup(); + } + } + + private void scheduleRemove(SocketSessionImpl session) + { + synchronized (removingSessions) + { + removingSessions.push(session); + } + } + + private void scheduleFlush(SocketSessionImpl session) + { + synchronized (flushingSessionsSet) + { + //if flushingSessions grows to contain Integer.MAX_VALUE sessions + // then this will fail. + if (flushingSessionsSet.add(session)) + { + flushingSessions.offer(session); + } + } + } + + private void scheduleTrafficControl(SocketSessionImpl session) + { + synchronized (trafficControllingSessions) + { + trafficControllingSessions.push(session); + } + } + + private void doAddNewReader() throws InterruptedException + { + if (newSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (newSessions) + { + session = (MultiThreadSocketSessionImpl) newSessions.peek(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + + + try + { + + ch.configureBlocking(false); + session.setSelectionKey(ch.register(selector, + SelectionKey.OP_READ, + session)); + + //System.out.println("ReadDebug:"+"Awaiting Registration"); + session.awaitRegistration(); + sessionCreated(session); + } + catch (IOException e) + { + // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute + // and call ConnectFuture.setException(). + session.getFilterChain().fireExceptionCaught(session, e); + } + } + } + + + private void doAddNewWrite() throws InterruptedException + { + if (newSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (newSessions) + { + session = (MultiThreadSocketSessionImpl) newSessions.peek(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + + try + { + ch.configureBlocking(false); + synchronized (flushingSessionsSet) + { + flushingSessionsSet.add(session); + } + + session.setWriteSelectionKey(ch.register(writeSelector, + SelectionKey.OP_WRITE, + session)); + + //System.out.println("WriteDebug:"+"Awaiting Registration"); + session.awaitRegistration(); + sessionCreated(session); + } + catch (IOException e) + { + + // Clear the AbstractIoFilterChain.CONNECT_FUTURE attribute + // and call ConnectFuture.setException(). + session.getFilterChain().fireExceptionCaught(session, e); + } + } + } + + + private void sessionCreated(SocketSessionImpl sessionParam) throws InterruptedException + { + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) sessionParam; + synchronized (newSessions) + { + if (!session.created()) + { + _logger.debug("Popping new session"); + newSessions.pop(); + + // AbstractIoFilterChain.CONNECT_FUTURE is cleared inside here + // in AbstractIoFilterChain.fireSessionOpened(). + session.getServiceListeners().fireSessionCreated(session); + + session.doneCreation(); + } + } + } + + private void doRemove() + { + if (removingSessions.isEmpty()) + { + return; + } + + for (; ;) + { + MultiThreadSocketSessionImpl session; + + synchronized (removingSessions) + { + session = (MultiThreadSocketSessionImpl) removingSessions.pop(); + } + + if (session == null) + { + break; + } + + SocketChannel ch = session.getChannel(); + SelectionKey key = session.getReadSelectionKey(); + SelectionKey writeKey = session.getWriteSelectionKey(); + + // Retry later if session is not yet fully initialized. + // (In case that Session.close() is called before addSession() is processed) + if (key == null || writeKey == null) + { + scheduleRemove(session); + break; + } + // skip if channel is already closed + if (!key.isValid() || !writeKey.isValid()) + { + continue; + } + + try + { + //System.out.println("ReadDebug:"+"Removing Session: " + System.identityHashCode(session)); + synchronized (readLock) + { + key.cancel(); + } + synchronized (writeLock) + { + writeKey.cancel(); + } + ch.close(); + } + catch (IOException e) + { + session.getFilterChain().fireExceptionCaught(session, e); + } + finally + { + releaseWriteBuffers(session); + session.getServiceListeners().fireSessionDestroyed(session); + } + } + } + + private void processRead(Set selectedKeys) + { + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) key.attachment(); + + synchronized (readLock) + { + if (key.isValid() && key.isReadable() && session.getTrafficMask().isReadable()) + { + read(session); + } + } + + } + + selectedKeys.clear(); + } + + private void processWrite(Set selectedKeys) + { + Iterator it = selectedKeys.iterator(); + + while (it.hasNext()) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + + synchronized (writeLock) + { + if (key.isValid() && key.isWritable() && session.getTrafficMask().isWritable()) + { + + // Clear OP_WRITE + key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); + + synchronized (flushingSessionsSet) + { + flushingSessions.offer(session); + } + } + } + } + + selectedKeys.clear(); + } + + private void read(SocketSessionImpl session) + { + + //if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Starting read for Session:" + System.identityHashCode(session)); + } + + int totalReadBytes = 0; + + while (totalReadBytes <= MAX_READ_BYTES_PER_SESSION) + { + ByteBuffer buf = ByteBuffer.allocate(session.getReadBufferSize()); + SocketChannel ch = session.getChannel(); + + try + { + buf.clear(); + + int readBytes = 0; + int ret; + + try + { + while ((ret = ch.read(buf.buf())) > 0) + { + readBytes += ret; + totalReadBytes += ret; + } + } + finally + { + buf.flip(); + } + + + if (readBytes > 0) + { + session.increaseReadBytes(readBytes); + + session.getFilterChain().fireMessageReceived(session, buf); + buf = null; + } + + if (ret <= 0) + { + if (ret == 0) + { + if (readBytes == session.getReadBufferSize()) + { + continue; + } + } + else + { + scheduleRemove(session); + } + + break; + } + } + catch (Throwable e) + { + if (e instanceof IOException) + { + scheduleRemove(session); + } + session.getFilterChain().fireExceptionCaught(session, e); + + //Stop Reading this session. + return; + } + finally + { + if (buf != null) + { + buf.release(); + } + } + }//for + + // if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Read for Session:" + System.identityHashCode(session) + " got: " + totalReadBytes); + } + } + + + private void notifyReadIdleness() + { + // process idle sessions + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastIdleReadCheckTime) >= 1000) + { + lastIdleReadCheckTime = currentTime; + Set keys = selector.keys(); + if (keys != null) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + notifyReadIdleness(session, currentTime); + } + } + } + } + + private void notifyWriteIdleness() + { + // process idle sessions + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastIdleWriteCheckTime) >= 1000) + { + lastIdleWriteCheckTime = currentTime; + Set keys = writeSelector.keys(); + if (keys != null) + { + for (Iterator it = keys.iterator(); it.hasNext();) + { + SelectionKey key = (SelectionKey) it.next(); + SocketSessionImpl session = (SocketSessionImpl) key.attachment(); + notifyWriteIdleness(session, currentTime); + } + } + } + } + + private void notifyReadIdleness(SocketSessionImpl session, long currentTime) + { + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, + Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.READER_IDLE), + IdleStatus.READER_IDLE, + Math.max(session.getLastReadTime(), session.getLastIdleTime(IdleStatus.READER_IDLE))); + + notifyWriteTimeout(session, currentTime, session + .getWriteTimeoutInMillis(), session.getLastWriteTime()); + } + + private void notifyWriteIdleness(SocketSessionImpl session, long currentTime) + { + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, + Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + notifyIdleness0( + session, currentTime, + session.getIdleTimeInMillis(IdleStatus.WRITER_IDLE), + IdleStatus.WRITER_IDLE, + Math.max(session.getLastWriteTime(), session.getLastIdleTime(IdleStatus.WRITER_IDLE))); + + notifyWriteTimeout(session, currentTime, session + .getWriteTimeoutInMillis(), session.getLastWriteTime()); + } + + private void notifyIdleness0(SocketSessionImpl session, long currentTime, + long idleTime, IdleStatus status, + long lastIoTime) + { + if (idleTime > 0 && lastIoTime != 0 + && (currentTime - lastIoTime) >= idleTime) + { + session.increaseIdleCount(status); + session.getFilterChain().fireSessionIdle(session, status); + } + } + + private void notifyWriteTimeout(SocketSessionImpl session, + long currentTime, + long writeTimeout, long lastIoTime) + { + + MultiThreadSocketSessionImpl sesh = (MultiThreadSocketSessionImpl) session; + SelectionKey key = sesh.getWriteSelectionKey(); + + synchronized (writeLock) + { + if (writeTimeout > 0 + && (currentTime - lastIoTime) >= writeTimeout + && key != null && key.isValid() + && (key.interestOps() & SelectionKey.OP_WRITE) != 0) + { + session.getFilterChain().fireExceptionCaught(session, new WriteTimeoutException()); + } + } + } + + private SocketSessionImpl getNextFlushingSession() + { + return (SocketSessionImpl) flushingSessions.poll(); + } + + private void releaseSession(SocketSessionImpl session) + { + synchronized (session.getWriteRequestQueue()) + { + synchronized (flushingSessionsSet) + { + if (session.getScheduledWriteRequests() > 0) + { + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Reflush" + System.identityHashCode(session)); + } + flushingSessions.offer(session); + } + else + { + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Releasing session " + System.identityHashCode(session)); + } + flushingSessionsSet.remove(session); + } + } + } + } + + private void releaseWriteBuffers(SocketSessionImpl session) + { + Queue writeRequestQueue = session.getWriteRequestQueue(); + WriteRequest req; + + //Should this be synchronized? + synchronized (writeRequestQueue) + { + while ((req = (WriteRequest) writeRequestQueue.pop()) != null) + { + try + { + ((ByteBuffer) req.getMessage()).release(); + } + catch (IllegalStateException e) + { + session.getFilterChain().fireExceptionCaught(session, e); + } + finally + { + req.getFuture().setWritten(false); + } + } + } + } + + private void doFlush() + { + MultiThreadSocketSessionImpl session; + + while ((session = (MultiThreadSocketSessionImpl) getNextFlushingSession()) != null) + { + if (!session.isConnected()) + { + releaseWriteBuffers(session); + releaseSession(session); + continue; + } + + SelectionKey key = session.getWriteSelectionKey(); + // Retry later if session is not yet fully initialized. + // (In case that Session.write() is called before addSession() is processed) + if (key == null) + { + scheduleFlush(session); + releaseSession(session); + continue; + } + // skip if channel is already closed + if (!key.isValid()) + { + releaseSession(session); + continue; + } + + try + { + if (doFlush(session)) + { + releaseSession(session); + } + } + catch (IOException e) + { + releaseSession(session); + scheduleRemove(session); + session.getFilterChain().fireExceptionCaught(session, e); + } + + } + + } + + private boolean doFlush(SocketSessionImpl sessionParam) throws IOException + { + MultiThreadSocketSessionImpl session = (MultiThreadSocketSessionImpl) sessionParam; + // Clear OP_WRITE + SelectionKey key = session.getWriteSelectionKey(); + synchronized (writeLock) + { + key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE)); + } + SocketChannel ch = session.getChannel(); + Queue writeRequestQueue = session.getWriteRequestQueue(); + + long totalFlushedBytes = 0; + while (true) + { + WriteRequest req; + + synchronized (writeRequestQueue) + { + req = (WriteRequest) writeRequestQueue.first(); + } + + if (req == null) + { + break; + } + + ByteBuffer buf = (ByteBuffer) req.getMessage(); + if (buf.remaining() == 0) + { + synchronized (writeRequestQueue) + { + writeRequestQueue.pop(); + } + + session.increaseWrittenMessages(); + + buf.reset(); + session.getFilterChain().fireMessageSent(session, req); + continue; + } + + + int writtenBytes = 0; + + // Reported as DIRMINA-362 + //note: todo: fixme: Not sure it is important but if we see NoyYetConnected exceptions or 100% CPU in the kernel then this is it. + if (key.isWritable()) + { + writtenBytes = ch.write(buf.buf()); + totalFlushedBytes += writtenBytes; + } + + if (writtenBytes > 0) + { + session.increaseWrittenBytes(writtenBytes); + } + + if (buf.hasRemaining() || (totalFlushedBytes <= MAX_FLUSH_BYTES_PER_SESSION)) + { + // Kernel buffer is full + synchronized (writeLock) + { + key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); + } + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Written BF: " + (session.getWrittenBytes() - totalFlushedBytes) + " bytes"); + } + return false; + } + } + + if (_loggerWrite.isDebugEnabled()) + { + //System.out.println("WriteDebug:"+"Written : " + (session.getWrittenBytes() - totalFlushedBytes) + " bytes"); + } + return true; + } + + private void doUpdateTrafficMask() + { + if (trafficControllingSessions.isEmpty() || trafficMaskUpdateLock.isLocked()) + { + return; + } + + // Synchronize over entire operation as this method should be called + // from both read and write thread and we don't want the order of the + // updates to get changed. + trafficMaskUpdateLock.lock(); + try + { + for (; ;) + { + MultiThreadSocketSessionImpl session; + + session = (MultiThreadSocketSessionImpl) trafficControllingSessions.pop(); + + if (session == null) + { + break; + } + + SelectionKey key = session.getReadSelectionKey(); + // Retry later if session is not yet fully initialized. + // (In case that Session.suspend??() or session.resume??() is + // called before addSession() is processed) + if (key == null) + { + scheduleTrafficControl(session); + break; + } + // skip if channel is already closed + if (!key.isValid()) + { + continue; + } + + // The normal is OP_READ and, if there are write requests in the + // session's write queue, set OP_WRITE to trigger flushing. + + //Sset to Read and Write if there is nothing then the cost + // is one loop through the flusher. + int ops = SelectionKey.OP_READ; + + // Now mask the preferred ops with the mask of the current session + int mask = session.getTrafficMask().getInterestOps(); + synchronized (readLock) + { + key.interestOps(ops & mask); + } + //Change key to the WriteSelection Key + key = session.getWriteSelectionKey(); + if (key != null && key.isValid()) + { + Queue writeRequestQueue = session.getWriteRequestQueue(); + synchronized (writeRequestQueue) + { + if (!writeRequestQueue.isEmpty()) + { + ops = SelectionKey.OP_WRITE; + synchronized (writeLock) + { + key.interestOps(ops & mask); + } + } + } + } + } + } + finally + { + trafficMaskUpdateLock.unlock(); + } + + } + + private class WriteWorker implements Runnable + { + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketIoProcessor.this.threadName + "Writer"); + + //System.out.println("WriteDebug:"+"Startup"); + for (; ;) + { + try + { + int nKeys = writeSelector.select(SELECTOR_TIMEOUT); + + doAddNewWrite(); + doUpdateTrafficMask(); + + if (nKeys > 0) + { + //System.out.println("WriteDebug:"+nKeys + " keys from writeselector"); + processWrite(writeSelector.selectedKeys()); + } + else + { + //System.out.println("WriteDebug:"+"No keys from writeselector"); + } + + doRemove(); + notifyWriteIdleness(); + + if (flushingSessionsSet.size() > 0) + { + doFlush(); + } + + if (writeSelector.keys().isEmpty()) + { + synchronized (writeLock) + { + + if (writeSelector.keys().isEmpty() && newSessions.isEmpty()) + { + writeWorker = null; + try + { + writeSelector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + writeSelector = null; + } + + break; + } + } + } + + } + catch (Throwable t) + { + ExceptionMonitor.getInstance().exceptionCaught(t); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + //System.out.println("WriteDebug:"+"Shutdown"); + } + + } + + private class ReadWorker implements Runnable + { + + public void run() + { + Thread.currentThread().setName(MultiThreadSocketIoProcessor.this.threadName + "Reader"); + + //System.out.println("ReadDebug:"+"Startup"); + for (; ;) + { + try + { + int nKeys = selector.select(SELECTOR_TIMEOUT); + + doAddNewReader(); + doUpdateTrafficMask(); + + if (nKeys > 0) + { + //System.out.println("ReadDebug:"+nKeys + " keys from selector"); + + processRead(selector.selectedKeys()); + } + else + { + //System.out.println("ReadDebug:"+"No keys from selector"); + } + + + doRemove(); + notifyReadIdleness(); + + if (selector.keys().isEmpty()) + { + + synchronized (readLock) + { + if (selector.keys().isEmpty() && newSessions.isEmpty()) + { + readWorker = null; + try + { + selector.close(); + } + catch (IOException e) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + finally + { + selector = null; + } + + break; + } + } + } + } + catch (Throwable t) + { + ExceptionMonitor.getInstance().exceptionCaught(t); + + try + { + Thread.sleep(1000); + } + catch (InterruptedException e1) + { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } + //System.out.println("ReadDebug:"+"Shutdown"); + } + + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java new file mode 100644 index 0000000000..043d4800b6 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionConfigImpl.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoConnectorConfig; +import org.apache.mina.common.support.BaseIoSessionConfig; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketException; + +/** + * An {@link IoConnectorConfig} for {@link SocketConnector}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class MultiThreadSocketSessionConfigImpl extends org.apache.mina.transport.socket.nio.SocketSessionConfigImpl +{ + private static boolean SET_RECEIVE_BUFFER_SIZE_AVAILABLE = false; + private static boolean SET_SEND_BUFFER_SIZE_AVAILABLE = false; + private static boolean GET_TRAFFIC_CLASS_AVAILABLE = false; + private static boolean SET_TRAFFIC_CLASS_AVAILABLE = false; + + private static boolean DEFAULT_REUSE_ADDRESS; + private static int DEFAULT_RECEIVE_BUFFER_SIZE; + private static int DEFAULT_SEND_BUFFER_SIZE; + private static int DEFAULT_TRAFFIC_CLASS; + private static boolean DEFAULT_KEEP_ALIVE; + private static boolean DEFAULT_OOB_INLINE; + private static int DEFAULT_SO_LINGER; + private static boolean DEFAULT_TCP_NO_DELAY; + + static + { + initialize(); + } + + private static void initialize() + { + Socket socket = null; + + socket = new Socket(); + + try + { + DEFAULT_REUSE_ADDRESS = socket.getReuseAddress(); + DEFAULT_RECEIVE_BUFFER_SIZE = socket.getReceiveBufferSize(); + DEFAULT_SEND_BUFFER_SIZE = socket.getSendBufferSize(); + DEFAULT_KEEP_ALIVE = socket.getKeepAlive(); + DEFAULT_OOB_INLINE = socket.getOOBInline(); + DEFAULT_SO_LINGER = socket.getSoLinger(); + DEFAULT_TCP_NO_DELAY = socket.getTcpNoDelay(); + + // Check if setReceiveBufferSize is supported. + try + { + socket.setReceiveBufferSize(DEFAULT_RECEIVE_BUFFER_SIZE); + SET_RECEIVE_BUFFER_SIZE_AVAILABLE = true; + } + catch( SocketException e ) + { + SET_RECEIVE_BUFFER_SIZE_AVAILABLE = false; + } + + // Check if setSendBufferSize is supported. + try + { + socket.setSendBufferSize(DEFAULT_SEND_BUFFER_SIZE); + SET_SEND_BUFFER_SIZE_AVAILABLE = true; + } + catch( SocketException e ) + { + SET_SEND_BUFFER_SIZE_AVAILABLE = false; + } + + // Check if getTrafficClass is supported. + try + { + DEFAULT_TRAFFIC_CLASS = socket.getTrafficClass(); + GET_TRAFFIC_CLASS_AVAILABLE = true; + } + catch( SocketException e ) + { + GET_TRAFFIC_CLASS_AVAILABLE = false; + DEFAULT_TRAFFIC_CLASS = 0; + } + } + catch( SocketException e ) + { + throw new ExceptionInInitializerError(e); + } + finally + { + if( socket != null ) + { + try + { + socket.close(); + } + catch( IOException e ) + { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + public static boolean isSetReceiveBufferSizeAvailable() { + return SET_RECEIVE_BUFFER_SIZE_AVAILABLE; + } + + public static boolean isSetSendBufferSizeAvailable() { + return SET_SEND_BUFFER_SIZE_AVAILABLE; + } + + public static boolean isGetTrafficClassAvailable() { + return GET_TRAFFIC_CLASS_AVAILABLE; + } + + public static boolean isSetTrafficClassAvailable() { + return SET_TRAFFIC_CLASS_AVAILABLE; + } + + private boolean reuseAddress = DEFAULT_REUSE_ADDRESS; + private int receiveBufferSize = DEFAULT_RECEIVE_BUFFER_SIZE; + private int sendBufferSize = DEFAULT_SEND_BUFFER_SIZE; + private int trafficClass = DEFAULT_TRAFFIC_CLASS; + private boolean keepAlive = DEFAULT_KEEP_ALIVE; + private boolean oobInline = DEFAULT_OOB_INLINE; + private int soLinger = DEFAULT_SO_LINGER; + private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY; + + /** + * Creates a new instance. + */ + MultiThreadSocketSessionConfigImpl() + { + } + + public boolean isReuseAddress() + { + return reuseAddress; + } + + public void setReuseAddress( boolean reuseAddress ) + { + this.reuseAddress = reuseAddress; + } + + public int getReceiveBufferSize() + { + return receiveBufferSize; + } + + public void setReceiveBufferSize( int receiveBufferSize ) + { + this.receiveBufferSize = receiveBufferSize; + } + + public int getSendBufferSize() + { + return sendBufferSize; + } + + public void setSendBufferSize( int sendBufferSize ) + { + this.sendBufferSize = sendBufferSize; + } + + public int getTrafficClass() + { + return trafficClass; + } + + public void setTrafficClass( int trafficClass ) + { + this.trafficClass = trafficClass; + } + + public boolean isKeepAlive() + { + return keepAlive; + } + + public void setKeepAlive( boolean keepAlive ) + { + this.keepAlive = keepAlive; + } + + public boolean isOobInline() + { + return oobInline; + } + + public void setOobInline( boolean oobInline ) + { + this.oobInline = oobInline; + } + + public int getSoLinger() + { + return soLinger; + } + + public void setSoLinger( int soLinger ) + { + this.soLinger = soLinger; + } + + public boolean isTcpNoDelay() + { + return tcpNoDelay; + } + + public void setTcpNoDelay( boolean tcpNoDelay ) + { + this.tcpNoDelay = tcpNoDelay; + } + + +} diff --git a/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java new file mode 100644 index 0000000000..be4a2d289d --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/socket/nio/MultiThreadSocketSessionImpl.java @@ -0,0 +1,488 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.socket.nio; + +import org.apache.mina.common.IoFilter.WriteRequest; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoService; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.RuntimeIOException; +import org.apache.mina.common.TransportType; +import org.apache.mina.common.support.BaseIoSessionConfig; +import org.apache.mina.common.support.IoServiceListenerSupport; +import org.apache.mina.util.Queue; + +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An {@link IoSession} for socket transport (TCP/IP). + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +class MultiThreadSocketSessionImpl extends SocketSessionImpl +{ + private final IoService manager; + private final IoServiceConfig serviceConfig; + private final SocketSessionConfig config = new SessionConfigImpl(); + private final MultiThreadSocketIoProcessor ioProcessor; + private final MultiThreadSocketFilterChain filterChain; + private final SocketChannel ch; + private final Queue writeRequestQueue; + private final IoHandler handler; + private final SocketAddress remoteAddress; + private final SocketAddress localAddress; + private final SocketAddress serviceAddress; + private final IoServiceListenerSupport serviceListeners; + private SelectionKey readKey, writeKey; + private int readBufferSize; + private CountDownLatch registeredReadyLatch = new CountDownLatch(2); + private AtomicBoolean created = new AtomicBoolean(false); + + /** + * Creates a new instance. + */ + MultiThreadSocketSessionImpl( IoService manager, + SocketIoProcessor ioProcessor, + IoServiceListenerSupport listeners, + IoServiceConfig serviceConfig, + SocketChannel ch, + IoHandler defaultHandler, + SocketAddress serviceAddress ) + { + super(manager, ioProcessor, listeners, serviceConfig, ch,defaultHandler,serviceAddress); + this.manager = manager; + this.serviceListeners = listeners; + this.ioProcessor = (MultiThreadSocketIoProcessor) ioProcessor; + this.filterChain = new MultiThreadSocketFilterChain(this); + this.ch = ch; + this.writeRequestQueue = new Queue(); + this.handler = defaultHandler; + this.remoteAddress = ch.socket().getRemoteSocketAddress(); + this.localAddress = ch.socket().getLocalSocketAddress(); + this.serviceAddress = serviceAddress; + this.serviceConfig = serviceConfig; + + // Apply the initial session settings + IoSessionConfig sessionConfig = serviceConfig.getSessionConfig(); + if( sessionConfig instanceof SocketSessionConfig ) + { + SocketSessionConfig cfg = ( SocketSessionConfig ) sessionConfig; + this.config.setKeepAlive( cfg.isKeepAlive() ); + this.config.setOobInline( cfg.isOobInline() ); + this.config.setReceiveBufferSize( cfg.getReceiveBufferSize() ); + this.readBufferSize = cfg.getReceiveBufferSize(); + this.config.setReuseAddress( cfg.isReuseAddress() ); + this.config.setSendBufferSize( cfg.getSendBufferSize() ); + this.config.setSoLinger( cfg.getSoLinger() ); + this.config.setTcpNoDelay( cfg.isTcpNoDelay() ); + + if( this.config.getTrafficClass() != cfg.getTrafficClass() ) + { + this.config.setTrafficClass( cfg.getTrafficClass() ); + } + } + } + + void awaitRegistration() throws InterruptedException + { + registeredReadyLatch.countDown(); + + registeredReadyLatch.await(); + } + + boolean created() throws InterruptedException + { + return created.get(); + } + + void doneCreation() + { + created.getAndSet(true); + } + + public IoService getService() + { + return manager; + } + + public IoServiceConfig getServiceConfig() + { + return serviceConfig; + } + + public IoSessionConfig getConfig() + { + return config; + } + + SocketIoProcessor getIoProcessor() + { + return ioProcessor; + } + + public IoFilterChain getFilterChain() + { + return filterChain; + } + + SocketChannel getChannel() + { + return ch; + } + + IoServiceListenerSupport getServiceListeners() + { + return serviceListeners; + } + + SelectionKey getSelectionKey() + { + return readKey; + } + + SelectionKey getReadSelectionKey() + { + return readKey; + } + + SelectionKey getWriteSelectionKey() + { + return writeKey; + } + + void setSelectionKey(SelectionKey key) + { + this.readKey = key; + } + + void setWriteSelectionKey(SelectionKey key) + { + this.writeKey = key; + } + + public IoHandler getHandler() + { + return handler; + } + + protected void close0() + { + filterChain.fireFilterClose( this ); + } + + Queue getWriteRequestQueue() + { + return writeRequestQueue; + } + + /** + @return int Number of write scheduled write requests + @deprecated + */ + public int getScheduledWriteMessages() + { + return getScheduledWriteRequests(); + } + + public int getScheduledWriteRequests() + { + synchronized( writeRequestQueue ) + { + return writeRequestQueue.size(); + } + } + + public int getScheduledWriteBytes() + { + synchronized( writeRequestQueue ) + { + return writeRequestQueue.byteSize(); + } + } + + protected void write0( WriteRequest writeRequest ) + { + filterChain.fireFilterWrite( this, writeRequest ); + } + + public TransportType getTransportType() + { + return TransportType.SOCKET; + } + + public SocketAddress getRemoteAddress() + { + //This is what I had previously +// return ch.socket().getRemoteSocketAddress(); + return remoteAddress; + } + + public SocketAddress getLocalAddress() + { + //This is what I had previously +// return ch.socket().getLocalSocketAddress(); + return localAddress; + } + + public SocketAddress getServiceAddress() + { + return serviceAddress; + } + + protected void updateTrafficMask() + { + this.ioProcessor.updateTrafficMask( this ); + } + + int getReadBufferSize() + { + return readBufferSize; + } + + private class SessionConfigImpl extends BaseIoSessionConfig implements SocketSessionConfig + { + public boolean isKeepAlive() + { + try + { + return ch.socket().getKeepAlive(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setKeepAlive( boolean on ) + { + try + { + ch.socket().setKeepAlive( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isOobInline() + { + try + { + return ch.socket().getOOBInline(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setOobInline( boolean on ) + { + try + { + ch.socket().setOOBInline( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isReuseAddress() + { + try + { + return ch.socket().getReuseAddress(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setReuseAddress( boolean on ) + { + try + { + ch.socket().setReuseAddress( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public int getSoLinger() + { + try + { + return ch.socket().getSoLinger(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setSoLinger( int linger ) + { + try + { + if( linger < 0 ) + { + ch.socket().setSoLinger( false, 0 ); + } + else + { + ch.socket().setSoLinger( true, linger ); + } + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public boolean isTcpNoDelay() + { + try + { + return ch.socket().getTcpNoDelay(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setTcpNoDelay( boolean on ) + { + try + { + ch.socket().setTcpNoDelay( on ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public int getTrafficClass() + { + if( SocketSessionConfigImpl.isGetTrafficClassAvailable() ) + { + try + { + return ch.socket().getTrafficClass(); + } + catch( SocketException e ) + { + // Throw an exception only when setTrafficClass is also available. + if( SocketSessionConfigImpl.isSetTrafficClassAvailable() ) + { + throw new RuntimeIOException( e ); + } + } + } + + return 0; + } + + public void setTrafficClass( int tc ) + { + if( SocketSessionConfigImpl.isSetTrafficClassAvailable() ) + { + try + { + ch.socket().setTrafficClass( tc ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + + public int getSendBufferSize() + { + try + { + return ch.socket().getSendBufferSize(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setSendBufferSize( int size ) + { + if( SocketSessionConfigImpl.isSetSendBufferSizeAvailable() ) + { + try + { + ch.socket().setSendBufferSize( size ); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + + public int getReceiveBufferSize() + { + try + { + return ch.socket().getReceiveBufferSize(); + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + + public void setReceiveBufferSize( int size ) + { + if( SocketSessionConfigImpl.isSetReceiveBufferSizeAvailable() ) + { + try + { + ch.socket().setReceiveBufferSize( size ); + MultiThreadSocketSessionImpl.this.readBufferSize = size; + } + catch( SocketException e ) + { + throw new RuntimeIOException( e ); + } + } + } + } +} diff --git a/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java b/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java new file mode 100644 index 0000000000..a23e546af5 --- /dev/null +++ b/java/common/src/main/java/org/apache/mina/transport/vmpipe/QpidVmPipeConnector.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.transport.vmpipe; + +import java.io.IOException; +import java.net.SocketAddress; + +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.ExceptionMonitor; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandler; +import org.apache.mina.common.IoServiceConfig; +import org.apache.mina.common.IoSessionConfig; +import org.apache.mina.common.support.AbstractIoFilterChain; +import org.apache.mina.common.support.BaseIoConnector; +import org.apache.mina.common.support.BaseIoConnectorConfig; +import org.apache.mina.common.support.BaseIoSessionConfig; +import org.apache.mina.common.support.DefaultConnectFuture; +import org.apache.mina.transport.vmpipe.support.VmPipe; +import org.apache.mina.transport.vmpipe.support.VmPipeIdleStatusChecker; +import org.apache.mina.transport.vmpipe.support.VmPipeSessionImpl; +import org.apache.mina.util.AnonymousSocketAddress; + +/** + * Connects to {@link IoHandler}s which is bound on the specified + * {@link VmPipeAddress}. + * + * @author The Apache Directory Project (mina-dev@directory.apache.org) + * @version $Rev: 619823 $, $Date: 2008-02-08 10:09:37 +0000 (Fri, 08 Feb 2008) $ + */ +public class QpidVmPipeConnector extends VmPipeConnector +{ + private static final IoSessionConfig CONFIG = new BaseIoSessionConfig() {}; + private final IoServiceConfig defaultConfig = new BaseIoConnectorConfig() + { + public IoSessionConfig getSessionConfig() + { + return CONFIG; + } + }; + + /** + * Creates a new instance. + */ + public QpidVmPipeConnector() + { + } + + public ConnectFuture connect( SocketAddress address, IoHandler handler, IoServiceConfig config ) + { + return connect( address, null, handler, config ); + } + + public ConnectFuture connect( SocketAddress address, SocketAddress localAddress, IoHandler handler, IoServiceConfig config ) + { + if( address == null ) + throw new NullPointerException( "address" ); + if( handler == null ) + throw new NullPointerException( "handler" ); + if( ! ( address instanceof VmPipeAddress ) ) + throw new IllegalArgumentException( + "address must be VmPipeAddress." ); + + if( config == null ) + { + config = getDefaultConfig(); + } + + VmPipe entry = ( VmPipe ) VmPipeAcceptor.boundHandlers.get( address ); + if( entry == null ) + { + return DefaultConnectFuture.newFailedFuture( + new IOException( "Endpoint unavailable: " + address ) ); + } + + DefaultConnectFuture future = new DefaultConnectFuture(); + VmPipeSessionImpl localSession = + new VmPipeSessionImpl( + this, + config, + getListeners(), + new Object(), // lock + new AnonymousSocketAddress(), + handler, + entry ); + + // initialize acceptor session + VmPipeSessionImpl remoteSession = localSession.getRemoteSession(); + try + { + IoFilterChain filterChain = remoteSession.getFilterChain(); + entry.getAcceptor().getFilterChainBuilder().buildFilterChain( filterChain ); + entry.getConfig().getFilterChainBuilder().buildFilterChain( filterChain ); + entry.getConfig().getThreadModel().buildFilterChain( filterChain ); + + // The following sentences don't throw any exceptions. + entry.getListeners().fireSessionCreated( remoteSession ); + VmPipeIdleStatusChecker.getInstance().addSession( remoteSession ); + } + catch( Throwable t ) + { + ExceptionMonitor.getInstance().exceptionCaught( t ); + remoteSession.close(); + } + + + // initialize connector session + try + { + IoFilterChain filterChain = localSession.getFilterChain(); + this.getFilterChainBuilder().buildFilterChain( filterChain ); + config.getFilterChainBuilder().buildFilterChain( filterChain ); + config.getThreadModel().buildFilterChain( filterChain ); + + // The following sentences don't throw any exceptions. + localSession.setAttribute( AbstractIoFilterChain.CONNECT_FUTURE, future ); + getListeners().fireSessionCreated( localSession ); + VmPipeIdleStatusChecker.getInstance().addSession( localSession); + } + catch( Throwable t ) + { + future.setException( t ); + } + + + + return future; + } + + public IoServiceConfig getDefaultConfig() + { + return defaultConfig; + } +} \ No newline at end of file diff --git a/java/common/src/main/java/org/apache/qpid/AMQChannelException.java b/java/common/src/main/java/org/apache/qpid/AMQChannelException.java index 19f5035e33..f6f596da95 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQChannelException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQChannelException.java @@ -20,9 +20,7 @@ */ package org.apache.qpid; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQConstant; /** @@ -55,7 +53,7 @@ public class AMQChannelException extends AMQException public AMQFrame getCloseFrame(int channel) { - return ChannelCloseBody.createAMQFrame(channel, major, minor, _classId, _methodId, getErrorCode().getCode(), - new AMQShortString(getMessage())); + MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); + return new AMQFrame(channel, reg.createChannelCloseBody(getErrorCode().getCode(), new AMQShortString(getMessage()),_classId,_methodId)); } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java b/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java index ba9f69a05c..afd415b1eb 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQConnectionException.java @@ -21,9 +21,7 @@ package org.apache.qpid; -import org.apache.qpid.framing.AMQFrame; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.*; import org.apache.qpid.protocol.AMQConstant; /** @@ -61,7 +59,12 @@ public class AMQConnectionException extends AMQException public AMQFrame getCloseFrame(int channel) { - return ConnectionCloseBody.createAMQFrame(channel, major, minor, _classId, _methodId, getErrorCode().getCode(), - new AMQShortString(getMessage())); + MethodRegistry reg = MethodRegistry.getMethodRegistry(new ProtocolVersion(major,minor)); + return new AMQFrame(channel, + reg.createConnectionCloseBody(getErrorCode().getCode(), + new AMQShortString(getMessage()), + _classId, + _methodId)); + } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java b/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java index b60f130652..6cdd57d6f2 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQConnectionFailureException.java @@ -1,40 +1,47 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -package org.apache.qpid; - -/** - * AMQConnectionFailureException indicates that a connection to a broker could not be formed. - * - *

- *
CRC Card
Responsibilities Collaborations - *
Represents failure to connect to a broker. - *
- * - * @todo Not an AMQP exception as no status code. - */ -public class AMQConnectionFailureException extends AMQException -{ - public AMQConnectionFailureException(String message, Throwable cause) - { - super(null, message, cause); - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid; + +import org.apache.qpid.protocol.AMQConstant; + +/** + * AMQConnectionFailureException indicates that a connection to a broker could not be formed. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Represents failure to connect to a broker. + *
+ * + * @todo Not an AMQP exception as no status code. + */ +public class AMQConnectionFailureException extends AMQException +{ + public AMQConnectionFailureException(String message, Throwable cause) + { + super(null, message, cause); + } + + public AMQConnectionFailureException(AMQConstant errorCode, String message, Throwable cause) + { + super(errorCode, message, cause); + } +} diff --git a/java/common/src/main/java/org/apache/qpid/AMQException.java b/java/common/src/main/java/org/apache/qpid/AMQException.java index 6cbb98fd86..eda532b64e 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQException.java @@ -20,6 +20,8 @@ */ package org.apache.qpid; +import javax.management.JMException; + import org.apache.qpid.protocol.AMQConstant; /** @@ -38,7 +40,7 @@ public class AMQException extends Exception { /** Holds the AMQ error code constant associated with this exception. */ private AMQConstant _errorCode; - + /** * Creates an exception with an optional error code, optional message and optional underlying cause. * @@ -52,6 +54,28 @@ public class AMQException extends Exception _errorCode = errorCode; } + /* + * Deprecated constructors brought from M2.1 + */ + @Deprecated + public AMQException (String msg) + { + this(null, (msg == null) ? "" : msg); + } + + @Deprecated + public AMQException (AMQConstant errorCode, String msg) + { + this(errorCode, (msg == null) ? "" : msg, null); + } + + @Deprecated + public AMQException(String msg, Throwable cause) + { + this(null, msg, cause); + } + + /** * Gets the AMQ protocol exception code associated with this exception. * @@ -61,4 +85,9 @@ public class AMQException extends Exception { return _errorCode; } + + public boolean isHardError() + { + return true; + } } diff --git a/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java b/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java index 15c8bea0a4..baca2a4773 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQInvalidArgumentException.java @@ -36,4 +36,10 @@ public class AMQInvalidArgumentException extends AMQException { super(AMQConstant.INVALID_ARGUMENT, message, cause); } + + public boolean isHardError() + { + return false; + } + } diff --git a/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java b/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java index 1502c0efc5..01a569b693 100644 --- a/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java +++ b/java/common/src/main/java/org/apache/qpid/AMQUndeliveredException.java @@ -45,4 +45,10 @@ public class AMQUndeliveredException extends AMQException { return _bounced; } + + public boolean isHardError() + { + return false; + } + } diff --git a/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java b/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java index 02ae3cb089..7eef73f337 100644 --- a/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java +++ b/java/common/src/main/java/org/apache/qpid/codec/AMQDecoder.java @@ -22,6 +22,7 @@ package org.apache.qpid.codec; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; import org.apache.mina.filter.codec.CumulativeProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; @@ -48,6 +49,9 @@ import org.apache.qpid.framing.ProtocolInitiation; */ public class AMQDecoder extends CumulativeProtocolDecoder { + + private static final String BUFFER = AMQDecoder.class.getName() + ".Buffer"; + /** Holds the 'normal' AMQP data decoder. */ private AMQDataBlockDecoder _dataBlockDecoder = new AMQDataBlockDecoder(); @@ -56,6 +60,7 @@ public class AMQDecoder extends CumulativeProtocolDecoder /** Flag to indicate whether this decoder needs to handle protocol initiation. */ private boolean _expectProtocolInitiation; + private boolean firstDecode = true; /** * Creates a new AMQP decoder. @@ -81,14 +86,24 @@ public class AMQDecoder extends CumulativeProtocolDecoder */ protected boolean doDecode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception { - if (_expectProtocolInitiation) + + boolean decoded; + if (_expectProtocolInitiation + || (firstDecode + && (in.remaining() > 0) + && (in.get(in.position()) == (byte)'A'))) { - return doDecodePI(session, in, out); + decoded = doDecodePI(session, in, out); } else { - return doDecodeDataBlock(session, in, out); + decoded = doDecodeDataBlock(session, in, out); } + if(firstDecode && decoded) + { + firstDecode = false; + } + return decoded; } /** @@ -160,4 +175,97 @@ public class AMQDecoder extends CumulativeProtocolDecoder { _expectProtocolInitiation = expectProtocolInitiation; } + + + /** + * Cumulates content of in into internal buffer and forwards + * decoding request to {@link #doDecode(IoSession, ByteBuffer, ProtocolDecoderOutput)}. + * doDecode() is invoked repeatedly until it returns false + * and the cumulative buffer is compacted after decoding ends. + * + * @throws IllegalStateException if your doDecode() returned + * true not consuming the cumulative buffer. + */ + public void decode( IoSession session, ByteBuffer in, + ProtocolDecoderOutput out ) throws Exception + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + // if we have a session buffer, append data to that otherwise + // use the buffer read from the network directly + if( buf != null ) + { + buf.put( in ); + buf.flip(); + } + else + { + buf = in; + } + + for( ;; ) + { + int oldPos = buf.position(); + boolean decoded = doDecode( session, buf, out ); + if( decoded ) + { + if( buf.position() == oldPos ) + { + throw new IllegalStateException( + "doDecode() can't return true when buffer is not consumed." ); + } + + if( !buf.hasRemaining() ) + { + break; + } + } + else + { + break; + } + } + + // if there is any data left that cannot be decoded, we store + // it in a buffer in the session and next time this decoder is + // invoked the session buffer gets appended to + if ( buf.hasRemaining() ) + { + storeRemainingInSession( buf, session ); + } + else + { + removeSessionBuffer( session ); + } + } + + /** + * Releases the cumulative buffer used by the specified session. + * Please don't forget to call super.dispose( session ) when + * you override this method. + */ + public void dispose( IoSession session ) throws Exception + { + removeSessionBuffer( session ); + } + + private void removeSessionBuffer(IoSession session) + { + ByteBuffer buf = ( ByteBuffer ) session.getAttribute( BUFFER ); + if( buf != null ) + { + buf.release(); + session.removeAttribute( BUFFER ); + } + } + + private static final SimpleByteBufferAllocator SIMPLE_BYTE_BUFFER_ALLOCATOR = new SimpleByteBufferAllocator(); + + private void storeRemainingInSession(ByteBuffer buf, IoSession session) + { + ByteBuffer remainingBuf = SIMPLE_BYTE_BUFFER_ALLOCATOR.allocate( buf.remaining(), false ); + remainingBuf.setAutoExpand( true ); + remainingBuf.put( buf ); + session.setAttribute( BUFFER, remainingBuf ); + } + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java b/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java index c497717870..fe04155bb8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQBody.java @@ -1,39 +1,40 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.framing; - -import org.apache.mina.common.ByteBuffer; - -public abstract class AMQBody -{ - public abstract byte getFrameType(); - - /** - * Get the size of the body - * @return unsigned short - */ - protected abstract int getSize(); - - protected abstract void writePayload(ByteBuffer buffer); - - protected abstract void populateFromBuffer(ByteBuffer buffer, long size) - throws AMQFrameDecodingException, AMQProtocolVersionException; -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; + +public interface AMQBody +{ + public byte getFrameType(); + + /** + * Get the size of the body + * @return unsigned short + */ + public abstract int getSize(); + + public void writePayload(ByteBuffer buffer); + + void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession) throws AMQException; +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java b/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java index 11f505fd4b..02a46f3748 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQFrame.java @@ -27,7 +27,7 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock private final int _channel; private final AMQBody _bodyFrame; - + public static final byte FRAME_END_BYTE = (byte) 0xCE; public AMQFrame(final int channel, final AMQBody bodyFrame) @@ -47,13 +47,19 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock return 1 + 2 + 4 + _bodyFrame.getSize() + 1; } + public static final int getFrameOverhead() + { + return 1 + 2 + 4 + 1; + } + + public void writePayload(ByteBuffer buffer) { buffer.put(_bodyFrame.getFrameType()); EncodingUtils.writeUnsignedShort(buffer, _channel); EncodingUtils.writeUnsignedInteger(buffer, _bodyFrame.getSize()); _bodyFrame.writePayload(buffer); - buffer.put((byte) 0xCE); + buffer.put(FRAME_END_BYTE); } public final int getChannel() @@ -66,10 +72,54 @@ public class AMQFrame extends AMQDataBlock implements EncodableAMQDataBlock return _bodyFrame; } - - public String toString() { return "Frame channelId: " + _channel + ", bodyFrame: " + String.valueOf(_bodyFrame); } + + public static void writeFrame(ByteBuffer buffer, final int channel, AMQBody body) + { + buffer.put(body.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body.getSize()); + body.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2) + { + buffer.put(body1.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); + body1.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); + body2.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + + public static void writeFrames(ByteBuffer buffer, final int channel, AMQBody body1, AMQBody body2, AMQBody body3) + { + buffer.put(body1.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body1.getSize()); + body1.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body2.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body2.getSize()); + body2.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + buffer.put(body3.getFrameType()); + EncodingUtils.writeUnsignedShort(buffer, channel); + EncodingUtils.writeUnsignedInteger(buffer, body3.getSize()); + body3.writePayload(buffer); + buffer.put(FRAME_END_BYTE); + + } + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java b/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java index 843b6a1e8c..2373edb478 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQFrameDecodingException.java @@ -38,4 +38,10 @@ public class AMQFrameDecodingException extends AMQException { super(errorCode, message, cause); } + + public AMQFrameDecodingException(AMQConstant errorCode, String message) + { + super(errorCode, message, null); + } + } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java index 0982847aac..4763b22290 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBody.java @@ -1,132 +1,83 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -package org.apache.qpid.framing; - -import org.apache.mina.common.ByteBuffer; -import org.apache.qpid.AMQChannelException; -import org.apache.qpid.AMQConnectionException; -import org.apache.qpid.protocol.AMQConstant; - -public abstract class AMQMethodBody extends AMQBody -{ - public static final byte TYPE = 1; - - /** AMQP version */ - protected byte major; - protected byte minor; - - public byte getMajor() - { - return major; - } - - public byte getMinor() - { - return minor; - } - - public AMQMethodBody(byte major, byte minor) - { - this.major = major; - this.minor = minor; - } - - /** unsigned short */ - protected abstract int getBodySize(); - - /** @return unsigned short */ - protected abstract int getClazz(); - - /** @return unsigned short */ - protected abstract int getMethod(); - - protected abstract void writeMethodPayload(ByteBuffer buffer); - - public byte getFrameType() - { - return TYPE; - } - - protected int getSize() - { - return 2 + 2 + getBodySize(); - } - - protected void writePayload(ByteBuffer buffer) - { - EncodingUtils.writeUnsignedShort(buffer, getClazz()); - EncodingUtils.writeUnsignedShort(buffer, getMethod()); - writeMethodPayload(buffer); - } - - protected abstract void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException; - - protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException - { - populateMethodBodyFromBuffer(buffer); - } - - public String toString() - { - StringBuffer buf = new StringBuffer(getClass().getName()); - buf.append("[ Class: ").append(getClazz()); - buf.append(" Method: ").append(getMethod()).append(']'); - return buf.toString(); - } - - /** - * Creates an AMQChannelException for the corresponding body type (a channel exception should include the class and - * method ids of the body it resulted from). - */ - - /** - * Convenience Method to create a channel not found exception - * - * @param channelId The channel id that is not found - * - * @return new AMQChannelException - */ - public AMQChannelException getChannelNotFoundException(int channelId) - { - return getChannelException(AMQConstant.NOT_FOUND, "Channel not found for id:" + channelId); - } - - public AMQChannelException getChannelException(AMQConstant code, String message) - { - return new AMQChannelException(code, message, getClazz(), getMethod(), major, minor, null); - } - - public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause) - { - return new AMQChannelException(code, message, getClazz(), getMethod(), major, minor, cause); - } - - public AMQConnectionException getConnectionException(AMQConstant code, String message) - { - return new AMQConnectionException(code, message, getClazz(), getMethod(), major, minor, null); - } - - public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause) - { - return new AMQConnectionException(code, message, getClazz(), getMethod(), major, minor, cause); - } - -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; + +public interface AMQMethodBody extends AMQBody +{ + public static final byte TYPE = 1; + + /** AMQP version */ + public byte getMajor(); + + public byte getMinor(); + + + + /** @return unsigned short */ + public int getClazz(); + + /** @return unsigned short */ + public int getMethod(); + + public void writeMethodPayload(ByteBuffer buffer); + + + public int getSize(); + + public void writePayload(ByteBuffer buffer); + + //public abstract void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException; + + //public void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException; + + public AMQFrame generateFrame(int channelId); + + public String toString(); + + + + /** + * Convenience Method to create a channel not found exception + * + * @param channelId The channel id that is not found + * + * @return new AMQChannelException + */ + public AMQChannelException getChannelNotFoundException(int channelId); + + public AMQChannelException getChannelException(AMQConstant code, String message); + + public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause); + + public AMQConnectionException getConnectionException(AMQConstant code, String message); + + + public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause); + + + public boolean execute(MethodDispatcher methodDispatcher, int channelId) throws AMQException; +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java index cf85bdab31..1a7022c11b 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyFactory.java @@ -40,7 +40,6 @@ public class AMQMethodBodyFactory implements BodyFactory public AMQBody createBody(ByteBuffer in, long bodySize) throws AMQFrameDecodingException { - return _protocolSession.getRegistry().get((short) in.getUnsignedShort(), (short) in.getUnsignedShort(), in, - bodySize); + return _protocolSession.getMethodRegistry().convertToBody(in, bodySize); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java new file mode 100644 index 0000000000..64af717342 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyImpl.java @@ -0,0 +1,96 @@ +package org.apache.qpid.framing; + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; + +public abstract class AMQMethodBodyImpl implements AMQMethodBody +{ + public static final byte TYPE = 1; + + public AMQMethodBodyImpl() + { + } + + public byte getFrameType() + { + return TYPE; + } + + + /** unsigned short */ + abstract protected int getBodySize(); + + + public AMQFrame generateFrame(int channelId) + { + return new AMQFrame(channelId, this); + } + + /** + * Creates an AMQChannelException for the corresponding body type (a channel exception should include the class and + * method ids of the body it resulted from). + */ + + /** + * Convenience Method to create a channel not found exception + * + * @param channelId The channel id that is not found + * + * @return new AMQChannelException + */ + public AMQChannelException getChannelNotFoundException(int channelId) + { + return getChannelException(AMQConstant.NOT_FOUND, "Channel not found for id:" + channelId); + } + + public AMQChannelException getChannelException(AMQConstant code, String message) + { + return new AMQChannelException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), null); + } + + public AMQChannelException getChannelException(AMQConstant code, String message, Throwable cause) + { + return new AMQChannelException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), cause); + } + + public AMQConnectionException getConnectionException(AMQConstant code, String message) + { + return new AMQConnectionException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), null); + } + + public AMQConnectionException getConnectionException(AMQConstant code, String message, Throwable cause) + { + return new AMQConnectionException(code, message, getClazz(), getMethod(), getMajor(), getMinor(), cause); + } + + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) throws AMQException + { + session.methodFrameReceived(channelId, this); + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java index 359efe7eb7..0030742e94 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQMethodBodyInstanceFactory.java @@ -26,6 +26,5 @@ import org.apache.mina.common.ByteBuffer; public abstract interface AMQMethodBodyInstanceFactory { - public AMQMethodBody newInstance(byte major, byte minor, ByteBuffer buffer, long size) throws AMQFrameDecodingException; - public AMQMethodBody newInstance(byte major, byte minor, int clazzID, int methodID, ByteBuffer buffer, long size) throws AMQFrameDecodingException; + public AMQMethodBody newInstance(ByteBuffer buffer, long size) throws AMQFrameDecodingException; } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java b/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java index ee63f2c83d..a747aaeda7 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQShortString.java @@ -1,444 +1,721 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -package org.apache.qpid.framing; - -import org.apache.mina.common.ByteBuffer; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Map; -import java.util.WeakHashMap; -import java.lang.ref.WeakReference; - -/** - * A short string is a representation of an AMQ Short String - * Short strings differ from the Java String class by being limited to on ASCII characters (0-127) - * and thus can be held more effectively in a byte buffer. - * - */ -public final class AMQShortString implements CharSequence, Comparable -{ - - private static final ThreadLocal>> _localInternMap = - new ThreadLocal>>() - { - protected Map> initialValue() - { - return new WeakHashMap>(); - }; - }; - - private static final Map> _globalInternMap = - new WeakHashMap>(); - - private static final Logger _logger = LoggerFactory.getLogger(AMQShortString.class); - - private final ByteBuffer _data; - private int _hashCode; - private final int _length; - private static final char[] EMPTY_CHAR_ARRAY = new char[0]; - private char[] chars; - private String str; - - public AMQShortString(byte[] data) - { - - _data = ByteBuffer.wrap(data); - _length = data.length; - } - - public AMQShortString(String data) - { - this((data == null) ? EMPTY_CHAR_ARRAY : data.toCharArray()); - if (data != null) - { - _hashCode = data.hashCode(); - } - } - - public AMQShortString(char[] data) - { - if (data == null) - { - throw new NullPointerException("Cannot create AMQShortString with null char[]"); - } - - final int length = data.length; - final byte[] stringBytes = new byte[length]; - for (int i = 0; i < length; i++) - { - stringBytes[i] = (byte) (0xFF & data[i]); - } - - _data = ByteBuffer.wrap(stringBytes); - _data.rewind(); - _length = length; - - } - - public AMQShortString(CharSequence charSequence) - { - final int length = charSequence.length(); - final byte[] stringBytes = new byte[length]; - int hash = 0; - for (int i = 0; i < length; i++) - { - stringBytes[i] = ((byte) (0xFF & charSequence.charAt(i))); - hash = (31 * hash) + stringBytes[i]; - - } - - _data = ByteBuffer.wrap(stringBytes); - _data.rewind(); - _hashCode = hash; - _length = length; - - } - - private AMQShortString(ByteBuffer data) - { - _data = data; - _length = data.limit(); - - } - - /** - * Get the length of the short string - * @return length of the underlying byte array - */ - public int length() - { - return _length; - } - - public char charAt(int index) - { - - return (char) _data.get(index); - - } - - public CharSequence subSequence(int start, int end) - { - return new CharSubSequence(start, end); - } - - public int writeToByteArray(byte[] encoding, int pos) - { - final int size = length(); - encoding[pos++] = (byte) length(); - for (int i = 0; i < size; i++) - { - encoding[pos++] = _data.get(i); - } - - return pos; - } - - public static AMQShortString readFromByteArray(byte[] byteEncodedDestination, int pos) - { - - final byte len = byteEncodedDestination[pos]; - if (len == 0) - { - return null; - } - - ByteBuffer data = ByteBuffer.wrap(byteEncodedDestination, pos + 1, len).slice(); - - return new AMQShortString(data); - } - - public static AMQShortString readFromBuffer(ByteBuffer buffer) - { - final short length = buffer.getUnsigned(); - if (length == 0) - { - return null; - } - else - { - ByteBuffer data = buffer.slice(); - data.limit(length); - data.rewind(); - buffer.skip(length); - - return new AMQShortString(data); - } - } - - public byte[] getBytes() - { - - if (_data.buf().hasArray() && (_data.arrayOffset() == 0)) - { - return _data.array(); - } - else - { - final int size = length(); - byte[] b = new byte[size]; - ByteBuffer buf = _data.duplicate(); - buf.rewind(); - buf.get(b); - - return b; - } - - } - - public void writeToBuffer(ByteBuffer buffer) - { - - final int size = length(); - if (size != 0) - { - - buffer.put((byte) size); - if (_data.buf().hasArray()) - { - buffer.put(_data.array(), _data.arrayOffset(), length()); - } - else - { - - for (int i = 0; i < size; i++) - { - - buffer.put(_data.get(i)); - } - } - } - else - { - // really writing out unsigned byte - buffer.put((byte) 0); - } - - } - - private final class CharSubSequence implements CharSequence - { - private final int _offset; - private final int _end; - - public CharSubSequence(final int offset, final int end) - { - _offset = offset; - _end = end; - } - - public int length() - { - return _end - _offset; - } - - public char charAt(int index) - { - return AMQShortString.this.charAt(index + _offset); - } - - public CharSequence subSequence(int start, int end) - { - return new CharSubSequence(start + _offset, end + _offset); - } - } - - public char[] asChars() - { - if (chars == null) - { - final int size = length(); - chars = new char[size]; - - for (int i = 0; i < size; i++) - { - chars[i] = (char) _data.get(i); - } - } - - return chars; - } - - public String asString() - { - if (str == null) - { - str = new String(asChars()); - } - - return str; - } - - public boolean equals(Object o) - { - if (o == null) - { - return false; - } - - if (o == this) - { - return true; - } - - if (o instanceof AMQShortString) - { - - final AMQShortString otherString = (AMQShortString) o; - - if ((_hashCode != 0) && (otherString._hashCode != 0) && (_hashCode != otherString._hashCode)) - { - return false; - } - - return _data.equals(otherString._data); - - } - - return (o instanceof CharSequence) && equals((CharSequence) o); - - } - - public boolean equals(CharSequence s) - { - if (s == null) - { - return false; - } - - if (s.length() != length()) - { - return false; - } - - for (int i = 0; i < length(); i++) - { - if (charAt(i) != s.charAt(i)) - { - return false; - } - } - - return true; - } - - public int hashCode() - { - int hash = _hashCode; - if (hash == 0) - { - final int size = length(); - - for (int i = 0; i < size; i++) - { - hash = (31 * hash) + _data.get(i); - } - - _hashCode = hash; - } - - return hash; - } - - public void setDirty() - { - _hashCode = 0; - } - - public String toString() - { - return asString(); - } - - public int compareTo(AMQShortString name) - { - if (name == null) - { - return 1; - } - else - { - - if (name.length() < length()) - { - return -name.compareTo(this); - } - - for (int i = 0; i < length(); i++) - { - final byte d = _data.get(i); - final byte n = name._data.get(i); - if (d < n) - { - return -1; - } - - if (d > n) - { - return 1; - } - } - - return (length() == name.length()) ? 0 : -1; - } - } - - public AMQShortString intern() - { - - hashCode(); - - Map> localMap = - _localInternMap.get(); - - WeakReference ref = localMap.get(this); - AMQShortString internString; - - if(ref != null) - { - internString = ref.get(); - if(internString != null) - { - return internString; - } - } - - - synchronized(_globalInternMap) - { - - ref = _globalInternMap.get(this); - if((ref == null) || ((internString = ref.get()) == null)) - { - internString = new AMQShortString(getBytes()); - ref = new WeakReference(internString); - _globalInternMap.put(internString, ref); - } - - } - localMap.put(internString, ref); - return internString; - - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.lang.ref.WeakReference; + +/** + * A short string is a representation of an AMQ Short String + * Short strings differ from the Java String class by being limited to on ASCII characters (0-127) + * and thus can be held more effectively in a byte buffer. + * + */ +public final class AMQShortString implements CharSequence, Comparable +{ + private static final byte MINUS = (byte)'-'; + private static final byte ZERO = (byte) '0'; + + + + private final class TokenizerImpl implements AMQShortStringTokenizer + { + private final byte _delim; + private int _count = -1; + private int _pos = 0; + + public TokenizerImpl(final byte delim) + { + _delim = delim; + } + + public int countTokens() + { + if(_count == -1) + { + _count = 1 + AMQShortString.this.occurences(_delim); + } + return _count; + } + + public AMQShortString nextToken() + { + if(_pos <= AMQShortString.this.length()) + { + int nextDelim = AMQShortString.this.indexOf(_delim, _pos); + if(nextDelim == -1) + { + nextDelim = AMQShortString.this.length(); + } + + AMQShortString nextToken = AMQShortString.this.substring(_pos, nextDelim++); + _pos = nextDelim; + return nextToken; + } + else + { + return null; + } + } + + public boolean hasMoreTokens() + { + return _pos <= AMQShortString.this.length(); + } + } + + private AMQShortString substring(final int from, final int to) + { + return new AMQShortString(_data, from+_offset, to+_offset); + } + + + private static final ThreadLocal>> _localInternMap = + new ThreadLocal>>() + { + protected Map> initialValue() + { + return new WeakHashMap>(); + }; + }; + + private static final Map> _globalInternMap = + new WeakHashMap>(); + + private static final Logger _logger = LoggerFactory.getLogger(AMQShortString.class); + + private final byte[] _data; + private final int _offset; + private int _hashCode; + private final int _length; + private static final char[] EMPTY_CHAR_ARRAY = new char[0]; + + public static final AMQShortString EMPTY_STRING = new AMQShortString((String)null); + + public AMQShortString(byte[] data) + { + + _data = data.clone(); + _length = data.length; + _offset = 0; + } + + public AMQShortString(byte[] data, int pos) + { + final int size = data[pos++]; + final byte[] dataCopy = new byte[size]; + System.arraycopy(data,pos,dataCopy,0,size); + _length = size; + _data = dataCopy; + _offset = 0; + } + + public AMQShortString(String data) + { + this((data == null) ? EMPTY_CHAR_ARRAY : data.toCharArray()); + + } + + public AMQShortString(char[] data) + { + if (data == null) + { + throw new NullPointerException("Cannot create AMQShortString with null char[]"); + } + + final int length = data.length; + final byte[] stringBytes = new byte[length]; + int hash = 0; + for (int i = 0; i < length; i++) + { + stringBytes[i] = (byte) (0xFF & data[i]); + hash = (31 * hash) + stringBytes[i]; + } + _hashCode = hash; + _data = stringBytes; + + _length = length; + _offset = 0; + + } + + public AMQShortString(CharSequence charSequence) + { + final int length = charSequence.length(); + final byte[] stringBytes = new byte[length]; + int hash = 0; + for (int i = 0; i < length; i++) + { + stringBytes[i] = ((byte) (0xFF & charSequence.charAt(i))); + hash = (31 * hash) + stringBytes[i]; + + } + + _data = stringBytes; + _hashCode = hash; + _length = length; + _offset = 0; + + } + + private AMQShortString(ByteBuffer data, final int length) + { + if(data.isDirect() || data.isReadOnly()) + { + byte[] dataBytes = new byte[length]; + data.get(dataBytes); + _data = dataBytes; + _offset = 0; + } + else + { + + _data = data.array(); + _offset = data.arrayOffset() + data.position(); + data.skip(length); + + } + _length = length; + + } + + private AMQShortString(final byte[] data, final int from, final int to) + { + _offset = from; + _length = to - from; + _data = data; + } + + public AMQShortString shrink() + { + if(_data.length != _length) + { + byte[] dataBytes = new byte[_length]; + System.arraycopy(_data,_offset,dataBytes,0,_length); + return new AMQShortString(dataBytes,0,_length); + } + else + { + return this; + } + } + + + /** + * Get the length of the short string + * @return length of the underlying byte array + */ + public int length() + { + return _length; + } + + public char charAt(int index) + { + + return (char) _data[_offset + index]; + + } + + public CharSequence subSequence(int start, int end) + { + return new CharSubSequence(start, end); + } + + public int writeToByteArray(byte[] encoding, int pos) + { + final int size = length(); + encoding[pos++] = (byte) size; + System.arraycopy(_data,_offset,encoding,pos,size); + return pos+size; + } + + public static AMQShortString readFromByteArray(byte[] byteEncodedDestination, int pos) + { + + + final AMQShortString shortString = new AMQShortString(byteEncodedDestination, pos); + if(shortString.length() == 0) + { + return null; + } + else + { + return shortString; + } + } + + public static AMQShortString readFromBuffer(ByteBuffer buffer) + { + final short length = buffer.getUnsigned(); + if (length == 0) + { + return null; + } + else + { + + return new AMQShortString(buffer, length); + } + } + + public byte[] getBytes() + { + if(_offset == 0 && _length == _data.length) + { + return _data.clone(); + } + else + { + byte[] data = new byte[_length]; + System.arraycopy(_data,_offset,data,0,_length); + return data; + } + } + + public void writeToBuffer(ByteBuffer buffer) + { + + final int size = length(); + //buffer.setAutoExpand(true); + buffer.put((byte) size); + buffer.put(_data, _offset, size); + + } + + public boolean endsWith(String s) + { + return endsWith(new AMQShortString(s)); + } + + + public boolean endsWith(AMQShortString otherString) + { + + if (otherString.length() > length()) + { + return false; + } + + + int thisLength = length(); + int otherLength = otherString.length(); + + for (int i = 1; i <= otherLength; i++) + { + if (charAt(thisLength - i) != otherString.charAt(otherLength - i)) + { + return false; + } + } + return true; + } + + public boolean startsWith(String s) + { + return startsWith(new AMQShortString(s)); + } + + public boolean startsWith(AMQShortString otherString) + { + + if (otherString.length() > length()) + { + return false; + } + + for (int i = 0; i < otherString.length(); i++) + { + if (charAt(i) != otherString.charAt(i)) + { + return false; + } + } + + return true; + + } + + public boolean startsWith(CharSequence otherString) + { + if (otherString.length() > length()) + { + return false; + } + + for (int i = 0; i < otherString.length(); i++) + { + if (charAt(i) != otherString.charAt(i)) + { + return false; + } + } + + return true; + } + + + private final class CharSubSequence implements CharSequence + { + private final int _sequenceOffset; + private final int _end; + + public CharSubSequence(final int offset, final int end) + { + _sequenceOffset = offset; + _end = end; + } + + public int length() + { + return _end - _sequenceOffset; + } + + public char charAt(int index) + { + return AMQShortString.this.charAt(index + _sequenceOffset); + } + + public CharSequence subSequence(int start, int end) + { + return new CharSubSequence(start + _sequenceOffset, end + _sequenceOffset); + } + } + + public char[] asChars() + { + final int size = length(); + final char[] chars = new char[size]; + + for (int i = 0; i < size; i++) + { + chars[i] = (char) _data[i + _offset]; + } + + return chars; + } + + public String asString() + { + return new String(asChars()); + } + + public boolean equals(Object o) + { + + + if(o instanceof AMQShortString) + { + return equals((AMQShortString)o); + } + if(o instanceof CharSequence) + { + return equals((CharSequence)o); + } + + if (o == null) + { + return false; + } + + if (o == this) + { + return true; + } + + + return false; + + } + + public boolean equals(final AMQShortString otherString) + { + if (otherString == this) + { + return true; + } + + if (otherString == null) + { + return false; + } + + if ((_hashCode != 0) && (otherString._hashCode != 0) && (_hashCode != otherString._hashCode)) + { + return false; + } + + return (_offset == 0 && otherString._offset == 0 && _length == _data.length && otherString._length == otherString._data.length && Arrays.equals(_data,otherString._data)) + || Arrays.equals(getBytes(),otherString.getBytes()); + + } + + public boolean equals(CharSequence s) + { + if(s instanceof AMQShortString) + { + return equals((AMQShortString)s); + } + + if (s == null) + { + return false; + } + + if (s.length() != length()) + { + return false; + } + + for (int i = 0; i < length(); i++) + { + if (charAt(i) != s.charAt(i)) + { + return false; + } + } + + return true; + } + + public int hashCode() + { + int hash = _hashCode; + if (hash == 0) + { + final int size = length(); + + for (int i = 0; i < size; i++) + { + hash = (31 * hash) + _data[i+_offset]; + } + + _hashCode = hash; + } + + return hash; + } + + public void setDirty() + { + _hashCode = 0; + } + + public String toString() + { + return asString(); + } + + public int compareTo(AMQShortString name) + { + if (name == null) + { + return 1; + } + else + { + + if (name.length() < length()) + { + return -name.compareTo(this); + } + + for (int i = 0; i < length(); i++) + { + final byte d = _data[i+_offset]; + final byte n = name._data[i+name._offset]; + if (d < n) + { + return -1; + } + + if (d > n) + { + return 1; + } + } + + return (length() == name.length()) ? 0 : -1; + } + } + + + public AMQShortStringTokenizer tokenize(byte delim) + { + return new TokenizerImpl(delim); + } + + + public AMQShortString intern() + { + + hashCode(); + + Map> localMap = + _localInternMap.get(); + + WeakReference ref = localMap.get(this); + AMQShortString internString; + + if(ref != null) + { + internString = ref.get(); + if(internString != null) + { + return internString; + } + } + + + synchronized(_globalInternMap) + { + + ref = _globalInternMap.get(this); + if((ref == null) || ((internString = ref.get()) == null)) + { + internString = shrink(); + ref = new WeakReference(internString); + _globalInternMap.put(internString, ref); + } + + } + localMap.put(internString, ref); + return internString; + + } + + private int occurences(final byte delim) + { + int count = 0; + final int end = _offset + _length; + for(int i = _offset ; i < end ; i++ ) + { + if(_data[i] == delim) + { + count++; + } + } + return count; + } + + private int indexOf(final byte val, final int pos) + { + + for(int i = pos; i < length(); i++) + { + if(_data[_offset+i] == val) + { + return i; + } + } + return -1; + } + + + public static AMQShortString join(final Collection terms, + final AMQShortString delim) + { + if(terms.size() == 0) + { + return EMPTY_STRING; + } + + int size = delim.length() * (terms.size() - 1); + for(AMQShortString term : terms) + { + size += term.length(); + } + + byte[] data = new byte[size]; + int pos = 0; + final byte[] delimData = delim._data; + final int delimOffset = delim._offset; + final int delimLength = delim._length; + + + for(AMQShortString term : terms) + { + + if(pos!=0) + { + System.arraycopy(delimData, delimOffset,data,pos, delimLength); + pos+=delimLength; + } + System.arraycopy(term._data,term._offset,data,pos,term._length); + pos+=term._length; + } + + + + return new AMQShortString(data,0,size); + } + + public int toIntValue() + { + int pos = _offset; + int val = 0; + + + boolean isNegative = (_data[pos] == MINUS); + if(isNegative) + { + pos++; + } + + final int end = _length + _offset; + + while(pos < end) + { + int digit = (int) (_data[pos++] - ZERO); + if((digit < 0) || (digit > 9)) + { + throw new NumberFormatException("\""+toString()+"\" is not a valid number"); + } + val = val * 10; + val += digit; + } + if(isNegative) + { + val = val * -1; + } + return val; + } + + public boolean contains(final byte b) + { + final int end = _length + _offset; + for(int i = _offset; i < end; i++) + { + if(_data[i] == b) + { + return true; + } + } + return false; //To change body of created methods use File | Settings | File Templates. + } + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java b/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java new file mode 100644 index 0000000000..e2db8906a1 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQShortStringTokenizer.java @@ -0,0 +1,31 @@ +package org.apache.qpid.framing; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +public interface AMQShortStringTokenizer +{ + + public int countTokens(); + + public AMQShortString nextToken(); + + boolean hasMoreTokens(); +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java index 6dda91a488..2c356d072c 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQType.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQType.java @@ -23,12 +23,24 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; import java.math.BigDecimal; -import java.math.BigInteger; +/** + * AMQType is a type that represents the different possible AMQP field table types. It provides operations for each + * of the types to perform tasks such as calculating the size of an instance of the type, converting types between AMQP + * and Java native types, and reading and writing instances of AMQP types in binary formats to and from byte buffers. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Get the equivalent one byte identifier for a type. + *
Calculate the size of an instance of an AMQP parameter type. {@link EncodingUtils} + *
Convert an instance of an AMQP parameter into a compatable Java object tagged with its AMQP type. + * {@link AMQTypedValue} + *
Write an instance of an AMQP parameter type to a byte buffer. {@link EncodingUtils} + *
Read an instance of an AMQP parameter from a byte buffer. {@link EncodingUtils} + *
+ */ public enum AMQType { - //AMQP FieldTable Wire Types - LONG_STRING('S') { public int getEncodingSize(Object value) @@ -36,7 +48,6 @@ public enum AMQType return EncodingUtils.encodedLongStringLength((String) value); } - public String toNativeValue(Object value) { if (value != null) @@ -58,12 +69,10 @@ public enum AMQType { return EncodingUtils.readLongString(buffer); } - }, INTEGER('i') { - public int getEncodingSize(Object value) { return EncodingUtils.unsignedIntegerLength(); @@ -89,12 +98,11 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Long.valueOf((String)value); + return Long.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to int."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int."); } } @@ -111,22 +119,21 @@ public enum AMQType DECIMAL('D') { - public int getEncodingSize(Object value) { - return EncodingUtils.encodedByteLength()+ EncodingUtils.encodedIntegerLength(); + return EncodingUtils.encodedByteLength() + EncodingUtils.encodedIntegerLength(); } public Object toNativeValue(Object value) { - if(value instanceof BigDecimal) + if (value instanceof BigDecimal) { return (BigDecimal) value; } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to BigDecimal."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to BigDecimal."); } } @@ -150,7 +157,8 @@ public enum AMQType int unscaled = EncodingUtils.readInteger(buffer); BigDecimal bd = new BigDecimal(unscaled); - return bd.setScale(places); + + return bd.setScale(places); } }, @@ -163,14 +171,14 @@ public enum AMQType public Object toNativeValue(Object value) { - if(value instanceof Long) + if (value instanceof Long) { return (Long) value; } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to timestamp."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to timestamp."); } } @@ -179,37 +187,97 @@ public enum AMQType EncodingUtils.writeLong(buffer, (Long) value); } - public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLong(buffer); } }, + /** + * Implements the field table type. The native value of a field table type will be an instance of + * {@link FieldTable}, which itself may contain name/value pairs encoded as {@link AMQTypedValue}s. + */ FIELD_TABLE('F') { + /** + * Calculates the size of an instance of the type in bytes. + * + * @param value An instance of the type. + * + * @return The size of the instance of the type in bytes. + */ public int getEncodingSize(Object value) { - // TODO : fixme - throw new UnsupportedOperationException(); + // Ensure that the value is a FieldTable. + if (!(value instanceof FieldTable)) + { + throw new IllegalArgumentException("Value is not a FieldTable."); + } + + FieldTable ftValue = (FieldTable) value; + + // Loop over all name/value pairs adding up size of each. FieldTable itself keeps track of its encoded + // size as entries are added, so no need to loop over all explicitly. + // EncodingUtils calculation of the encoded field table lenth, will include 4 bytes for its 'size' field. + return EncodingUtils.encodedFieldTableLength(ftValue); } + /** + * Converts an instance of the type to an equivalent Java native representation. + * + * @param value An instance of the type. + * + * @return An equivalent Java native representation. + */ public Object toNativeValue(Object value) { - // TODO : fixme - throw new UnsupportedOperationException(); + // Ensure that the value is a FieldTable. + if (!(value instanceof FieldTable)) + { + throw new IllegalArgumentException("Value is not a FieldTable."); + } + + return (FieldTable) value; } + /** + * Writes an instance of the type to a specified byte buffer. + * + * @param value An instance of the type. + * @param buffer The byte buffer to write it to. + */ public void writeValueImpl(Object value, ByteBuffer buffer) { - // TODO : fixme - throw new UnsupportedOperationException(); + // Ensure that the value is a FieldTable. + if (!(value instanceof FieldTable)) + { + throw new IllegalArgumentException("Value is not a FieldTable."); + } + + FieldTable ftValue = (FieldTable) value; + + // Loop over all name/values writing out into buffer. + ftValue.writeToBuffer(buffer); } + /** + * Reads an instance of the type from a specified byte buffer. + * + * @param buffer The byte buffer to write it to. + * + * @return An instance of the type. + */ public Object readValueFromBuffer(ByteBuffer buffer) { - // TODO : fixme - throw new UnsupportedOperationException(); + try + { + // Read size of field table then all name/value pairs. + return EncodingUtils.readFieldTable(buffer); + } + catch (AMQFrameDecodingException e) + { + throw new IllegalArgumentException("Unable to read field table from buffer.", e); + } } }, @@ -220,7 +288,6 @@ public enum AMQType return 0; } - public Object toNativeValue(Object value) { if (value == null) @@ -229,14 +296,13 @@ public enum AMQType } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to null String."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to null String."); } } public void writeValueImpl(Object value, ByteBuffer buffer) - { - } + { } public Object readValueFromBuffer(ByteBuffer buffer) { @@ -244,8 +310,6 @@ public enum AMQType } }, - // Extended types - BINARY('x') { public int getEncodingSize(Object value) @@ -253,21 +317,19 @@ public enum AMQType return EncodingUtils.encodedLongstrLength((byte[]) value); } - public Object toNativeValue(Object value) { - if((value instanceof byte[]) || (value == null)) + if ((value instanceof byte[]) || (value == null)) { return value; } else { - throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName() + - ") cannot be converted to byte[]"); + throw new IllegalArgumentException("Value: " + value + " (" + value.getClass().getName() + + ") cannot be converted to byte[]"); } } - public void writeValueImpl(Object value, ByteBuffer buffer) { EncodingUtils.writeLongstr(buffer, (byte[]) value); @@ -277,7 +339,6 @@ public enum AMQType { return EncodingUtils.readLongstr(buffer); } - }, ASCII_STRING('c') @@ -287,7 +348,6 @@ public enum AMQType return EncodingUtils.encodedLongStringLength((String) value); } - public String toNativeValue(Object value) { if (value != null) @@ -309,7 +369,6 @@ public enum AMQType { return EncodingUtils.readLongString(buffer); } - }, WIDE_STRING('C') @@ -320,7 +379,6 @@ public enum AMQType return EncodingUtils.encodedLongStringLength((String) value); } - public String toNativeValue(Object value) { if (value != null) @@ -351,7 +409,6 @@ public enum AMQType return EncodingUtils.encodedBooleanLength(); } - public Object toNativeValue(Object value) { if (value instanceof Boolean) @@ -360,12 +417,12 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Boolean.valueOf((String)value); + return Boolean.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to boolean."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to boolean."); } } @@ -374,7 +431,6 @@ public enum AMQType EncodingUtils.writeBoolean(buffer, (Boolean) value); } - public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readBoolean(buffer); @@ -388,7 +444,6 @@ public enum AMQType return EncodingUtils.encodedCharLength(); } - public Character toNativeValue(Object value) { if (value instanceof Character) @@ -401,8 +456,8 @@ public enum AMQType } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to char."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to char."); } } @@ -415,7 +470,6 @@ public enum AMQType { return EncodingUtils.readChar(buffer); } - }, BYTE('b') @@ -425,7 +479,6 @@ public enum AMQType return EncodingUtils.encodedByteLength(); } - public Byte toNativeValue(Object value) { if (value instanceof Byte) @@ -434,12 +487,12 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Byte.valueOf((String)value); + return Byte.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to byte."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to byte."); } } @@ -456,13 +509,11 @@ public enum AMQType SHORT('s') { - public int getEncodingSize(Object value) { return EncodingUtils.encodedShortLength(); } - public Short toNativeValue(Object value) { if (value instanceof Short) @@ -475,16 +526,13 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Short.valueOf((String)value); + return Short.valueOf((String) value); } - else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to short."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to short."); } - - } public void writeValueImpl(Object value, ByteBuffer buffer) @@ -521,12 +569,11 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Integer.valueOf((String)value); + return Integer.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to int."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + ") to int."); } } @@ -543,7 +590,6 @@ public enum AMQType LONG('l') { - public int getEncodingSize(Object value) { return EncodingUtils.encodedLongLength(); @@ -551,7 +597,7 @@ public enum AMQType public Object toNativeValue(Object value) { - if(value instanceof Long) + if (value instanceof Long) { return (Long) value; } @@ -569,12 +615,12 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Long.valueOf((String)value); + return Long.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to long."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to long."); } } @@ -583,7 +629,6 @@ public enum AMQType EncodingUtils.writeLong(buffer, (Long) value); } - public Object readValueFromBuffer(ByteBuffer buffer) { return EncodingUtils.readLong(buffer); @@ -597,7 +642,6 @@ public enum AMQType return EncodingUtils.encodedFloatLength(); } - public Float toNativeValue(Object value) { if (value instanceof Float) @@ -606,12 +650,12 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Float.valueOf((String)value); + return Float.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to float."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to float."); } } @@ -628,13 +672,11 @@ public enum AMQType DOUBLE('d') { - public int getEncodingSize(Object value) { return EncodingUtils.encodedDoubleLength(); } - public Double toNativeValue(Object value) { if (value instanceof Double) @@ -647,12 +689,12 @@ public enum AMQType } else if ((value instanceof String) || (value == null)) { - return Double.valueOf((String)value); + return Double.valueOf((String) value); } else { - throw new NumberFormatException("Cannot convert: " + value + "(" + - value.getClass().getName() + ") to double."); + throw new NumberFormatException("Cannot convert: " + value + "(" + value.getClass().getName() + + ") to double."); } } @@ -667,35 +709,87 @@ public enum AMQType } }; + /** Holds the defined one byte identifier for the type. */ private final byte _identifier; + /** + * Creates an instance of an AMQP type from its defined one byte identifier. + * + * @param identifier The one byte identifier for the type. + */ AMQType(char identifier) { _identifier = (byte) identifier; } + /** + * Extracts the byte identifier for the typ. + * + * @return The byte identifier for the typ. + */ public final byte identifier() { return _identifier; } - + /** + * Calculates the size of an instance of the type in bytes. + * + * @param value An instance of the type. + * + * @return The size of the instance of the type in bytes. + */ public abstract int getEncodingSize(Object value); + /** + * Converts an instance of the type to an equivalent Java native representation. + * + * @param value An instance of the type. + * + * @return An equivalent Java native representation. + */ public abstract Object toNativeValue(Object value); + /** + * Converts an instance of the type to an equivalent Java native representation, packaged as an + * {@link AMQTypedValue} tagged with its AMQP type. + * + * @param value An instance of the type. + * + * @return An equivalent Java native representation, tagged with its AMQP type. + */ public AMQTypedValue asTypedValue(Object value) { return new AMQTypedValue(this, toNativeValue(value)); } + /** + * Writes an instance of the type to a specified byte buffer, preceded by its one byte identifier. As the type and + * value are both written, this provides a fully encoded description of a parameters type and value. + * + * @param value An instance of the type. + * @param buffer The byte buffer to write it to. + */ public void writeToBuffer(Object value, ByteBuffer buffer) { - buffer.put((byte)identifier()); + buffer.put(identifier()); writeValueImpl(value, buffer); } + /** + * Writes an instance of the type to a specified byte buffer. + * + * @param value An instance of the type. + * @param buffer The byte buffer to write it to. + */ abstract void writeValueImpl(Object value, ByteBuffer buffer); + /** + * Reads an instance of the type from a specified byte buffer. + * + * @param buffer The byte buffer to write it to. + * + * @return An instance of the type. + */ abstract Object readValueFromBuffer(ByteBuffer buffer); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java index 7193580884..e5b1fad9a8 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java +++ b/java/common/src/main/java/org/apache/qpid/framing/AMQTypedValue.java @@ -18,23 +18,40 @@ * under the License. * */ - package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +/** + * AMQTypedValue combines together a native Java Object value, and an {@link AMQType}, as a fully typed AMQP parameter + * value. It provides the ability to read and write fully typed parameters to and from byte buffers. It also provides + * the ability to create such parameters from Java native value and a type tag or to extract the native value and type + * from one. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Create a fully typed AMQP value from a native type and a type tag. {@link AMQType} + *
Create a fully typed AMQP value from a binary representation in a byte buffer. {@link AMQType} + *
Write a fully typed AMQP value to a binary representation in a byte buffer. {@link AMQType} + *
Extract the type from a fully typed AMQP value. + *
Extract the value from a fully typed AMQP value. + *
+ */ public class AMQTypedValue { + /** The type of the value. */ private final AMQType _type; - private final Object _value; + /** The Java native representation of the AMQP typed value. */ + private final Object _value; public AMQTypedValue(AMQType type, Object value) { - if(type == null) + if (type == null) { throw new NullPointerException("Cannot create a typed value with null type"); } + _type = type; _value = type.toNativeValue(value); } @@ -42,10 +59,9 @@ public class AMQTypedValue private AMQTypedValue(AMQType type, ByteBuffer buffer) { _type = type; - _value = type.readValueFromBuffer( buffer ); + _value = type.readValueFromBuffer(buffer); } - public AMQType getType() { return _type; @@ -56,10 +72,9 @@ public class AMQTypedValue return _value; } - public void writeToBuffer(ByteBuffer buffer) { - _type.writeToBuffer(_value,buffer); + _type.writeToBuffer(_value, buffer); } public int getEncodingSize() @@ -70,11 +85,12 @@ public class AMQTypedValue public static AMQTypedValue readFromBuffer(ByteBuffer buffer) { AMQType type = AMQTypeMap.getType(buffer.get()); + return new AMQTypedValue(type, buffer); } public String toString() { - return "["+getType()+": "+getValue()+"]"; + return "[" + getType() + ": " + getValue() + "]"; } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java index 5ec62ede93..94030f383e 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/CompositeAMQDataBlock.java @@ -24,7 +24,6 @@ import org.apache.mina.common.ByteBuffer; public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock { - private ByteBuffer _encodedBlock; private AMQDataBlock[] _blocks; @@ -33,27 +32,12 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD _blocks = blocks; } - /** - * The encoded block will be logically first before the AMQDataBlocks which are encoded - * into the buffer afterwards. - * @param encodedBlock already-encoded data - * @param blocks some blocks to be encoded. - */ - public CompositeAMQDataBlock(ByteBuffer encodedBlock, AMQDataBlock[] blocks) - { - this(blocks); - _encodedBlock = encodedBlock; - } public AMQDataBlock[] getBlocks() { return _blocks; } - public ByteBuffer getEncodedBlock() - { - return _encodedBlock; - } public long getSize() { @@ -62,20 +46,11 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD { frameSize += _blocks[i].getSize(); } - if (_encodedBlock != null) - { - _encodedBlock.rewind(); - frameSize += _encodedBlock.remaining(); - } return frameSize; } public void writePayload(ByteBuffer buffer) { - if (_encodedBlock != null) - { - buffer.put(_encodedBlock); - } for (int i = 0; i < _blocks.length; i++) { _blocks[i].writePayload(buffer); @@ -91,7 +66,7 @@ public class CompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQD else { StringBuilder buf = new StringBuilder(this.getClass().getName()); - buf.append("{encodedBlock=").append(_encodedBlock); + buf.append("{"); for (int i = 0 ; i < _blocks.length; i++) { buf.append(" ").append(i).append("=[").append(_blocks[i].toString()).append("]"); diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java b/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java index be38695384..9d39f8aa86 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentBody.java @@ -21,8 +21,10 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; -public class ContentBody extends AMQBody +public class ContentBody implements AMQBody { public static final byte TYPE = 3; @@ -63,11 +65,24 @@ public class ContentBody extends AMQBody { if (payload != null) { - ByteBuffer copy = payload.duplicate(); - buffer.put(copy.rewind()); + if(payload.isDirect() || payload.isReadOnly()) + { + ByteBuffer copy = payload.duplicate(); + buffer.put(copy.rewind()); + } + else + { + buffer.put(payload.array(),payload.arrayOffset(),payload.limit()); + } } } + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.contentBodyReceived(channelId, this); + } + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException { if (size > 0) diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java index 02631a5f88..83e5a7e341 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderBody.java @@ -21,8 +21,10 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; -public class ContentHeaderBody extends AMQBody +public class ContentHeaderBody implements AMQBody { public static final byte TYPE = 2; @@ -110,6 +112,12 @@ public class ContentHeaderBody extends AMQBody properties.writePropertyListPayload(buffer); } + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.contentHeaderReceived(channelId, this); + } + public static AMQFrame createAMQFrame(int channelId, int classId, int weight, BasicContentHeaderProperties properties, long bodySize) { diff --git a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java index 712eb437db..46189b63d7 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ContentHeaderPropertiesFactory.java @@ -22,6 +22,8 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.amqp_8_0.BasicConsumeBodyImpl; + public class ContentHeaderPropertiesFactory { private static final ContentHeaderPropertiesFactory _instance = new ContentHeaderPropertiesFactory(); @@ -43,7 +45,7 @@ public class ContentHeaderPropertiesFactory // AMQP version change: "Hardwired" version to major=8, minor=0 // TODO: Change so that the actual version is obtained from // the ProtocolInitiation object for this session. - if (classId == BasicConsumeBody.getClazz((byte)8, (byte)0)) + if (classId == BasicConsumeBodyImpl.CLASS_ID) { properties = new BasicContentHeaderProperties(); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java new file mode 100644 index 0000000000..f6795ff200 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/DeferredDataBlock.java @@ -0,0 +1,50 @@ +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +public abstract class DeferredDataBlock extends AMQDataBlock +{ + private AMQDataBlock _underlyingDataBlock; + + + public long getSize() + { + if(_underlyingDataBlock == null) + { + _underlyingDataBlock = createAMQDataBlock(); + } + return _underlyingDataBlock.getSize(); + } + + public void writePayload(ByteBuffer buffer) + { + if(_underlyingDataBlock == null) + { + _underlyingDataBlock = createAMQDataBlock(); + } + _underlyingDataBlock.writePayload(buffer); + } + + abstract protected AMQDataBlock createAMQDataBlock(); + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java index ccba8bd41e..6425f8c591 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java +++ b/java/common/src/main/java/org/apache/qpid/framing/EncodingUtils.java @@ -229,7 +229,11 @@ public class EncodingUtils encodedString[i] = (byte) cha[i]; } - writeBytes(buffer, encodedString); + // TODO: check length fits in an unsigned byte + writeUnsignedByte(buffer, (short)encodedString.length); + buffer.put(encodedString); + + } else { @@ -928,15 +932,15 @@ public class EncodingUtils public static byte[] readBytes(ByteBuffer buffer) { - short length = buffer.getUnsigned(); + long length = buffer.getUnsignedInt(); if (length == 0) { return null; } else { - byte[] dataBytes = new byte[length]; - buffer.get(dataBytes, 0, length); + byte[] dataBytes = new byte[(int)length]; + buffer.get(dataBytes, 0, (int)length); return dataBytes; } @@ -947,13 +951,14 @@ public class EncodingUtils if (data != null) { // TODO: check length fits in an unsigned byte - writeUnsignedByte(buffer, (short) data.length); + writeUnsignedInteger(buffer, (long)data.length); buffer.put(data); } else - { + { // really writing out unsigned byte - buffer.put((byte) 0); + //buffer.put((byte) 0); + writeUnsignedInteger(buffer, 0L); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java index 3438770450..9ba9b53b13 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java +++ b/java/common/src/main/java/org/apache/qpid/framing/FieldTable.java @@ -18,7 +18,6 @@ * under the License. * */ - package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; @@ -360,6 +359,41 @@ public class FieldTable } } + /** + * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name. + * + * @param string The name of the parameter to get the associated FieldTable value for. + * + * @return The associated FieldTable value, or null if the associated value is not of FieldTable type or + * not present in the field table at all. + */ + public FieldTable getFieldTable(String string) + { + return getFieldTable(new AMQShortString(string)); + } + + /** + * Extracts a value from the field table that is itself a FieldTable associated with the specified parameter name. + * + * @param string The name of the parameter to get the associated FieldTable value for. + * + * @return The associated FieldTable value, or null if the associated value is not of FieldTable type or + * not present in the field table at all. + */ + public FieldTable getFieldTable(AMQShortString string) + { + AMQTypedValue value = getProperty(string); + + if ((value != null) && (value.getType() == AMQType.FIELD_TABLE)) + { + return (FieldTable) value.getValue(); + } + else + { + return null; + } + } + public Object getObject(String string) { return getObject(new AMQShortString(string)); @@ -568,6 +602,32 @@ public class FieldTable return setProperty(string, AMQType.VOID.asTypedValue(null)); } + /** + * Associates a nested field table with the specified parameter name. + * + * @param string The name of the parameter to store in the table. + * @param ftValue The field table value to associate with the parameter name. + * + * @return The stored value. + */ + public Object setFieldTable(String string, FieldTable ftValue) + { + return setFieldTable(new AMQShortString(string), ftValue); + } + + /** + * Associates a nested field table with the specified parameter name. + * + * @param string The name of the parameter to store in the table. + * @param ftValue The field table value to associate with the parameter name. + * + * @return The stored value. + */ + public Object setFieldTable(AMQShortString string, FieldTable ftValue) + { + return setProperty(string, AMQType.FIELD_TABLE.asTypedValue(ftValue)); + } + public Object setObject(AMQShortString string, Object object) { if (object instanceof Boolean) @@ -706,12 +766,15 @@ public class FieldTable public void writeToBuffer(ByteBuffer buffer) { - final boolean trace = _logger.isTraceEnabled(); + final boolean trace = _logger.isDebugEnabled(); if (trace) { - _logger.trace("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "..."); - _logger.trace(_properties.toString()); + _logger.debug("FieldTable::writeToBuffer: Writing encoded length of " + getEncodedSize() + "..."); + if (_properties != null) + { + _logger.debug(_properties.toString()); + } } EncodingUtils.writeUnsignedInteger(buffer, getEncodedSize()); @@ -894,14 +957,21 @@ public class FieldTable if (_encodedForm != null) { + if(buffer.isDirect() || buffer.isReadOnly()) + { + ByteBuffer encodedForm = _encodedForm.duplicate(); - if (_encodedForm.position() != 0) + if (encodedForm.position() != 0) + { + encodedForm.flip(); + } + + buffer.put(encodedForm); + } + else { - _encodedForm.flip(); + buffer.put(_encodedForm.array(),_encodedForm.arrayOffset(),(int)_encodedSize); } - // _encodedForm.limit((int)getEncodedSize()); - - buffer.put(_encodedForm); } else if (_properties != null) { @@ -915,11 +985,11 @@ public class FieldTable final Map.Entry me = it.next(); try { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + me.getValue().getValue()); - _logger.trace("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); + _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); } // Write the actual parameter name @@ -928,12 +998,12 @@ public class FieldTable } catch (Exception e) { - if (_logger.isTraceEnabled()) + if (_logger.isDebugEnabled()) { - _logger.trace("Exception thrown:" + e); - _logger.trace("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + _logger.debug("Exception thrown:" + e); + _logger.debug("Writing Property:" + me.getKey() + " Type:" + me.getValue().getType() + " Value:" + me.getValue().getValue()); - _logger.trace("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); + _logger.debug("Buffer Position:" + buffer.position() + " Remaining:" + buffer.remaining()); } throw new RuntimeException(e); @@ -945,7 +1015,7 @@ public class FieldTable private void setFromBuffer(ByteBuffer buffer, long length) throws AMQFrameDecodingException { - final boolean trace = _logger.isTraceEnabled(); + final boolean trace = _logger.isDebugEnabled(); if (length > 0) { @@ -961,7 +1031,7 @@ public class FieldTable if (trace) { - _logger.trace("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType() + _logger.debug("FieldTable::PropFieldTable(buffer," + length + "): Read type '" + value.getType() + "', key '" + key + "', value '" + value.getValue() + "'"); } @@ -976,7 +1046,7 @@ public class FieldTable if (trace) { - _logger.trace("FieldTable::FieldTable(buffer," + length + "): Done."); + _logger.debug("FieldTable::FieldTable(buffer," + length + "): Done."); } } diff --git a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java index 7246c4a1cf..15a43345b5 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java +++ b/java/common/src/main/java/org/apache/qpid/framing/HeartbeatBody.java @@ -21,8 +21,10 @@ package org.apache.qpid.framing; import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.AMQException; -public class HeartbeatBody extends AMQBody +public class HeartbeatBody implements AMQBody { public static final byte TYPE = 8; public static AMQFrame FRAME = new HeartbeatBody().toFrame(); @@ -46,15 +48,21 @@ public class HeartbeatBody extends AMQBody return TYPE; } - protected int getSize() + public int getSize() { return 0;//heartbeats we generate have no payload } - protected void writePayload(ByteBuffer buffer) + public void writePayload(ByteBuffer buffer) { } + public void handle(final int channelId, final AMQVersionAwareProtocolSession session) + throws AMQException + { + session.heartbeatBodyReceived(channelId, this); + } + protected void populateFromBuffer(ByteBuffer buffer, long size) throws AMQFrameDecodingException { if(size > 0) diff --git a/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java b/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java index 4c253b9973..3ac17e9204 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java +++ b/java/common/src/main/java/org/apache/qpid/framing/ProtocolInitiation.java @@ -139,7 +139,7 @@ public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQData } } - public void checkVersion() throws AMQException + public ProtocolVersion checkVersion() throws AMQException { if(_protocolHeader.length != 4) @@ -180,6 +180,7 @@ public class ProtocolInitiation extends AMQDataBlock implements EncodableAMQData throw new AMQProtocolVersionException("Protocol version " + _protocolMajor + "." + _protocolMinor + " not suppoerted by this version of the Qpid broker.", null); } + return pv; } public String toString() diff --git a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java index 26c048e34a..f8cf3f3011 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java +++ b/java/common/src/main/java/org/apache/qpid/framing/SmallCompositeAMQDataBlock.java @@ -25,7 +25,7 @@ import org.apache.mina.common.ByteBuffer; public class SmallCompositeAMQDataBlock extends AMQDataBlock implements EncodableAMQDataBlock { - private ByteBuffer _encodedBlock; + private AMQDataBlock _firstFrame; private AMQDataBlock _block; @@ -40,10 +40,10 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl * @param encodedBlock already-encoded data * @param block a block to be encoded. */ - public SmallCompositeAMQDataBlock(ByteBuffer encodedBlock, AMQDataBlock block) + public SmallCompositeAMQDataBlock(AMQDataBlock encodedBlock, AMQDataBlock block) { this(block); - _encodedBlock = encodedBlock; + _firstFrame = encodedBlock; } public AMQDataBlock getBlock() @@ -51,28 +51,28 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl return _block; } - public ByteBuffer getEncodedBlock() + public AMQDataBlock getFirstFrame() { - return _encodedBlock; + return _firstFrame; } public long getSize() { long frameSize = _block.getSize(); - if (_encodedBlock != null) + if (_firstFrame != null) { - _encodedBlock.rewind(); - frameSize += _encodedBlock.remaining(); + + frameSize += _firstFrame.getSize(); } return frameSize; } public void writePayload(ByteBuffer buffer) { - if (_encodedBlock != null) + if (_firstFrame != null) { - buffer.put(_encodedBlock); + _firstFrame.writePayload(buffer); } _block.writePayload(buffer); @@ -87,7 +87,7 @@ public class SmallCompositeAMQDataBlock extends AMQDataBlock implements Encodabl else { StringBuilder buf = new StringBuilder(this.getClass().getName()); - buf.append("{encodedBlock=").append(_encodedBlock); + buf.append("{encodedBlock=").append(_firstFrame); buf.append(" _block=[").append(_block.toString()).append("]"); diff --git a/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java b/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java index 6006e9793c..516d0c569c 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java +++ b/java/common/src/main/java/org/apache/qpid/framing/VersionSpecificRegistry.java @@ -182,7 +182,7 @@ public class VersionSpecificRegistry + " method " + methodID + ".", null); } - return bodyFactory.newInstance(_protocolMajorVersion, _protocolMinorVersion, classID, methodID, in, size); + return bodyFactory.newInstance( in, size); } diff --git a/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java b/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java index 706499c1b0..49c28bb06b 100644 --- a/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java +++ b/java/common/src/main/java/org/apache/qpid/framing/abstraction/MessagePublishInfo.java @@ -27,6 +27,8 @@ public interface MessagePublishInfo public AMQShortString getExchange(); + public void setExchange(AMQShortString exchange); + public boolean isImmediate(); public boolean isMandatory(); diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java new file mode 100644 index 0000000000..3c5cb74773 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/AMQMethodBody_0_9.java @@ -0,0 +1,209 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing.amqp_0_9; + +import org.apache.qpid.framing.EncodingUtils; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.Content; + +import org.apache.mina.common.ByteBuffer; + +public abstract class AMQMethodBody_0_9 extends org.apache.qpid.framing.AMQMethodBodyImpl +{ + + public byte getMajor() + { + return 0; + } + + public byte getMinor() + { + return 9; + } + + public int getSize() + { + return 2 + 2 + getBodySize(); + } + + public void writePayload(ByteBuffer buffer) + { + EncodingUtils.writeUnsignedShort(buffer, getClazz()); + EncodingUtils.writeUnsignedShort(buffer, getMethod()); + writeMethodPayload(buffer); + } + + + protected byte readByte(ByteBuffer buffer) + { + return buffer.get(); + } + + protected AMQShortString readAMQShortString(ByteBuffer buffer) + { + return EncodingUtils.readAMQShortString(buffer); + } + + protected int getSizeOf(AMQShortString string) + { + return EncodingUtils.encodedShortStringLength(string); + } + + protected void writeByte(ByteBuffer buffer, byte b) + { + buffer.put(b); + } + + protected void writeAMQShortString(ByteBuffer buffer, AMQShortString string) + { + EncodingUtils.writeShortStringBytes(buffer, string); + } + + protected int readInt(ByteBuffer buffer) + { + return buffer.getInt(); + } + + protected void writeInt(ByteBuffer buffer, int i) + { + buffer.putInt(i); + } + + protected FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException + { + return EncodingUtils.readFieldTable(buffer); + } + + protected int getSizeOf(FieldTable table) + { + return EncodingUtils.encodedFieldTableLength(table); //To change body of created methods use File | Settings | File Templates. + } + + protected void writeFieldTable(ByteBuffer buffer, FieldTable table) + { + EncodingUtils.writeFieldTableBytes(buffer, table); + } + + protected long readLong(ByteBuffer buffer) + { + return buffer.getLong(); + } + + protected void writeLong(ByteBuffer buffer, long l) + { + buffer.putLong(l); + } + + protected int getSizeOf(byte[] response) + { + return (response == null) ? 4 :response.length + 4; + } + + protected void writeBytes(ByteBuffer buffer, byte[] data) + { + EncodingUtils.writeBytes(buffer,data); + } + + protected byte[] readBytes(ByteBuffer buffer) + { + return EncodingUtils.readBytes(buffer); + } + + protected short readShort(ByteBuffer buffer) + { + return EncodingUtils.readShort(buffer); + } + + protected void writeShort(ByteBuffer buffer, short s) + { + EncodingUtils.writeShort(buffer, s); + } + + protected Content readContent(ByteBuffer buffer) + { + return null; //To change body of created methods use File | Settings | File Templates. + } + + protected int getSizeOf(Content body) + { + return 0; //To change body of created methods use File | Settings | File Templates. + } + + protected void writeContent(ByteBuffer buffer, Content body) + { + //To change body of created methods use File | Settings | File Templates. + } + + protected byte readBitfield(ByteBuffer buffer) + { + return readByte(buffer); //To change body of created methods use File | Settings | File Templates. + } + + protected int readUnsignedShort(ByteBuffer buffer) + { + return buffer.getUnsignedShort(); //To change body of created methods use File | Settings | File Templates. + } + + protected void writeBitfield(ByteBuffer buffer, byte bitfield0) + { + buffer.put(bitfield0); + } + + protected void writeUnsignedShort(ByteBuffer buffer, int s) + { + EncodingUtils.writeUnsignedShort(buffer, s); + } + + protected long readUnsignedInteger(ByteBuffer buffer) + { + return buffer.getUnsignedInt(); + } + protected void writeUnsignedInteger(ByteBuffer buffer, long i) + { + EncodingUtils.writeUnsignedInteger(buffer, i); + } + + + protected short readUnsignedByte(ByteBuffer buffer) + { + return buffer.getUnsigned(); + } + + protected void writeUnsignedByte(ByteBuffer buffer, short unsignedByte) + { + EncodingUtils.writeUnsignedByte(buffer, unsignedByte); + } + + protected long readTimestamp(ByteBuffer buffer) + { + return EncodingUtils.readTimestamp(buffer); + } + + protected void writeTimestamp(ByteBuffer buffer, long t) + { + EncodingUtils.writeTimestamp(buffer, t); + } + + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java new file mode 100644 index 0000000000..2fd4f70138 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_0_9/MethodConverter_0_9.java @@ -0,0 +1,172 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing.amqp_0_9; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.framing.abstraction.AbstractMethodConverter; +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.*; +import org.apache.qpid.framing.amqp_0_9.*; +import org.apache.qpid.framing.amqp_0_9.BasicPublishBodyImpl; + +public class MethodConverter_0_9 extends AbstractMethodConverter implements ProtocolVersionMethodConverter +{ + private int _basicPublishClassId; + private int _basicPublishMethodId; + + public MethodConverter_0_9() + { + super((byte)0,(byte)9); + + + } + + public AMQBody convertToBody(ContentChunk contentChunk) + { + if(contentChunk instanceof ContentChunk_0_9) + { + return ((ContentChunk_0_9)contentChunk).toBody(); + } + else + { + return new ContentBody(contentChunk.getData()); + } + } + + public ContentChunk convertToContentChunk(AMQBody body) + { + final ContentBody contentBodyChunk = (ContentBody) body; + + return new ContentChunk_0_9(contentBodyChunk); + + } + + public void configure() + { + + _basicPublishClassId = org.apache.qpid.framing.amqp_0_9.BasicPublishBodyImpl.CLASS_ID; + _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID; + + } + + public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) + { + final BasicPublishBody publishBody = ((BasicPublishBody) methodBody); + + final AMQShortString exchange = publishBody.getExchange(); + final AMQShortString routingKey = publishBody.getRoutingKey(); + + return new MethodConverter_0_9.MessagePublishInfoImpl(exchange, + publishBody.getImmediate(), + publishBody.getMandatory(), + routingKey); + + } + + public AMQMethodBody convertToBody(MessagePublishInfo info) + { + + return new BasicPublishBodyImpl(0, + info.getExchange(), + info.getRoutingKey(), + info.isMandatory(), + info.isImmediate()) ; + + } + + private static class MessagePublishInfoImpl implements MessagePublishInfo + { + private AMQShortString _exchange; + private final boolean _immediate; + private final boolean _mandatory; + private final AMQShortString _routingKey; + + public MessagePublishInfoImpl(final AMQShortString exchange, + final boolean immediate, + final boolean mandatory, + final AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange; + } + + public void setExchange(AMQShortString exchange) + { + _exchange = exchange; + } + + public boolean isImmediate() + { + return _immediate; + } + + public boolean isMandatory() + { + return _mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + } + + private static class ContentChunk_0_9 implements ContentChunk + { + private final ContentBody _contentBodyChunk; + + public ContentChunk_0_9(final ContentBody contentBodyChunk) + { + _contentBodyChunk = contentBodyChunk; + } + + public int getSize() + { + return _contentBodyChunk.getSize(); + } + + public ByteBuffer getData() + { + return _contentBodyChunk.payload; + } + + public void reduceToFit() + { + _contentBodyChunk.reduceBufferToFit(); + } + + public AMQBody toBody() + { + return _contentBodyChunk; + } + } +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java new file mode 100644 index 0000000000..2b7c9534a9 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/AMQMethodBody_8_0.java @@ -0,0 +1,209 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing.amqp_8_0; + +import org.apache.qpid.framing.EncodingUtils; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.Content; + +import org.apache.mina.common.ByteBuffer; + +public abstract class AMQMethodBody_8_0 extends org.apache.qpid.framing.AMQMethodBodyImpl +{ + + public byte getMajor() + { + return 8; + } + + public byte getMinor() + { + return 0; + } + + public int getSize() + { + return 2 + 2 + getBodySize(); + } + + public void writePayload(ByteBuffer buffer) + { + EncodingUtils.writeUnsignedShort(buffer, getClazz()); + EncodingUtils.writeUnsignedShort(buffer, getMethod()); + writeMethodPayload(buffer); + } + + + protected byte readByte(ByteBuffer buffer) + { + return buffer.get(); + } + + protected AMQShortString readAMQShortString(ByteBuffer buffer) + { + return EncodingUtils.readAMQShortString(buffer); + } + + protected int getSizeOf(AMQShortString string) + { + return EncodingUtils.encodedShortStringLength(string); + } + + protected void writeByte(ByteBuffer buffer, byte b) + { + buffer.put(b); + } + + protected void writeAMQShortString(ByteBuffer buffer, AMQShortString string) + { + EncodingUtils.writeShortStringBytes(buffer, string); + } + + protected int readInt(ByteBuffer buffer) + { + return buffer.getInt(); + } + + protected void writeInt(ByteBuffer buffer, int i) + { + buffer.putInt(i); + } + + protected FieldTable readFieldTable(ByteBuffer buffer) throws AMQFrameDecodingException + { + return EncodingUtils.readFieldTable(buffer); + } + + protected int getSizeOf(FieldTable table) + { + return EncodingUtils.encodedFieldTableLength(table); //To change body of created methods use File | Settings | File Templates. + } + + protected void writeFieldTable(ByteBuffer buffer, FieldTable table) + { + EncodingUtils.writeFieldTableBytes(buffer, table); + } + + protected long readLong(ByteBuffer buffer) + { + return buffer.getLong(); + } + + protected void writeLong(ByteBuffer buffer, long l) + { + buffer.putLong(l); + } + + protected int getSizeOf(byte[] response) + { + return (response == null) ? 4 : response.length + 4; + } + + protected void writeBytes(ByteBuffer buffer, byte[] data) + { + EncodingUtils.writeBytes(buffer,data); + } + + protected byte[] readBytes(ByteBuffer buffer) + { + return EncodingUtils.readBytes(buffer); + } + + protected short readShort(ByteBuffer buffer) + { + return EncodingUtils.readShort(buffer); + } + + protected void writeShort(ByteBuffer buffer, short s) + { + EncodingUtils.writeShort(buffer, s); + } + + protected Content readContent(ByteBuffer buffer) + { + return null; //To change body of created methods use File | Settings | File Templates. + } + + protected int getSizeOf(Content body) + { + return 0; //To change body of created methods use File | Settings | File Templates. + } + + protected void writeContent(ByteBuffer buffer, Content body) + { + //To change body of created methods use File | Settings | File Templates. + } + + protected byte readBitfield(ByteBuffer buffer) + { + return readByte(buffer); //To change body of created methods use File | Settings | File Templates. + } + + protected int readUnsignedShort(ByteBuffer buffer) + { + return buffer.getUnsignedShort(); //To change body of created methods use File | Settings | File Templates. + } + + protected void writeBitfield(ByteBuffer buffer, byte bitfield0) + { + buffer.put(bitfield0); + } + + protected void writeUnsignedShort(ByteBuffer buffer, int s) + { + EncodingUtils.writeUnsignedShort(buffer, s); + } + + protected long readUnsignedInteger(ByteBuffer buffer) + { + return buffer.getUnsignedInt(); + } + protected void writeUnsignedInteger(ByteBuffer buffer, long i) + { + EncodingUtils.writeUnsignedInteger(buffer, i); + } + + + protected short readUnsignedByte(ByteBuffer buffer) + { + return buffer.getUnsigned(); + } + + protected void writeUnsignedByte(ByteBuffer buffer, short unsignedByte) + { + EncodingUtils.writeUnsignedByte(buffer, unsignedByte); + } + + protected long readTimestamp(ByteBuffer buffer) + { + return EncodingUtils.readTimestamp(buffer); + } + + protected void writeTimestamp(ByteBuffer buffer, long t) + { + EncodingUtils.writeTimestamp(buffer, t); + } + + +} diff --git a/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java new file mode 100644 index 0000000000..b1be49a350 --- /dev/null +++ b/java/common/src/main/java/org/apache/qpid/framing/amqp_8_0/MethodConverter_8_0.java @@ -0,0 +1,151 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing.amqp_8_0; + +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.AbstractMethodConverter; +import org.apache.qpid.framing.amqp_8_0.BasicPublishBodyImpl; +import org.apache.qpid.framing.*; + +import org.apache.mina.common.ByteBuffer; + +public class MethodConverter_8_0 extends AbstractMethodConverter implements ProtocolVersionMethodConverter +{ + private int _basicPublishClassId; + private int _basicPublishMethodId; + + public MethodConverter_8_0() + { + super((byte)8,(byte)0); + + + } + + public AMQBody convertToBody(ContentChunk contentChunk) + { + return new ContentBody(contentChunk.getData()); + } + + public ContentChunk convertToContentChunk(AMQBody body) + { + final ContentBody contentBodyChunk = (ContentBody) body; + + return new ContentChunk() + { + + public int getSize() + { + return contentBodyChunk.getSize(); + } + + public ByteBuffer getData() + { + return contentBodyChunk.payload; + } + + public void reduceToFit() + { + contentBodyChunk.reduceBufferToFit(); + } + }; + + } + + public void configure() + { + + _basicPublishClassId = BasicPublishBodyImpl.CLASS_ID; + _basicPublishMethodId = BasicPublishBodyImpl.METHOD_ID; + + } + + public MessagePublishInfo convertToInfo(AMQMethodBody methodBody) + { + final BasicPublishBody publishBody = ((BasicPublishBody) methodBody); + + final AMQShortString exchange = publishBody.getExchange(); + final AMQShortString routingKey = publishBody.getRoutingKey(); + + return new MessagePublishInfoImpl(exchange == null ? null : exchange.intern(), + publishBody.getImmediate(), + publishBody.getMandatory(), + routingKey == null ? null : routingKey.intern()); + + } + + public AMQMethodBody convertToBody(MessagePublishInfo info) + { + + return new BasicPublishBodyImpl(0, + info.getExchange(), + info.getRoutingKey(), + info.isMandatory(), + info.isImmediate()) ; + + } + + private static class MessagePublishInfoImpl implements MessagePublishInfo + { + private AMQShortString _exchange; + private final boolean _immediate; + private final boolean _mandatory; + private final AMQShortString _routingKey; + + public MessagePublishInfoImpl(final AMQShortString exchange, + final boolean immediate, + final boolean mandatory, + final AMQShortString routingKey) + { + _exchange = exchange; + _immediate = immediate; + _mandatory = mandatory; + _routingKey = routingKey; + } + + public AMQShortString getExchange() + { + return _exchange; + } + + public void setExchange(AMQShortString exchange) + { + _exchange = exchange; + } + + public boolean isImmediate() + { + return _immediate; + } + + public boolean isMandatory() + { + return _mandatory; + } + + public AMQShortString getRoutingKey() + { + return _routingKey; + } + } +} diff --git a/java/common/src/main/java/org/apache/qpid/pool/Job.java b/java/common/src/main/java/org/apache/qpid/pool/Job.java index ba3c5d03fa..b2a09ac592 100644 --- a/java/common/src/main/java/org/apache/qpid/pool/Job.java +++ b/java/common/src/main/java/org/apache/qpid/pool/Job.java @@ -94,21 +94,23 @@ public class Job implements Runnable /** * Sequentially processes, up to the maximum number per job, the aggregated continuations in enqueued in this job. */ - void processAll() + boolean processAll() { // limit the number of events processed in one run - for (int i = 0; i < _maxEvents; i++) + int i = _maxEvents; + while( --i != 0 ) { Event e = _eventQueue.poll(); if (e == null) { - break; + return true; } else { e.process(_session); } } + return false; } /** @@ -144,9 +146,15 @@ public class Job implements Runnable */ public void run() { - processAll(); - deactivate(); - _completionHandler.completed(_session, this); + if(processAll()) + { + deactivate(); + _completionHandler.completed(_session, this); + } + else + { + _completionHandler.notCompleted(_session, this); + } } /** @@ -158,5 +166,7 @@ public class Job implements Runnable static interface JobCompletionHandler { public void completed(IoSession session, Job job); + + public void notCompleted(final IoSession session, final Job job); } } diff --git a/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java b/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java index d0dfb1adcf..2912e54662 100644 --- a/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java +++ b/java/common/src/main/java/org/apache/qpid/pool/PoolingFilter.java @@ -30,6 +30,8 @@ import org.apache.qpid.pool.Event.CloseEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ExecutorService; /** * PoolingFilter, is a no-op pass through filter that hands all events down the Mina filter chain by default. As it @@ -83,9 +85,6 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo /** Used for debugging purposes. */ private static final Logger _logger = LoggerFactory.getLogger(PoolingFilter.class); - /** Holds a mapping from Mina sessions to batched jobs for execution. */ - private final ConcurrentMap _jobs = new ConcurrentHashMap(); - /** Holds the managed reference to obtain the executor for the batched jobs. */ private final ReferenceCountingExecutorService _poolReference; @@ -93,7 +92,9 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo private final String _name; /** Defines the maximum number of events that will be batched into a single job. */ - private final int _maxEvents = Integer.getInteger("amqj.server.read_write_pool.max_events", 10); + static final int MAX_JOB_EVENTS = Integer.getInteger("amqj.server.read_write_pool.max_events", 10); + + private final int _maxEvents; /** * Creates a named pooling filter, on the specified shared thread pool. @@ -101,10 +102,11 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo * @param refCountingPool The thread pool reference. * @param name The identifying name of the filter type. */ - public PoolingFilter(ReferenceCountingExecutorService refCountingPool, String name) + public PoolingFilter(ReferenceCountingExecutorService refCountingPool, String name, int maxEvents) { _poolReference = refCountingPool; _name = name; + _maxEvents = maxEvents; } /** @@ -159,20 +161,34 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo /** * Adds an {@link Event} to a {@link Job}, triggering the execution of the job if it is not already running. * - * @param session The Mina session to work in. + * @param job The job. * @param event The event to hand off asynchronously. */ - void fireAsynchEvent(IoSession session, Event event) + void fireAsynchEvent(Job job, Event event) { - Job job = getJobForSession(session); + // job.acquire(); //prevents this job being removed from _jobs job.add(event); - // Additional checks on pool to check that it hasn't shutdown. - // The alternative is to catch the RejectedExecutionException that will result from executing on a shutdown pool - if (job.activate() && (_poolReference.getPool() != null) && !_poolReference.getPool().isShutdown()) + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) { - _poolReference.getPool().execute(job); + return; + } + + // rather than perform additional checks on pool to check that it hasn't shutdown. + // catch the RejectedExecutionException that will result from executing on a shutdown pool + if (job.activate()) + { + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } } } @@ -185,7 +201,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void createNewJobForSession(IoSession session) { - Job job = new Job(session, this, _maxEvents); + Job job = new Job(session, this, MAX_JOB_EVENTS); session.setAttribute(_name, job); } @@ -196,7 +212,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo * * @return The Job for this filter to place asynchronous events into. */ - private Job getJobForSession(IoSession session) + public Job getJobForSession(IoSession session) { return (Job) session.getAttribute(_name); } @@ -210,17 +226,57 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void completed(IoSession session, Job job) { + + if (!job.isComplete()) { + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) + { + return; + } + + // ritchiem : 2006-12-13 Do we need to perform the additional checks here? // Can the pool be shutdown at this point? - if (job.activate() && (_poolReference.getPool() != null) && !_poolReference.getPool().isShutdown()) + if (job.activate()) { - _poolReference.getPool().execute(job); + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + } } } + public void notCompleted(IoSession session, Job job) + { + final ExecutorService pool = _poolReference.getPool(); + + if(pool == null) + { + return; + } + + try + { + pool.execute(job); + } + catch(RejectedExecutionException e) + { + _logger.warn("Thread pool shutdown while tasks still outstanding"); + } + + } + + + /** * No-op pass through filter to the next filter in the chain. * @@ -377,7 +433,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public AsynchReadPoolingFilter(ReferenceCountingExecutorService refCountingPool, String name) { - super(refCountingPool, name); + super(refCountingPool, name, Integer.getInteger("amqj.server.read_write_pool.max_read_events", MAX_JOB_EVENTS)); } /** @@ -389,8 +445,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void messageReceived(NextFilter nextFilter, final IoSession session, Object message) { - - fireAsynchEvent(session, new Event.ReceivedEvent(nextFilter, message)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new Event.ReceivedEvent(nextFilter, message)); } /** @@ -401,7 +457,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void sessionClosed(final NextFilter nextFilter, final IoSession session) { - fireAsynchEvent(session, new CloseEvent(nextFilter)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new CloseEvent(nextFilter)); } } @@ -419,7 +476,7 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public AsynchWritePoolingFilter(ReferenceCountingExecutorService refCountingPool, String name) { - super(refCountingPool, name); + super(refCountingPool, name, Integer.getInteger("amqj.server.read_write_pool.max_write_events", MAX_JOB_EVENTS)); } /** @@ -431,7 +488,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) { - fireAsynchEvent(session, new Event.WriteEvent(nextFilter, writeRequest)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new Event.WriteEvent(nextFilter, writeRequest)); } /** @@ -442,7 +500,8 @@ public abstract class PoolingFilter extends IoFilterAdapter implements Job.JobCo */ public void sessionClosed(final NextFilter nextFilter, final IoSession session) { - fireAsynchEvent(session, new CloseEvent(nextFilter)); + Job job = getJobForSession(session); + fireAsynchEvent(job, new CloseEvent(nextFilter)); } } } diff --git a/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java b/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java index 2fbeeda1d4..5a7679a972 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/AMQMethodListener.java @@ -21,6 +21,7 @@ package org.apache.qpid.protocol; import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.AMQException; /** * AMQMethodListener is a listener that receives notifications of AMQP methods. The methods are packaged as events in @@ -57,7 +58,7 @@ public interface AMQMethodListener * * @todo Consider narrowing the exception. */ - boolean methodReceived(AMQMethodEvent evt) throws Exception; + boolean methodReceived(AMQMethodEvent evt) throws AMQException; /** * Notifies the listener of an error on the event context to which it is listening. The listener should perform diff --git a/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java b/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java index 7c1d6fdaa0..b56a05f725 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/AMQVersionAwareProtocolSession.java @@ -20,7 +20,8 @@ */ package org.apache.qpid.protocol; -import org.apache.qpid.framing.VersionSpecificRegistry; +import org.apache.qpid.framing.*; +import org.apache.qpid.AMQException; /** * AMQVersionAwareProtocolSession is implemented by all AMQP session classes, that need to provide an awareness to @@ -42,5 +43,15 @@ public interface AMQVersionAwareProtocolSession extends AMQProtocolWriter, Proto * * @return The method registry for a specific version of the AMQP. */ - public VersionSpecificRegistry getRegistry(); +// public VersionSpecificRegistry getRegistry(); + + MethodRegistry getMethodRegistry(); + + + public void methodFrameReceived(int channelId, AMQMethodBody body) throws AMQException; + public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException; + public void contentBodyReceived(int channelId, ContentBody body) throws AMQException; + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException; + + } diff --git a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java index 60a7f30185..dea80cdcf4 100644 --- a/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java +++ b/java/common/src/main/java/org/apache/qpid/protocol/ProtocolVersionAware.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.protocol; +import org.apache.qpid.framing.ProtocolVersion; + /** * ProtocolVersionAware is implemented by all AMQP handling classes, that need to provide an awareness to callers of * the version of the AMQP protocol that they are able to handle. @@ -32,6 +34,7 @@ package org.apache.qpid.protocol; public interface ProtocolVersionAware { /** + * @deprecated * Reports the AMQP minor version, that the implementer can handle. * * @return The AMQP minor version. @@ -39,9 +42,12 @@ public interface ProtocolVersionAware public byte getProtocolMinorVersion(); /** + * @deprecated * Reports the AMQP major version, that the implementer can handle. * * @return The AMQP major version. */ public byte getProtocolMajorVersion(); + + public ProtocolVersion getProtocolVersion(); } diff --git a/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java b/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java index 461cf9591d..633cf4fe3a 100644 --- a/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java +++ b/java/common/src/main/java/org/apache/qpid/util/ConcurrentLinkedMessageQueueAtomicSize.java @@ -215,6 +215,14 @@ public class ConcurrentLinkedMessageQueueAtomicSize extends ConcurrentLinkedQ public void remove() { last.remove(); + if(last == _mainIterator) + { + _size.decrementAndGet(); + } + else + { + _messageHeadSize.decrementAndGet(); + } } }; } diff --git a/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java new file mode 100644 index 0000000000..b93dc46741 --- /dev/null +++ b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterClient.java @@ -0,0 +1,396 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.mina.SocketIOTest; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.CloseFuture; +import org.apache.mina.common.ConnectFuture; +import org.apache.mina.common.IoConnector; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; + +public class IOWriterClient implements Runnable +{ + private static final Logger _logger = LoggerFactory.getLogger(IOWriterClient.class); + + public static int DEFAULT_TEST_SIZE = 2; + + private IoSession _session; + + private long _startTime; + + private long[] _chunkTimes; + + public int _chunkCount = 200000; + + private int _chunkSize = 1024; + + private CountDownLatch _notifier; + + private int _maximumWriteQueueLength; + + static public int _PORT = IOWriterServer._PORT; + + public void run() + { + _logger.info("Starting to send " + _chunkCount + " buffers of " + _chunkSize + "B"); + _startTime = System.currentTimeMillis(); + _notifier = new CountDownLatch(1); + + for (int i = 0; i < _chunkCount; i++) + { + ByteBuffer buf = ByteBuffer.allocate(_chunkSize, false); + byte check = (byte) (i % 128); + buf.put(check); + buf.fill((byte) 88, buf.remaining()); + buf.flip(); + + _session.write(buf); + } + + long _sentall = System.currentTimeMillis(); + long _receivedall = _sentall; + try + { + _logger.info("All buffers sent; waiting for receipt from server"); + _notifier.await(); + _receivedall = System.currentTimeMillis(); + } + catch (InterruptedException e) + { + //Ignore + } + _logger.info("Completed"); + _logger.info("Total time waiting for server after last write: " + (_receivedall - _sentall)); + + long totalTime = System.currentTimeMillis() - _startTime; + + _logger.info("Total time: " + totalTime); + _logger.info("MB per second: " + (int) ((1.0 * _chunkSize * _chunkCount) / totalTime)); + long lastChunkTime = _startTime; + double average = 0; + for (int i = 0; i < _chunkTimes.length; i++) + { + if (i == 0) + { + average = _chunkTimes[i] - _startTime; + } + else + { + long delta = _chunkTimes[i] - lastChunkTime; + if (delta != 0) + { + average = (average + delta) / 2; + } + } + lastChunkTime = _chunkTimes[i]; + } + _logger.info("Average chunk time: " + average + "ms"); + _logger.info("Maximum WriteRequestQueue size: " + _maximumWriteQueueLength); + + CloseFuture cf = _session.close(); + _logger.info("Closing session"); + cf.join(); + } + + private class WriterHandler extends IoHandlerAdapter + { + private int _chunksReceived = 0; + + private int _partialBytesRead = 0; + + private byte _partialCheckNumber; + + private int _totalBytesReceived = 0; + + private int _receivedCount = 0; + private int _sentCount = 0; + private static final String DEFAULT_READ_BUFFER = "262144"; + private static final String DEFAULT_WRITE_BUFFER = "262144"; + + public void sessionCreated(IoSession session) throws Exception + { + IoFilterChain chain = session.getFilterChain(); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER))); + + writefilter.attach(chain); + } + + public void messageSent(IoSession session, Object message) throws Exception + { + _maximumWriteQueueLength = Math.max(session.getScheduledWriteRequests(), _maximumWriteQueueLength); + + if (_logger.isDebugEnabled()) + { + ++_sentCount; + if (_sentCount % 1000 == 0) + { + _logger.debug("Sent count " + _sentCount + ":WQueue" + session.getScheduledWriteRequests()); + + } + } + } + + public void messageReceived(IoSession session, Object message) throws Exception + { + if (_logger.isDebugEnabled()) + { + ++_receivedCount; + + if (_receivedCount % 1000 == 0) + { + _logger.debug("Receieved count " + _receivedCount); + } + } + + ByteBuffer result = (ByteBuffer) message; + _totalBytesReceived += result.remaining(); + int size = result.remaining(); + long now = System.currentTimeMillis(); + if (_partialBytesRead > 0) + { + int offset = _chunkSize - _partialBytesRead; + if (size >= offset) + { + _chunkTimes[_chunksReceived++] = now; + result.position(offset); + } + else + { + // have not read even one chunk, including the previous partial bytes + _partialBytesRead += size; + return; + } + } + + + int chunkCount = result.remaining() / _chunkSize; + + for (int i = 0; i < chunkCount; i++) + { + _chunkTimes[_chunksReceived++] = now; + byte check = result.get(); + _logger.debug("Check number " + check + " read"); + if (check != (byte) ((_chunksReceived - 1) % 128)) + { + _logger.error("Check number " + check + " read when expected " + (_chunksReceived % 128)); + } + _logger.debug("Chunk times recorded"); + + try + { + result.skip(_chunkSize - 1); + } + catch (IllegalArgumentException e) + { + _logger.error("Position was: " + result.position()); + _logger.error("Tried to skip to: " + (_chunkSize * i)); + _logger.error("limit was; " + result.limit()); + } + } + _logger.debug("Chunks received now " + _chunksReceived); + _logger.debug("Bytes received: " + _totalBytesReceived); + _partialBytesRead = result.remaining(); + + if (_partialBytesRead > 0) + { + _partialCheckNumber = result.get(); + } + + + if (_chunksReceived >= _chunkCount) + { + _notifier.countDown(); + } + + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + _logger.error("Error: " + cause, cause); + } + } + + public void startWriter() throws IOException, InterruptedException + { + + _maximumWriteQueueLength = 0; + + IoConnector ioConnector = null; + + if (Boolean.getBoolean("multinio")) + { + _logger.warn("Using MultiThread NIO"); + ioConnector = new org.apache.mina.transport.socket.nio.MultiThreadSocketConnector(); + } + else + { + _logger.warn("Using MINA NIO"); + ioConnector = new org.apache.mina.transport.socket.nio.SocketConnector(); + } + + SocketSessionConfig scfg = (SocketSessionConfig) ioConnector.getDefaultConfig().getSessionConfig(); + scfg.setTcpNoDelay(true); + scfg.setSendBufferSize(32768); + scfg.setReceiveBufferSize(32768); + + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + + + final InetSocketAddress address = new InetSocketAddress("localhost", _PORT); + _logger.info("Attempting connection to " + address); + + //Old mina style +// ioConnector.setHandler(new WriterHandler()); +// ConnectFuture future = ioConnector.connect(address); + ConnectFuture future = ioConnector.connect(address, new WriterHandler()); + // wait for connection to complete + future.join(); + _logger.info("Connection completed"); + // we call getSession which throws an IOException if there has been an error connecting + _session = future.getSession(); + + _chunkTimes = new long[_chunkCount]; + Thread t = new Thread(this); + t.start(); + t.join(); + _logger.info("Test Complete"); + } + + + public void test1k() throws IOException, InterruptedException + { + _logger.info("Starting 1k test"); + _chunkSize = 1024; + startWriter(); + } + + + public void test2k() throws IOException, InterruptedException + { + _logger.info("Starting 2k test"); + _chunkSize = 2048; + startWriter(); + } + + + public void test4k() throws IOException, InterruptedException + { + _logger.info("Starting 4k test"); + _chunkSize = 4096; + startWriter(); + } + + + public void test8k() throws IOException, InterruptedException + { + _logger.info("Starting 8k test"); + _chunkSize = 8192; + startWriter(); + } + + + public void test16k() throws IOException, InterruptedException + { + _logger.info("Starting 16k test"); + _chunkSize = 16384; + startWriter(); + } + + + public void test32k() throws IOException, InterruptedException + { + _logger.info("Starting 32k test"); + _chunkSize = 32768; + startWriter(); + } + + + public static int getIntArg(String[] args, int index, int defaultValue) + { + if (args.length > index) + { + try + { + return Integer.parseInt(args[index]); + } + catch (NumberFormatException e) + { + //Do nothing + } + } + return defaultValue; + } + + public static void main(String[] args) throws IOException, InterruptedException + { + _PORT = getIntArg(args, 0, _PORT); + + int test = getIntArg(args, 1, DEFAULT_TEST_SIZE); + + IOWriterClient w = new IOWriterClient(); + w._chunkCount = getIntArg(args, 2, w._chunkCount); + switch (test) + { + case 0: + w.test1k(); + w.test2k(); + w.test4k(); + w.test8k(); + w.test16k(); + w.test32k(); + break; + case 1: + w.test1k(); + break; + case 2: + w.test2k(); + break; + case 4: + w.test4k(); + break; + case 8: + w.test8k(); + break; + case 16: + w.test16k(); + break; + case 32: + w.test32k(); + break; + } + } +} diff --git a/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java new file mode 100644 index 0000000000..423e98c67b --- /dev/null +++ b/java/common/src/test/java/org/apache/mina/SocketIOTest/IOWriterServer.java @@ -0,0 +1,157 @@ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.mina.SocketIOTest; + +import org.apache.mina.common.ByteBuffer; +import org.apache.mina.common.IoAcceptor; +import org.apache.mina.common.IoFilterChain; +import org.apache.mina.common.IoHandlerAdapter; +import org.apache.mina.common.IoSession; +import org.apache.mina.common.SimpleByteBufferAllocator; +import org.apache.mina.filter.ReadThrottleFilterBuilder; +import org.apache.mina.filter.WriteBufferLimitFilterBuilder; +import org.apache.mina.transport.socket.nio.SocketSessionConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; + +/** Tests MINA socket performance. This acceptor simply reads data from the network and writes it back again. */ +public class IOWriterServer +{ + private static final Logger _logger = LoggerFactory.getLogger(IOWriterServer.class); + + static public int _PORT = 9999; + + private static final String DEFAULT_READ_BUFFER = "262144"; + private static final String DEFAULT_WRITE_BUFFER = "262144"; + + + private static class TestHandler extends IoHandlerAdapter + { + private int _sentCount = 0; + + private int _bytesSent = 0; + + private int _receivedCount = 0; + + public void sessionCreated(IoSession ioSession) throws java.lang.Exception + { + IoFilterChain chain = ioSession.getFilterChain(); + + ReadThrottleFilterBuilder readfilter = new ReadThrottleFilterBuilder(); + readfilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.read.buffer.limit", DEFAULT_READ_BUFFER))); + readfilter.attach(chain); + + WriteBufferLimitFilterBuilder writefilter = new WriteBufferLimitFilterBuilder(); + + writefilter.setMaximumConnectionBufferSize(Integer.parseInt(System.getProperty("qpid.write.buffer.limit", DEFAULT_WRITE_BUFFER))); + + writefilter.attach(chain); + + } + + public void messageReceived(IoSession session, Object message) throws Exception + { + ((ByteBuffer) message).acquire(); + session.write(message); + + if (_logger.isDebugEnabled()) + { + _bytesSent += ((ByteBuffer) message).remaining(); + + _sentCount++; + + if (_sentCount % 1000 == 0) + { + _logger.debug("Bytes sent: " + _bytesSent); + } + } + } + + public void messageSent(IoSession session, Object message) throws Exception + { + if (_logger.isDebugEnabled()) + { + ++_receivedCount; + + if (_receivedCount % 1000 == 0) + { + _logger.debug("Receieved count " + _receivedCount); + } + } + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception + { + _logger.error("Error: " + cause, cause); + } + } + + public void startAcceptor() throws IOException + { + IoAcceptor acceptor; + if (Boolean.getBoolean("multinio")) + { + _logger.warn("Using MultiThread NIO"); + acceptor = new org.apache.mina.transport.socket.nio.MultiThreadSocketAcceptor(); + } + else + { + _logger.warn("Using MINA NIO"); + acceptor = new org.apache.mina.transport.socket.nio.SocketAcceptor(); + } + + + SocketSessionConfig sc = (SocketSessionConfig) acceptor.getDefaultConfig().getSessionConfig(); + sc.setTcpNoDelay(true); + sc.setSendBufferSize(32768); + sc.setReceiveBufferSize(32768); + + ByteBuffer.setAllocator(new SimpleByteBufferAllocator()); + + //The old mina style +// acceptor.setLocalAddress(new InetSocketAddress(_PORT)); +// acceptor.setHandler(new TestHandler()); +// acceptor.bind(); + acceptor.bind(new InetSocketAddress(_PORT), new TestHandler()); + + _logger.info("Bound on port " + _PORT + ":" + _logger.isDebugEnabled()); + _logger.debug("debug on"); + } + + public static void main(String[] args) throws IOException + { + + if (args.length > 0) + { + try + { + _PORT = Integer.parseInt(args[0]); + } + catch (NumberFormatException e) + { + //IGNORE so use default port 9999; + } + } + + IOWriterServer a = new IOWriterServer(); + a.startAcceptor(); + } +} diff --git a/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java b/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java new file mode 100644 index 0000000000..92e7ce0a80 --- /dev/null +++ b/java/common/src/test/java/org/apache/qpid/framing/AMQShortStringTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.framing; + +import junit.framework.TestCase; +public class AMQShortStringTest extends TestCase +{ + + public static final AMQShortString HELLO = new AMQShortString("Hello"); + public static final AMQShortString HELL = new AMQShortString("Hell"); + public static final AMQShortString GOODBYE = new AMQShortString("Goodbye"); + public static final AMQShortString GOOD = new AMQShortString("Good"); + public static final AMQShortString BYE = new AMQShortString("BYE"); + + public void testStartsWith() + { + assertTrue(HELLO.startsWith(HELL)); + + assertFalse(HELL.startsWith(HELLO)); + + assertTrue(GOODBYE.startsWith(GOOD)); + + assertFalse(GOOD.startsWith(GOODBYE)); + } + + public void testEndWith() + { + assertFalse(HELL.endsWith(HELLO)); + + assertTrue(GOODBYE.endsWith(new AMQShortString("bye"))); + + assertFalse(GOODBYE.endsWith(BYE)); + } + + + public void testTokenize() + { + AMQShortString dotSeparatedWords = new AMQShortString("this.is.a.test.with.1.2.3.-numbers-and-then--dashes-"); + AMQShortStringTokenizer dotTokenizer = dotSeparatedWords.tokenize((byte) '.'); + + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("this"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("is"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("a"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("test"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(new AMQShortString("with"),(dotTokenizer.nextToken())); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 1); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 2); + assertTrue(dotTokenizer.hasMoreTokens()); + assertEquals(dotTokenizer.nextToken().toIntValue() , 3); + assertTrue(dotTokenizer.hasMoreTokens()); + AMQShortString dashString = dotTokenizer.nextToken(); + assertEquals(new AMQShortString("-numbers-and-then--dashes-"),(dashString)); + + AMQShortStringTokenizer dashTokenizer = dashString.tokenize((byte)'-'); + assertEquals(dashTokenizer.countTokens(), 7); + + AMQShortString[] expectedResults = new AMQShortString[] + { AMQShortString.EMPTY_STRING, + new AMQShortString("numbers"), + new AMQShortString("and"), + new AMQShortString("then"), + AMQShortString.EMPTY_STRING, + new AMQShortString("dashes"), + AMQShortString.EMPTY_STRING }; + + for(int i = 0; i < 7; i++) + { + assertTrue(dashTokenizer.hasMoreTokens()); + assertEquals(dashTokenizer.nextToken(), expectedResults[i]); + } + + assertFalse(dotTokenizer.hasMoreTokens()); + } + + + public void testEquals() + { + assertEquals(GOODBYE, new AMQShortString("Goodbye")); + assertEquals(new AMQShortString("A"), new AMQShortString("A")); + assertFalse(new AMQShortString("A").equals(new AMQShortString("a"))); + } + + +} diff --git a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java index e63b0df770..007da7423e 100644 --- a/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java +++ b/java/common/src/test/java/org/apache/qpid/framing/PropertyFieldTableTest.java @@ -1,21 +1,21 @@ /* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. * */ package org.apache.qpid.framing; @@ -439,6 +439,60 @@ public class PropertyFieldTableTest extends TestCase Assert.assertEquals("Hello", table1.getString("value")); } + /** Check that a nested field table parameter correctly encodes and decodes to a byte buffer. */ + public void testNestedFieldTable() + { + byte[] testBytes = new byte[] { 0, 1, 2, 3, 4, 5 }; + + FieldTable outerTable = new FieldTable(); + FieldTable innerTable = new FieldTable(); + + // Put some stuff in the inner table. + innerTable.setBoolean("bool", true); + innerTable.setByte("byte", Byte.MAX_VALUE); + innerTable.setBytes("bytes", testBytes); + innerTable.setChar("char", 'c'); + innerTable.setDouble("double", Double.MAX_VALUE); + innerTable.setFloat("float", Float.MAX_VALUE); + innerTable.setInteger("int", Integer.MAX_VALUE); + innerTable.setLong("long", Long.MAX_VALUE); + innerTable.setShort("short", Short.MAX_VALUE); + innerTable.setString("string", "hello"); + innerTable.setString("null-string", null); + + // Put the inner table in the outer one. + outerTable.setFieldTable("innerTable", innerTable); + + // Write the outer table into the buffer. + final ByteBuffer buffer = ByteBuffer.allocate((int) outerTable.getEncodedSize() + 4); + outerTable.writeToBuffer(buffer); + buffer.flip(); + + // Extract the table back from the buffer again. + try + { + FieldTable extractedOuterTable = EncodingUtils.readFieldTable(buffer); + + FieldTable extractedTable = extractedOuterTable.getFieldTable("innerTable"); + + Assert.assertEquals((Boolean) true, extractedTable.getBoolean("bool")); + Assert.assertEquals((Byte) Byte.MAX_VALUE, extractedTable.getByte("byte")); + assertBytesEqual(testBytes, extractedTable.getBytes("bytes")); + Assert.assertEquals((Character) 'c', extractedTable.getCharacter("char")); + Assert.assertEquals(Double.MAX_VALUE, extractedTable.getDouble("double")); + Assert.assertEquals(Float.MAX_VALUE, extractedTable.getFloat("float")); + Assert.assertEquals((Integer) Integer.MAX_VALUE, extractedTable.getInteger("int")); + Assert.assertEquals((Long) Long.MAX_VALUE, extractedTable.getLong("long")); + Assert.assertEquals((Short) Short.MAX_VALUE, extractedTable.getShort("short")); + Assert.assertEquals("hello", extractedTable.getString("string")); + Assert.assertEquals(null, extractedTable.getString("null-string")); + } + catch (AMQFrameDecodingException e) + { + fail("Failed to decode field table with nested inner table."); + } + } + public void testValues() { FieldTable table = new FieldTable(); diff --git a/java/common/templates/method/MethodBodyClass.tmpl b/java/common/templates/method/MethodBodyClass.tmpl deleted file mode 100644 index eb730fd891..0000000000 --- a/java/common/templates/method/MethodBodyClass.tmpl +++ /dev/null @@ -1,183 +0,0 @@ -&{${CLASS}${METHOD}Body.java} -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -/* - * This file is auto-generated by ${GENERATOR} - do not modify. - * Supported AMQP versions: -%{VLIST} * ${major}-${minor} - */ - -package org.apache.qpid.framing; - -import java.util.HashMap; - -import org.apache.mina.common.ByteBuffer; - -public class ${CLASS}${METHOD}Body extends AMQMethodBody implements EncodableAMQDataBlock -{ - private static final AMQMethodBodyInstanceFactory factory = new AMQMethodBodyInstanceFactory() - { - public AMQMethodBody newInstance(byte major, byte minor, ByteBuffer in, long size) throws AMQFrameDecodingException - { - return new ${CLASS}${METHOD}Body(major, minor, in); - } - - public AMQMethodBody newInstance(byte major, byte minor, int clazzID, int methodID, ByteBuffer in, long size) throws AMQFrameDecodingException - { - return new ${CLASS}${METHOD}Body(major, minor, clazzID, methodID, in); - } - - }; - - public static AMQMethodBodyInstanceFactory getFactory() - { - return factory; - } - - public static HashMap classIdMap = new HashMap(); - public static HashMap methodIdMap = new HashMap(); - - private static void registerMethodId(byte major, byte minor, int methodId) - { - methodIdMap.put((0xff & (int) major) | ((0xff & (int) minor)<<8), methodId); - } - - private static void registerClassId(byte major, byte minor, int classId) - { - classIdMap.put((0xff & (int) major) | ((0xff & (int) minor)<<8), classId); - } - - - static - { - - ${CLASS_ID_INIT} - ${METHOD_ID_INIT} - - } - - // Fields declared in specification -%{FLIST} ${field_declaration} - - private final int _clazz; - private final int _method; - - - // Constructor - - public ${CLASS}${METHOD}Body(byte major, byte minor, ByteBuffer buffer) throws AMQFrameDecodingException - { - this(major, minor, getClazz(major,minor), getMethod(major,minor), buffer); - } - - public ${CLASS}${METHOD}Body(byte major, byte minor, int clazzID, int methodID, ByteBuffer buffer) throws AMQFrameDecodingException - { - - super(major, minor); - _clazz = clazzID; - _method = methodID; - %{FLIST} ${mb_field_decode} - } - public ${CLASS}${METHOD}Body(byte major, byte minor, int clazzID, int methodID - %{FLIST} ${mb_field_parameter_list} - ) - { - super(major, minor); - _clazz = getClazz(major,minor); - _method = getMethod(major,minor); - %{FLIST} ${mb_field_body_initialize} - } - - public int getClazz() - { - return _clazz; - } - - public int getMethod() - { - return _method; - } - - public static int getClazz(byte major, byte minor) - { - return classIdMap.get((0xff & (int) major) | ((0xff & (int) minor)<<8)); - } - - public static int getMethod(byte major, byte minor) - { - return methodIdMap.get((0xff & (int) major) | ((0xff & (int) minor)<<8)); - } - - - // Field methods -%{FLIST} ${mb_field_get_method} - - protected int getBodySize() - { - int size = 0; -%{FLIST} ${mb_field_size} - return size; - } - - protected void writeMethodPayload(ByteBuffer buffer) - { -%{FLIST} ${mb_field_encode} - } - - public void populateMethodBodyFromBuffer(ByteBuffer buffer) throws AMQFrameDecodingException - { -%{FLIST} ${mb_field_decode} - } - - public String toString() - { - StringBuffer buf = new StringBuffer(super.toString()); -%{FLIST} ${mb_field_to_string} - return buf.toString(); - } - - public static AMQFrame createAMQFrame(int channelId, byte major, byte minor -%{FLIST} ${mb_field_parameter_list} - ) - { - return createAMQFrame(channelId, major, minor, getClazz(major,minor), getMethod(major,minor) -%{FLIST} ${mb_field_passed_parameter_list} - ); - - - - } - - public static AMQFrame createAMQFrame(int channelId, byte major, byte minor, int clazzID, int methodID -%{FLIST} ${mb_field_parameter_list} - ) - { - ${CLASS}${METHOD}Body body = new ${CLASS}${METHOD}Body(major, minor, clazzID, methodID -%{FLIST} ${mb_field_passed_parameter_list} - ); - - - AMQFrame frame = new AMQFrame(channelId, body); - return frame; - } - -} diff --git a/java/common/templates/method/MethodBodyInterface.vm b/java/common/templates/method/MethodBodyInterface.vm new file mode 100644 index 0000000000..d5feba12de --- /dev/null +++ b/java/common/templates/method/MethodBodyInterface.vm @@ -0,0 +1,62 @@ +#macro( UpperCamel $name ) +#set( $name = "${name.substring(0,1).toUpperCase()}${name.substring(1)}" ) +#end +#macro( toUpperCamel $name )${name.substring(0,1).toUpperCase()}${name.substring(1)}#end + + + +#set( $amqp_ClassName = $amqpClass.Name) +#UpperCamel( $amqp_ClassName ) +#set( $amqp_MethodName = $amqpMethod.Name ) +#UpperCamel( $amqp_MethodName ) +#set( $javaClassName = "${amqp_ClassName}${amqp_MethodName}Body" ) + + +#set( $filename = "${javaClassName}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by ${generator} - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + + +package org.apache.qpid.framing; + + +public interface ${javaClassName} extends EncodableAMQDataBlock, AMQMethodBody +{ + + +#foreach( $field in $amqpMethod.Fields ) + +#if( $amqpMethod.isCommon( $field ) ) + public $field.ConsistentNativeType get#toUpperCamel( ${field.Name} )(); +#end +#end + + + +} diff --git a/java/common/templates/method/version/MethodBodyClass.vm b/java/common/templates/method/version/MethodBodyClass.vm new file mode 100644 index 0000000000..9b2ba0fa39 --- /dev/null +++ b/java/common/templates/method/version/MethodBodyClass.vm @@ -0,0 +1,209 @@ +#macro( UpperCamel $name ) +#set( $name = "${name.substring(0,1).toUpperCase()}${name.substring(1)}" ) +#end +#macro( toUpperCamel $name )${name.substring(0,1).toUpperCase()}${name.substring(1)}#end + + + +#set( $amqp_ClassName = $amqpClass.Name) +#UpperCamel( $amqp_ClassName ) +#set( $amqp_MethodName = $amqpMethod.Name ) +#UpperCamel( $amqp_MethodName ) +#set( $javaClassName = "${amqp_ClassName}${amqp_MethodName}BodyImpl" ) +#set( $interfaceName = "${amqp_ClassName}${amqp_MethodName}Body" ) +#set( $amqpPackageName = "amqp_$version.getMajor()_$version.getMinor()" ) + +#set( $filename = "${amqpPackageName}/${javaClassName}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by ${generator} - do not modify. + * Supported AMQP version: + * $version.getMajor()-$version.getMinor() + */ + +#set( $clazz = $amqpClass.asSingleVersionClass( $version ) ) +#set( $method = $amqpMethod.asSingleVersionMethod( $version ) ) + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import java.util.HashMap; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.*; +import org.apache.qpid.AMQException; + +public class ${javaClassName} extends AMQMethodBody_$version.getMajor()_$version.getMinor() implements $interfaceName +{ + private static final AMQMethodBodyInstanceFactory FACTORY_INSTANCE = new AMQMethodBodyInstanceFactory() + { + public AMQMethodBody newInstance(ByteBuffer in, long size) throws AMQFrameDecodingException + { + return new ${javaClassName}(in); + } + + + }; + + + public static AMQMethodBodyInstanceFactory getFactory() + { + return FACTORY_INSTANCE; + } + + public static int CLASS_ID = $clazz.ClassId; + + public static int METHOD_ID = $method.MethodId; + + + + // Fields declared in specification +#foreach( $field in $method.ConsolidatedFields ) + private final $field.NativeType _$field.getName(); // $field.UnderlyingFields +#end + + + // Constructor + + public ${javaClassName}(ByteBuffer buffer) throws AMQFrameDecodingException + { +#foreach( $field in $method.ConsolidatedFields ) + _$field.Name = read$field.getEncodingType()( buffer ); +#end + } + + public ${javaClassName}( +#foreach( $field in $method.FieldList ) +#if( $velocityCount == $method.getFieldList().size() ) + $field.NativeType $field.Name +#else + $field.NativeType $field.Name, +#end +#end + ) + { +#set( $consolidatedFieldName = "" ) +#foreach( $field in $method.FieldList ) +#if( $method.isConsolidated( $field.Name ) ) +#if( !$method.getConsolidatedFieldName( $field.Name ).equals( $consolidatedFieldName ) ) +#if( !$consolidatedFieldName.equals("") ) + _$consolidatedFieldName = $consolidatedFieldName; // 1 +#end +#set( $consolidatedFieldName = $method.getConsolidatedFieldName( $field.Name ) ) + byte $consolidatedFieldName = (byte)0; +#end + if( $field.Name ) + { + $consolidatedFieldName = (byte) (((int) $consolidatedFieldName) | (1 << $method.getPositionInBitField( $field.Name ))); + } +#if( $velocityCount == $method.getFieldList().size()) + _$consolidatedFieldName = $consolidatedFieldName; +#else + +#end +#else +#if( !$consolidatedFieldName.equals("") ) + _$consolidatedFieldName = $consolidatedFieldName; +#end +#set( $consolidatedFieldName = "" ) + _$field.Name = $field.Name; +#end +#end + } + + public int getClazz() + { + return CLASS_ID; + } + + public int getMethod() + { + return METHOD_ID; + } + + +#foreach( $field in $method.FieldList ) + public final $field.NativeType get#toUpperCamel( ${field.Name} )() + { +#if( $method.isConsolidated( $field.Name ) ) + return (((int)(_$method.getConsolidatedFieldName( $field.Name ))) & ( 1 << $method.getPositionInBitField( $field.Name ))) != 0; +#else + return _$field.Name; +#end + } +#end + + protected int getBodySize() + { +#set( $fixedSize = 0 ) +#foreach( $field in $method.ConsolidatedFields ) +#if( $field.isFixedSize() ) +#set( $fixedSize = $fixedSize + $field.Size ) +#end +#end + int size = $fixedSize; +#foreach( $field in $method.ConsolidatedFields ) +#if( ! $field.isFixedSize() ) + size += getSizeOf( _$field.Name ); +#end +#end + return size; + } + + public void writeMethodPayload(ByteBuffer buffer) + { +#foreach( $field in $method.ConsolidatedFields ) + write$field.getEncodingType()( buffer, _$field.Name ); +#end + } + + public boolean execute(MethodDispatcher dispatcher, int channelId) throws AMQException + { +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) +#if( $amqpMethod.inAllVersions() ) + return dispatcher.dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(this, channelId); +#else + return ((MethodDispatcher_$version.getMajor()_$version.getMinor())dispatcher).dispatch${amqp_ClassName}${amqpMethodNameUpperCamel}(this, channelId); + +#end + + } + + + public String toString() + { + StringBuilder buf = new StringBuilder("[$javaClassName: "); +#foreach( $field in $method.FieldList ) + buf.append( "$field.Name=" ); + buf.append( get#toUpperCamel( $field.Name )() ); +#if( $velocityCount != $method.FieldList.size() ) + buf.append( ", " ); +#end +#end + buf.append("]"); + return buf.toString(); + } + + +} diff --git a/java/common/templates/model/AmqpConstantsClass.tmpl b/java/common/templates/model/AmqpConstantsClass.tmpl deleted file mode 100644 index 8d459f2977..0000000000 --- a/java/common/templates/model/AmqpConstantsClass.tmpl +++ /dev/null @@ -1,37 +0,0 @@ -&{AmqpConstants.java} -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -/* - * This file is auto-generated by ${GENERATOR} - do not modify. - * Supported AMQP versions: -%{VLIST} * ${major}-${minor} - */ - -package org.apache.qpid.framing; - -class AmqpConstants -{ - // Constant getValue methods - -%{TLIST} ${const_get_method} - -} diff --git a/java/common/templates/model/ClientMethodDispatcherInterface.vm b/java/common/templates/model/ClientMethodDispatcherInterface.vm new file mode 100644 index 0000000000..9e4aee7dee --- /dev/null +++ b/java/common/templates/model/ClientMethodDispatcherInterface.vm @@ -0,0 +1,56 @@ +#set( $filename = "ClientMethodDispatcher.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.qpid.AMQException; + + +public interface ClientMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#if( $amqpMethodVersions.inAllVersions() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isClientMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end +#end + +} \ No newline at end of file diff --git a/java/common/templates/model/MethodDispatcherInterface.vm b/java/common/templates/model/MethodDispatcherInterface.vm new file mode 100644 index 0000000000..ff14715fef --- /dev/null +++ b/java/common/templates/model/MethodDispatcherInterface.vm @@ -0,0 +1,39 @@ +#set( $filename = "MethodDispatcher.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.qpid.AMQException; + + +public interface MethodDispatcher extends + ClientMethodDispatcher, ServerMethodDispatcher +{ +} \ No newline at end of file diff --git a/java/common/templates/model/MethodRegistryClass.tmpl b/java/common/templates/model/MethodRegistryClass.tmpl deleted file mode 100644 index 474d9e31d1..0000000000 --- a/java/common/templates/model/MethodRegistryClass.tmpl +++ /dev/null @@ -1,159 +0,0 @@ -&{MainRegistry.java} -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -/* - * This file is auto-generated by ${GENERATOR} - do not modify. - * Supported AMQP versions: -%{VLIST} * ${major}-${minor} - */ - -package org.apache.qpid.framing; - -import java.util.HashMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.mina.common.ByteBuffer; - -public class MainRegistry -{ - private static final HashMap classIDMethodIDVersionBodyMap = new HashMap(); - - - private static final Logger _log = LoggerFactory.getLogger(MainRegistry.class); - - - private static final int DEFAULT_MINOR_VERSION_COUNT = 10; - private static final int DEFAULT_MAJOR_VERSION_COUNT = 10; - - private static VersionSpecificRegistry[][] _specificRegistries = new VersionSpecificRegistry[DEFAULT_MAJOR_VERSION_COUNT][]; - - static - { -%{CLIST} ${reg_map_put_method} - - configure(); - } - - public static AMQMethodBody get(short classID, short methodID, byte major, byte minor, ByteBuffer in, long size) - throws AMQFrameDecodingException - { - VersionSpecificRegistry registry = getVersionSpecificRegistry(major, minor); - AMQMethodBodyInstanceFactory bodyFactory = registry.getMethodBody(classID,methodID); - - if (bodyFactory == null) - { - throw new AMQFrameDecodingException(null, - "Unable to find a suitable decoder for class " + classID + " and method " + - methodID + " in AMQP version " + major + "-" + minor + ".", null); - } - return bodyFactory.newInstance(major, minor, in, size); - - - } - - - public static VersionSpecificRegistry getVersionSpecificRegistry(ProtocolVersion pv) - { - return getVersionSpecificRegistry(pv.getMajorVersion(), pv.getMinorVersion()); - } - public static VersionSpecificRegistry getVersionSpecificRegistry(byte major, byte minor) - { - try - { - return _specificRegistries[(int)major][(int)minor]; - } - catch (IndexOutOfBoundsException e) - { - return null; - } - catch (NullPointerException e) - { - return null; - } - - - } - - private static VersionSpecificRegistry addVersionSpecificRegistry(byte major, byte minor) - { - VersionSpecificRegistry[][] registries = _specificRegistries; - if(major >= registries.length) - { - _specificRegistries = new VersionSpecificRegistry[(int)major + 1][]; - System.arraycopy(registries, 0, _specificRegistries, 0, registries.length); - registries = _specificRegistries; - } - if(registries[major] == null) - { - registries[major] = new VersionSpecificRegistry[ minor >= DEFAULT_MINOR_VERSION_COUNT ? minor + 1 : DEFAULT_MINOR_VERSION_COUNT ]; - } - else if(registries[major].length <= minor) - { - VersionSpecificRegistry[] minorArray = registries[major]; - registries[major] = new VersionSpecificRegistry[ minor + 1 ]; - System.arraycopy(minorArray, 0, registries[major], 0, minorArray.length); - - } - - VersionSpecificRegistry newRegistry = new VersionSpecificRegistry(major,minor); - - registries[major][minor] = newRegistry; - - return newRegistry; - } - - private static void registerMethod(short classID, short methodID, byte major, byte minor, AMQMethodBodyInstanceFactory instanceFactory ) - { - VersionSpecificRegistry registry = getVersionSpecificRegistry(major,minor); - if(registry == null) - { - registry = addVersionSpecificRegistry(major,minor); - - } - - registry.registerMethod(classID, methodID, instanceFactory); - - } - - - private static void configure() - { - for(int i = 0 ; i < _specificRegistries.length; i++) - { - VersionSpecificRegistry[] registries = _specificRegistries[i]; - if(registries != null) - { - for(int j = 0 ; j < registries.length; j++) - { - VersionSpecificRegistry registry = registries[j]; - - if(registry != null) - { - registry.configure(); - } - } - } - } - - } - -} diff --git a/java/common/templates/model/MethodRegistryClass.vm b/java/common/templates/model/MethodRegistryClass.vm new file mode 100644 index 0000000000..759e5e4a42 --- /dev/null +++ b/java/common/templates/model/MethodRegistryClass.vm @@ -0,0 +1,104 @@ +#set( $filename = "MethodRegistry.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; + +import java.util.Map; +import java.util.HashMap; + + +public abstract class MethodRegistry +{ + private static final Map _registries = + new HashMap(); + +#foreach( $supportedVersion in $model.VersionSet ) + +#set( $verName = "_$supportedVersion.getMajor()_$supportedVersion.getMinor()" ) +#set( $regPackage = "org.apache.qpid.framing.amqp$verName" ) + public static final MethodRegistry registry_$supportedVersion.getMajor()_$supportedVersion.getMinor() = + new ${regPackage}.MethodRegistry${verName}(); + +#end + + + public abstract AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException; + + public abstract int getMaxClassId(); + + public abstract int getMaxMethodId(int classId); + + + protected MethodRegistry(ProtocolVersion pv) + { + _registries.put(pv, this); + } + + public static MethodRegistry getMethodRegistry(ProtocolVersion pv) + { + return _registries.get(pv); + } + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#if( $amqpMethodVersions.isVersionInterfaceConsistent() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) + +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public abstract ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body create${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body( +#foreach( $field in $amqpMethod.FieldList ) +#if( $velocityCount == $amqpMethod.getFieldList().size() ) + final $field.NativeType $field.Name +#else + final $field.NativeType $field.Name, +#end +#end + ); + + +#end +#end +#end + + + public abstract ProtocolVersionMethodConverter getProtocolVersionMethodConverter(); + +} \ No newline at end of file diff --git a/java/common/templates/model/ProtocolVersionListClass.tmpl b/java/common/templates/model/ProtocolVersionListClass.tmpl deleted file mode 100644 index 4a2592d11b..0000000000 --- a/java/common/templates/model/ProtocolVersionListClass.tmpl +++ /dev/null @@ -1,127 +0,0 @@ -&{ProtocolVersion.java} -/* -* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -* -*/ - -/* -* This file is auto-generated by ${GENERATOR} - do not modify. -* Supported AMQP versions: -%{VLIST} * ${major}-${minor} -*/ - -package org.apache.qpid.framing; - -import java.util.SortedSet; -import java.util.Collections; -import java.util.TreeSet; - - -public class ProtocolVersion implements Comparable -{ - private final byte _majorVersion; - private final byte _minorVersion; - - - public ProtocolVersion(byte majorVersion, byte minorVersion) - { - _majorVersion = majorVersion; - _minorVersion = minorVersion; - } - - public byte getMajorVersion() - { - return _majorVersion; - } - - public byte getMinorVersion() - { - return _minorVersion; - } - - - public int compareTo(Object o) - { - ProtocolVersion pv = (ProtocolVersion) o; - if(getMajorVersion() > pv.getMajorVersion()) - { - return 1; - } - else if(getMajorVersion() < pv.getMajorVersion()) - { - return -1; - } - else if(getMajorVersion() > pv.getMajorVersion()) - { - return 1; - } - else if(getMinorVersion() < pv.getMinorVersion()) - { - return -1; - } - else - { - return 0; - } - - } - - public boolean equals(Object o) - { - return o != null && (o == this || (compareTo(o) == 0)); - } - - public int hashCode() - { - return (0xFF & (int)_minorVersion) | ((0xFF & (int)_majorVersion) << 8); - } - - - public boolean isSupported() - { - return _supportedVersions.contains(this); - } - - public static ProtocolVersion getLatestSupportedVersion() - { - return _supportedVersions.last(); - } - - private static final SortedSet _supportedVersions; - - static - { - SortedSet versions = new TreeSet(); - -%{VLIST} versions.add(new ProtocolVersion((byte)${major},(byte)${minor})); - - _supportedVersions = Collections.unmodifiableSortedSet(versions); - } - - - public static SortedSet getSupportedProtocolVersions() - { - return _supportedVersions; - } - - - - - -} diff --git a/java/common/templates/model/ProtocolVersionListClass.vm b/java/common/templates/model/ProtocolVersionListClass.vm new file mode 100644 index 0000000000..9ac6adfdf5 --- /dev/null +++ b/java/common/templates/model/ProtocolVersionListClass.vm @@ -0,0 +1,178 @@ +#set( $filename = "ProtocolVersion.java" ) +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ + +/* +* This file is auto-generated by $generator - do not modify. +* Supported AMQP versions: +#foreach( $version in $model.getVersionSet() ) +* $version.getMajor()-$version.getMinor() +#end +*/ + +package org.apache.qpid.framing; + +import java.util.SortedSet; +import java.util.Collections; +import java.util.TreeSet; +import java.util.Map; +import java.util.HashMap; + + +public class ProtocolVersion implements Comparable +{ + private final byte _majorVersion; + private final byte _minorVersion; + + + public ProtocolVersion(byte majorVersion, byte minorVersion) + { + _majorVersion = majorVersion; + _minorVersion = minorVersion; + } + + public byte getMajorVersion() + { + return _majorVersion; + } + + public byte getMinorVersion() + { + return _minorVersion; + } + + + public int compareTo(Object o) + { + ProtocolVersion pv = (ProtocolVersion) o; + + /* + * 0-8 has it's major and minor numbers the wrong way round (it's actually 8-0)... + * so we need to deal with that case specially + */ + + if((_majorVersion == (byte) 8) && (_minorVersion == (byte) 0)) + { + ProtocolVersion fixedThis = new ProtocolVersion(_minorVersion, _majorVersion); + return fixedThis.compareTo(pv); + } + + if((pv.getMajorVersion() == (byte) 8) && (pv.getMinorVersion() == (byte) 0)) + { + ProtocolVersion fixedOther = new ProtocolVersion(pv.getMinorVersion(), pv.getMajorVersion()); + return this.compareTo(fixedOther); + } + + if(_majorVersion > pv.getMajorVersion()) + { + return 1; + } + else if(_majorVersion < pv.getMajorVersion()) + { + return -1; + } + else if(_minorVersion > pv.getMinorVersion()) + { + return 1; + } + else if(getMinorVersion() < pv.getMinorVersion()) + { + return -1; + } + else + { + return 0; + } + + } + + public boolean equals(Object o) + { + return o != null && (o == this || (compareTo(o) == 0)); + } + + public int hashCode() + { + return (0xFF & (int)_minorVersion) | ((0xFF & (int)_majorVersion) << 8); + } + + + public boolean isSupported() + { + return _supportedVersions.contains(this); + } + + public static ProtocolVersion getLatestSupportedVersion() + { + return _supportedVersions.last(); + } + + private static final SortedSet _supportedVersions; + private static final Map _nameToVersionMap = + new HashMap(); + private static final ProtocolVersion _defaultVersion; + + +#foreach( $version in $model.getVersionSet() ) +#set( $versionId = "v$version.getMajor()_$version.getMinor()" ) + public static final ProtocolVersion $versionId = new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor()); +#end + + static + { + SortedSet versions = new TreeSet(); + +#foreach( $version in $model.getVersionSet() ) +#set( $versionId = "v$version.getMajor()_$version.getMinor()" ) + versions.add($versionId); + _nameToVersionMap.put("${version.getMajor()}-${version.getMinor()}", $versionId); +#end + _supportedVersions = Collections.unmodifiableSortedSet(versions); + + + ProtocolVersion systemDefinedVersion = + _nameToVersionMap.get(System.getProperty("org.apache.qpid.amqp_version")); + + _defaultVersion = (systemDefinedVersion == null) + ? getLatestSupportedVersion() + : systemDefinedVersion; + } + + + public static SortedSet getSupportedProtocolVersions() + { + return _supportedVersions; + } + + + + public static ProtocolVersion parse(String name) + { + return _nameToVersionMap.get(name); + } + + public static ProtocolVersion defaultProtocolVersion() + { + return _defaultVersion; + } + + +} diff --git a/java/common/templates/model/ServerMethodDispatcherInterface.vm b/java/common/templates/model/ServerMethodDispatcherInterface.vm new file mode 100644 index 0000000000..b80d6027b7 --- /dev/null +++ b/java/common/templates/model/ServerMethodDispatcherInterface.vm @@ -0,0 +1,56 @@ +#set( $filename = "ServerMethodDispatcher.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing; + +import org.apache.qpid.AMQException; + + +public interface ServerMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#if( $amqpMethodVersions.inAllVersions() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isServerMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end +#end + +} \ No newline at end of file diff --git a/java/common/templates/model/version/AmqpConstantsClass.vm b/java/common/templates/model/version/AmqpConstantsClass.vm new file mode 100644 index 0000000000..8d459f2977 --- /dev/null +++ b/java/common/templates/model/version/AmqpConstantsClass.vm @@ -0,0 +1,37 @@ +&{AmqpConstants.java} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by ${GENERATOR} - do not modify. + * Supported AMQP versions: +%{VLIST} * ${major}-${minor} + */ + +package org.apache.qpid.framing; + +class AmqpConstants +{ + // Constant getValue methods + +%{TLIST} ${const_get_method} + +} diff --git a/java/common/templates/model/version/ClientMethodDispatcherInterface.vm b/java/common/templates/model/version/ClientMethodDispatcherInterface.vm new file mode 100644 index 0000000000..80705c1a39 --- /dev/null +++ b/java/common/templates/model/version/ClientMethodDispatcherInterface.vm @@ -0,0 +1,55 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/ClientMethodDispatcher_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; + + +public interface ClientMethodDispatcher_${version.getMajor()}_${version.getMinor()} extends ClientMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isClientMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end + +} \ No newline at end of file diff --git a/java/common/templates/model/version/MethodDispatcherInterface.vm b/java/common/templates/model/version/MethodDispatcherInterface.vm new file mode 100644 index 0000000000..8a7b667a91 --- /dev/null +++ b/java/common/templates/model/version/MethodDispatcherInterface.vm @@ -0,0 +1,43 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/MethodDispatcher_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; + + +public interface MethodDispatcher_${version.getMajor()}_${version.getMinor()} + extends MethodDispatcher, + ServerMethodDispatcher_${version.getMajor()}_${version.getMinor()}, + ClientMethodDispatcher_${version.getMajor()}_${version.getMinor()} +{ + +} \ No newline at end of file diff --git a/java/common/templates/model/version/MethodRegistryClass.vm b/java/common/templates/model/version/MethodRegistryClass.vm new file mode 100644 index 0000000000..277605e34b --- /dev/null +++ b/java/common/templates/model/version/MethodRegistryClass.vm @@ -0,0 +1,193 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/MethodRegistry_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + * $version.getMajor()-$version.getMinor() + */ + +package org.apache.qpid.framing.amqp_${version.getMajor()}_${version.getMinor()}; + +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.mina.common.ByteBuffer; + +import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter; + + +public class MethodRegistry_$version.getMajor()_$version.getMinor() extends MethodRegistry +{ + + private static final Logger _log = LoggerFactory.getLogger(MethodRegistry.class); + + private ProtocolVersionMethodConverter _protocolVersionConverter = new MethodConverter_$version.getMajor()_$version.getMinor()(); + +#set( $specificModel = $model.asSingleVersionModel() ) + + +#set( $maxClassId = $specificModel.getMaximumClassId()+1 ) + private final AMQMethodBodyInstanceFactory[][] _factories = new AMQMethodBodyInstanceFactory[$maxClassId][]; + + public MethodRegistry_$version.getMajor()_$version.getMinor()() + { + this(new ProtocolVersion((byte)$version.getMajor(),(byte)$version.getMinor())); + } + + public MethodRegistry_$version.getMajor()_$version.getMinor()(ProtocolVersion pv) + { + super(pv); +#foreach( $amqpClass in $specificModel.getClassList() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + + + + // Register method body instance factories for the $amqpClassNameUpperCamel class. + +#set( $maxMethodId = $amqpClass.getMaximumMethodId()+1 ) + _factories[$amqpClass.getClassId()] = new AMQMethodBodyInstanceFactory[$maxMethodId]; + +#foreach( $amqpMethod in $amqpClass.getMethodList() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + _factories[$amqpClass.getClassId()][$amqpMethod.getMethodId()] = ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl.getFactory(); +#end + +#end + + + } + + + public AMQMethodBody convertToBody(ByteBuffer in, long size) + throws AMQFrameDecodingException + { + int classId = in.getUnsignedShort(); + int methodId = in.getUnsignedShort(); + + AMQMethodBodyInstanceFactory bodyFactory; + try + { + bodyFactory = _factories[classId][methodId]; + } + catch(NullPointerException e) + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Class " + classId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + } + catch(IndexOutOfBoundsException e) + { + if(classId >= _factories.length) + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Class " + classId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + + } + else + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Method " + methodId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + + } + } + + + if (bodyFactory == null) + { + throw new AMQFrameDecodingException(AMQConstant.COMMAND_INVALID, + "Method " + methodId + " unknown in AMQP version $version.getMajor()-$version.getMinor()" + + " (while trying to decode class " + classId + " method " + methodId + "."); + } + + + return bodyFactory.newInstance(in, size); + + + } + + + public int getMaxClassId() + { + return $specificModel.getMaximumClassId(); + } + + public int getMaxMethodId(int classId) + { + return _factories[classId].length - 1; + } + + + +#foreach( $amqpClass in $specificModel.getClassList() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) + + +#foreach( $amqpMethod in $amqpClass.getMethodList() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body create${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body( +#foreach( $field in $amqpMethod.FieldList ) +#if( $velocityCount == $amqpMethod.getFieldList().size() ) + final $field.NativeType $field.Name +#else + final $field.NativeType $field.Name, +#end +#end + ) + { + return new ${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}BodyImpl( +#foreach( $field in $amqpMethod.FieldList ) +#if( $velocityCount == $amqpMethod.getFieldList().size() ) + $field.Name +#else + $field.Name, +#end +#end + ); + } + +#end + +#end + + + public ProtocolVersionMethodConverter getProtocolVersionMethodConverter() + { + return _protocolVersionConverter; + } + + +} diff --git a/java/common/templates/model/version/ServerMethodDispatcherInterface.vm b/java/common/templates/model/version/ServerMethodDispatcherInterface.vm new file mode 100644 index 0000000000..db388fcc65 --- /dev/null +++ b/java/common/templates/model/version/ServerMethodDispatcherInterface.vm @@ -0,0 +1,55 @@ +#set( $filename = "amqp_$version.getMajor()_$version.getMinor()/ServerMethodDispatcher_${version.getMajor()}_${version.getMinor()}.java") +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +/* + * This file is auto-generated by $generator - do not modify. + * Supported AMQP version: + #foreach( $supportedVersion in $model.VersionSet ) + * $supportedVersion.getMajor()-$supportedVersion.getMinor() + #end + */ + +package org.apache.qpid.framing.amqp_$version.getMajor()_$version.getMinor(); + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; + + +public interface ServerMethodDispatcher_${version.getMajor()}_${version.getMinor()} extends ServerMethodDispatcher +{ + +#foreach( $amqpClass in $model.getClasses() ) +#set( $amqpClassNameFirstChar = $amqpClass.getName().substring(0,1) ) +#set( $amqpClassNameFirstCharU = $amqpClassNameFirstChar.toUpperCase() ) +#set( $amqpClassNameUpperCamel = "$amqpClassNameFirstCharU$amqpClass.getName().substring(1)" ) +#foreach( $amqpMethodVersions in $amqpClass.getMethods() ) +#set( $amqpMethod = $amqpMethodVersions.asSingleVersionMethod($amqpMethodVersions.getVersionSet().first()) ) +#if( $amqpMethod.isServerMethod() ) +#set( $amqpMethodNameFirstChar = $amqpMethod.getName().substring(0,1) ) +#set( $amqpMethodNameFirstCharU = $amqpMethodNameFirstChar.toUpperCase() ) +#set( $amqpMethodNameUpperCamel = "$amqpMethodNameFirstCharU$amqpMethod.getName().substring(1)" ) + public boolean dispatch${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}(${amqpClassNameUpperCamel}${amqpMethodNameUpperCamel}Body body, int channelId) throws AMQException; +#end +#end +#end + +} \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index a4e4de5057..084358a6e8 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -48,6 +48,12 @@ under the License. scp://people.apache.org/www/people.apache.org/repo/m2-snapshot-repository + + apache.incubating + Apache Incubating Repository + scp://people.apache.org/www/people.apache.org/repo/m2-incubating-repository + + - + --> + install @@ -502,18 +486,23 @@ under the License. org.apache.mina mina-core - 1.0.0 + 1.0.1 org.apache.mina mina-filter-ssl - 1.0.0 + 1.0.1 @@ -537,12 +526,14 @@ under the License. test + @@ -580,6 +571,16 @@ under the License. qpid-mgmt-client ${project.version} + + org.apache.qpid + junit-toolkit + ${project.version} + + + org.apache.qpid + junit-toolkit-maven-plugin + ${project.version} + @@ -676,25 +677,28 @@ under the License. --> - - junit-toolkit.snapshots - JUnit Toolkit SNAPSHOT Repository - http://junit-toolkit.svn.sourceforge.net/svnroot/junit-toolkit/snapshots/ - - true - - - - Codehaus Snapshots - http://snapshots.repository.codehaus.org/ - - true - - - false - - - + + Codehaus Snapshots + http://snapshots.repository.codehaus.org/ + + true + + + false + + + + + + @@ -715,6 +719,16 @@ under the License. + + diff --git a/java/systests/build.xml b/java/systests/build.xml index 4a45bfb28e..b84cb9fc32 100644 --- a/java/systests/build.xml +++ b/java/systests/build.xml @@ -19,7 +19,7 @@ - --> - + diff --git a/java/systests/etc/bin/fail.py b/java/systests/etc/bin/fail.py new file mode 100644 index 0000000000..517f31d075 --- /dev/null +++ b/java/systests/etc/bin/fail.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import os +import re +import datetime + +from optparse import OptionParser + +BASE_CMD = "mvn -Dskip.python.test=true %s test" + +def main(): + parser = OptionParser() + parser.add_option("-t", "--test", dest="test", + action="store", type="string", + help="run specific tests") + parser.add_option("-c", "--continuous", dest="continuous", + action="store_true", default=False, + help="run tests after failures, don't stop") + + + (options, args) = parser.parse_args() + + # determine command to run + if (options.test != None): + cmd = (BASE_CMD % ("-Dtest="+options.test)) + else: + cmd = (BASE_CMD % ("")) + + run_forever = options.continuous + + + failed_runs = [] + iteration = 0 + fail_match = re.compile("BUILD SUCCESSFUL") + done = False + + while (run_forever or not (len(failed_runs) > 0)): + iteration = iteration + 1 + if (run_forever): + extra_text = (", %d failures so far: %s:" % (len(failed_runs), failed_runs)) + else: + extra_text = "" + print ("%s Test run %d%s" % (datetime.datetime.today().isoformat(), iteration, extra_text)) + (child_stdin, child_stdout_and_stderr) = os.popen4(cmd) + output = child_stdout_and_stderr.read() + child_stdin.close() + child_stdout_and_stderr.close() + matches = fail_match.search(output) + if (matches == None): + failed_runs.append(iteration) + output_name = ("test-run-%d.out" % (iteration)) + #write testouput + test_output = file(output_name, "w") + test_output.write(output) + test_output.close() + #tar test-output and surefire reports together + find_stdout = os.popen("find . -type d -name surefire-reports") + surefire_dirs = find_stdout.read().replace('\n', ' ') + find_stdout.close() + tarcmd = ("tar -zcf test-failures-%d.tar.gz %s %s" % (iteration, output_name, surefire_dirs)) + tar_stdout = os.popen(tarcmd) + tar_output = tar_stdout.read() + tar_exitstatus = tar_stdout.close() + print ("Something failed! Check %s" % (output_name)) + if (tar_exitstatus != None): + print ("tar exited abornmally, aborting\n %s" % (tar_output)) + run_forever = False + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/java/systests/pom.xml b/java/systests/pom.xml index 64af96150a..49f088ec1a 100644 --- a/java/systests/pom.xml +++ b/java/systests/pom.xml @@ -1,20 +1,20 @@ @@ -57,16 +57,16 @@ - uk.co.thebadgerset + org.apache.qpid junit-toolkit - - org.slf4j - slf4j-log4j12 - 1.4.0 - test + + org.slf4j + slf4j-log4j12 + 1.4.0 + test @@ -82,7 +82,97 @@ **/*Test.class + + + + example.plugin.target + ${basedir}/${topDirectoryLocation}/plugins/target + + + QPID_EXAMPLE_HOME + ${basedir}/${topDirectoryLocation}/broker + + + QPID_HOME + ${basedir}/${topDirectoryLocation}/broker + + + + + **/testcases/ImmediateMessageTest.class + **/testcases/MandatoryMessageTest.class + **/testcases/RollbackTest.class + **/testcases/TTLTest.class + **/testcases/FailoverTest.class + + + + + + + org.apache.qpid + junit-toolkit-maven-plugin + + + + + log4j.configuration + ${log4j.configuration} + + + + org.apache.qpid.junit.extensions.TKTestRunner + + + + + + + + + + + notApplicableAssertion + warn + + + + + -n AMQBrokerManagerMBeanTest org.apache.qpid.server.AMQBrokerManagerMBeanTest + -n TxAckTest org.apache.qpid.server.ack.TxAckTest + + -n ReturnUnroutableMandatoryMessageTest org.apache.qpid.server.exchange.ReturnUnroutableMandatoryMessageTest + + -n DeadlockTest org.apache.qpid.server.failure.DeadlockTest + + -n AMQProtocolSessionMBeanTest org.apache.qpid.server.protocol.AMQProtocolSessionMBeanTest + -n MaxChannelsTest org.apache.qpid.server.protocol.MaxChannelsTest + -n AckTest org.apache.qpid.server.queue.AckTest + -n MessageReturnTest org.apache.qpid.server.queue.MessageReturnTest + -n QueueDepthWithSelectorTest org.apache.qpid.server.queue.QueueDepthWithSelectorTest + + -n SubscriptionSetTest org.apache.qpid.server.queue.SubscriptionSetTest + -n TimeToLiveTest org.apache.qpid.server.queue.TimeToLiveTest + -n TxnBufferTest org.apache.qpid.server.txn.TxnBufferTest + + + + + + + + + + + + framework_tests + test + + tktest + + + @@ -109,3 +199,4 @@ + diff --git a/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java b/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java index fd28c2f77e..7fd53a64fd 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/ack/TxAckTest.java @@ -28,6 +28,8 @@ import org.apache.qpid.framing.abstraction.MessagePublishInfo; import org.apache.qpid.server.RequiredDeliveryException; import org.apache.qpid.server.store.MemoryMessageStore; import org.apache.qpid.server.queue.AMQMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.store.StoreContext; import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.txn.NonTransactionalContext; @@ -101,8 +103,8 @@ public class TxAckTest extends TestCase { TransactionalContext txnContext = new NonTransactionalContext(new MemoryMessageStore(), _storeContext, null, - new LinkedList(), - new HashSet()); + new LinkedList() + ); for (int i = 0; i < messageCount; i++) { long deliveryTag = i + 1; @@ -115,6 +117,11 @@ public class TxAckTest extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -132,7 +139,7 @@ public class TxAckTest extends TestCase }; TestMessage message = new TestMessage(deliveryTag, i, info, txnContext); - _map.add(deliveryTag, new UnacknowledgedMessage(null, message, null, deliveryTag)); + _map.add(deliveryTag, new UnacknowledgedMessage(new QueueEntry(null,message), null, deliveryTag)); } _acked = acked; _unacked = unacked; @@ -149,7 +156,7 @@ public class TxAckTest extends TestCase { UnacknowledgedMessage u = _map.get(tag); assertTrue("Message not found for tag " + tag, u != null); - ((TestMessage) u.message).assertCountEquals(expected); + ((TestMessage) u.getMessage()).assertCountEquals(expected); } } diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java index 4419768416..0968f0c468 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/AbstractHeadersExchangeTestBase.java @@ -27,6 +27,7 @@ import org.apache.qpid.framing.abstraction.MessagePublishInfo; import org.apache.qpid.server.queue.AMQMessage; import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.queue.MessageHandleFactory; +import org.apache.qpid.server.queue.QueueEntry; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.MemoryMessageStore; @@ -250,9 +251,9 @@ public class AbstractHeadersExchangeTestBase extends TestCase * @param deliverFirst * @throws AMQException */ - public void process(StoreContext context, AMQMessage msg, boolean deliverFirst) throws AMQException + public void process(StoreContext context, QueueEntry msg, boolean deliverFirst) throws AMQException { - messages.add(new HeadersExchangeTest.Message(msg)); + messages.add(new HeadersExchangeTest.Message(msg.getMessage())); } } @@ -267,8 +268,8 @@ public class AbstractHeadersExchangeTestBase extends TestCase private static TransactionalContext _txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, - new LinkedList(), - new HashSet()); + new LinkedList() + ); Message(String id, String... headers) throws AMQException { diff --git a/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java b/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java index b584c8c80b..3a3b7f92dd 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java +++ b/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java @@ -21,8 +21,7 @@ package org.apache.qpid.server.exchange; import org.apache.qpid.jms.Session; - -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; /** * MessagingTestConfigProperties defines a set of property names and default values for specifying a messaging topology, diff --git a/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java new file mode 100644 index 0000000000..9b2a3a6e28 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.failover; + +import junit.framework.TestCase; +import org.apache.qpid.AMQDisconnectedException; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQConnectionURL; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException; +import org.apache.qpid.url.URLSyntaxException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import java.util.concurrent.CountDownLatch; + +public class FailoverMethodTest extends TestCase implements ExceptionListener +{ + private CountDownLatch _failoverComplete = new CountDownLatch(1); + + public void setUp() throws AMQVMBrokerCreationException + { + TransportConnection.createVMBroker(1); + } + + public void tearDown() throws AMQVMBrokerCreationException + { + TransportConnection.killAllVMBrokers(); + } + + /** + * Test that the round robin method has the correct delays. + * The first connection to vm://:1 will work but the localhost connection should fail but the duration it takes + * to report the failure is what is being tested. + * + * @throws URLSyntaxException + * @throws InterruptedException + * @throws JMSException + */ + public void testFailoverRoundRobinDelay() throws URLSyntaxException, InterruptedException, JMSException + { + String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1;tcp://localhost:5670?connectdelay='2000',retries='3''"; + + AMQConnectionURL url = new AMQConnectionURL(connectionString); + + try + { + long start = System.currentTimeMillis(); + AMQConnection connection = new AMQConnection(url, null); + + connection.setExceptionListener(this); + + TransportConnection.killAllVMBrokers(); + + _failoverComplete.await(); + + long end = System.currentTimeMillis(); + + //Failover should take less that 10 seconds. + // This is calculated by vm://:1 two retries left after initial connection + // localhost get three retries so (6s) so 10s in total for connection dropping + assertTrue("Failover took less than 6 seconds:"+(end - start), (end - start) > 6000); + // The sleep method is not 100% accurate under windows so with 5 sleeps and a 10ms accuracy then there is + // the potential for the tests to finish in 500ms sooner than the predicted 10s. + + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + + public void testFailoverSingleDelay() throws URLSyntaxException, AMQVMBrokerCreationException, InterruptedException, JMSException + { + String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1?connectdelay='2000',retries='3''"; + + AMQConnectionURL url = new AMQConnectionURL(connectionString); + + try + { + long start = System.currentTimeMillis(); + AMQConnection connection = new AMQConnection(url, null); + + connection.setExceptionListener(this); + + TransportConnection.killAllVMBrokers(); + + _failoverComplete.await(); + + long end = System.currentTimeMillis(); + + //Failover should take less that 10 seconds. + // This is calculated by vm://:1 two retries left after initial connection + // so 4s in total for connection dropping + + assertTrue("Failover took less than 3.7 seconds", (end - start) > 3700); + // The sleep method is not 100% accurate under windows so with 3 sleeps and a 10ms accuracy then there is + // the potential for the tests to finish in 300ms sooner than the predicted 4s. + + } + catch (AMQException e) + { + fail(e.getMessage()); + } + } + + + public void onException(JMSException e) + { + if (e.getLinkedException() instanceof AMQDisconnectedException) + { + _failoverComplete.countDown(); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java b/java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java new file mode 100644 index 0000000000..0762a7a561 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/server/plugins/PluginTest.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.plugins; + +import java.util.Map; + +import org.apache.qpid.server.exchange.ExchangeType; + +import junit.framework.TestCase; + +public class PluginTest extends TestCase +{ + + private static final String TEST_EXCHANGE_CLASS = "org.apache.qpid.extras.exchanges.example.TestExchangeType"; + private static final String PLUGIN_DIRECTORY = System.getProperty("example.plugin.target"); + + public void testLoadExchanges() throws Exception + { + PluginManager manager = new PluginManager(PLUGIN_DIRECTORY); + Map> exchanges = manager.getExchanges(); + assertNotNull("No exchanges found in "+PLUGIN_DIRECTORY, exchanges); + assertEquals("Wrong number of exchanges found in "+PLUGIN_DIRECTORY, + 2, exchanges.size()); + assertNotNull("Wrong exchange found in "+PLUGIN_DIRECTORY, + exchanges.get(TEST_EXCHANGE_CLASS)); + } + + public void testNoExchanges() throws Exception + { + PluginManager manager = new PluginManager("/path/to/nowhere"); + Map> exchanges = manager.getExchanges(); + assertNull("Exchanges found", exchanges); + } + +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java b/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java index 66cd1cc7da..e4555e020e 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBeanTest.java @@ -21,6 +21,9 @@ package org.apache.qpid.server.protocol; import junit.framework.TestCase; + +import org.apache.log4j.Logger; + import org.apache.qpid.AMQException; import org.apache.qpid.codec.AMQCodecFactory; import org.apache.qpid.framing.AMQShortString; @@ -28,10 +31,9 @@ import org.apache.qpid.server.AMQChannel; import org.apache.qpid.server.queue.AMQQueue; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; -import org.apache.qpid.server.store.MemoryMessageStore; import org.apache.qpid.server.store.MessageStore; -import org.apache.qpid.server.txn.MemoryTransactionManager; -import org.apache.qpid.server.txn.TransactionManager; +import org.apache.qpid.server.store.SkeletonMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; import javax.management.JMException; @@ -41,8 +43,10 @@ import javax.management.JMException; */ public class AMQProtocolSessionMBeanTest extends TestCase { - private MessageStore _messageStore = new MemoryMessageStore(); - private TransactionManager _txm = new MemoryTransactionManager(); + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(AMQProtocolSessionMBeanTest.class); + + private MessageStore _messageStore = new SkeletonMessageStore(); private AMQMinaProtocolSession _protocolSession; private AMQChannel _channel; private AMQProtocolSessionMBean _mbean; @@ -57,7 +61,8 @@ public class AMQProtocolSessionMBeanTest extends TestCase new AMQShortString("test"), true, _protocolSession.getVirtualHost()); - AMQChannel channel = new AMQChannel(_protocolSession, 2, _txm, _messageStore, null); + AMQChannel channel = new AMQChannel(_protocolSession, 2, _messageStore); + channel.setDefaultQueue(queue); _protocolSession.addChannel(channel); channelCount = _mbean.channels().size(); @@ -68,7 +73,7 @@ public class AMQProtocolSessionMBeanTest extends TestCase assertTrue(_mbean.getMaximumNumberOfChannels() == 1000L); // check APIs - AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _txm, _messageStore, null); + AMQChannel channel3 = new AMQChannel(_protocolSession, 3, _messageStore); channel3.setLocalTransactional(); _protocolSession.addChannel(channel3); _mbean.rollbackTransactions(2); @@ -84,23 +89,23 @@ public class AMQProtocolSessionMBeanTest extends TestCase } catch (JMException ex) { - System.out.println("expected exception is thrown :" + ex.getMessage()); + log.debug("expected exception is thrown :" + ex.getMessage()); } // check if closing of session works - _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _txm, _messageStore, null)); + _protocolSession.addChannel(new AMQChannel(_protocolSession, 5, _messageStore)); _mbean.closeConnection(); try { channelCount = _mbean.channels().size(); assertTrue(channelCount == 0); // session is now closed so adding another channel should throw an exception - _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _txm, _messageStore, null)); + _protocolSession.addChannel(new AMQChannel(_protocolSession, 6, _messageStore)); fail(); } catch (AMQException ex) { - System.out.println("expected exception is thrown :" + ex.getMessage()); + log.debug("expected exception is thrown :" + ex.getMessage()); } } @@ -110,12 +115,11 @@ public class AMQProtocolSessionMBeanTest extends TestCase super.setUp(); IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); - _protocolSession = new AMQMinaProtocolSession(new MockIoSession(), - appRegistry.getVirtualHostRegistry(), - new AMQCodecFactory(true), - null); + _protocolSession = + new AMQMinaProtocolSession(new MockIoSession(), appRegistry.getVirtualHostRegistry(), new AMQCodecFactory(true), + null); _protocolSession.setVirtualHost(appRegistry.getVirtualHostRegistry().getVirtualHost("test")); - _channel = new AMQChannel(_protocolSession, 1, _txm, _messageStore, null); + _channel = new AMQChannel(_protocolSession, 1, _messageStore); _protocolSession.addChannel(_channel); _mbean = (AMQProtocolSessionMBean) _protocolSession.getManagedObject(); } diff --git a/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java b/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java index c1d7b0f598..62f5e0c6bf 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/protocol/MaxChannelsTest.java @@ -27,6 +27,8 @@ import org.apache.qpid.protocol.AMQConstant; import org.apache.qpid.server.AMQChannel; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; /** Test class to test MBean operations for AMQMinaProtocolSession. */ public class MaxChannelsTest extends TestCase @@ -55,7 +57,7 @@ public class MaxChannelsTest extends TestCase { for (long currentChannel = 0L; currentChannel < maxChannels; currentChannel++) { - _protocolSession.addChannel(new AMQChannel(_protocolSession, (int) currentChannel, null, null, null)); + _protocolSession.addChannel(new AMQChannel(_protocolSession, (int) currentChannel, null)); } } catch (AMQException e) diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java index 65ac12463f..2416442b10 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/AckTest.java @@ -34,7 +34,6 @@ import org.apache.qpid.server.ack.UnacknowledgedMessageMap; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.store.StoreContext; import org.apache.qpid.server.store.TestableMemoryMessageStore; -import org.apache.qpid.server.txn.MemoryTransactionManager; import org.apache.qpid.server.txn.NonTransactionalContext; import org.apache.qpid.server.txn.TransactionalContext; import org.apache.qpid.server.util.NullApplicationRegistry; @@ -56,8 +55,6 @@ public class AckTest extends TestCase private TestableMemoryMessageStore _messageStore; - private MemoryTransactionManager _txm; - private StoreContext _storeContext = new StoreContext(); private AMQChannel _channel; @@ -77,9 +74,8 @@ public class AckTest extends TestCase { super.setUp(); _messageStore = new TestableMemoryMessageStore(); - _txm = new MemoryTransactionManager(); _protocolSession = new MockProtocolSession(_messageStore); - _channel = new AMQChannel(_protocolSession, 5, _txm, _messageStore, null/*dont need exchange registry*/); + _channel = new AMQChannel(_protocolSession, 5, _messageStore); _protocolSession.addChannel(_channel); _subscriptionManager = new SubscriptionSet(); @@ -94,8 +90,8 @@ public class AckTest extends TestCase private void publishMessages(int count, boolean persistent) throws AMQException { TransactionalContext txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, - new LinkedList(), - new HashSet()); + new LinkedList() + ); MessageHandleFactory factory = new MessageHandleFactory(); for (int i = 1; i <= count; i++) { @@ -109,6 +105,11 @@ public class AckTest extends TestCase return new AMQShortString("someExchange"); } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -144,7 +145,7 @@ public class AckTest extends TestCase msg.incrementReference(); msg.routingComplete(_messageStore, _storeContext, factory); // we manually send the message to the subscription - _subscription.send(msg, _queue); + _subscription.send(new QueueEntry(_queue,msg), _queue); } } @@ -172,7 +173,7 @@ public class AckTest extends TestCase assertTrue(deliveryTag == i); i++; UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); } assertTrue(map.size() == msgCount); @@ -219,7 +220,7 @@ public class AckTest extends TestCase { assertTrue(deliveryTag == i); UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); // 5 is the delivery tag of the message that *should* be removed if (++i == 5) { @@ -248,7 +249,7 @@ public class AckTest extends TestCase { assertTrue(deliveryTag == i + 5); UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); ++i; } } @@ -272,7 +273,7 @@ public class AckTest extends TestCase { assertTrue(deliveryTag == i + 5); UnacknowledgedMessage unackedMsg = map.get(deliveryTag); - assertTrue(unackedMsg.queue == _queue); + assertTrue(unackedMsg.getQueue() == _queue); ++i; } } diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java b/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java index 6a70fea711..4f92cc94b7 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/ConcurrencyTestDisabled.java @@ -42,9 +42,9 @@ public class ConcurrencyTestDisabled extends MessageTestHelper private final List _subscribers = new ArrayList(); private final Set _active = new HashSet(); - private final List _messages = new ArrayList(); + 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 List _received = Collections.synchronizedList(new ArrayList()); private final Executor _executor = new OnCurrentThreadExecutor(); private final List _threads = new ArrayList(); @@ -159,7 +159,7 @@ public class ConcurrencyTestDisabled extends MessageTestHelper } } - private AMQMessage nextMessage() + private QueueEntry nextMessage() { synchronized (_messages) { @@ -191,7 +191,7 @@ public class ConcurrencyTestDisabled extends MessageTestHelper { void doRun() throws Throwable { - AMQMessage msg = nextMessage(); + QueueEntry msg = nextMessage(); if (msg != null) { _deliveryMgr.deliver(null, new AMQShortString(toString()), msg, false); diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java index dc5a6d3cf6..b33259cfba 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/DeliveryManagerTest.java @@ -40,7 +40,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper public void testStartInQueueingMode() throws AMQException { - AMQMessage[] messages = new AMQMessage[10]; + QueueEntry[] messages = new QueueEntry[10]; for (int i = 0; i < messages.length; i++) { messages[i] = message(); @@ -85,7 +85,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper public void testStartInDirectMode() throws AMQException { - AMQMessage[] messages = new AMQMessage[10]; + QueueEntry[] messages = new QueueEntry[10]; for (int i = 0; i < messages.length; i++) { messages[i] = message(); @@ -132,7 +132,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper { try { - AMQMessage msg = message(true); + QueueEntry msg = message(true); _mgr.deliver(_storeContext, DEFAULT_QUEUE_NAME, msg, false); msg.checkDeliveredToConsumer(); fail("expected exception did not occur"); @@ -154,7 +154,7 @@ abstract public class DeliveryManagerTest extends MessageTestHelper SubscriptionTestHelper s = new SubscriptionTestHelper("A"); _subscriptions.addSubscriber(s); s.setSuspended(true); - AMQMessage msg = message(true); + QueueEntry msg = message(true); _mgr.deliver(_storeContext, DEFAULT_QUEUE_NAME, msg, false); msg.checkDeliveredToConsumer(); fail("expected exception did not occur"); diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java b/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java index 0d4e3e748b..114c8cac32 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/MessageTestHelper.java @@ -36,7 +36,6 @@ import org.apache.qpid.AMQException; import junit.framework.TestCase; import java.util.LinkedList; -import java.util.HashSet; class MessageTestHelper extends TestCase { @@ -45,20 +44,20 @@ class MessageTestHelper extends TestCase private final StoreContext _storeContext = new StoreContext(); private final TransactionalContext _txnContext = new NonTransactionalContext(_messageStore, _storeContext, null, - new LinkedList(), - new HashSet()); + new LinkedList() + ); MessageTestHelper() throws Exception { ApplicationRegistry.initialise(new NullApplicationRegistry()); } - AMQMessage message() throws AMQException + QueueEntry message() throws AMQException { return message(false); } - AMQMessage message(final boolean immediate) throws AMQException + QueueEntry message(final boolean immediate) throws AMQException { MessagePublishInfo publish = new MessagePublishInfo() { @@ -68,6 +67,11 @@ class MessageTestHelper extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return immediate; @@ -84,8 +88,8 @@ class MessageTestHelper extends TestCase } }; - return new AMQMessage(_messageStore.getNewMessageId(), publish, _txnContext, - new ContentHeaderBody()); + return new QueueEntry(null,new AMQMessage(_messageStore.getNewMessageId(), publish, _txnContext, + new ContentHeaderBody())); } } diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java b/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java index 0ad6502755..cf986e7803 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/MockProtocolSession.java @@ -21,10 +21,7 @@ package org.apache.qpid.server.queue; import org.apache.qpid.AMQException; -import org.apache.qpid.framing.AMQDataBlock; -import org.apache.qpid.framing.FieldTable; -import org.apache.qpid.framing.AMQShortString; -import org.apache.qpid.framing.VersionSpecificRegistry; +import org.apache.qpid.framing.*; import org.apache.qpid.server.AMQChannel; import org.apache.qpid.server.output.ProtocolOutputConverter; import org.apache.qpid.server.output.ProtocolOutputConverterRegistry; @@ -188,16 +185,53 @@ public class MockProtocolSession implements AMQProtocolSession return null; //To change body of implemented methods use File | Settings | File Templates. } + public MethodRegistry getMethodRegistry() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void methodFrameReceived(int channelId, AMQMethodBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void contentBodyReceived(int channelId, ContentBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public MethodDispatcher getMethodDispatcher() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + public byte getProtocolMajorVersion() { - return 8; //To change body of implemented methods use File | Settings | File Templates. + return getProtocolVersion().getMajorVersion(); } public byte getProtocolMinorVersion() { - return 0; //To change body of implemented methods use File | Settings | File Templates. + return getProtocolVersion().getMinorVersion(); } + + public ProtocolVersion getProtocolVersion() + { + return ProtocolVersion.getLatestSupportedVersion(); //To change body of implemented methods use File | Settings | File Templates. + } + + public VersionSpecificRegistry getRegistry() { return null; //To change body of implemented methods use File | Settings | File Templates. diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java b/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java index fe947ef3bc..1fa70a08d4 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java @@ -28,13 +28,13 @@ import java.util.Queue; public class SubscriptionTestHelper implements Subscription { - private final List messages; + private final List messages; private final Object key; private boolean isSuspended; public SubscriptionTestHelper(Object key) { - this(key, new ArrayList()); + this(key, new ArrayList()); } public SubscriptionTestHelper(final Object key, final boolean isSuspended) @@ -43,18 +43,18 @@ public class SubscriptionTestHelper implements Subscription setSuspended(isSuspended); } - SubscriptionTestHelper(Object key, List messages) + SubscriptionTestHelper(Object key, List messages) { this.key = key; this.messages = messages; } - List getMessages() + List getMessages() { return messages; } - public void send(AMQMessage msg, AMQQueue queue) + public void send(QueueEntry msg, AMQQueue queue) { messages.add(msg); } @@ -69,12 +69,12 @@ public class SubscriptionTestHelper implements Subscription return isSuspended; } - public boolean wouldSuspend(AMQMessage msg) + public boolean wouldSuspend(QueueEntry msg) { return isSuspended; } - public void addToResendQueue(AMQMessage msg) + public void addToResendQueue(QueueEntry msg) { //no-op } @@ -88,6 +88,11 @@ public class SubscriptionTestHelper implements Subscription { return null; } + + public void start() + { + //no-op + } public void queueDeleted(AMQQueue queue) { @@ -98,36 +103,31 @@ public class SubscriptionTestHelper implements Subscription return false; } - public boolean hasInterest(AMQMessage msg) + public boolean hasInterest(QueueEntry msg) { return true; } - public Queue getPreDeliveryQueue() + public Queue getPreDeliveryQueue() { return null; } - public Queue getResendQueue() + public Queue getResendQueue() { return null; } - public Queue getNextQueue(Queue messages) + public Queue getNextQueue(Queue messages) { return messages; } - public void enqueueForPreDelivery(AMQMessage msg, boolean deliverFirst) + public void enqueueForPreDelivery(QueueEntry msg, boolean deliverFirst) { //no-op } - public boolean isAutoClose() - { - return false; - } - public void close() { //no-op @@ -157,4 +157,5 @@ public class SubscriptionTestHelper implements Subscription { return key.toString(); } + } diff --git a/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java b/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java index 06956ba52f..a803bf7da5 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java @@ -25,7 +25,11 @@ import junit.framework.TestCase; import junit.framework.Assert; import org.apache.qpid.client.transport.TransportConnection; import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQDestination; import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.qpid.AMQException; import org.apache.log4j.Logger; import javax.jms.JMSException; @@ -38,6 +42,7 @@ import javax.jms.Connection; import javax.jms.Message; import javax.naming.spi.InitialContextFactory; import javax.naming.Context; +import javax.naming.NamingException; import java.util.Hashtable; @@ -53,21 +58,37 @@ public class TimeToLiveTest extends TestCase private final long TIME_TO_LIVE = 1000L; - Context _context; - - private Connection _clientConnection, _producerConnection; - - private MessageConsumer _consumer; - MessageProducer _producer; - Session _clientSession, _producerSession; private static final int MSG_COUNT = 50; + private static final long SERVER_TTL_TIMEOUT = 60000L; protected void setUp() throws Exception { - if (BROKER.startsWith("vm://")) + super.setUp(); + + if (usingInVMBroker()) { TransportConnection.createVMBroker(1); } + + + } + + private boolean usingInVMBroker() + { + return BROKER.startsWith("vm://"); + } + + protected void tearDown() throws Exception + { + if (usingInVMBroker()) + { + TransportConnection.killAllVMBrokers(); + } + super.tearDown(); + } + + public void testPassiveTTL() throws JMSException, NamingException + { InitialContextFactory factory = new PropertiesFileInitialContextFactory(); Hashtable env = new Hashtable(); @@ -75,56 +96,40 @@ public class TimeToLiveTest extends TestCase env.put("connectionfactory.connection", "amqp://guest:guest@TTL_TEST_ID" + VHOST + "?brokerlist='" + BROKER + "'"); env.put("queue.queue", QUEUE); - _context = factory.getInitialContext(env); + Context context = factory.getInitialContext(env); - Queue queue = (Queue) _context.lookup("queue"); + Queue queue = (Queue) context.lookup("queue"); //Create Client 1 - _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + Connection clientConnection = ((ConnectionFactory) context.lookup("connection")).createConnection(); - _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - _consumer = _clientSession.createConsumer(queue); + MessageConsumer consumer = clientSession.createConsumer(queue); //Create Producer - _producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + Connection producerConnection = ((ConnectionFactory) context.lookup("connection")).createConnection(); - _producerConnection.start(); + producerConnection.start(); - _producerSession = _producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - _producer = _producerSession.createProducer(queue); - } + MessageProducer producer = producerSession.createProducer(queue); - protected void tearDown() throws Exception - { - _clientConnection.close(); - - _producerConnection.close(); - super.tearDown(); - - if (BROKER.startsWith("vm://")) - { - TransportConnection.killAllVMBrokers(); - } - } - - public void test() throws JMSException - { //Set TTL int msg = 0; - _producer.send(nextMessage(String.valueOf(msg), true)); + producer.send(nextMessage(String.valueOf(msg), true, producerSession, producer)); - _producer.setTimeToLive(TIME_TO_LIVE); + producer.setTimeToLive(TIME_TO_LIVE); for (; msg < MSG_COUNT - 2; msg++) { - _producer.send(nextMessage(String.valueOf(msg), false)); + producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer)); } //Reset TTL - _producer.setTimeToLive(0L); - _producer.send(nextMessage(String.valueOf(msg), false)); + producer.setTimeToLive(0L); + producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer)); try { @@ -136,31 +141,71 @@ public class TimeToLiveTest extends TestCase } - _clientConnection.start(); + clientConnection.start(); //Receive Message 0 - Message received = _consumer.receive(100); + Message received = consumer.receive(1000); Assert.assertNotNull("First message not received", received); Assert.assertTrue("First message doesn't have first set.", received.getBooleanProperty("first")); Assert.assertEquals("First message has incorrect TTL.", 0L, received.getLongProperty("TTL")); - received = _consumer.receive(100); + received = consumer.receive(1000); Assert.assertNotNull("Final message not received", received); Assert.assertFalse("Final message has first set.", received.getBooleanProperty("first")); Assert.assertEquals("Final message has incorrect TTL.", 0L, received.getLongProperty("TTL")); - received = _consumer.receive(100); + received = consumer.receive(1000); Assert.assertNull("More messages received", received); + + clientConnection.close(); + + producerConnection.close(); } - private Message nextMessage(String msg, boolean first) throws JMSException + private Message nextMessage(String msg, boolean first, Session producerSession, MessageProducer producer) throws JMSException { - Message send = _producerSession.createTextMessage("Message " + msg); + Message send = producerSession.createTextMessage("Message " + msg); send.setBooleanProperty("first", first); - send.setLongProperty("TTL", _producer.getTimeToLive()); + send.setLongProperty("TTL", producer.getTimeToLive()); return send; } + /** + * Tests the expired messages get actively deleted even on queues which have no consumers + */ + public void testActiveTTL() throws URLSyntaxException, AMQException, JMSException, InterruptedException + { + Connection producerConnection = new AMQConnection(BROKER,"guest","guest","activeTTLtest","test"); + AMQSession producerSession = (AMQSession) producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + Queue queue = producerSession.createTemporaryQueue(); + producerSession.declareAndBind((AMQDestination) queue); + MessageProducer producer = producerSession.createProducer(queue); + producer.setTimeToLive(1000L); + + // send Messages + for(int i = 0; i < MSG_COUNT; i++) + { + producer.send(producerSession.createTextMessage("Message: "+i)); + } + long failureTime = System.currentTimeMillis() + 2*SERVER_TTL_TIMEOUT; + + // check Queue depth for up to TIMEOUT seconds + long messageCount; + + do + { + Thread.sleep(100); + messageCount = producerSession.getQueueDepth((AMQDestination) queue); + } + while(messageCount > 0L && System.currentTimeMillis() < failureTime); + + assertEquals("Messages not automatically expired: ", 0L, messageCount); + + producer.close(); + producerSession.close(); + producerConnection.close(); + } + } diff --git a/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java b/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java new file mode 100644 index 0000000000..9ba0f6024c --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java @@ -0,0 +1,607 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ + +package org.apache.qpid.server.security.acl; + +import junit.framework.TestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.client.*; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.AMQException; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.url.URLSyntaxException; + +import javax.jms.*; +import javax.jms.IllegalStateException; +import java.io.File; + + +public class SimpleACLTest extends TestCase implements ConnectionListener +{ + private String BROKER = "vm://:1";//"tcp://localhost:5672"; + + public void setUp() throws Exception + { + // Initialise ACLs. + final String QpidExampleHome = System.getProperty("QPID_EXAMPLE_HOME"); + final File defaultaclConfigFile = new File(QpidExampleHome, "etc/acl.config.xml"); + + if (!defaultaclConfigFile.exists()) + { + System.err.println("Configuration file not found:" + defaultaclConfigFile); + fail("Configuration file not found:" + defaultaclConfigFile); + } + + if (System.getProperty("QPID_HOME") == null) + { + fail("QPID_HOME not set"); + } + + ConfigurationFileApplicationRegistry config = new ConfigurationFileApplicationRegistry(defaultaclConfigFile); + + ApplicationRegistry.initialise(config, 1); + + TransportConnection.createVMBroker(1); + } + + public void tearDown() + { + ApplicationRegistry.remove(1); + TransportConnection.killAllVMBrokers(); + } + + public String createConnectionString(String username, String password, String broker) + { + + return "amqp://" + username + ":" + password + "@clientid/test?brokerlist='" + broker + "'"; + } + + public void testAccessAuthorized() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(true, Session.SESSION_TRANSACTED); + + conn.start(); + + //Do something to show connection is active. + sesh.rollback(); + + conn.close(); + } + catch (Exception e) + { + fail("Connection was not created due to:" + e.getMessage()); + } + } + + public void testAccessNoRights() throws URLSyntaxException, JMSException + { + try + { + Connection conn = new AMQConnection(createConnectionString("guest", "guest", BROKER)); + + //Attempt to do do things to test connection. + Session sesh = conn.createSession(true, Session.SESSION_TRANSACTED); + conn.start(); + sesh.rollback(); + + conn.close(); + fail("Connection was created."); + } + catch (AMQException amqe) + { + if (amqe.getCause().getClass() == Exception.class) + { + System.err.println("QPID-594 : WARNING RACE CONDITION. Unable to determine cause of Connection Failure."); + return; + } + assertEquals("Linked Exception Incorrect", JMSException.class, amqe.getCause().getClass()); + Exception linked = ((JMSException) amqe.getCause()).getLinkedException(); + assertEquals("Exception was wrong type", AMQAuthenticationException.class, linked.getClass()); + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) linked).getErrorCode().getCode()); + } + } + + public void testClientConsumeFromTempQueueValid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createTemporaryQueue()); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testClientConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + //Prevent Failover + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createQueue("IllegalQueue")); + fail("Test failed as consumer was created."); + //conn will be automatically closed + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + + assertNotNull("There was no liked exception", cause); + assertEquals("Wrong linked exception type", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code received", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testClientCreateTemporaryQueue() throws JMSException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create Temporary Queue - can't use the createTempQueue as QueueName is null. + ((AMQSession) sesh).createQueue(new AMQShortString("doesnt_matter_as_autodelete_means_tmp"), + true, false, false); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testClientCreateNamedQueue() throws JMSException, URLSyntaxException, AMQException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create a Named Queue + ((AMQSession) sesh).createQueue(new AMQShortString("IllegalQueue"), false, false, false); + + fail("Test failed as Queue creation succeded."); + //conn will be automatically closed + } + catch (AMQAuthenticationException amqe) + { + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) amqe).getErrorCode().getCode()); + } + } + + public void testClientPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(true, Session.SESSION_TRANSACTED); + + conn.start(); + + MessageProducer sender = sesh.createProducer(sesh.createQueue("example.RequestQueue")); + + sender.send(sesh.createTextMessage("test")); + + //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker. + sesh.commit(); + + conn.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testClientPublishValidQueueSuccess() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession) sesh).createProducer(null); + + Queue queue = sesh.createQueue("example.RequestQueue"); + + // Send a message that we will wait to be sent, this should give the broker time to process the msg + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, sesh.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + conn.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testClientPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession) session).createProducer(null); + + Queue queue = session.createQueue("Invalid"); + + // Send a message that we will wait to be sent, this should give the broker time to close the connection + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + // Test the connection with a valid consumer + // This may fail as the session may be closed before the queue or the consumer created. + session.createConsumer(session.createTemporaryQueue()).close(); + + //Connection should now be closed and will throw the exception caused by the above send + conn.close(); + + fail("Close is not expected to succeed."); + } + catch (IllegalStateException ise) + { + System.err.println("QPID-826 : WARNING : Unable to determine cause of failure due to closure as we don't " + + "record it for reporting after connection closed asynchronously"); + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + assertEquals("Incorrect exception", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testServerConsumeFromNamedQueueValid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createQueue("example.RequestQueue")); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createQueue("Invalid")); + + fail("Test failed as consumer was created."); + //conn will be automatically closed + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + + assertNotNull("There was no liked exception", cause); + assertEquals("Wrong linked exception type", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code received", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testServerConsumeFromTemporaryQueue() throws AMQException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + //Prevent Failover + ((AMQConnection) conn).setConnectionListener(this); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + sesh.createConsumer(sesh.createTemporaryQueue()); + fail("Test failed as consumer was created."); + //conn will be automatically closed + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + + assertNotNull("There was no liked exception", cause); + assertEquals("Wrong linked exception type", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code received", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + public void testServerCreateNamedQueueValid() throws JMSException, URLSyntaxException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create Temporary Queue + ((AMQSession) sesh).createQueue(new AMQShortString("example.RequestQueue"), false, false, false); + + conn.close(); + } + catch (Exception e) + { + fail("Test failed due to:" + e.getMessage()); + } + } + + public void testServerCreateNamedQueueInvalid() throws JMSException, URLSyntaxException, AMQException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + //Create a Named Queue + ((AMQSession) sesh).createQueue(new AMQShortString("IllegalQueue"), false, false, false); + + fail("Test failed as creation succeded."); + //conn will be automatically closed + } + catch (AMQAuthenticationException amqe) + { + assertEquals("Incorrect error code thrown", 403, amqe.getErrorCode().getCode()); + } + } + + public void testServerCreateTemporyQueueInvalid() throws JMSException, URLSyntaxException, AMQException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + Session sesh = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + ((AMQSession) sesh).createQueue(new AMQShortString("again_ensure_auto_delete_queue_for_temporary"), + true, false, false); + + fail("Test failed as creation succeded."); + //conn will be automatically closed + } + catch (AMQAuthenticationException amqe) + { + assertEquals("Incorrect error code thrown", 403, amqe.getErrorCode().getCode()); + } + } + + /** + * This test uses both the cilent and sender to validate that the Server is able to publish to a temporary queue. + * The reason the client must be in volved is that the Serve is unable to create its own Temporary Queues. + * + * @throws AMQException + * @throws URLSyntaxException + * @throws JMSException + */ + public void testServerPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, JMSException + { + //Set up the Server + Connection serverConnection = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + ((AMQConnection) serverConnection).setConnectionListener(this); + + Session serverSession = serverConnection.createSession(true, Session.SESSION_TRANSACTED); + + Queue requestQueue = serverSession.createQueue("example.RequestQueue"); + + MessageConsumer server = serverSession.createConsumer(requestQueue); + + serverConnection.start(); + + //Set up the consumer + Connection clientConnection = new AMQConnection(createConnectionString("client", "guest", BROKER)); + + //Send a test mesage + Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + Queue responseQueue = clientSession.createTemporaryQueue(); + + MessageConsumer clientResponse = clientSession.createConsumer(responseQueue); + + clientConnection.start(); + + Message request = clientSession.createTextMessage("Request"); + + assertNotNull("Response Queue is null", responseQueue); + + request.setJMSReplyTo(responseQueue); + + clientSession.createProducer(requestQueue).send(request); + + try + { + Message msg = null; + + msg = server.receive(2000); + + while (msg != null && !((TextMessage) msg).getText().equals("Request")) + { + msg = server.receive(2000); + } + + assertNotNull("Message not received", msg); + + assertNotNull("Reply-To is Null", msg.getJMSReplyTo()); + + MessageProducer sender = serverSession.createProducer(msg.getJMSReplyTo()); + + sender.send(serverSession.createTextMessage("Response")); + + //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker. + serverSession.commit(); + + serverConnection.close(); + + //Ensure Response is received. + Message clientResponseMsg = clientResponse.receive(2000); + assertNotNull("Client did not receive response message,", clientResponseMsg); + assertEquals("Incorrect message received", "Response", ((TextMessage) clientResponseMsg).getText()); + + clientConnection.close(); + } + catch (Exception e) + { + fail("Test publish failed:" + e); + } + } + + public void testServerPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException + { + try + { + Connection conn = new AMQConnection(createConnectionString("server", "guest", BROKER)); + + ((AMQConnection) conn).setConnectionListener(this); + + Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); + + conn.start(); + + MessageProducer sender = ((AMQSession) session).createProducer(null); + + Queue queue = session.createQueue("Invalid"); + + // Send a message that we will wait to be sent, this should give the broker time to close the connection + // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not + // queue existence. + ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"), + DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true); + + // Test the connection with a valid consumer + // This may not work as the session may be closed before the queue or consumer creation can occur. + // The correct JMSexception with linked error will only occur when the close method is recevied whilst in + // the failover safe block + session.createConsumer(session.createQueue("example.RequestQueue")).close(); + + //Connection should now be closed and will throw the exception caused by the above send + conn.close(); + + fail("Close is not expected to succeed."); + } + catch (IllegalStateException ise) + { + System.err.println("QPID-826 : WARNING : Unable to determine cause of failure due to closure as we don't " + + "record it for reporting after connection closed asynchronously"); + } + catch (JMSException e) + { + Throwable cause = e.getLinkedException(); + assertEquals("Incorrect exception", AMQAuthenticationException.class, cause.getClass()); + assertEquals("Incorrect error code thrown", 403, ((AMQAuthenticationException) cause).getErrorCode().getCode()); + } + } + + // Connection Listener Interface - Used here to block failover + + public void bytesSent(long count) + { + } + + public void bytesReceived(long count) + { + } + + public boolean preFailover(boolean redirect) + { + //Prevent failover. + return false; + } + + public boolean preResubscribe() + { + return false; + } + + public void failoverComplete() + { + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java index ab6d9742e4..c7984d5d33 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java +++ b/java/systests/src/main/java/org/apache/qpid/server/store/TestReferenceCounting.java @@ -23,7 +23,6 @@ package org.apache.qpid.server.store; import junit.framework.TestCase; import org.apache.qpid.AMQException; import org.apache.qpid.framing.BasicContentHeaderProperties; -import org.apache.qpid.framing.BasicPublishBody; import org.apache.qpid.framing.ContentHeaderBody; import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.framing.abstraction.MessagePublishInfo; @@ -61,6 +60,11 @@ public class TestReferenceCounting extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -78,7 +82,7 @@ public class TestReferenceCounting extends TestCase }; AMQMessage message = new AMQMessage(_store.getNewMessageId(), info, - new NonTransactionalContext(_store, _storeContext, null, null, null), + new NonTransactionalContext(_store, _storeContext, null, null), createPersistentContentHeader()); message = message.takeReference(); @@ -109,6 +113,11 @@ public class TestReferenceCounting extends TestCase return null; } + public void setExchange(AMQShortString exchange) + { + //To change body of implemented methods use File | Settings | File Templates. + } + public boolean isImmediate() { return false; @@ -127,7 +136,7 @@ public class TestReferenceCounting extends TestCase AMQMessage message = new AMQMessage(_store.getNewMessageId(), info, - new NonTransactionalContext(_store, _storeContext, null, null, null), + new NonTransactionalContext(_store, _storeContext, null, null), createPersistentContentHeader()); message = message.takeReference(); diff --git a/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java b/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java index 58ea392306..b34f28b1a8 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java +++ b/java/systests/src/main/java/org/apache/qpid/server/txn/TxnBufferTest.java @@ -27,6 +27,7 @@ import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.store.StoreContext; import java.util.LinkedList; +import java.util.NoSuchElementException; public class TxnBufferTest extends TestCase { @@ -78,7 +79,15 @@ public class TxnBufferTest extends TestCase buffer.enlist(new FailedPrepare()); buffer.enlist(new MockOp()); - buffer.commit(null); + try + { + buffer.commit(null); + } + catch (NoSuchElementException e) + { + + } + validateOps(); store.validate(); } diff --git a/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java b/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java index bd7ed60d1d..83b4665be6 100644 --- a/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java +++ b/java/systests/src/main/java/org/apache/qpid/server/util/TestApplicationRegistry.java @@ -23,6 +23,7 @@ package org.apache.qpid.server.util; import org.apache.qpid.server.exchange.ExchangeFactory; import org.apache.qpid.server.exchange.ExchangeRegistry; import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; import org.apache.qpid.server.queue.QueueRegistry; import org.apache.qpid.server.registry.ApplicationRegistry; import org.apache.qpid.server.registry.IApplicationRegistry; @@ -30,8 +31,8 @@ import org.apache.qpid.server.security.auth.manager.AuthenticationManager; import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; import org.apache.qpid.server.security.auth.database.PropertiesPrincipalDatabaseManager; -import org.apache.qpid.server.security.access.AccessManager; -import org.apache.qpid.server.security.access.AllowAll; +import org.apache.qpid.server.security.access.ACLPlugin; +import org.apache.qpid.server.security.access.plugins.AllowAll; import org.apache.qpid.server.store.MessageStore; import org.apache.qpid.server.store.TestableMemoryMessageStore; import org.apache.qpid.server.virtualhost.VirtualHost; @@ -53,7 +54,7 @@ public class TestApplicationRegistry extends ApplicationRegistry private ManagedObjectRegistry _managedObjectRegistry; - private AccessManager _accessManager; + private ACLPlugin _accessManager; private PrincipalDatabaseManager _databaseManager; @@ -136,14 +137,25 @@ public class TestApplicationRegistry extends ApplicationRegistry return null; //To change body of implemented methods use File | Settings | File Templates. } - public AccessManager getAccessManager() + public ACLPlugin getAccessManager() { return _accessManager; } + public void setAccessManager(ACLPlugin newManager) + { + _accessManager = newManager; + } + public MessageStore getMessageStore() { return _messageStore; } + + public PluginManager getPluginManager() + { + return null; + } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java new file mode 100644 index 0000000000..4dd957c121 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/FailoverBaseCase.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test; + +import org.apache.qpid.test.VMTestCase; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.naming.spi.InitialContextFactory; +import javax.naming.NamingException; +import java.util.Hashtable; +import java.util.Map; + +public class FailoverBaseCase extends VMTestCase +{ + private boolean failedOver = true; + + public void setUp() throws Exception + { + // Make Broker 2 the first one so we can kill it and allow VMTestCase to clean up vm://:1 + _brokerlist = "vm://:2;vm://:1"; + _clientID = this.getClass().getName(); + _virtualhost = "/test"; + + _connections.put("connection1", "amqp://guest:guest@" + _clientID + _virtualhost + "?brokerlist='vm://:1'"); + _connections.put("connection2", "amqp://guest:guest@" + _clientID + _virtualhost + "?brokerlist='vm://:2'"); + + try + { + TransportConnection.createVMBroker(2); + } + catch (Exception e) + { + fail("Unable to create broker: " + e); + } + + super.setUp(); + } + + public void tearDown() throws Exception + { + if (!failedOver) + { + TransportConnection.killVMBroker(2); + ApplicationRegistry.remove(2); + } + super.tearDown(); + } + + + public void failBroker() + { + failedOver = true; + TransportConnection.killVMBroker(2); + ApplicationRegistry.remove(2); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java b/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java index 4d8ddeaddd..dcdfb11618 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/VMTestCase.java @@ -19,24 +19,28 @@ */ package org.apache.qpid.test; -import junit.extensions.TestSetup; -import junit.framework.Test; import junit.framework.TestCase; - +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.framing.AMQShortString; import org.apache.qpid.jndi.PropertiesFileInitialContextFactory; import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.AMQException; -import javax.naming.Context; -import javax.naming.spi.InitialContextFactory; -import javax.jms.Queue; +import javax.jms.Connection; import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Queue; import javax.jms.Session; -import javax.jms.Connection; -import javax.jms.MessageProducer; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.spi.InitialContextFactory; +import java.util.HashMap; +import java.util.HashSet; import java.util.Hashtable; -import java.util.List; import java.util.LinkedList; +import java.util.Iterator; import java.util.Map; import java.util.HashMap; @@ -85,22 +89,22 @@ public class VMTestCase extends TestCase _brokerlist = "vm://:1"; } - env.put("connectionfactory.connection", "amqp://guest:guest@" + - _clientID + _virtualhost + "?brokerlist='" + _brokerlist + "'"); + env.put("connectionfactory.connection", "amqp://guest:guest@" + _clientID + _virtualhost + "?brokerlist='" + + _brokerlist + "'"); for (Map.Entry c : _connections.entrySet()) { env.put("connectionfactory." + c.getKey(), c.getValue()); } - env.put("queue.queue", "queue"); + _queues.put("queue", "queue"); for (Map.Entry q : _queues.entrySet()) { env.put("queue." + q.getKey(), q.getValue()); } - env.put("topic.topic", "topic"); + _topics.put("topic", "topic"); for (Map.Entry t : _topics.entrySet()) { @@ -112,14 +116,50 @@ public class VMTestCase extends TestCase protected void tearDown() throws Exception { + //Disabled +// checkQueuesClean(); + TransportConnection.killVMBroker(1); ApplicationRegistry.remove(1); super.tearDown(); } - public void testDummyinVMTestCase() + private void checkQueuesClean() throws NamingException, JMSException + { + Connection connection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + connection.start(); + + Iterator queueNames = new HashSet(_queues.values()).iterator(); + + assertTrue("QueueNames doesn't have next", queueNames.hasNext()); + + while (queueNames.hasNext()) + { + Queue queue = session.createQueue(queueNames.next()); + + //Validate that the queue are reporting empty. + long queueDepth = 0; + try + { + queueDepth = ((AMQSession) session).getQueueDepth((AMQDestination) queue); + } + catch (AMQException e) + { + //ignore + } + + assertEquals("Session reports Queue depth not as expected", 0, queueDepth); + } + + connection.close(); + } + + public int getMessageCount(String queueName) { - // keep maven happy + return ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(_virtualhost.substring(1)) + .getQueueRegistry().getQueue(new AMQShortString(queueName)).getMessageCount(); } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java new file mode 100644 index 0000000000..2b02f1cbbf --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java @@ -0,0 +1,111 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.test.client; + +import org.apache.log4j.Logger; +import org.apache.qpid.test.VMTestCase; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueBrowser; +import javax.jms.Session; +import javax.jms.JMSException; +import javax.naming.NamingException; +import java.util.Enumeration; +public class CancelTest extends VMTestCase +{ + private static final Logger _logger = Logger.getLogger(CancelTest.class); + + private Connection _clientConnection; + private Session _clientSession; + private Queue _queue; + + public void setUp() throws Exception + { + + super.setUp(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } + + /** + * Simply + */ + public void test() throws JMSException, NamingException + { + Connection producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + producerConnection.start(); + + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + MessageProducer producer = producerSession.createProducer(_queue); + producer.send(producerSession.createTextMessage()); + producerConnection.close(); + + + QueueBrowser browser = _clientSession.createBrowser(_queue); + Enumeration e = browser.getEnumeration(); + + + while (e.hasMoreElements()) + { + e.nextElement(); + } + + browser.close(); + + MessageConsumer consumer = _clientSession.createConsumer(_queue); + consumer.receive(); + consumer.close(); + } + + public void loop() + { + try + { + int run = 0; + while (true) + { + System.err.println(run++); + test(); + } + } + catch (Exception e) + { + _logger.error(e, e); + } + } + +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java new file mode 100644 index 0000000000..463946e14a --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java @@ -0,0 +1,160 @@ +package org.apache.qpid.test.client; + +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.test.VMTestCase; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageListener; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.NamingException; +import java.util.concurrent.CountDownLatch;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +public class DupsOkTest extends VMTestCase +{ + + private Queue _queue; + private static final int MSG_COUNT = 9999; + private CountDownLatch _awaitCompletion = new CountDownLatch(1); + + public void setUp() throws Exception + { + super.setUp(); + + _queue = (Queue) _context.lookup("queue"); + + //CreateQueue + ((ConnectionFactory) _context.lookup("connection")).createConnection().createSession(false, Session.AUTO_ACKNOWLEDGE).createConsumer(_queue).close(); + + //Create Producer put some messages on the queue + Connection producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + producerConnection.start(); + + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + MessageProducer producer = producerSession.createProducer(_queue); + + for (int count = 1; count <= MSG_COUNT; count++) + { + Message msg = producerSession.createTextMessage("Message " + count); + msg.setIntProperty("count", count); + producer.send(msg); + } + + producerConnection.close(); + } + + public void testDupsOK() throws NamingException, JMSException, InterruptedException, AMQException + { + //Create Client + Connection clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + clientConnection.start(); + + final Session clientSession = clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE); + + MessageConsumer consumer = clientSession.createConsumer(_queue); + + consumer.setMessageListener(new MessageListener() + { + int _msgCount = 0; + + public void onMessage(Message message) + { + _msgCount++; + if (message == null) + { + fail("Should not get null messages"); + } + + if (message instanceof TextMessage) + { + try + { + /*if (message.getIntProperty("count") == 5000) + { + assertEquals("The queue should have 4999 msgs left", 4999, getMessageCount(_queue.getQueueName())); + }*/ + + if (message.getIntProperty("count") == MSG_COUNT) + { + try + { + long remainingMessages = ((AMQSession) clientSession).getQueueDepth((AMQDestination) _queue); + if(remainingMessages != 0) + { + + assertEquals("The queue should have 0 msgs left, seen " + _msgCount + " messages.", 0, getMessageCount(_queue.getQueueName())); + } + } + catch (AMQException e) + { + assertNull("Got AMQException", e); + } + finally + { + //This is the last message so release test. + _awaitCompletion.countDown(); + } + } + + } + catch (JMSException e) + { + fail("Unable to get int property 'count'"); + } + } + else + { + fail(""); + } + } + }); + + try + { + _awaitCompletion.await(); + } + catch (InterruptedException e) + { + fail("Unable to wait for test completion"); + throw e; + } + +// consumer.close(); + + assertEquals("The queue should have 0 msgs left", 0, ((AMQSession) clientSession).getQueueDepth((AMQDestination) _queue)); + clientConnection.close(); + + } + + +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java new file mode 100644 index 0000000000..9beaa9844a --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java @@ -0,0 +1,510 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.test.client; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.test.FailoverBaseCase; + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.QueueBrowser; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.NamingException; +import java.util.Enumeration; +import java.util.Random; + +public class QueueBrowserAutoAckTest extends FailoverBaseCase +{ + private static final Logger _logger = Logger.getLogger(QueueBrowserAutoAckTest.class); + + protected Connection _clientConnection; + protected Session _clientSession; + protected Queue _queue; + protected static final String MESSAGE_ID_PROPERTY = "MessageIDProperty"; + + public void setUp() throws Exception + { + super.setUp(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + //Ensure there are no messages on the queue to start with. + checkQueueDepth(0); + } + + public void tearDown() throws Exception + { + if (_clientConnection != null) + { + _clientConnection.close(); + } + + super.tearDown(); + } + + protected void sendMessages(int num) throws JMSException + { + Connection producerConnection = null; + try + { + producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + } + catch (NamingException e) + { + fail("Unable to lookup connection in JNDI."); + } + + sendMessages(producerConnection, num); + } + + protected void sendMessages(String connection, int num) throws JMSException + { + Connection producerConnection = null; + try + { + producerConnection = ((ConnectionFactory) _context.lookup(connection)).createConnection(); + } + catch (NamingException e) + { + fail("Unable to lookup connection in JNDI."); + } + sendMessages(producerConnection, num); + } + + + protected void sendMessages(Connection producerConnection, int num) throws JMSException + { + producerConnection.start(); + + Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + //Ensure _queue is created + producerSession.createConsumer(_queue).close(); + + MessageProducer producer = producerSession.createProducer(_queue); + + for (int messsageID = 0; messsageID < num; messsageID++) + { + TextMessage textMsg = producerSession.createTextMessage("Message " + messsageID); + textMsg.setIntProperty(MESSAGE_ID_PROPERTY, messsageID); + producer.send(textMsg); + } + + producerConnection.close(); + } + + protected void checkQueueDepth(int depth) throws JMSException + { + + // create QueueBrowser + _logger.info("Creating Queue Browser"); + + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + // check for messages + if (_logger.isDebugEnabled()) + { + _logger.debug("Checking for " + depth + " messages with QueueBrowser"); + } + + //Check what the session believes the queue count to be. + long queueDepth = 0; + + try + { + queueDepth = ((AMQSession) _clientSession).getQueueDepth((AMQDestination) _queue); + } + catch (AMQException e) + { + } + + assertEquals("Session reports Queue depth not as expected", depth, queueDepth); + + // Browse the queue to get a second opinion + int msgCount = 0; + Enumeration msgs = queueBrowser.getEnumeration(); + + while (msgs.hasMoreElements()) + { + msgs.nextElement(); + msgCount++; + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Found " + msgCount + " messages total in browser"); + } + + // check to see if all messages found + assertEquals("Browser did not find all messages", depth, msgCount); + + //Close browser + queueBrowser.close(); + } + + protected void closeBrowserBeforeAfterGetNext(int messageCount) throws JMSException + { + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + Enumeration msgs = queueBrowser.getEnumeration(); + + int msgCount = 0; + + while (msgs.hasMoreElements() && msgCount < messageCount) + { + msgs.nextElement(); + msgCount++; + } + + try + { + queueBrowser.close(); + } + catch (JMSException e) + { + fail("Close should happen without error:" + e.getMessage()); + } + } + + + protected void checkMultipleGetEnum(int sentMessages, int browserCount) throws JMSException + { + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + for (int count = 0; count < browserCount; count++) + { + Enumeration msgs = queueBrowser.getEnumeration(); + + int msgCount = 0; + + while (msgs.hasMoreElements()) + { + msgs.nextElement(); + msgCount++; + } + assertEquals(msgCount, sentMessages); + } + + try + { + queueBrowser.close(); + } + catch (JMSException e) + { + fail("Close should happen without error:" + e.getMessage()); + } + } + + protected void checkOverlappingMultipleGetEnum(int browserCount, int expectedMessages) throws JMSException + { + checkOverlappingMultipleGetEnum(browserCount, expectedMessages, null); + } + + protected void checkOverlappingMultipleGetEnum(int browserCount, int expectedMessages, String selector) throws JMSException + { + QueueBrowser queueBrowser = selector == null ? + _clientSession.createBrowser(_queue, selector) : + _clientSession.createBrowser(_queue); + + Enumeration[] msgs = new Enumeration[browserCount]; + int[] msgCount = new int[browserCount]; + + //create Enums + for (int count = 0; count < browserCount; count++) + { + msgs[count] = queueBrowser.getEnumeration(); + } + + //interleave reads + for (int cnt = 0; cnt < expectedMessages; cnt++) + { + for (int count = 0; count < browserCount; count++) + { + if (msgs[count].hasMoreElements()) + { + msgs[count].nextElement(); + msgCount[count]++; + } + } + } + + //validate all browsers get right message count. + for (int count = 0; count < browserCount; count++) + { + assertEquals(msgCount[count], expectedMessages); + } + + try + { + queueBrowser.close(); + } + catch (JMSException e) + { + fail("Close should happen without error:" + e.getMessage()); + } + } + + protected void validate(int messages) throws JMSException + { + //Create a new connection to validate message content + Connection connection = null; + + try + { + connection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + } + catch (NamingException e) + { + fail("Unable to make validation connection"); + } + + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + connection.start(); + + MessageConsumer consumer = session.createConsumer(_queue); + + _logger.info("Verify messages are still on the queue"); + + Message tempMsg; + + for (int msgCount = 0; msgCount < messages; msgCount++) + { + tempMsg = (TextMessage) consumer.receive(RECEIVE_TIMEOUT); + if (tempMsg == null) + { + fail("Message " + msgCount + " not retrieved from queue"); + } + } + + //Close this new connection + connection.close(); + + _logger.info("All messages recevied from queue"); + + //ensure no message left. + checkQueueDepth(0); + } + + protected void checkQueueDepthWithSelectors(int clients, int totalMessages) throws JMSException + { + + String selector = MESSAGE_ID_PROPERTY + " % " + clients; + + checkOverlappingMultipleGetEnum(clients, totalMessages / clients, selector); + } + + + /** + * This tests you can browse an empty queue, see QPID-785 + * + * @throws Exception + */ + public void testBrowsingEmptyQueue() throws Exception + { + checkQueueDepth(0); + } + + /* + * Test Messages Remain on Queue + * Create a queu and send messages to it. Browse them and then receive them all to verify they were still there + * + */ + public void testQueueBrowserMsgsRemainOnQueue() throws Exception + { + int messages = 10; + + sendMessages(messages); + + checkQueueDepth(messages); + + validate(messages); + } + + + public void testClosingBrowserMidReceiving() throws NamingException, JMSException + { + int messages = 100; + + sendMessages(messages); + + checkQueueDepth(messages); + + closeBrowserBeforeAfterGetNext(10); + + validate(messages); + + } + + public void testMultipleGetEnum() throws NamingException, JMSException + { + int messages = 100; + + sendMessages(messages); + + checkQueueDepth(messages); + + checkMultipleGetEnum(messages, 5); + + validate(messages); + } + + public void testMultipleOverlappingGetEnum() throws NamingException, JMSException + { + int messages = 25; + + sendMessages(messages); + + checkQueueDepth(messages); + + checkOverlappingMultipleGetEnum(5, messages); + + validate(messages); + } + + + public void testBrowsingWithSelector() throws JMSException + { + int messages = 40; + + sendMessages(messages); + + checkQueueDepth(messages); + + for (int clients = 2; clients <= 10; clients++) + { + checkQueueDepthWithSelectors(clients, messages); + } + + validate(messages); + } + + public void testFailoverWithQueueBrowser() throws JMSException + { + int messages = 50; + + sendMessages("connection1", messages); + sendMessages("connection2", messages); + + + checkQueueDepth(messages); + + + _logger.info("Creating Queue Browser"); + + QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); + + long queueDepth = 0; + + try + { + queueDepth = ((AMQSession) _clientSession).getQueueDepth((AMQDestination) _queue); + } + catch (AMQException e) + { + } + + assertEquals("Session reports Queue depth not as expected", messages, queueDepth); + + + int msgCount = 0; + + int failPoint = 0; + + failPoint = new Random().nextInt(messages) + 1; + + Enumeration msgs = queueBrowser.getEnumeration(); + + while (msgs.hasMoreElements()) + { + msgs.nextElement(); + msgCount++; + + if (msgCount == failPoint) + { + failBroker(); + } + } + + assertTrue("We should get atleast " + messages + " msgs.", msgCount >= messages); + + if (_logger.isDebugEnabled()) + { + _logger.debug("QBAAT Found " + msgCount + " messages total in browser"); + } + + //Close browser + queueBrowser.close(); + + //Validate all messages still on Broker 1 + validate(messages); + } + + public void testFailoverAsQueueBrowserCreated() throws JMSException + { + // The IoServiceListenerSupport seems to get stuck in with a managedSession that isn't closing when requested. + // So it hangs waiting for the session. + int messages = 50; + + sendMessages("connection1", messages); + sendMessages("connection2", messages); + + failBroker(); + + checkQueueDepth(messages); + + //Validate all messages still on Broker 1 + validate(messages); + } + + public void loop() throws JMSException + { + int run = 0; + try + { + while (true) + { + System.err.println(run++ + ":************************************************************************"); + testQueueBrowserMsgsRemainOnQueue(); + } + } + catch (Exception e) + { + _logger.error(e, e); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java new file mode 100644 index 0000000000..0ef0217234 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.client; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; +import javax.jms.Session; + +public class QueueBrowserClientAckTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java new file mode 100644 index 0000000000..80d74b1b79 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.client; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; +import javax.jms.Session; + +public class QueueBrowserDupsOkTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java new file mode 100644 index 0000000000..1bc5f07b4e --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.client; + +import org.apache.qpid.client.AMQSession; + +import javax.jms.ConnectionFactory; +import javax.jms.Queue; + +public class QueueBrowserNoAckTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, AMQSession.NO_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java new file mode 100644 index 0000000000..42e13c89e4 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.client; + +import org.apache.qpid.client.AMQSession; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; + +public class QueueBrowserPreAckTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(false, AMQSession.PRE_ACKNOWLEDGE); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java deleted file mode 100644 index ec9df8f1b3..0000000000 --- a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - * - */ -package org.apache.qpid.test.client; - -import org.apache.log4j.Logger; -import org.apache.qpid.test.VMTestCase; - -import javax.jms.Queue; -import javax.jms.ConnectionFactory; -import javax.jms.Session; -import javax.jms.Connection; -import javax.jms.MessageProducer; -import javax.jms.MessageConsumer; -import javax.jms.QueueBrowser; -import javax.jms.TextMessage; -import javax.jms.JMSException; -import javax.jms.QueueReceiver; -import javax.jms.Message; -import java.util.Enumeration; - -import junit.framework.TestCase; - -public class QueueBrowserTest extends VMTestCase -{ - private static final Logger _logger = Logger.getLogger(QueueBrowserTest.class); - - private static final int MSG_COUNT = 10; - - private Connection _clientConnection; - private Session _clientSession; - private Queue _queue; - - public void setUp() throws Exception - { - - super.setUp(); - - _queue = (Queue) _context.lookup("queue"); - - //Create Client - _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); - - _clientConnection.start(); - - _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - //Ensure _queue is created - _clientSession.createConsumer(_queue).close(); - - //Create Producer put some messages on the queue - Connection producerConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); - - producerConnection.start(); - - Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE); - - MessageProducer producer = producerSession.createProducer(_queue); - - for (int msg = 0; msg < MSG_COUNT; msg++) - { - producer.send(producerSession.createTextMessage("Message " + msg)); - } - - producerConnection.close(); - - } - - /* - * Test Messages Remain on Queue - * Create a queu and send messages to it. Browse them and then receive them all to verify they were still there - * - */ - - public void testQueueBrowserMsgsRemainOnQueue() throws JMSException - { - - // create QueueBrowser - _logger.info("Creating Queue Browser"); - - QueueBrowser queueBrowser = _clientSession.createBrowser(_queue); - - // check for messages - if (_logger.isDebugEnabled()) - { - _logger.debug("Checking for " + MSG_COUNT + " messages with QueueBrowser"); - } - - int msgCount = 0; - Enumeration msgs = queueBrowser.getEnumeration(); - - while (msgs.hasMoreElements()) - { - msgs.nextElement(); - msgCount++; - } - - if (_logger.isDebugEnabled()) - { - _logger.debug("Found " + msgCount + " messages total in browser"); - } - - // check to see if all messages found -// assertEquals("browser did not find all messages", MSG_COUNT, msgCount); - if (msgCount != MSG_COUNT) - { - _logger.warn(msgCount + "/" + MSG_COUNT + " messages received."); - } - - //Close browser - queueBrowser.close(); - - // VERIFY - - // continue and try to receive all messages - MessageConsumer consumer = _clientSession.createConsumer(_queue); - - _logger.info("Verify messages are still on the queue"); - - Message tempMsg; - - for (msgCount = 0; msgCount < MSG_COUNT; msgCount++) - { - tempMsg = (TextMessage) consumer.receive(RECEIVE_TIMEOUT); - if (tempMsg == null) - { - fail("Message " + msgCount + " not retrieved from queue"); - } - } - - _logger.info("All messages recevied from queue"); - } - - -} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java new file mode 100644 index 0000000000..0d63373e61 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java @@ -0,0 +1,51 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.client; + +import javax.jms.Queue; +import javax.jms.ConnectionFactory; +import javax.jms.Session; + +public class QueueBrowserTransactedTest extends QueueBrowserAutoAckTest +{ + public void setUp() throws Exception + { + + super.setUp(); + + _clientConnection.close(); + _clientSession.close(); + + _queue = (Queue) _context.lookup("queue"); + + //Create Client + _clientConnection = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + + _clientConnection.start(); + + _clientSession = _clientConnection.createSession(true, Session.SESSION_TRANSACTED); + + //Ensure _queue is created + _clientSession.createConsumer(_queue).close(); + } + + +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java b/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java new file mode 100644 index 0000000000..e7d7c7eba6 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java @@ -0,0 +1,257 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.test.client.failover; + +import junit.framework.TestCase; +import org.apache.qpid.client.AMQConnection; +import org.apache.qpid.client.AMQConnectionFactory; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.jms.ConnectionListener; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.url.URLSyntaxException; +import org.apache.log4j.Logger; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import java.util.concurrent.CountDownLatch; + +public class FailoverTest extends TestCase implements ConnectionListener +{ + private static final Logger _logger = Logger.getLogger(FailoverTest.class); + + private static final int NUM_BROKERS = 2; + private static final String BROKER = "amqp://guest:guest@/test?brokerlist='vm://:%d;vm://:%d'"; + private static final String QUEUE = "queue"; + private static final int NUM_MESSAGES = 10; + private Connection con; + private AMQConnectionFactory conFactory; + private Session prodSess; + private AMQQueue q; + private MessageProducer prod; + private Session conSess; + private MessageConsumer consumer; + + private static int usedBrokers = 0; + private CountDownLatch failoverComplete; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + // Create two VM brokers + + for (int i = 0; i < NUM_BROKERS; i++) + { + usedBrokers++; + + TransportConnection.createVMBroker(usedBrokers); + } + + conFactory = new AMQConnectionFactory(String.format(BROKER, usedBrokers - 1, usedBrokers)); + _logger.info("Connecting on:" + conFactory.getConnectionURL()); + con = conFactory.createConnection(); + ((AMQConnection) con).setConnectionListener(this); + con.start(); + failoverComplete = new CountDownLatch(1); + } + + private void init(boolean transacted, int mode) throws JMSException + { + prodSess = con.createSession(transacted, mode); + q = new AMQQueue("amq.direct", QUEUE); + prod = prodSess.createProducer(q); + conSess = con.createSession(transacted, mode); + consumer = conSess.createConsumer(q); + } + + @Override + protected void tearDown() throws Exception + { + try + { + con.close(); + } + catch (Exception e) + { + + } + + try + { + TransportConnection.killAllVMBrokers(); + ApplicationRegistry.removeAll(); + } + catch (Exception e) + { + fail("Unable to clean up"); + } + super.tearDown(); + } + + private void consumeMessages(int toConsume) throws JMSException + { + Message msg; + for (int i = 0; i < toConsume; i++) + { + msg = consumer.receive(1000); + assertNotNull("Message " + i + " was null!", msg); + assertEquals("message " + i, ((TextMessage) msg).getText()); + } + } + + private void sendMessages(int totalMessages) throws JMSException + { + for (int i = 0; i < totalMessages; i++) + { + prod.send(prodSess.createTextMessage("message " + i)); + } + +// try +// { +// Thread.sleep(100 * totalMessages); +// } +// catch (InterruptedException e) +// { +// //evil ignoring of IE +// } + } + + public void testP2PFailover() throws Exception + { + testP2PFailover(NUM_MESSAGES, true); + } + + public void testP2PFailoverWithMessagesLeft() throws Exception + { + testP2PFailover(NUM_MESSAGES, false); + } + + private void testP2PFailover(int totalMessages, boolean consumeAll) throws JMSException + { + Message msg = null; + init(false, Session.AUTO_ACKNOWLEDGE); + sendMessages(totalMessages); + + // Consume some messages + int toConsume = totalMessages; + if (!consumeAll) + { + toConsume = totalMessages / 2; + } + + consumeMessages(toConsume); + + _logger.info("Failing over"); + + causeFailure(); + + msg = consumer.receive(500); + //todo: reinstate + assertNull("Should not have received message from new broker!", msg); + // Check that messages still sent / received + sendMessages(totalMessages); + consumeMessages(totalMessages); + } + + private void causeFailure() + { + _logger.info("Failover"); + + TransportConnection.killVMBroker(usedBrokers - 1); + ApplicationRegistry.remove(usedBrokers - 1); + + _logger.info("Awaiting Failover completion"); + try + { + failoverComplete.await(); + } + catch (InterruptedException e) + { + //evil ignore IE. + } + } + + public void testClientAckFailover() throws Exception + { + init(false, Session.CLIENT_ACKNOWLEDGE); + sendMessages(1); + Message msg = consumer.receive(); + assertNotNull("Expected msgs not received", msg); + + + causeFailure(); + + Exception failure = null; + try + { + msg.acknowledge(); + } + catch (Exception e) + { + failure = e; + } + assertNotNull("Exception should be thrown", failure); + } + + // This test disabled so that it doesn't add 4 minnutes to the length of time it takes to run, which would be lame + public void txest4MinuteFailover() throws Exception + { + conFactory = new AMQConnectionFactory("amqp://guest:guest@/test?brokerlist='vm://:"+(usedBrokers-1)+"?connectdelay='60000'&retries='2''"); + _logger.info("Connecting on:" + conFactory.getConnectionURL()); + con = conFactory.createConnection(); + ((AMQConnection) con).setConnectionListener(this); + con.start(); + + long failTime = System.currentTimeMillis() + 60000; + causeFailure(); + assertTrue("Failover did not take long enough", System.currentTimeMillis() > failTime); + } + + public void bytesSent(long count) + { + } + + public void bytesReceived(long count) + { + } + + public boolean preFailover(boolean redirect) + { + return true; + } + + public boolean preResubscribe() + { + return true; + } + + public void failoverComplete() + { + failoverComplete.countDown(); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java b/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java new file mode 100644 index 0000000000..706d99ffe2 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; + +/** + * An AMQPPublisher represents the status of the publishing side of a test circuit that exposes AMQP specific features. + * Its provides additional assertions not available through the plain JMS {@link Publisher} interface. + * + *

+ *
CRC Card
Responsibilities + *
Provide assertion that the publishers received a no consumers error code on every message. + *
Provide assertion that the publishers received a no route error code on every message. + *
+ */ +public interface AMQPPublisher extends Publisher +{ + /** + * Provides an assertion that the publisher got a no consumers exception on every message. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the publisher got a no consumers exception on every message. + */ + Assertion noConsumersAssertion(ParsedProperties testProps); + + /** + * Provides an assertion that the publisher got a no rout exception on every message. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the publisher got a no rout exception on every message. + */ + Assertion noRouteAssertion(ParsedProperties testProps); +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java b/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java new file mode 100644 index 0000000000..e8b7da2537 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +/** + * BrokerLifecycleAware is an awareness interface implemented by test cases that can run control the life-cycle of + * the brokers on which they run. Its purpose is to expose additional instrumentation of brokers during testing, that + * enables tests to use an automated failure mechanism to simulate broker failures, and to re-start failed brokers. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Indicate whether or not a test case is using an in-vm broker. + *
Track which in-vm broker is currently in use. + *
Accept setting of a failure mechanism. {@link CauseFailure}. + *
+ * + * @todo Need to think about how to present the brokers through this interface. Thinking numbering the available + * brokers from 1 will do. Then can kill 1 and assume failing onto 2. Restart 1 and kill 2 and fail back onto + * 1 again? + */ +public interface BrokerLifecycleAware +{ + public void setInVmBrokers(); + + /** + * Indicates whether or not a test case is using in-vm brokers. + * + * @return true if the test is using in-vm brokers, false otherwise. + */ + public boolean usingInVmBroker(); + + /** + * Sets the currently live in-vm broker. + * + * @param i The currently live in-vm broker. + */ + public void setLiveBroker(int i); + + /** + * Reports the currently live in-vm broker. + * + * @return The currently live in-vm broker. + */ + public int getLiveBroker(); + + /** + * Accepts a failure mechanism. + * + * @param failureMechanism The failure mechanism. + */ + public void setFailureMechanism(CauseFailure failureMechanism); +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java new file mode 100644 index 0000000000..8a5a9560a0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +/** + * CauseFailure provides a method to cause a failure in a messaging broker, usually used in conjunction with fail-over + * or other failure mode testing. In some cases failures may be automated, for example by shutting down an in-vm broker, + * or by sending a special control signal to a broker over a network connection. In other cases, it may be preferable + * to ask a user interactively to cause a failure scenario, in which case an implementation may display a prompt or + * dialog box asking for notification once the failure has been caused. The purpose of this interface is to abstract + * the exact cause and nature of a failure out of failure test cases. + * + *

+ *
CRC Card
Responsibilities + *
Cause messaging broker failure. + *
+ */ +public interface CauseFailure +{ + /** + * Causes the active message broker to fail. + */ + void causeFailure(); +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java new file mode 100644 index 0000000000..6b96ade674 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java @@ -0,0 +1,65 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +import org.apache.qpid.test.framework.CauseFailure; + +import java.io.IOException; + +/** + * Causes a message broker failure by interactively prompting the user to cause it. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Cause messaging broker failure. + *
+ */ +public class CauseFailureUserPrompt implements CauseFailure +{ + /** + * Causes the active message broker to fail. + */ + public void causeFailure() + { + waitForUser("Cause a broker failure now, then press Return."); + } + + /** + * Outputs a prompt to the console and waits for the user to press return. + * + * @param prompt The prompt to display on the console. + */ + private void waitForUser(String prompt) + { + System.out.println(prompt); + + try + { + System.in.read(); + } + catch (IOException e) + { + // Ignored. + } + + System.out.println("Continuing."); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java index d971aa5385..d5a33514df 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java @@ -51,11 +51,14 @@ public class CircuitEndBase implements CircuitEnd ExceptionMonitor exceptionMonitor; /** - * Creates a circuit end point on the specified producer, consumer and controlSession. + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. * - * @param producer The message producer for the circuit end point. - * @param consumer The message consumer for the circuit end point. - * @param session The controlSession for the circuit end point. + * @param producer The message producer for the circuit end point. + * @param consumer The message consumer for the circuit end point. + * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. */ public CircuitEndBase(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java b/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java index 1ac94b5244..7d06aba1c0 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java @@ -1,3 +1,17 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an /* * * Licensed to the Apache Software Foundation (ASF) under one @@ -36,7 +50,7 @@ import java.util.List; * *

*
CRC Card
Responsibilities Collaborations - *
Record all exceptions received. {@link ExceptionListener} + *
Record all exceptions received. *
*/ public class ExceptionMonitor implements ExceptionListener @@ -45,14 +59,14 @@ public class ExceptionMonitor implements ExceptionListener private final Logger log = Logger.getLogger(ExceptionMonitor.class); /** Holds the received exceptions. */ - List exceptions = new ArrayList(); + List exceptions = new ArrayList(); /** * Receives incoming exceptions. * * @param e The exception to record. */ - public void onException(JMSException e) + public synchronized void onException(JMSException e) { log.debug("public void onException(JMSException e): called", e); @@ -64,7 +78,7 @@ public class ExceptionMonitor implements ExceptionListener * * @return true if no exceptions have been received, false otherwise. */ - public boolean assertNoExceptions() + public synchronized boolean assertNoExceptions() { return exceptions.isEmpty(); } @@ -74,7 +88,7 @@ public class ExceptionMonitor implements ExceptionListener * * @return true if exactly one exception been received, false otherwise. */ - public boolean assertOneJMSException() + public synchronized boolean assertOneJMSException() { return exceptions.size() == 1; } @@ -82,32 +96,65 @@ public class ExceptionMonitor implements ExceptionListener /** * Checks that exactly one exception, with a linked cause of the specified type, has been received. * + * @param aClass The type of the linked cause. + * * @return true if exactly one exception, with a linked cause of the specified type, been received, * false otherwise. */ - public boolean assertOneJMSExceptionWithLinkedCause(Class aClass) + public synchronized boolean assertOneJMSExceptionWithLinkedCause(Class aClass) { if (exceptions.size() == 1) { - JMSException e = exceptions.get(0); - - Exception linkedCause = e.getLinkedException(); + Exception e = exceptions.get(0); - if ((linkedCause != null) && aClass.isInstance(linkedCause)) + if (e instanceof JMSException) { - return true; + JMSException jmse = (JMSException) e; + + Exception linkedCause = jmse.getLinkedException(); + + if ((linkedCause != null) && aClass.isInstance(linkedCause)) + { + return true; + } } } return false; } + /** + * Checks that at least one exception of the the specified type, has been received. + * + * @param exceptionClass The type of the exception. + * + * @return true if at least one exception of the specified type has been received, false otherwise. + */ + public synchronized boolean assertExceptionOfType(Class exceptionClass) + { + // Start by assuming that the exception has no been received. + boolean passed = false; + + // Scan all the exceptions for a match. + for (Exception e : exceptions) + { + if (exceptionClass.isInstance(e)) + { + passed = true; + + break; + } + } + + return passed; + } + /** * Reports the number of exceptions held by this monitor. * * @return The number of exceptions held by this monitor. */ - public int size() + public synchronized int size() { return exceptions.size(); } @@ -115,9 +162,9 @@ public class ExceptionMonitor implements ExceptionListener /** * Clears the record of received exceptions. */ - public void reset() + public synchronized void reset() { - exceptions = new ArrayList(); + exceptions = new ArrayList(); } /** @@ -126,11 +173,11 @@ public class ExceptionMonitor implements ExceptionListener * * @return A string containing a dump of the stack traces of all exceptions. */ - public String toString() + public synchronized String toString() { String result = "ExceptionMonitor: holds " + exceptions.size() + " exceptions.\n\n"; - for (JMSException ex : exceptions) + for (Exception ex : exceptions) { result += getStackTrace(ex) + "\n"; } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java index fa70e14d16..51b053d2b2 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java @@ -20,23 +20,20 @@ */ package org.apache.qpid.test.framework; -import junit.framework.TestCase; - import org.apache.log4j.Logger; import org.apache.log4j.NDC; -import org.apache.qpid.client.transport.TransportConnection; -import org.apache.qpid.server.registry.ApplicationRegistry; -import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; +import org.apache.qpid.test.framework.BrokerLifecycleAware; import org.apache.qpid.test.framework.sequencers.CircuitFactory; -import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.AsymptoticTestCase; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.AsymptoticTestCase; +import org.apache.qpid.junit.extensions.SetupTaskAware; +import org.apache.qpid.junit.extensions.SetupTaskHandler; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; import java.util.ArrayList; import java.util.List; -import java.util.Properties; /** * FrameworkBaseCase provides a starting point for writing test cases against the test framework. Its main purpose is @@ -50,13 +47,26 @@ import java.util.Properties; * Convert failed assertions to error messages. * */ -public class FrameworkBaseCase extends AsymptoticTestCase +public class FrameworkBaseCase extends AsymptoticTestCase implements FrameworkTestContext, SetupTaskAware, + BrokerLifecycleAware { /** Used for debugging purposes. */ private static final Logger log = Logger.getLogger(FrameworkBaseCase.class); /** Holds the test sequencer to create and run test circuits with. */ - protected CircuitFactory circuitFactory = new DefaultCircuitFactory(); + protected CircuitFactory circuitFactory = new LocalCircuitFactory(); + + /** Used to read the tests configurable properties through. */ + protected ParsedProperties testProps; + + /** A default setup task processor to delegate setup tasks to. */ + protected SetupTaskHandler taskHandler = new SetupTaskHandler(); + + /** Flag used to track whether the test is in-vm or not. */ + protected boolean isUsingInVM; + + /** Holds the failure mechanism. */ + protected CauseFailure failureMechanism = new CauseFailureUserPrompt(); /** * Creates a new test case with the specified name. @@ -91,6 +101,26 @@ public class FrameworkBaseCase extends AsymptoticTestCase this.circuitFactory = circuitFactory; } + /** + * Reports the current test case name. + * + * @return The current test case name. + */ + public TestCaseVector getTestCaseVector() + { + return new TestCaseVector(this.getName(), 0); + } + + /** + * Reports the current test case parameters. + * + * @return The current test case parameters. + */ + public MessagingTestConfigProperties getTestParameters() + { + return new MessagingTestConfigProperties(testProps); + } + /** * Creates a list of assertions. * @@ -116,7 +146,7 @@ public class FrameworkBaseCase extends AsymptoticTestCase * * @param asserts The list of failed assertions. */ - protected void assertNoFailures(List asserts) + protected static void assertNoFailures(List asserts) { log.debug("protected void assertNoFailures(List asserts = " + asserts + "): called"); @@ -140,7 +170,7 @@ public class FrameworkBaseCase extends AsymptoticTestCase * * @return The error message. */ - protected String assertionsToString(List asserts) + protected static String assertionsToString(List asserts) { String errorMessage = ""; @@ -161,8 +191,10 @@ public class FrameworkBaseCase extends AsymptoticTestCase { NDC.push(getName()); - // Ensure that the in-vm broker is created. - TransportConnection.createVMBroker(1); + testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + // Process all optional setup tasks. This may include in-vm broker creation, if a decorator has added it. + taskHandler.runSetupTasks(); } /** @@ -170,16 +202,30 @@ public class FrameworkBaseCase extends AsymptoticTestCase */ protected void tearDown() { - try - { - // Ensure that the in-vm broker is cleaned up so that the next test starts afresh. - TransportConnection.killVMBroker(1); - ApplicationRegistry.remove(1); - } - finally - { - NDC.pop(); - } + NDC.pop(); + + // Process all optional tear down tasks. This may include in-vm broker clean up, if a decorator has added it. + taskHandler.runTearDownTasks(); + } + + /** + * Adds the specified task to the tests setup. + * + * @param task The task to add to the tests setup. + */ + public void chainSetupTask(Runnable task) + { + taskHandler.chainSetupTask(task); + } + + /** + * Adds the specified task to the tests tear down. + * + * @param task The task to add to the tests tear down. + */ + public void chainTearDownTask(Runnable task) + { + taskHandler.chainTearDownTask(task); } /** @@ -197,84 +243,46 @@ public class FrameworkBaseCase extends AsymptoticTestCase return methodName; } + public void setInVmBrokers() + { + isUsingInVM = true; + } + /** - * DefaultCircuitFactory is a test sequencer that creates test circuits with publishing and receiving ends rooted - * on the same JVM. + * Indicates whether or not a test case is using in-vm brokers. + * + * @return true if the test is using in-vm brokers, false otherwise. */ - public class DefaultCircuitFactory implements CircuitFactory + public boolean usingInVmBroker() { - /** - * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, - * begining the test and gathering the test reports from the participants. - * - * @param testCircuit The test circuit. - * @param assertions The list of assertions to apply to the test circuit. - * @param testProperties The test case definition. - */ - public void sequenceTest(Circuit testCircuit, List assertions, Properties testProperties) - { - assertNoFailures(testCircuit.test(1, assertions)); - } - - /** - * Creates a test circuit for the test, configered by the test parameters specified. - * - * @param testProperties The test parameters. - * @return A test circuit. - */ - public Circuit createCircuit(ParsedProperties testProperties) - { - return LocalCircuitImpl.createCircuit(testProperties); - } - - /** - * Sets the sender test client to coordinate the test with. - * - * @param sender The contact details of the sending client in the test. - */ - public void setSender(TestClientDetails sender) - { - throw new RuntimeException("Not implemented."); - } - - /** - * Sets the receiving test client to coordinate the test with. - * - * @param receiver The contact details of the sending client in the test. - */ - public void setReceiver(TestClientDetails receiver) - { - throw new RuntimeException("Not implemented."); - } + return isUsingInVM; + } - /** - * Supplies the sending test client. - * - * @return The sending test client. - */ - public TestClientDetails getSender() - { - throw new RuntimeException("Not implemented."); - } + /** + * Sets the currently live in-vm broker. + * + * @param i The currently live in-vm broker. + */ + public void setLiveBroker(int i) + { } - /** - * Supplies the receiving test client. - * - * @return The receiving test client. - */ - public List getReceivers() - { - throw new RuntimeException("Not implemented."); - } + /** + * Reports the currently live in-vm broker. + * + * @return The currently live in-vm broker. + */ + public int getLiveBroker() + { + return 0; + } - /** - * Accepts the conversation factory over which to hold the test coordinating conversation. - * - * @param conversationFactory The conversation factory to coordinate the test over. - */ - public void setConversationFactory(ConversationFactory conversationFactory) - { - throw new RuntimeException("Not implemented."); - } + /** + * Accepts a failure mechanism. + * + * @param failureMechanism The failure mechanism. + */ + public void setFailureMechanism(CauseFailure failureMechanism) + { + this.failureMechanism = failureMechanism; } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java index 7bd65618e3..2322955253 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java @@ -1,3 +1,23 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ package org.apache.qpid.test.framework; /** diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java new file mode 100644 index 0000000000..e7268db8eb --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +/** + * A FrameworkTestContext provides context information to test code about the current test case being run; its name, its + * parameters. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Provide the name of the current test case. + *
Provide the test parameters. + *
+ */ +public interface FrameworkTestContext +{ + /** + * Reports the current test case name. + * + * @return The current test case name. + */ + TestCaseVector getTestCaseVector(); + + /** + * Reports the current test case parameters. + * + * @return The current test case parameters. + */ + MessagingTestConfigProperties getTestParameters(); +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java new file mode 100644 index 0000000000..d1fcad9cc0 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java @@ -0,0 +1,168 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +import org.apache.log4j.Logger; + +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.test.framework.localcircuit.LocalAMQPPublisherImpl; +import org.apache.qpid.test.framework.localcircuit.LocalPublisherImpl; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; + +import javax.jms.*; + +/** + * LocalAMQPCircuitFactory is a test sequencer that creates test circuits with publishing and receiving ends rooted + * on the same JVM, allowing AMQP/Qpid specific options to be applied to the circuit. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Provide a standard test procedure over a test circuit. + *
Construct test circuits appropriate to a tests context. + *
Construct test circuits the support AMQP specific options. + *
+ */ +public class LocalAMQPCircuitFactory extends LocalCircuitFactory +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(LocalAMQPCircuitFactory.class); + + /** + * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters. + * + * @param connection The connection to build the circuit end on. + * @param testProps The test parameters to configure the circuit end construction. + * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. + * + * @return A circuit end suitable for the publishing side of a test circuit. + * + * @throws javax.jms.JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. + */ + public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) + throws JMSException + { + log.debug( + "public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " + + uniqueId + "): called"); + + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode()); + + Destination destination = + props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) + : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); + + MessageProducer producer = + props.getPublisherProducerBind() + ? ((props.getImmediate() | props.getMandatory()) + ? ((AMQSession) session).createProducer(destination, props.getMandatory(), props.getImmediate()) + : session.createProducer(destination)) : null; + + MessageConsumer consumer = + props.getPublisherConsumerBind() + ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; + + MessageMonitor messageMonitor = new MessageMonitor(); + + if (consumer != null) + { + consumer.setMessageListener(messageMonitor); + } + + ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); + connection.setExceptionListener(exceptionMonitor); + + if (!props.getPublisherConsumerActive() && (consumer != null)) + { + consumer.close(); + } + + return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor); + } + + /** + * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters. + * + * @param connection The connection to build the circuit end on. + * @param testProps The test parameters to configure the circuit end construction. + * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. + * + * @return A circuit end suitable for the receiving side of a test circuit. + * + * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. + */ + public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) + throws JMSException + { + log.debug( + "public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " + + uniqueId + "): called"); + + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode()); + + MessageProducer producer = + props.getReceiverProducerBind() + ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; + + Destination destination = + props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) + : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); + + MessageConsumer consumer = + props.getReceiverConsumerBind() + ? ((props.getDurableSubscription() && props.getPubsub()) + ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination)) + : null; + + MessageMonitor messageMonitor = new MessageMonitor(); + + if (consumer != null) + { + consumer.setMessageListener(messageMonitor); + } + + if (!props.getReceiverConsumerActive() && (consumer != null)) + { + consumer.close(); + } + + return new CircuitEndBase(producer, consumer, session, messageMonitor, null); + } + + /** + * Creates a local {@link Publisher} from a {@link CircuitEnd}. The publisher implementation provides AMQP + * specific assertion methods, for testing beyond JMS. + * + * @param publisherEnd The publishing circuit end. + * + * @return A {@link Receiver}. + */ + protected LocalPublisherImpl createPublisherFromCircuitEnd(CircuitEndBase publisherEnd) + { + return new LocalAMQPPublisherImpl(publisherEnd); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java new file mode 100644 index 0000000000..38a924a4ee --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java @@ -0,0 +1,316 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +import org.apache.log4j.Logger; + +import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; +import org.apache.qpid.test.framework.localcircuit.LocalPublisherImpl; +import org.apache.qpid.test.framework.localcircuit.LocalReceiverImpl; +import org.apache.qpid.test.framework.sequencers.CircuitFactory; +import org.apache.qpid.util.ConversationFactory; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; + +import javax.jms.*; + +import java.util.List; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicLong; + +/** + * LocalCircuitFactory is a circuit factory that creates test circuits with publishing and receiving ends rooted + * on the same JVM. The ends of the circuit are presented as {@link Publisher} and {@link Receiver} interfaces, which + * in turn provide methods to apply assertions to the circuit. The creation of the circuit ends, and the presentation + * of the ends as publisher/receiver interfaces, are designed to be overriden, so that circuits and assertions that + * use messaging features not available in JMS can be written. This provides an extension point for writing tests + * against proprietary features of JMS implementations. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Provide a standard test procedure over a test circuit. + *
Construct test circuits appropriate to a tests context. + *
+ */ +public class LocalCircuitFactory implements CircuitFactory +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(LocalCircuitFactory.class); + + /** Used to create unique destination names for each test. */ + protected static AtomicLong uniqueDestsId = new AtomicLong(); + + /** + * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles, + * begining the test and gathering the test reports from the participants. + * + * @param testCircuit The test circuit. + * @param assertions The list of assertions to apply to the test circuit. + * @param testProperties The test case definition. + */ + public void sequenceTest(Circuit testCircuit, List assertions, Properties testProperties) + { + FrameworkBaseCase.assertNoFailures(testCircuit.test(1, assertions)); + } + + /** + * Creates a test circuit for the test, configered by the test parameters specified. + * + * @param testProperties The test parameters. + * + * @return A test circuit. + */ + public Circuit createCircuit(ParsedProperties testProperties) + { + Circuit result; + + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProperties); + + // Create a standard publisher/receivers test client pair on a shared connection, individual sessions. + try + { + // Get a unique offset to append to destination names to make them unique to the connection. + long uniqueId = uniqueDestsId.incrementAndGet(); + + // Set up the connection. + Connection connection = TestUtils.createConnection(testProperties); + + // Add the connection exception listener to assert on exception conditions with. + // ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); + // connection.setExceptionListener(exceptionMonitor); + + // Set up the publisher. + CircuitEndBase publisherEnd = createPublisherCircuitEnd(connection, props, uniqueId); + + // Set up the receiver. + CircuitEndBase receiverEnd = createReceiverCircuitEnd(connection, props, uniqueId); + + // Start listening for incoming messages. + connection.start(); + + // Package everything up. + LocalPublisherImpl publisher = createPublisherFromCircuitEnd(publisherEnd); + LocalReceiverImpl receiver = createReceiverFromCircuitEnd(receiverEnd); + + result = new LocalCircuitImpl(testProperties, publisher, receiver, connection, publisher.getExceptionMonitor()); + } + catch (JMSException e) + { + throw new RuntimeException("Could not create publisher/receivers pair due to a JMSException.", e); + } + + return result; + } + + /** + * Creates a local {@link Receiver} from a {@link CircuitEnd}. Sub-classes may override this to provide more + * specialized receivers if necessary. + * + * @param receiverEnd The receiving circuit end. + * + * @return A {@link Receiver}. + */ + protected LocalReceiverImpl createReceiverFromCircuitEnd(CircuitEndBase receiverEnd) + { + return new LocalReceiverImpl(receiverEnd); + } + + /** + * Creates a local {@link Publisher} from a {@link CircuitEnd}. Sub-classes may override this to provide more + * specialized receivers if necessary. + * + * @param publisherEnd The publishing circuit end. + * + * @return A {@link Receiver}. + */ + protected LocalPublisherImpl createPublisherFromCircuitEnd(CircuitEndBase publisherEnd) + { + return new LocalPublisherImpl(publisherEnd); + } + + /** + * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters. + * + * @param connection The connection to build the circuit end on. + * @param testProps The test parameters to configure the circuit end construction. + * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. + * + * @return A circuit end suitable for the publishing side of a test circuit. + * + * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. + */ + public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) + throws JMSException + { + log.debug( + "public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " + + uniqueId + "): called"); + + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + // Check that the test properties do not contain AMQP/Qpid specific settings, and fail if they do. + if (props.getImmediate() || props.getMandatory()) + { + throw new RuntimeException( + "Cannot create a pure JMS circuit as the test properties require AMQP specific options."); + } + + Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode()); + + Destination destination = + props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) + : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); + + MessageProducer producer = props.getPublisherProducerBind() ? session.createProducer(destination) : null; + + MessageConsumer consumer = + props.getPublisherConsumerBind() + ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; + + MessageMonitor messageMonitor = new MessageMonitor(); + + if (consumer != null) + { + consumer.setMessageListener(messageMonitor); + } + + ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); + connection.setExceptionListener(exceptionMonitor); + + if (!props.getPublisherConsumerActive() && (consumer != null)) + { + consumer.close(); + } + + return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor); + } + + /** + * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters. + * + * @param connection The connection to build the circuit end on. + * @param testProps The test parameters to configure the circuit end construction. + * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. + * + * @return A circuit end suitable for the receiving side of a test circuit. + * + * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. + */ + public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) + throws JMSException + { + log.debug( + "public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " + + uniqueId + "): called"); + + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + // Check that the test properties do not contain AMQP/Qpid specific settings, and fail if they do. + if (props.getImmediate() || props.getMandatory()) + { + throw new RuntimeException( + "Cannot create a pure JMS circuit as the test properties require AMQP specific options."); + } + + Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode()); + + MessageProducer producer = + props.getReceiverProducerBind() + ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; + + Destination destination = + props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) + : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); + + MessageConsumer consumer = + props.getReceiverConsumerBind() + ? ((props.getDurableSubscription() && props.getPubsub()) + ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination)) + : null; + + MessageMonitor messageMonitor = new MessageMonitor(); + + if (consumer != null) + { + consumer.setMessageListener(messageMonitor); + } + + if (!props.getReceiverConsumerActive() && (consumer != null)) + { + consumer.close(); + } + + return new CircuitEndBase(producer, consumer, session, messageMonitor, null); + } + + /** + * Sets the sender test client to coordinate the test with. + * + * @param sender The contact details of the sending client in the test. + */ + public void setSender(TestClientDetails sender) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Sets the receiving test client to coordinate the test with. + * + * @param receiver The contact details of the sending client in the test. + */ + public void setReceiver(TestClientDetails receiver) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Supplies the sending test client. + * + * @return The sending test client. + */ + public TestClientDetails getSender() + { + throw new RuntimeException("Not implemented."); + } + + /** + * Supplies the receiving test client. + * + * @return The receiving test client. + */ + public List getReceivers() + { + throw new RuntimeException("Not implemented."); + } + + /** + * Accepts the conversation factory over which to hold the test coordinating conversation. + * + * @param conversationFactory The conversation factory to coordinate the test over. + */ + public void setConversationFactory(ConversationFactory conversationFactory) + { + throw new RuntimeException("Not implemented."); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java b/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java new file mode 100644 index 0000000000..b672b9c3ce --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java @@ -0,0 +1,167 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +/** + * MessageIdentityVector provides a message identification scheme, that matches individual messages with test cases. + * Test messages are being sent by a number of test clients, sending messages over a set of routes, and being received + * by another set of test clients. Each test is itself, being run within a test cycle, of which there could be many. It + * is the job of the test coordinator to request and receive reports from the available test clients, on what has been + * sent, what has been received, and what errors may have occurred, and to reconcile this information against the + * assertions being applied by the test case. In order to be able to figure out which messages belong to which test, + * there needs to be an identification scheme, that the coordinator can use to correlate messages in senders and + * receiver reports. Every message sent in a test can be associated with this information. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Identify a test case, a handling client id, a circuit end within the client, and a test cycle number. + *
+ */ +public class MessageIdentityVector +{ + /** Holds the test case vector component of the message identity vector. */ + private TestCaseVector testCaseVector; + + /** The unique client id. */ + private String clientId; + + /** The unique circuit end number within the client id. */ + private int circuitEndId; + + /** + * Creates a new identity vector for test messages. + * + * @param testCase The name of the test case generating the messages. + * @param clientId The unique id of the client implementing a circuit end that is handling the messages. + * @param circuitEndId The unique id number of the circuit end within the client. + * @param testCycleNumber The cycle iteration number of the test case. + */ + public MessageIdentityVector(String testCase, String clientId, int circuitEndId, int testCycleNumber) + { + this.testCaseVector = new TestCaseVector(testCase, testCycleNumber); + this.clientId = clientId; + this.circuitEndId = circuitEndId; + } + + /** + * Reports the test case vector component of the message identity vector. + * + * @return The test case vector component of the message identity vector. + */ + public TestCaseVector getTestCaseVector() + { + return testCaseVector; + } + + /** + * Reports the name of the test case. + * + * @return The name of the test case. + */ + public String getTestCase() + { + return testCaseVector.getTestCase(); + } + + /** + * Reports the test iteration cycle number within the test case. + * + * @return The test iteration cycle number within the test case. + */ + public int getTestCycleNumber() + { + return testCaseVector.getTestCycleNumber(); + } + + /** + * Resports the client id. + * + * @return The client id. + */ + public String getClientId() + { + return clientId; + } + + /** + * Reports the circuit end number within the test client. + * + * @return The circuit end number within the test client. + */ + public int getCircuitEndId() + { + return circuitEndId; + } + + /** + * Compares this identity vector with another for equality. All fields must match. + * + * @param o The identity vector to compare with. + * + * @return true if the identity vector is identical to this one by all fields, false otherwise. + */ + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if ((o == null) || (getClass() != o.getClass())) + { + return false; + } + + MessageIdentityVector that = (MessageIdentityVector) o; + + if (circuitEndId != that.circuitEndId) + { + return false; + } + + if ((clientId != null) ? (!clientId.equals(that.clientId)) : (that.clientId != null)) + { + return false; + } + + if ((testCaseVector != null) ? (!testCaseVector.equals(that.testCaseVector)) : (that.testCaseVector != null)) + { + return false; + } + + return true; + } + + /** + * Computes a hash code for this identity vector based on all fields. + * + * @return A hash code for this identity vector based on all fields. + */ + public int hashCode() + { + int result; + result = ((testCaseVector != null) ? testCaseVector.hashCode() : 0); + result = (31 * result) + ((clientId != null) ? clientId.hashCode() : 0); + result = (31 * result) + circuitEndId; + + return result; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java b/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java index 9e91286683..27f9261d94 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java @@ -20,9 +20,9 @@ */ package org.apache.qpid.test.framework; -import org.apache.qpid.jms.Session; +import org.apache.qpid.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import javax.jms.Session; import java.util.Properties; @@ -181,10 +181,16 @@ public class MessagingTestConfigProperties extends ParsedProperties public static final boolean PERSISTENT_MODE_DEFAULT = false; /** Holds the name of the property to get the test transactional mode from. */ - public static final String TRANSACTED_PROPNAME = "transacted"; + public static final String TRANSACTED_PUBLISHER_PROPNAME = "transactedPublisher"; /** Holds the transactional mode to use for the test. */ - public static final boolean TRANSACTED_DEFAULT = false; + public static final boolean TRANSACTED_PUBLISHER_DEFAULT = false; + + /** Holds the name of the property to get the test transactional mode from. */ + public static final String TRANSACTED_RECEIVER_PROPNAME = "transactedReceiver"; + + /** Holds the transactional mode to use for the test. */ + public static final boolean TRANSACTED_RECEIVER_DEFAULT = false; /** Holds the name of the property to set the no local flag from. */ public static final String NO_LOCAL_PROPNAME = "noLocal"; @@ -204,7 +210,7 @@ public class MessagingTestConfigProperties extends ParsedProperties /** Defines the default value of the durable subscriptions flag. */ public static final boolean DURABLE_SUBSCRIPTION_DEFAULT = false; - // ====================== Qpid Options and Flags ================================ + // ====================== Qpid/AMQP Options and Flags ================================ /** Holds the name of the property to set the exclusive flag from. */ public static final String EXCLUSIVE_PROPNAME = "exclusive"; @@ -230,7 +236,7 @@ public class MessagingTestConfigProperties extends ParsedProperties /** Default value for the durable destinations flag. */ public static final boolean DURABLE_DESTS_DEFAULT = false; - /** Holds the name of the proeprty to set the prefetch size from. */ + /** Holds the name of the property to set the prefetch size from. */ public static final String PREFETCH_PROPNAME = "prefetch"; /** Defines the default prefetch size to use when consuming messages. */ @@ -274,6 +280,26 @@ public class MessagingTestConfigProperties extends ParsedProperties /** Defines the default maximum quantity of pending message data to allow producers to hold. */ public static final int MAX_PENDING_DEFAULT = 0; + /** Holds the name of the property to get the publisher rollback flag from. */ + public static final String ROLLBACK_PUBLISHER_PROPNAME = "rollbackPublisher"; + + /** Holds the default publisher roll back setting. */ + public static final boolean ROLLBACK_PUBLISHER_DEFAULT = false; + + /** Holds the name of the property to get the publisher rollback flag from. */ + public static final String ROLLBACK_RECEIVER_PROPNAME = "rollbackReceiver"; + + /** Holds the default publisher roll back setting. */ + public static final boolean ROLLBACK_RECEIVER_DEFAULT = false; + + // ====================== Options that control the bahviour of the test framework. ========================= + + /** Holds the name of the property to get the behavioural mode of not applicable assertions. */ + public static final String NOT_APPLICABLE_ASSERTION_PROPNAME = "notApplicableAssertion"; + + /** Holds the default behavioral mode of not applicable assertions, which is logging them as a warning. */ + public static final String NOT_APPLICABLE_ASSERTION_DEFAULT = "warn"; + /** Holds the name of the property to get the verbose mode proeprty from. */ public static final String VERBOSE_PROPNAME = "verbose"; @@ -286,8 +312,11 @@ public class MessagingTestConfigProperties extends ParsedProperties static { defaults.setPropertyIfNull(INITIAL_CONTEXT_FACTORY_PROPNAME, INITIAL_CONTEXT_FACTORY_DEFAULT); - // defaults.setPropertyIfNull(CONNECTION_PROPNAME, CONNECTION_DEFAULT); - defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT); + defaults.setPropertyIfNull(BROKER_PROPNAME, BROKER_DEFAULT); + defaults.setPropertyIfNull(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT); + defaults.setPropertyIfNull(USERNAME_PROPNAME, USERNAME_DEFAULT); + defaults.setPropertyIfNull(PASSWORD_PROPNAME, PASSWORD_DEFAULT); + defaults.setPropertyIfNull(PUBLISHER_PRODUCER_BIND_PROPNAME, PUBLISHER_PRODUCER_BIND_DEFAULT); defaults.setPropertyIfNull(PUBLISHER_CONSUMER_BIND_PROPNAME, PUBLISHER_CONSUMER_BIND_DEFAULT); defaults.setPropertyIfNull(RECEIVER_PRODUCER_BIND_PROPNAME, RECEIVER_PRODUCER_BIND_DEFAULT); @@ -296,28 +325,33 @@ public class MessagingTestConfigProperties extends ParsedProperties defaults.setPropertyIfNull(RECEIVER_CONSUMER_ACTIVE_PROPNAME, RECEIVER_CONSUMER_ACTIVE_DEFAULT); defaults.setPropertyIfNull(SEND_DESTINATION_NAME_ROOT_PROPNAME, SEND_DESTINATION_NAME_ROOT_DEFAULT); defaults.setPropertyIfNull(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME, RECEIVE_DESTINATION_NAME_ROOT_DEFAULT); - defaults.setPropertyIfNull(PERSISTENT_MODE_PROPNAME, PERSISTENT_MODE_DEFAULT); - defaults.setPropertyIfNull(TRANSACTED_PROPNAME, TRANSACTED_DEFAULT); - defaults.setPropertyIfNull(BROKER_PROPNAME, BROKER_DEFAULT); - defaults.setPropertyIfNull(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT); - defaults.setPropertyIfNull(RATE_PROPNAME, RATE_DEFAULT); - defaults.setPropertyIfNull(VERBOSE_PROPNAME, VERBOSE_DEFAULT); - defaults.setPropertyIfNull(PUBSUB_PROPNAME, PUBSUB_DEFAULT); - defaults.setPropertyIfNull(USERNAME_PROPNAME, USERNAME_DEFAULT); - defaults.setPropertyIfNull(PASSWORD_PROPNAME, PASSWORD_DEFAULT); - defaults.setPropertyIfNull(SELECTOR_PROPNAME, SELECTOR_DEFAULT); defaults.setPropertyIfNull(DESTINATION_COUNT_PROPNAME, DESTINATION_COUNT_DEFAULT); - defaults.setPropertyIfNull(TIMEOUT_PROPNAME, TIMEOUT_DEFAULT); - defaults.setPropertyIfNull(TX_BATCH_SIZE_PROPNAME, TX_BATCH_SIZE_DEFAULT); - defaults.setPropertyIfNull(DURABLE_DESTS_PROPNAME, DURABLE_DESTS_DEFAULT); + defaults.setPropertyIfNull(PUBSUB_PROPNAME, PUBSUB_DEFAULT); + + defaults.setPropertyIfNull(PERSISTENT_MODE_PROPNAME, PERSISTENT_MODE_DEFAULT); + defaults.setPropertyIfNull(TRANSACTED_PUBLISHER_PROPNAME, TRANSACTED_PUBLISHER_DEFAULT); + defaults.setPropertyIfNull(TRANSACTED_RECEIVER_PROPNAME, TRANSACTED_RECEIVER_DEFAULT); + defaults.setPropertyIfNull(NO_LOCAL_PROPNAME, NO_LOCAL_DEFAULT); defaults.setPropertyIfNull(ACK_MODE_PROPNAME, ACK_MODE_DEFAULT); defaults.setPropertyIfNull(DURABLE_SUBSCRIPTION_PROPNAME, DURABLE_SUBSCRIPTION_DEFAULT); - defaults.setPropertyIfNull(MAX_PENDING_PROPNAME, MAX_PENDING_DEFAULT); - defaults.setPropertyIfNull(PREFETCH_PROPNAME, PREFETCH_DEFAULT); - defaults.setPropertyIfNull(NO_LOCAL_PROPNAME, NO_LOCAL_DEFAULT); + defaults.setPropertyIfNull(EXCLUSIVE_PROPNAME, EXCLUSIVE_DEFAULT); defaults.setPropertyIfNull(IMMEDIATE_PROPNAME, IMMEDIATE_DEFAULT); defaults.setPropertyIfNull(MANDATORY_PROPNAME, MANDATORY_DEFAULT); + defaults.setPropertyIfNull(DURABLE_DESTS_PROPNAME, DURABLE_DESTS_DEFAULT); + defaults.setPropertyIfNull(PREFETCH_PROPNAME, PREFETCH_DEFAULT); + + defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT); + defaults.setPropertyIfNull(RATE_PROPNAME, RATE_DEFAULT); + defaults.setPropertyIfNull(SELECTOR_PROPNAME, SELECTOR_DEFAULT); + defaults.setPropertyIfNull(TIMEOUT_PROPNAME, TIMEOUT_DEFAULT); + defaults.setPropertyIfNull(TX_BATCH_SIZE_PROPNAME, TX_BATCH_SIZE_DEFAULT); + defaults.setPropertyIfNull(MAX_PENDING_PROPNAME, MAX_PENDING_DEFAULT); + defaults.setPropertyIfNull(ROLLBACK_PUBLISHER_PROPNAME, ROLLBACK_PUBLISHER_DEFAULT); + defaults.setPropertyIfNull(ROLLBACK_RECEIVER_PROPNAME, ROLLBACK_RECEIVER_DEFAULT); + + defaults.setPropertyIfNull(NOT_APPLICABLE_ASSERTION_PROPNAME, NOT_APPLICABLE_ASSERTION_DEFAULT); + defaults.setPropertyIfNull(VERBOSE_PROPNAME, VERBOSE_DEFAULT); } /** @@ -338,148 +372,314 @@ public class MessagingTestConfigProperties extends ParsedProperties super(properties); } + /** + * The size of test messages to send. + * + * @return The size of test messages to send. + */ public int getMessageSize() { return getPropertyAsInteger(MESSAGE_SIZE_PROPNAME); } + /** + * Flag to indicate that the publishing producer should be set up to publish to a destination. + * + * @return Flag to indicate that the publishing producer should be set up to publish to a destination. + */ public boolean getPublisherProducerBind() { return getPropertyAsBoolean(PUBLISHER_PRODUCER_BIND_PROPNAME); } + /** + * Flag to indicate that the publishing consumer should be set up to receive from a destination. + * + * @return Flag to indicate that the publishing consumer should be set up to receive from a destination. + */ public boolean getPublisherConsumerBind() { return getPropertyAsBoolean(PUBLISHER_CONSUMER_BIND_PROPNAME); } + /** + * Flag to indicate that the receiving producer should be set up to publish to a destination. + * + * @return Flag to indicate that the receiving producer should be set up to publish to a destination. + */ public boolean getReceiverProducerBind() { return getPropertyAsBoolean(RECEIVER_PRODUCER_BIND_PROPNAME); } + /** + * Flag to indicate that the receiving consumer should be set up to receive from a destination. + * + * @return Flag to indicate that the receiving consumer should be set up to receive from a destination. + */ public boolean getReceiverConsumerBind() { return getPropertyAsBoolean(RECEIVER_CONSUMER_BIND_PROPNAME); } + /** + * Flag to indicate that the publishing consumer should be created and actively listening. + * + * @return Flag to indicate that the publishing consumer should be created. + */ public boolean getPublisherConsumerActive() { return getPropertyAsBoolean(PUBLISHER_CONSUMER_ACTIVE_PROPNAME); } + /** + * Flag to indicate that the receiving consumers should be created and actively listening. + * + * @return Flag to indicate that the receiving consumers should be created and actively listening. + */ public boolean getReceiverConsumerActive() { return getPropertyAsBoolean(RECEIVER_CONSUMER_ACTIVE_PROPNAME); } + /** + * A root to create all test destination names from. + * + * @return A root to create all test destination names from. + */ public String getSendDestinationNameRoot() { return getProperty(SEND_DESTINATION_NAME_ROOT_PROPNAME); } + /** + * A root to create all receiving destination names from. + * + * @return A root to create all receiving destination names from. + */ public String getReceiveDestinationNameRoot() { return getProperty(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME); } + /** + * Flag to indicate that persistent messages should be used. + * + * @return Flag to indicate that persistent messages should be used. + */ public boolean getPersistentMode() { return getPropertyAsBoolean(PERSISTENT_MODE_PROPNAME); } - public boolean getTransacted() + /** + * Flag to indicate that transactional messages should be sent by the publisher. + * + * @return Flag to indicate that transactional messages should be sent by the publisher. + */ + public boolean getPublisherTransacted() { - return getPropertyAsBoolean(TRANSACTED_PROPNAME); + return getPropertyAsBoolean(TRANSACTED_PUBLISHER_PROPNAME); } - public String getBroker() + /** + * Flag to indicate that transactional receives should be used by the receiver. + * + * @return Flag to indicate that transactional receives should be used by the receiver. + */ + public boolean getReceiverTransacted() { - return getProperty(BROKER_PROPNAME); + return getPropertyAsBoolean(TRANSACTED_PUBLISHER_PROPNAME); } + /** + * The name of the virtual host to run all tests over. + * + * @return The name of the virtual host to run all tests over. + */ public String getVirtualHost() { return getProperty(VIRTUAL_HOST_PROPNAME); } + /** + * Limiting rate for each sender in messages per second, or zero for unlimited. + * + * @return Limiting rate for each sender in messages per second, or zero for unlimited. + */ public String getRate() { return getProperty(RATE_PROPNAME); } + /** + * Flag to indicate that test messages should be received publish/subscribe style by all receivers. + * + * @return Flag to indicate that test messages should be received publish/subscribe style by all receivers. + */ public boolean getPubsub() { return getPropertyAsBoolean(PUBSUB_PROPNAME); } + /** + * The username credentials to run tests with. + * + * @return The username credentials to run tests with. + */ public String getUsername() { return getProperty(USERNAME_PROPNAME); } + /** + * The password credentials to run tests with. + * + * @return The password credentials to run tests with. + */ public String getPassword() { return getProperty(PASSWORD_PROPNAME); } - public int getDestinationCount() - { - return getPropertyAsInteger(DESTINATION_COUNT_PROPNAME); - } - + /** + * The timeout duration to fail tests on, should they receive no messages within it. + * + * @return The timeout duration to fail tests on, should they receive no messages within it. + */ public long getTimeout() { return getPropertyAsLong(TIMEOUT_PROPNAME); } + /** + * The number of messages to batch into each transaction in transational tests. + * + * @return The number of messages to batch into each transaction in transational tests. + */ public int getTxBatchSize() { return getPropertyAsInteger(TX_BATCH_SIZE_PROPNAME); } + /** + * Flag to indicate that tests should use durable destinations. + * + * @return Flag to indicate that tests should use durable destinations. + */ public boolean getDurableDests() { return getPropertyAsBoolean(DURABLE_DESTS_PROPNAME); } + /** + * The ack mode for message receivers to use. + * + * @return The ack mode for message receivers to use. + */ public int getAckMode() { return getPropertyAsInteger(ACK_MODE_PROPNAME); } + /** + * Flag to indicate that tests should use durable subscriptions. + * + * @return Flag to indicate that tests should use durable subscriptions. + */ public boolean getDurableSubscription() { return getPropertyAsBoolean(DURABLE_SUBSCRIPTION_PROPNAME); } + /** + * The maximum amount of in-flight data, in bytes, that tests should send at any time. + * + * @return The maximum amount of in-flight data, in bytes, that tests should send at any time. + */ public int getMaxPending() { return getPropertyAsInteger(MAX_PENDING_PROPNAME); } - public int getPrefecth() + /** + * The size of the prefetch queue to use. + * + * @return The size of the prefetch queue to use. + */ + public int getPrefetch() { return getPropertyAsInteger(PREFETCH_PROPNAME); } + /** + * Flag to indicate that subscriptions should be no-local. + * + * @return Flag to indicate that subscriptions should be no-local. + */ public boolean getNoLocal() { return getPropertyAsBoolean(NO_LOCAL_PROPNAME); } + /** + * Flag to indicate that subscriptions should be exclusive. + * + * @return Flag to indicate that subscriptions should be exclusive. + */ public boolean getExclusive() { return getPropertyAsBoolean(EXCLUSIVE_PROPNAME); } + /** + * Flag to indicate that messages must be delivered immediately. + * + * @return Flag to indicate that messages must be delivered immediately. + */ public boolean getImmediate() { return getPropertyAsBoolean(IMMEDIATE_PROPNAME); } + /** + * Flag to indicate that messages must be routable. + * + * @return Flag to indicate that messages must be routable. + */ public boolean getMandatory() { return getPropertyAsBoolean(MANDATORY_PROPNAME); } + + /** + * Gets the value of a flag to indicate that the publisher should rollback all messages sent. + * + * @return A flag to indicate that the publisher should rollback all messages sent. + */ + public boolean getRollbackPublisher() + { + return getPropertyAsBoolean(ROLLBACK_PUBLISHER_PROPNAME); + } + + /** + * Gets the value of a flag to indicate that the receiver should rollback all messages received, then receive them + * again. + * + * @return A flag to indicate that the publisher should rollback all messages received. + */ + public boolean getRollbackReceiver() + { + return getPropertyAsBoolean(ROLLBACK_RECEIVER_PROPNAME); + } + + /** + * Gets the behavioural mode of not applicable assertions. Should be one of 'quiet', 'warn' or 'fail'. + * + * @return The behavioural mode of not applicable assertions. + */ + public String getNotApplicableAssertionMode() + { + return getProperty(NOT_APPLICABLE_ASSERTION_PROPNAME); + } } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java b/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java new file mode 100644 index 0000000000..63c7fd61c3 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java @@ -0,0 +1,112 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +import org.apache.log4j.Logger; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; + +/** + * NotApplicableAssertion is a messaging assertion that can be used when an assertion requested by a test-case is not + * applicable to the testing scenario. For example an assertion may relate to AMQP functionality, but a test case may be + * being run over a non-AMQP JMS implementation, in which case the request to create the assertion may return this + * instead of the proper assertion. The test framework is configurable to quietly drop these assertions, log them + * as warnings to the console, or raise them as test failures. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Quitely pass. + *
Log a warning. + *
Raise a test failure. + *
+ */ +public class NotApplicableAssertion implements Assertion +{ + /** Used for logging to the console. */ + private static final Logger console = Logger.getLogger("CONSOLE." + NotApplicableAssertion.class.getName()); + + /** The possible behavioural modes of this assertion. */ + private enum Mode + { + /** Quietly ignore the assertion by passing. */ + Quiet, + + /** Ignore the assertion by passing but log a warning about it. */ + Warn, + + /** Fail the assertion. */ + Fail; + } + + /** The behavioural mode of the assertion. */ + private Mode mode; + + /** + * Creates an assertion that is driven by the value of the 'notApplicableAssertion' property of the test + * configuration. Its value should match one of 'quiet', 'warn' or 'fail' and if it does not it is automatically + * read as 'fail'. + * + * @param testProperties The test configuration properties. + */ + public NotApplicableAssertion(ParsedProperties testProperties) + { + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProperties); + + String modeName = props.getNotApplicableAssertionMode(); + + if ("quiet".equals(modeName)) + { + mode = Mode.Quiet; + } + else if ("warn".equals(modeName)) + { + mode = Mode.Warn; + } + else + { + mode = Mode.Fail; + } + } + + /** + * Applies the assertion. + * + * @return true if the assertion passes, false if it fails. + */ + public boolean apply() + { + switch (mode) + { + case Quiet: + return true; + + case Warn: + console.warn("Warning: Not applicable assertion being ignored."); + + return true; + + case Fail: + default: + return false; + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java b/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java index 8e61deedcf..2c8be4f787 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java @@ -20,37 +20,55 @@ */ package org.apache.qpid.test.framework; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** - * A Publisher is a {@link CircuitEnd} that represents the status of the publishing side of a test circuit. Its main - * purpose is to provide assertions that can be applied to test the behaviour of the publishers. + * A Publisher represents the status of the publishing side of a test circuit. Its main purpose is to provide assertions + * that can be applied to test the behaviour of the publishers. * *

*
CRC Card
Responsibilities *
Provide assertion that the publishers received no exceptions. - *
Provide assertion that the publishers received a no consumers error code on every message. - *
Provide assertion that the publishers received a no route error code on every message. *
+ * + * @todo There are mixtures of AMQP and JMS assertions in this interface. Either keep them here, but quietly (or with a + * warning or error) drop them from test cases where they are not relevant, or push them down into sub-classes. + * I am tempted to go with the dropping/warning/error approach, that would imply that it makes sense to pull + * the assertions back from AMQPPublisher to here. */ public interface Publisher { + // Assertions that are meaningfull to AMQP and to JMS. + /** * Provides an assertion that the publisher encountered no exceptions. * + * @param testProps The test configuration properties. + * * @return An assertion that the publisher encountered no exceptions. */ - public Assertion noExceptionsAssertion(); + public Assertion noExceptionsAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to AMQP. /** - * Provides an assertion that the publisher got a no consumers exception on every message. + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. * - * @return An assertion that the publisher got a no consumers exception on every message. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. */ - public Assertion noConsumersAssertion(); + public Assertion channelClosedAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to Java/JMS. /** - * Provides an assertion that the publisher got a no rout exception on every message. + * Provides an assertion that the publisher got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. * - * @return An assertion that the publisher got a no rout exception on every message. + * @return An assertion that the publisher got a given exception during the test. */ - public Assertion noRouteAssertion(); + public Assertion exceptionAssertion(ParsedProperties testProps, Class exceptionClass); } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java b/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java index f1343b9997..19dc4d90e7 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java @@ -20,6 +20,8 @@ */ package org.apache.qpid.test.framework; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** * A Receiver is a {@link CircuitEnd} that represents the status of the receiving side of a test circuit. Its main * purpose is to provide assertions that can be applied to check the behaviour of the receivers. @@ -29,20 +31,62 @@ package org.apache.qpid.test.framework; * Provide assertion that the receivers received no exceptions. * Provide assertion that the receivers received all test messages sent to it. * + * + * @todo There are mixtures of AMQP and JMS assertions in this interface. Either keep them here, but quietly (or with a + * warning or error) drop them from test cases where they are not relevant, or push them down into sub-classes. + * I am tempted to go with the dropping/warning/error approach. */ public interface Receiver { + // Assertions that are meaningfull to AMQP and to JMS. + /** * Provides an assertion that the receivers encountered no exceptions. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers encountered no exceptions. */ - public Assertion noExceptionsAssertion(); + public Assertion noExceptionsAssertion(ParsedProperties testProps); /** * Provides an assertion that the receivers got all messages that were sent to it. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers got all messages that were sent to it. */ - public Assertion allMessagesAssertion(); + public Assertion allMessagesReceivedAssertion(ParsedProperties testProps); + + /** + * Provides an assertion that the receivers got none of the messages that were sent to it. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the receivers got none of the messages that were sent to it. + */ + public Assertion noMessagesReceivedAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to AMQP. + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps); + + // Assertions that are meaningfull only to Java/JMS. + + /** + * Provides an assertion that the receiver got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. + * + * @return An assertion that the receiver got a given exception during the test. + */ + public Assertion exceptionAssertion(ParsedProperties testProps, Class exceptionClass); } diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java b/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java new file mode 100644 index 0000000000..0518a827ba --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework; + +/** + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ */ +public class TestCaseVector +{ + /** The test case name. */ + private String testCase; + + /** The test cycle number within the test case. */ + private int testCycleNumber; + + public TestCaseVector(String testCase, int testCycleNumber) + { + this.testCase = testCase; + this.testCycleNumber = testCycleNumber; + } + + public String getTestCase() + { + return testCase; + } + + public int getTestCycleNumber() + { + return testCycleNumber; + } + + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if ((o == null) || (getClass() != o.getClass())) + { + return false; + } + + TestCaseVector that = (TestCaseVector) o; + + if (testCycleNumber != that.testCycleNumber) + { + return false; + } + + if ((testCase != null) ? (!testCase.equals(that.testCase)) : (that.testCase != null)) + { + return false; + } + + return true; + } + + public int hashCode() + { + int result; + result = ((testCase != null) ? testCase.hashCode() : 0); + result = (31 * result) + testCycleNumber; + + return result; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java b/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java index c855ee21fa..d7a6f83527 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java @@ -24,7 +24,7 @@ import org.apache.log4j.Logger; import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.*; import javax.naming.Context; @@ -32,7 +32,6 @@ import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Map; -import java.util.Properties; /** * TestUtils provides static helper methods that are usefull for writing tests against QPid. @@ -40,7 +39,9 @@ import java.util.Properties; *

*
CRC Card
Responsibilities Collaborations *
Create connections from test properties. {@link MessagingTestConfigProperties} + *
Create test messages. *
Inject a short pause in a test. + *
Serialize properties into a message. *
*/ public class TestUtils @@ -48,7 +49,8 @@ public class TestUtils /** Used for debugging. */ private static Logger log = Logger.getLogger(TestUtils.class); - private static byte[] MESSAGE_DATA_BYTES = + /** Some dummy data to stuff all test messages with. */ + private static final byte[] MESSAGE_DATA_BYTES = "Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- " .getBytes(); @@ -118,7 +120,7 @@ public class TestUtils * * @return A bytes message, of the specified size, filled with dummy data. * - * + * @throws JMSException Any underlying JMSExceptions are allowed to fall through. */ public static Message createTestMessageOfSize(Session session, int size) throws JMSException { @@ -187,3 +189,4 @@ public class TestUtils } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java index 51b839d51e..00cc2d8966 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java @@ -21,8 +21,8 @@ package org.apache.qpid.test.framework.clocksynch; /** - * ClockSynchFailureException represents failure of a {@link ClockSynchronizer} to achieve synchronization. This could - * be because a reference signal is not available, or because a desired accurracy cannot be attained, for example. + * ClockSynchFailureException represents failure of a {@link ClockSynchronizer} to achieve synchronization. For example, + * this could be because a reference signal is not available, or because a desired accurracy cannot be attained. * *

*
CRC Card
Responsibilities Collaborations diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java index 88f0043067..3d4c4f7d12 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java @@ -22,8 +22,8 @@ package org.apache.qpid.test.framework.clocksynch; import org.apache.log4j.Logger; -import uk.co.thebadgerset.junit.extensions.ShutdownHookable; -import uk.co.thebadgerset.junit.extensions.Throttle; +import org.apache.qpid.junit.extensions.ShutdownHookable; +import org.apache.qpid.junit.extensions.Throttle; /** * ClockSynchThread is a convenient utility for running a thread that periodically synchronizes the clock against @@ -121,3 +121,4 @@ public class ClockSynchThread extends Thread implements ShutdownHookable }); } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java index 96485edbea..a92c551bc2 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java @@ -32,7 +32,7 @@ package org.apache.qpid.test.framework.clocksynch; * *

*
CRC Card
Responsibilities Collaborations - *
Trigger a clock synchronziation. + *
Trigger a clock synchronization. *
Compute a clock delta to apply to the local clock. *
Estimate the error in the synchronzation. *
diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java index 8b68097158..8bce752f68 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java @@ -20,14 +20,12 @@ */ package org.apache.qpid.test.framework.clocksynch; -import org.apache.log4j.Logger; +import org.apache.qpid.junit.extensions.ShutdownHookable; import java.io.IOException; import java.net.*; import java.nio.ByteBuffer; -import uk.co.thebadgerset.junit.extensions.ShutdownHookable; - /** * UDPClockReference supplies a refernce clock signal (generated from System.nanoTime()). * @@ -49,7 +47,7 @@ public class UDPClockReference implements Runnable, ShutdownHookable private static final int TIMEOUT = 200; /** Defines the port to run the clock reference on. */ - public static final int REFERENCE_PORT = 4445; + public static final int REFERENCE_PORT = 4444; /** Holds the socket to receive clock reference requests on. */ protected DatagramSocket socket = null; @@ -164,3 +162,4 @@ public class UDPClockReference implements Runnable, ShutdownHookable } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java index a5ae0a0db6..c89112eff8 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java @@ -20,10 +20,8 @@ */ package org.apache.qpid.test.framework.clocksynch; -import org.apache.log4j.Logger; - -import uk.co.thebadgerset.junit.extensions.util.CommandLineParser; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.CommandLineParser; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import java.io.IOException; import java.net.*; @@ -462,3 +460,4 @@ public class UDPClockSynchronizer implements ClockSynchronizer } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java index 159f364825..aefeb17d59 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java @@ -25,9 +25,9 @@ import org.apache.log4j.Logger; import org.apache.qpid.test.framework.*; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.TimingController; -import uk.co.thebadgerset.junit.extensions.TimingControllerAware; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.TimingController; +import org.apache.qpid.junit.extensions.TimingControllerAware; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.Destination; import javax.jms.JMSException; @@ -232,7 +232,7 @@ public class DistributedCircuitImpl implements Circuit, TimingControllerAware } /** - * Used by tests cases that can supply a {@link uk.co.thebadgerset.junit.extensions.TimingController} to set the + * Used by tests cases that can supply a {@link org.apache.qpid.junit.extensions.TimingController} to set the * controller on an aware test. * * @param controller The timing controller. diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java index 810e1ae685..c51f710494 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java @@ -23,10 +23,17 @@ package org.apache.qpid.test.framework.distributedcircuit; import org.apache.qpid.test.framework.Assertion; import org.apache.qpid.test.framework.Publisher; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** + * DistributedPublisherImpl represents the status of the publishing side of a test circuit. Its main purpose is to + * provide assertions that can be applied to verify the behaviour of a non-local publisher. + * *

*
CRC Card
Responsibilities Collaborations - *
+ *
Provide assertion that the publishers received no exceptions. + *
Provide assertion that the publishers received a no consumers error code on every message. + *
Provide assertion that the publishers received a no route error code on every message. *
*/ public class DistributedPublisherImpl implements Publisher @@ -35,8 +42,9 @@ public class DistributedPublisherImpl implements Publisher * Provides an assertion that the publisher encountered no exceptions. * * @return An assertion that the publisher encountered no exceptions. + * @param testProps */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) { throw new RuntimeException("Not implemented."); } @@ -60,4 +68,28 @@ public class DistributedPublisherImpl implements Publisher { throw new RuntimeException("Not implemented."); } + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the publisher got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. + * @return An assertion that the publisher got a given exception during the test. + */ + public Assertion exceptionAssertion(ParsedProperties testProps, Class exceptionClass) + { + throw new RuntimeException("Not implemented."); + } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java index db85921b85..863921e387 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java @@ -23,10 +23,16 @@ package org.apache.qpid.test.framework.distributedcircuit; import org.apache.qpid.test.framework.Assertion; import org.apache.qpid.test.framework.Receiver; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + /** + * DistributedReceiverImpl represents the status of the receiving side of a test circuit. Its main purpose is to + * provide assertions that can be applied to verify the behaviour of a non-local receiver. + * *

*
CRC Card
Responsibilities Collaborations - *
+ *
Provide assertion that the receivers received no exceptions. + *
Provide assertion that the receivers received all test messages sent to it. *
*/ public class DistributedReceiverImpl implements Receiver @@ -35,8 +41,9 @@ public class DistributedReceiverImpl implements Receiver * Provides an assertion that the receivers encountered no exceptions. * * @return An assertion that the receivers encountered no exceptions. + * @param testProps */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) { throw new RuntimeException("Not implemented."); } @@ -45,9 +52,44 @@ public class DistributedReceiverImpl implements Receiver * Provides an assertion that the receivers got all messages that were sent to it. * * @return An assertion that the receivers got all messages that were sent to it. + * @param testProps + */ + public Assertion allMessagesReceivedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the receivers got none of the messages that were sent to it. + * + * @return An assertion that the receivers got none of the messages that were sent to it. + * @param testProps + */ + public Assertion noMessagesReceivedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps) + { + throw new RuntimeException("Not implemented."); + } + + /** + * Provides an assertion that the receiver got a given exception during the test. + * + * @param testProps + *@param exceptionClass The exception class to check for. @return An assertion that the receiver got a given exception during the test. */ - public Assertion allMessagesAssertion() + public Assertion exceptionAssertion(ParsedProperties testProps, Class exceptionClass) { throw new RuntimeException("Not implemented."); } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java index 95f02aa70e..dce2706bc4 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java @@ -24,10 +24,9 @@ import org.apache.log4j.Logger; import org.apache.qpid.test.framework.*; import org.apache.qpid.test.framework.distributedtesting.TestClientControlledTest; -import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; import javax.jms.*; @@ -48,7 +47,15 @@ import javax.jms.*; * *

*
CRC Card
Responsibilities Collaborations - *
+ *
Provide a message producer for sending messages. + * {@link CircuitEnd}, {@link LocalCircuitFactory}, {@link TestUtils} + *
Provide a message consumer for receiving messages. + * {@link CircuitEnd}, {@link LocalCircuitFactory}, {@link TestUtils} + *
Supply the name of the test case that this implements. + *
Accept/Reject invites based on test parameters. {@link MessagingTestConfigProperties} + *
Adapt to assigned roles. {@link TestClientControlledTest.Roles} + *
Perform test case actions. {@link MessageMonitor} + *
Generate test reports. {@link MessageMonitor} *
*/ public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTest @@ -145,13 +152,15 @@ public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTes connection = TestUtils.createConnection(testProps); // Create a circuit end that matches the assigned role and test parameters. + LocalCircuitFactory circuitFactory = new LocalCircuitFactory(); + switch (role) { // Check if the sender role is being assigned, and set up a message producer if so. case SENDER: // Set up the publisher. - circuitEnd = LocalCircuitImpl.createPublisherCircuitEnd(connection, testProps, 0L); + circuitEnd = circuitFactory.createPublisherCircuitEnd(connection, testProps, 0L); // Create a custom message monitor that will be updated on every message sent. messageMonitor = new MessageMonitor(); @@ -162,7 +171,7 @@ public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTes case RECEIVER: // Set up the receiver. - circuitEnd = LocalCircuitImpl.createReceiverCircuitEnd(connection, testProps, 0L); + circuitEnd = circuitFactory.createReceiverCircuitEnd(connection, testProps, 0L); // Use the message monitor from the consumer for stats. messageMonitor = getMessageMonitor(); diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java index f79cde9dcc..55f05ec6f2 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java @@ -20,33 +20,31 @@ */ package org.apache.qpid.test.framework.distributedtesting; +import java.net.InetAddress; +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; + +import javax.jms.*; + import junit.framework.Test; import junit.framework.TestResult; import junit.framework.TestSuite; import org.apache.log4j.Logger; import org.apache.log4j.NDC; - import org.apache.qpid.test.framework.FrameworkBaseCase; import org.apache.qpid.test.framework.MessagingTestConfigProperties; import org.apache.qpid.test.framework.TestClientDetails; import org.apache.qpid.test.framework.TestUtils; import org.apache.qpid.test.framework.clocksynch.UDPClockReference; import org.apache.qpid.util.ConversationFactory; -import org.apache.qpid.util.PrettyPrintingUtils; - -import uk.co.thebadgerset.junit.extensions.TKTestRunner; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; -import uk.co.thebadgerset.junit.extensions.util.CommandLineParser; -import uk.co.thebadgerset.junit.extensions.util.MathUtils; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; -import uk.co.thebadgerset.junit.extensions.util.TestContextProperties; - -import javax.jms.*; -import java.net.InetAddress; -import java.util.*; -import java.util.concurrent.LinkedBlockingQueue; +import org.apache.qpid.junit.extensions.TKTestRunner; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.util.CommandLineParser; +import org.apache.qpid.junit.extensions.util.MathUtils; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; /** *

Implements the coordinator client described in the interop testing specification @@ -56,7 +54,7 @@ import java.util.concurrent.LinkedBlockingQueue; *

*
CRC Card
Responsibilities Collaborations *
Find out what test clients are available. {@link ConversationFactory} - *
Decorate available tests to run all available clients. {@link DistributedTestDecorator} + *
Decorate available tests to run on all available clients. {@link DistributedTestDecorator} *
Attach XML test result logger. *
Terminate the interop testing framework. *
@@ -64,8 +62,6 @@ import java.util.concurrent.LinkedBlockingQueue; * @todo Should accumulate failures over all tests, and return with success or fail code based on all results. May need * to write a special TestResult to do this properly. At the moment only the last one used will be tested for * errors, as the start method creates a fresh one for each test case run. - * - * @todo Remove hard coding of test cases and put on command line instead. */ public class Coordinator extends TKTestRunner { @@ -119,27 +115,28 @@ public class Coordinator extends TKTestRunner /** * Creates an interop test coordinator on the specified broker and virtual host. * - * @param repetitions The number of times to repeat the test, or test batch size. - * @param duration The length of time to run the tests for. -1 means no duration has been set. - * @param threads The concurrency levels to ramp up to. - * @param delay A delay in milliseconds between test runs. - * @param params The sets of 'size' parameters to pass to test. - * @param testCaseName The name of the test case to run. - * @param reportDir The directory to output the test results to. - * @param runName The name of the test run; used to name the output file. - * @param verbose Whether to print comments during test run. - * @param brokerUrl The URL of the broker to connect to. - * @param virtualHost The virtual host to run all tests on. Optional, may be null. - * @param engine The distributed test engine type to run the tests with. - * @param terminate true if test client nodes should be terminated at the end of the tests. - * @param csv true if the CSV results listener should be attached. - * @param xml true if the XML results listener should be attached. + * @param repetitions The number of times to repeat the test, or test batch size. + * @param duration The length of time to run the tests for. -1 means no duration has been set. + * @param threads The concurrency levels to ramp up to. + * @param delay A delay in milliseconds between test runs. + * @param params The sets of 'size' parameters to pass to test. + * @param testCaseName The name of the test case to run. + * @param reportDir The directory to output the test results to. + * @param runName The name of the test run; used to name the output file. + * @param verbose Whether to print comments during test run. + * @param brokerUrl The URL of the broker to connect to. + * @param virtualHost The virtual host to run all tests on. Optional, may be null. + * @param engine The distributed test engine type to run the tests with. + * @param terminate true if test client nodes should be terminated at the end of the tests. + * @param csv true if the CSV results listener should be attached. + * @param xml true if the XML results listener should be attached. + * @param decoratorFactories List of factories for user specified decorators. */ public Coordinator(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName, String reportDir, String runName, boolean verbose, String brokerUrl, String virtualHost, TestEngine engine, - boolean terminate, boolean csv, boolean xml) + boolean terminate, boolean csv, boolean xml, List decoratorFactories) { - super(repetitions, duration, threads, delay, params, testCaseName, reportDir, runName, csv, xml, verbose); + super(repetitions, duration, threads, delay, params, testCaseName, reportDir, runName, csv, xml, decoratorFactories); log.debug("public Coordinator(Integer repetitions = " + repetitions + " , Long duration = " + duration + ", int[] threads = " + Arrays.toString(threads) + ", int delay = " + delay + ", int[] params = " @@ -221,7 +218,11 @@ public class Coordinator extends TKTestRunner }, { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP }, { "v", "Verbose mode.", null, "false" }, - { "n", "A name for this test run, used to name the output file.", "name", "true" } + { "n", "A name for this test run, used to name the output file.", "name", "true" }, + { + "X:decorators", "A list of additional test decorators to wrap the tests in.", + "\"class.name[:class.name]*\"", "false" + } }), testContextProperties)); // Extract the command line options. @@ -234,13 +235,13 @@ public class Coordinator extends TKTestRunner boolean terminate = options.getPropertyAsBoolean("t"); boolean csvResults = options.getPropertyAsBoolean("-csv"); boolean xmlResults = options.getPropertyAsBoolean("-xml"); - String threadsString = options.getProperty("c"); Integer repetitions = options.getPropertyAsInteger("r"); String durationString = options.getProperty("d"); String paramsString = options.getProperty("s"); boolean verbose = options.getPropertyAsBoolean("v"); String testRunName = options.getProperty("n"); + String decorators = options.getProperty("X:decorators"); int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString); int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString); @@ -253,6 +254,9 @@ public class Coordinator extends TKTestRunner Collection> testCaseClasses = new ArrayList>(); + // Create a list of test decorator factories for use specified decorators to be applied. + List decoratorFactories = parseDecorators(decorators); + // Scan for available test cases using a classpath scanner. // ClasspathScanner.getMatches(DistributedTestCase.class, "^Test.*", true); @@ -306,7 +310,7 @@ public class Coordinator extends TKTestRunner // Create a coordinator and begin its test procedure. Coordinator coordinator = new Coordinator(repetitions, duration, threads, 0, params, null, reportDir, testRunName, verbose, brokerUrl, - virtualHost, engine, terminate, csvResults, xmlResults); + virtualHost, engine, terminate, csvResults, xmlResults, decoratorFactories); TestResult testResult = coordinator.start(testClassNames); @@ -324,6 +328,7 @@ public class Coordinator extends TKTestRunner { log.debug("Top level handler caught execption.", e); console.info(e.getMessage()); + e.printStackTrace(); System.exit(EXCEPTION_EXIT); } } @@ -339,8 +344,7 @@ public class Coordinator extends TKTestRunner */ public TestResult start(String[] testClassNames) throws Exception { - log.debug("public TestResult start(String[] testClassNames = " + PrettyPrintingUtils.printArray(testClassNames) - + ": called"); + log.debug("public TestResult start(String[] testClassNames = " + Arrays.toString(testClassNames) + ": called"); // Connect to the broker. connection = TestUtils.createConnection(TestContextProperties.getInstance()); @@ -474,7 +478,7 @@ public class Coordinator extends TKTestRunner { log.debug("targetTest is a TestSuite"); - TestSuite suite = (TestSuite) test; + TestSuite suite = (TestSuite)test; int numTests = suite.countTestCases(); log.debug("There are " + numTests + " in the suite."); @@ -494,6 +498,9 @@ public class Coordinator extends TKTestRunner log.debug("Wrapped with a WrappedSuiteTestDecorator."); } + // Apply any optional user specified decorators. + targetTest = applyOptionalUserDecorators(targetTest); + // Wrap the tests in a suitable distributed test decorator, to perform the invite/test cycle. targetTest = newTestDecorator(targetTest, enlistedClients, conversationFactory, connection); diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java index d11348cbad..d2f8ca896c 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java @@ -29,7 +29,7 @@ import org.apache.qpid.test.framework.TestClientDetails; import org.apache.qpid.test.framework.sequencers.CircuitFactory; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; import javax.jms.Connection; import javax.jms.Destination; diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java index 6fc3b2937e..d6ae390a4a 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java @@ -32,14 +32,13 @@ import org.apache.qpid.test.framework.sequencers.CircuitFactory; import org.apache.qpid.test.framework.sequencers.FanOutCircuitFactory; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; import javax.jms.Connection; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; -import java.util.Collection; import java.util.Iterator; import java.util.Set; diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java index 130173cc96..38ab66d6ae 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java @@ -31,7 +31,7 @@ import org.apache.qpid.test.framework.sequencers.CircuitFactory; import org.apache.qpid.test.framework.sequencers.InteropCircuitFactory; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.WrappedSuiteTestDecorator; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; import javax.jms.Connection; @@ -50,7 +50,7 @@ import java.util.*; *

Broadcast test invitations and collect enlists. {@link org.apache.qpid.util.ConversationFactory}. *
Output test failures for clients unwilling to run the test case. {@link Coordinator} *
Execute distributed test cases. {@link FrameworkBaseCase} - *
Fail non participating pairings. {@link OptOutTestCase} + *
Fail non-participating pairings. {@link OptOutTestCase} *
*/ public class InteropTestDecorator extends DistributedTestDecorator @@ -206,3 +206,4 @@ public class InteropTestDecorator extends DistributedTestDecorator } } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java new file mode 100644 index 0000000000..1c138fe575 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java @@ -0,0 +1,497 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework.distributedtesting; + +import org.apache.log4j.Logger; +import org.apache.log4j.NDC; + +import org.apache.qpid.test.framework.MessagingTestConfigProperties; +import org.apache.qpid.test.framework.TestUtils; +import org.apache.qpid.test.framework.clocksynch.ClockSynchThread; +import org.apache.qpid.test.framework.clocksynch.UDPClockSynchronizer; +import org.apache.qpid.util.ReflectionUtils; +import org.apache.qpid.util.ReflectionUtilsException; + +import org.apache.qpid.junit.extensions.SleepThrottle; +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; + +import javax.jms.*; + +import java.util.*; + +/** + * Implements a test client as described in the interop testing spec + * (http://cwiki.apache.org/confluence/display/qpid/Interop+Testing+Specification). A test client is an agent that + * reacts to control message sequences send by the test {@link Coordinator}. + * + *

+ *
Messages Handled by TestClient
Message Action + *
Invite(compulsory) Reply with Enlist. + *
Invite(test case) Reply with Enlist if test case available. + *
AssignRole(test case) Reply with Accept Role if matches an enlisted test. Keep test parameters. + *
Start Send test messages defined by test parameters. Send report on messages sent. + *
Status Request Send report on messages received. + *
Terminate Terminate the test client. + *
ClockSynch Synch clock against the supplied UDP address. + *
+ * + *

+ *
CRC Card
Responsibilities Collaborations + *
Handle all incoming control messages. {@link TestClientControlledTest} + *
Configure and look up test cases by name. {@link TestClientControlledTest} + *
+ */ +public class TestClient implements MessageListener +{ + /** Used for debugging. */ + private static final Logger log = Logger.getLogger(TestClient.class); + + /** Used for reporting to the console. */ + private static final Logger console = Logger.getLogger("CONSOLE"); + + /** Holds the default identifying name of the test client. */ + public static final String CLIENT_NAME = "java"; + + /** Holds the URL of the broker to run the tests on. */ + public static String brokerUrl; + + /** Holds the virtual host to run the tests on. If null, then the default virtual host is used. */ + public static String virtualHost; + + /** + * Holds the test context properties that provides the default test parameters, plus command line overrides. + * This is initialized with the default test parameters, to which command line overrides may be applied. + */ + public static ParsedProperties testContextProperties = + TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + /** Holds all the test cases loaded from the classpath. */ + Map testCases = new HashMap(); + + /** Holds the test case currently being run by this client. */ + protected TestClientControlledTest currentTestCase; + + /** Holds the connection to the broker that the test is being coordinated on. */ + protected Connection connection; + + /** Holds the message producer to hold the test coordination over. */ + protected MessageProducer producer; + + /** Holds the JMS controlSession for the test coordination. */ + protected Session session; + + /** Holds the name of this client, with a default value. */ + protected String clientName = CLIENT_NAME; + + /** This flag indicates that the test client should attempt to join the currently running test case on start up. */ + protected boolean join; + + /** Holds the clock synchronizer for the test node. */ + ClockSynchThread clockSynchThread; + + /** + * Creates a new interop test client, listenting to the specified broker and virtual host, with the specified client + * identifying name. + * + * @param pBrokerUrl The url of the broker to connect to. + * @param pVirtualHost The virtual host to conect to. + * @param clientName The client name to use. + * @param join Flag to indicate that this client should attempt to join running tests. + */ + public TestClient(String pBrokerUrl, String pVirtualHost, String clientName, boolean join) + { + log.debug("public TestClient(String pBrokerUrl = " + pBrokerUrl + ", String pVirtualHost = " + pVirtualHost + + ", String clientName = " + clientName + ", boolean join = " + join + "): called"); + + // Retain the connection parameters. + brokerUrl = pBrokerUrl; + virtualHost = pVirtualHost; + this.clientName = clientName; + this.join = join; + } + + /** + * The entry point for the interop test coordinator. This client accepts the following command line arguments: + * + *

+ *
-b The broker URL. Optional. + *
-h The virtual host. Optional. + *
-n The test client name. Optional. + *
name=value Trailing argument define name/value pairs. Added to system properties. Optional. + *
+ * + * @param args The command line arguments. + */ + public static void main(String[] args) + { + log.debug("public static void main(String[] args = " + Arrays.toString(args) + "): called"); + console.info("Qpid Distributed Test Client."); + + // Override the default broker url to be localhost:5672. + testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, "tcp://localhost:5672"); + + // Use the command line parser to evaluate the command line with standard handling behaviour (print errors + // and usage then exist if there are errors). + // Any options and trailing name=value pairs are also injected into the test context properties object, + // to override any defaults that may have been set up. + ParsedProperties options = + new ParsedProperties(org.apache.qpid.junit.extensions.util.CommandLineParser.processCommandLine(args, + new org.apache.qpid.junit.extensions.util.CommandLineParser( + new String[][] + { + { "b", "The broker URL.", "broker", "false" }, + { "h", "The virtual host to use.", "virtual host", "false" }, + { "o", "The name of the directory to output test timings to.", "dir", "false" }, + { "n", "The name of the test client.", "name", "false" }, + { "j", "Join this test client to running test.", "false" } + }), testContextProperties)); + + // Extract the command line options. + String brokerUrl = options.getProperty("b"); + String virtualHost = options.getProperty("h"); + String clientName = options.getProperty("n"); + clientName = (clientName == null) ? CLIENT_NAME : clientName; + boolean join = options.getPropertyAsBoolean("j"); + + // To distinguish logging output set up an NDC on the client name. + NDC.push(clientName); + + // Create a test client and start it running. + TestClient client = new TestClient(brokerUrl, virtualHost, clientName, join); + + // Use a class path scanner to find all the interop test case implementations. + // Hard code the test classes till the classpath scanner is fixed. + Collection> testCaseClasses = + new ArrayList>(); + // ClasspathScanner.getMatches(TestClientControlledTest.class, "^TestCase.*", true); + testCaseClasses.addAll(loadTestCases("org.apache.qpid.interop.clienttestcases.TestCase1DummyRun", + "org.apache.qpid.interop.clienttestcases.TestCase2BasicP2P", + "org.apache.qpid.interop.clienttestcases.TestCase3BasicPubSub", + "org.apache.qpid.interop.clienttestcases.TestCase4P2PMessageSize", + "org.apache.qpid.interop.clienttestcases.TestCase5PubSubMessageSize", + "org.apache.qpid.test.framework.distributedcircuit.TestClientCircuitEnd")); + + try + { + client.start(testCaseClasses); + } + catch (Exception e) + { + log.error("The test client was unable to start.", e); + console.info(e.getMessage()); + System.exit(1); + } + } + + /** + * Parses a list of class names, and loads them if they are available on the class path. + * + * @param classNames The names of the classes to load. + * + * @return A list of the loaded test case classes. + */ + public static List> loadTestCases(String... classNames) + { + List> testCases = + new LinkedList>(); + + for (String className : classNames) + { + try + { + Class cls = ReflectionUtils.forName(className); + testCases.add((Class) cls); + } + catch (ReflectionUtilsException e) + { + // Ignore, class could not be found, so test not available. + console.warn("Requested class " + className + " cannot be found, ignoring it."); + } + catch (ClassCastException e) + { + // Ignore, class was not of correct type to be a test case. + console.warn("Requested class " + className + " is not an instance of TestClientControlledTest."); + } + } + + return testCases; + } + + /** + * Starts the interop test client running. This causes it to start listening for incoming test invites. + * + * @param testCaseClasses The classes of the available test cases. The test case names from these are used to + * matchin incoming test invites against. + * + * @throws JMSException Any underlying JMSExceptions are allowed to fall through. + */ + protected void start(Collection> testCaseClasses) throws JMSException + { + log.debug("protected void start(Collection> testCaseClasses = " + + testCaseClasses + "): called"); + + // Create all the test case implementations and index them by the test names. + for (Class nextClass : testCaseClasses) + { + try + { + TestClientControlledTest testCase = nextClass.newInstance(); + testCases.put(testCase.getName(), testCase); + } + catch (InstantiationException e) + { + log.warn("Could not instantiate test case class: " + nextClass.getName(), e); + // Ignored. + } + catch (IllegalAccessException e) + { + log.warn("Could not instantiate test case class due to illegal access: " + nextClass.getName(), e); + // Ignored. + } + } + + // Open a connection to communicate with the coordinator on. + connection = TestUtils.createConnection(testContextProperties); + session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + + // Set this up to listen for control messages. + Topic privateControlTopic = session.createTopic("iop.control." + clientName); + MessageConsumer consumer = session.createConsumer(privateControlTopic); + consumer.setMessageListener(this); + + Topic controlTopic = session.createTopic("iop.control"); + MessageConsumer consumer2 = session.createConsumer(controlTopic); + consumer2.setMessageListener(this); + + // Create a producer to send replies with. + producer = session.createProducer(null); + + // If the join flag was set, then broadcast a join message to notify the coordinator that a new test client + // is available to join the current test case, if it supports it. This message may be ignored, or it may result + // in this test client receiving a test invite. + if (join) + { + Message joinMessage = session.createMessage(); + + joinMessage.setStringProperty("CONTROL_TYPE", "JOIN"); + joinMessage.setStringProperty("CLIENT_NAME", clientName); + joinMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName); + producer.send(controlTopic, joinMessage); + } + + // Start listening for incoming control messages. + connection.start(); + } + + /** + * Handles all incoming control messages. + * + * @param message The incoming message. + */ + public void onMessage(Message message) + { + NDC.push(clientName); + log.debug("public void onMessage(Message message = " + message + "): called"); + + try + { + String controlType = message.getStringProperty("CONTROL_TYPE"); + String testName = message.getStringProperty("TEST_NAME"); + + log.debug("Received control of type '" + controlType + "' for the test '" + testName + "'"); + + // Check if the message is a test invite. + if ("INVITE".equals(controlType)) + { + // Flag used to indicate that an enlist should be sent. Only enlist to compulsory invites or invites + // for which test cases exist. + boolean enlist = false; + + if (testName != null) + { + log.debug("Got an invite to test: " + testName); + + // Check if the requested test case is available. + TestClientControlledTest testCase = testCases.get(testName); + + if (testCase != null) + { + log.debug("Found implementing class for test '" + testName + "', enlisting for it."); + + // Check if the test case will accept the invitation. + enlist = testCase.acceptInvite(message); + + log.debug("The test case " + + (enlist ? " accepted the invite, enlisting for it." + : " did not accept the invite, not enlisting.")); + + // Make the requested test case the current test case. + currentTestCase = testCase; + } + else + { + log.debug("Received an invite to the test '" + testName + "' but this test is not known."); + } + } + else + { + log.debug("Got a compulsory invite, enlisting for it."); + + enlist = true; + } + + if (enlist) + { + // Reply with the client name in an Enlist message. + Message enlistMessage = session.createMessage(); + enlistMessage.setStringProperty("CONTROL_TYPE", "ENLIST"); + enlistMessage.setStringProperty("CLIENT_NAME", clientName); + enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName); + enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID()); + + log.debug("Sending enlist message '" + enlistMessage + "' to " + message.getJMSReplyTo()); + + producer.send(message.getJMSReplyTo(), enlistMessage); + } + else + { + // Reply with the client name in an Decline message. + Message enlistMessage = session.createMessage(); + enlistMessage.setStringProperty("CONTROL_TYPE", "DECLINE"); + enlistMessage.setStringProperty("CLIENT_NAME", clientName); + enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName); + enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID()); + + log.debug("Sending decline message '" + enlistMessage + "' to " + message.getJMSReplyTo()); + + producer.send(message.getJMSReplyTo(), enlistMessage); + } + } + else if ("ASSIGN_ROLE".equals(controlType)) + { + // Assign the role to the current test case. + String roleName = message.getStringProperty("ROLE"); + + log.debug("Got a role assignment to role: " + roleName); + + TestClientControlledTest.Roles role = Enum.valueOf(TestClientControlledTest.Roles.class, roleName); + + currentTestCase.assignRole(role, message); + + // Reply by accepting the role in an Accept Role message. + Message acceptRoleMessage = session.createMessage(); + acceptRoleMessage.setStringProperty("CLIENT_NAME", clientName); + acceptRoleMessage.setStringProperty("CONTROL_TYPE", "ACCEPT_ROLE"); + acceptRoleMessage.setJMSCorrelationID(message.getJMSCorrelationID()); + + log.debug("Sending accept role message '" + acceptRoleMessage + "' to " + message.getJMSReplyTo()); + + producer.send(message.getJMSReplyTo(), acceptRoleMessage); + } + else if ("START".equals(controlType) || "STATUS_REQUEST".equals(controlType)) + { + if ("START".equals(controlType)) + { + log.debug("Got a start notification."); + + // Extract the number of test messages to send from the start notification. + int numMessages; + + try + { + numMessages = message.getIntProperty("MESSAGE_COUNT"); + } + catch (NumberFormatException e) + { + // If the number of messages is not specified, use the default of one. + numMessages = 1; + } + + // Start the current test case. + currentTestCase.start(numMessages); + } + else + { + log.debug("Got a status request."); + } + + // Generate the report from the test case and reply with it as a Report message. + Message reportMessage = currentTestCase.getReport(session); + reportMessage.setStringProperty("CLIENT_NAME", clientName); + reportMessage.setStringProperty("CONTROL_TYPE", "REPORT"); + reportMessage.setJMSCorrelationID(message.getJMSCorrelationID()); + + log.debug("Sending report message '" + reportMessage + "' to " + message.getJMSReplyTo()); + + producer.send(message.getJMSReplyTo(), reportMessage); + } + else if ("TERMINATE".equals(controlType)) + { + console.info("Received termination instruction from coordinator."); + + // Is a cleaner shutdown needed? + connection.close(); + System.exit(0); + } + else if ("CLOCK_SYNCH".equals(controlType)) + { + log.debug("Received clock synch command."); + String address = message.getStringProperty("ADDRESS"); + + log.debug("address = " + address); + + // Re-create (if necessary) and start the clock synch thread to synch the clock every ten seconds. + if (clockSynchThread != null) + { + clockSynchThread.terminate(); + } + + SleepThrottle throttle = new SleepThrottle(); + throttle.setRate(0.1f); + + clockSynchThread = new ClockSynchThread(new UDPClockSynchronizer(address), throttle); + clockSynchThread.start(); + } + else + { + // Log a warning about this but otherwise ignore it. + log.warn("Got an unknown control message, controlType = " + controlType + ", message = " + message); + } + } + catch (JMSException e) + { + // Log a warning about this, but otherwise ignore it. + log.warn("Got JMSException whilst handling message: " + message, e); + } + // Log any runtimes that fall through this message handler. These are fatal errors for the test client. + catch (RuntimeException e) + { + log.error("The test client message handler got an unhandled exception: ", e); + console.info("The message handler got an unhandled exception, terminating the test client."); + System.exit(1); + } + finally + { + NDC.pop(); + } + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java b/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java index 3752888a9e..8a82b12832 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java @@ -26,8 +26,8 @@ import junit.framework.TestCase; import org.apache.log4j.Logger; -import uk.co.thebadgerset.junit.extensions.ShutdownHookable; -import uk.co.thebadgerset.junit.extensions.listeners.TKTestListener; +import org.apache.qpid.junit.extensions.ShutdownHookable; +import org.apache.qpid.junit.extensions.listeners.TKTestListener; import java.io.IOException; import java.io.PrintWriter; diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java new file mode 100644 index 0000000000..14ae108da8 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java @@ -0,0 +1,133 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework.localcircuit; + +import org.apache.qpid.client.AMQNoConsumersException; +import org.apache.qpid.client.AMQNoRouteException; +import org.apache.qpid.test.framework.*; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; + +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; + +/** + * LocalAMQPPublisherImpl is an extension of {@link LocalPublisherImpl} that adds AMQP specific features. Specifically + * extra assertions for AMQP features not available through generic JMS. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ */ +public class LocalAMQPPublisherImpl extends LocalPublisherImpl implements AMQPPublisher +{ + /** + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. + * + * @param producer The message producer for the circuit end point. + * @param consumer The message consumer for the circuit end point. + * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. + */ + public LocalAMQPPublisherImpl(MessageProducer producer, MessageConsumer consumer, Session session, + MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) + { + super(producer, consumer, session, messageMonitor, exceptionMonitor); + } + + /** + * Creates a circuit end point from the producer, consumer and controlSession in a circuit end base implementation. + * + * @param end The circuit end base implementation to take producers and consumers from. + */ + public LocalAMQPPublisherImpl(CircuitEndBase end) + { + super(end); + } + + /** + * Provides an assertion that the publisher got a no consumers exception on every message. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the publisher got a no consumers exception on every message. + */ + public Assertion noConsumersAssertion(ParsedProperties testProps) + { + return new AssertionBase() + { + public boolean apply() + { + boolean passed = true; + ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor(); + + if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoConsumersException.class)) + { + passed = false; + + addError("Was expecting linked exception type " + AMQNoConsumersException.class.getName() + + " on the connection.\n"); + addError((connectionExceptionMonitor.size() > 0) + ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor) + : "Got no exceptions on the connection."); + } + + return passed; + } + }; + } + + /** + * Provides an assertion that the publisher got a no rout exception on every message. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the publisher got a no rout exception on every message. + */ + public Assertion noRouteAssertion(ParsedProperties testProps) + { + return new AssertionBase() + { + public boolean apply() + { + boolean passed = true; + ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor(); + + if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoRouteException.class)) + { + passed = false; + + addError("Was expecting linked exception type " + AMQNoRouteException.class.getName() + + " on the connection.\n"); + addError((connectionExceptionMonitor.size() > 0) + ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor) + : "Got no exceptions on the connection."); + } + + return passed; + } + }; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java index 3a5ff49fd1..391091266c 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java @@ -22,16 +22,14 @@ package org.apache.qpid.test.framework.localcircuit; import org.apache.log4j.Logger; -import org.apache.qpid.client.AMQSession; import org.apache.qpid.test.framework.*; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.*; import java.util.LinkedList; import java.util.List; -import java.util.concurrent.atomic.AtomicLong; /** * LocalCircuitImpl provides an implementation of the test circuit. This is a local only circuit implementation that @@ -47,7 +45,7 @@ import java.util.concurrent.atomic.AtomicLong; * Apply assertions against the circuits state. {@link Assertion} * Send test messages over the circuit. * Perform the default test procedure on the circuit. - * Provide access to connection and controlSession exception monitors {@link ExceptionMonitor} + * Provide access to connection and controlSession exception monitors. {@link ExceptionMonitor} * */ public class LocalCircuitImpl implements Circuit @@ -55,9 +53,6 @@ public class LocalCircuitImpl implements Circuit /** Used for debugging. */ private static final Logger log = Logger.getLogger(LocalCircuitImpl.class); - /** Used to create unique destination names for each test. */ - private static AtomicLong uniqueDestsId = new AtomicLong(); - /** Holds the test configuration for the circuit. */ private ParsedProperties testProps; @@ -86,7 +81,7 @@ public class LocalCircuitImpl implements Circuit * @param connection The connection. * @param connectionExceptionMonitor The connection exception monitor. */ - protected LocalCircuitImpl(ParsedProperties testProps, LocalPublisherImpl publisher, LocalReceiverImpl receiver, + public LocalCircuitImpl(ParsedProperties testProps, LocalPublisherImpl publisher, LocalReceiverImpl receiver, Connection connection, ExceptionMonitor connectionExceptionMonitor) { this.testProps = testProps; @@ -101,159 +96,6 @@ public class LocalCircuitImpl implements Circuit receiver.setCircuit(this); } - /** - * Creates a local test circuit from the specified test parameters. - * - * @param testProps The test parameters. - * - * @return A connected and ready to start, test circuit. - */ - public static Circuit createCircuit(ParsedProperties testProps) - { - // Create a standard publisher/receivers test client pair on a shared connection, individual sessions. - try - { - // Cast the test properties into a typed interface for convenience. - MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); - - // Get a unique offset to append to destination names to make them unique to the connection. - long uniqueId = uniqueDestsId.incrementAndGet(); - - // Set up the connection. - Connection connection = TestUtils.createConnection(testProps); - - // Add the connection exception listener to assert on exception conditions with. - // ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); - // connection.setExceptionListener(exceptionMonitor); - - // Set up the publisher. - CircuitEndBase publisherEnd = createPublisherCircuitEnd(connection, props, uniqueId); - - // Set up the receiver. - CircuitEndBase receiverEnd = createReceiverCircuitEnd(connection, props, uniqueId); - - // Start listening for incoming messages. - connection.start(); - - // Package everything up. - LocalPublisherImpl publisher = new LocalPublisherImpl(publisherEnd); - LocalReceiverImpl receiver = new LocalReceiverImpl(receiverEnd); - - return new LocalCircuitImpl(testProps, publisher, receiver, connection, publisher.getExceptionMonitor()); - } - catch (JMSException e) - { - throw new RuntimeException("Could not create publisher/receivers pair due to a JMSException.", e); - } - } - - /** - * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters. - * - * @param connection The connection to build the circuit end on. - * @param testProps The test parameters to configure the circuit end construction. - * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. - * - * @return A circuit end suitable for the publishing side of a test circuit. - * - * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. - */ - public static CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) - throws JMSException - { - log.debug( - "public static CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " - + uniqueId + "): called"); - - // Cast the test properties into a typed interface for convenience. - MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); - - Session session = connection.createSession(props.getTransacted(), props.getAckMode()); - - Destination destination = - props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) - : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); - - MessageProducer producer = - props.getPublisherProducerBind() - ? ((props.getImmediate() | props.getMandatory()) - ? ((AMQSession) session).createProducer(destination, props.getMandatory(), props.getImmediate()) - : session.createProducer(destination)) : null; - - MessageConsumer consumer = - props.getPublisherConsumerBind() - ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; - - MessageMonitor messageMonitor = new MessageMonitor(); - - if (consumer != null) - { - consumer.setMessageListener(messageMonitor); - } - - ExceptionMonitor exceptionMonitor = new ExceptionMonitor(); - connection.setExceptionListener(exceptionMonitor); - - if (!props.getPublisherConsumerActive() && (consumer != null)) - { - consumer.close(); - } - - return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor); - } - - /** - * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters. - * - * @param connection The connection to build the circuit end on. - * @param testProps The test parameters to configure the circuit end construction. - * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique. - * - * @return A circuit end suitable for the receiving side of a test circuit. - * - * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation. - */ - public static CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId) - throws JMSException - { - log.debug( - "public static CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = " - + uniqueId + "): called"); - - // Cast the test properties into a typed interface for convenience. - MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); - - Session session = connection.createSession(props.getTransacted(), props.getAckMode()); - - MessageProducer producer = - props.getReceiverProducerBind() - ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null; - - Destination destination = - props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId) - : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId); - - MessageConsumer consumer = - props.getReceiverConsumerBind() - ? ((props.getDurableSubscription() && props.getPubsub()) - ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination)) - : null; - - MessageMonitor messageMonitor = new MessageMonitor(); - - if (consumer != null) - { - consumer.setMessageListener(messageMonitor); - } - - if (!props.getReceiverConsumerActive() && (consumer != null)) - { - consumer.close(); - } - - return new CircuitEndBase(producer, consumer, session, messageMonitor, null); - } - /** * Gets the interface on the publishing end of the circuit. * @@ -342,7 +184,7 @@ public class LocalCircuitImpl implements Circuit } catch (JMSException e) { - throw new RuntimeException("Got JMSException during close.", e); + throw new RuntimeException("Got JMSException during close:" + e.getMessage(), e); } } @@ -351,16 +193,24 @@ public class LocalCircuitImpl implements Circuit */ protected void send() { - boolean transactional = testProps.getPropertyAsBoolean(MessagingTestConfigProperties.TRANSACTED_PROPNAME); + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + boolean transactional = props.getPublisherTransacted(); + boolean rollback = props.getRollbackPublisher(); - // Send an immediate message through the publisher and ensure that it results in a JMSException. + // Send a message through the publisher and log any exceptions raised. try { CircuitEnd end = getLocalPublisherCircuitEnd(); end.send(createTestMessage(end)); - if (transactional) + if (rollback) + { + end.getSession().rollback(); + } + else if (transactional) { end.getSession().commit(); } @@ -406,12 +256,12 @@ public class LocalCircuitImpl implements Circuit // Request a status report. check(); - // Apply all of the requested assertions, keeping record of any that fail. - List failures = applyAssertions(assertions); - // Clean up the publisher/receivers/controlSession/connections. close(); + // Apply all of the requested assertions, keeping record of any that fail. + List failures = applyAssertions(assertions); + // Return any failed assertions to the caller. return failures; } @@ -427,7 +277,10 @@ public class LocalCircuitImpl implements Circuit */ private Message createTestMessage(CircuitEnd client) throws JMSException { - return client.getSession().createTextMessage("Hello"); + // Cast the test properties into a typed interface for convenience. + MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps); + + return TestUtils.createTestMessageOfSize(client.getSession(), props.getMessageSize()); } /** @@ -450,3 +303,4 @@ public class LocalCircuitImpl implements Circuit return exceptionMonitor; } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java index 5c5807dcd9..3ec3f62538 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java @@ -20,10 +20,10 @@ */ package org.apache.qpid.test.framework.localcircuit; -import org.apache.qpid.client.AMQNoConsumersException; -import org.apache.qpid.client.AMQNoRouteException; import org.apache.qpid.test.framework.*; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; @@ -31,7 +31,7 @@ import javax.jms.Session; /** * Provides an implementation of the {@link Publisher} interface and wraps a single message producer and consumer on * a single controlSession, as a {@link CircuitEnd}. A local publisher also acts as a circuit end, because for a locally - * located circuit the assertions may be applied directly, there does not need to be any inter process messaging + * located circuit the assertions may be applied directly, there does not need to be any inter-process messaging * between the publisher and its single circuit end, in order to ascertain its status. * *

@@ -46,14 +46,17 @@ import javax.jms.Session; public class LocalPublisherImpl extends CircuitEndBase implements Publisher { /** Holds a reference to the containing circuit. */ - private LocalCircuitImpl circuit; + protected LocalCircuitImpl circuit; /** - * Creates a circuit end point on the specified producer, consumer and controlSession. + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. * * @param producer The message producer for the circuit end point. * @param consumer The message consumer for the circuit end point. * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. */ public LocalPublisherImpl(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) @@ -74,9 +77,11 @@ public class LocalPublisherImpl extends CircuitEndBase implements Publisher /** * Provides an assertion that the publisher encountered no exceptions. * + * @param testProps + * * @return An assertion that the publisher encountered no exceptions. */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) { return new AssertionBase() { @@ -109,41 +114,26 @@ public class LocalPublisherImpl extends CircuitEndBase implements Publisher } /** - * Provides an assertion that the publisher got a no consumers exception on every message. + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. * - * @return An assertion that the publisher got a no consumers exception on every message. + * @return An assertion that the AMQP channel was forcibly closed by an error condition. */ - public Assertion noConsumersAssertion() + public Assertion channelClosedAssertion(ParsedProperties testProps) { - return new AssertionBase() - { - public boolean apply() - { - boolean passed = true; - ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor(); - - if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoConsumersException.class)) - { - passed = false; - - addError("Was expecting linked exception type " + AMQNoConsumersException.class.getName() - + " on the connection.\n"); - addError((connectionExceptionMonitor.size() > 0) - ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor) - : "Got no exceptions on the connection."); - } - - return passed; - } - }; + return new NotApplicableAssertion(testProps); } /** - * Provides an assertion that the publisher got a no rout exception on every message. + * Provides an assertion that the publisher got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. * - * @return An assertion that the publisher got a no rout exception on every message. + * @return An assertion that the publisher got a given exception during the test. */ - public Assertion noRouteAssertion() + public Assertion exceptionAssertion(ParsedProperties testProps, final Class exceptionClass) { return new AssertionBase() { @@ -152,11 +142,11 @@ public class LocalPublisherImpl extends CircuitEndBase implements Publisher boolean passed = true; ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor(); - if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoRouteException.class)) + if (!connectionExceptionMonitor.assertExceptionOfType(exceptionClass)) { passed = false; - addError("Was expecting linked exception type " + AMQNoRouteException.class.getName() + addError("Was expecting linked exception type " + exceptionClass.getName() + " on the connection.\n"); addError((connectionExceptionMonitor.size() > 0) ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor) diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java index 0c5dae096e..74f414c974 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java @@ -22,6 +22,8 @@ package org.apache.qpid.test.framework.localcircuit; import org.apache.qpid.test.framework.*; +import org.apache.qpid.junit.extensions.util.ParsedProperties; + import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; @@ -46,11 +48,14 @@ public class LocalReceiverImpl extends CircuitEndBase implements Receiver private LocalCircuitImpl circuit; /** - * Creates a circuit end point on the specified producer, consumer and controlSession. + * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured + * for messages and exceptions received by the circuit end. * * @param producer The message producer for the circuit end point. * @param consumer The message consumer for the circuit end point. * @param session The controlSession for the circuit end point. + * @param messageMonitor The monitor to notify of all messages received by the circuit end. + * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end. */ public LocalReceiverImpl(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor) @@ -71,21 +76,60 @@ public class LocalReceiverImpl extends CircuitEndBase implements Receiver /** * Provides an assertion that the receivers encountered no exceptions. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers encountered no exceptions. */ - public Assertion noExceptionsAssertion() + public Assertion noExceptionsAssertion(ParsedProperties testProps) + { + return new NotApplicableAssertion(testProps); + } + + /** + * Provides an assertion that the AMQP channel was forcibly closed by an error condition. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the AMQP channel was forcibly closed by an error condition. + */ + public Assertion channelClosedAssertion(ParsedProperties testProps) { - return null; + return new NotApplicableAssertion(testProps); } /** * Provides an assertion that the receivers got all messages that were sent to it. * + * @param testProps The test configuration properties. + * * @return An assertion that the receivers got all messages that were sent to it. */ - public Assertion allMessagesAssertion() + public Assertion allMessagesReceivedAssertion(ParsedProperties testProps) + { + return new NotApplicableAssertion(testProps); + } + + /** + * Provides an assertion that the receivers got none of the messages that were sent to it. + * + * @param testProps The test configuration properties. + * + * @return An assertion that the receivers got none of the messages that were sent to it. + */ + public Assertion noMessagesReceivedAssertion(ParsedProperties testProps) + { + return new NotApplicableAssertion(testProps); + } + + /** + * Provides an assertion that the receiver got a given exception during the test. + * + * @param testProps The test configuration properties. + * @param exceptionClass The exception class to check for. @return An assertion that the receiver got a given exception during the test. + */ + public Assertion exceptionAssertion(ParsedProperties testProps, Class exceptionClass) { - return null; + return new NotApplicableAssertion(testProps); } /** diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java new file mode 100644 index 0000000000..4545e3c164 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework.qpid; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.apache.qpid.test.framework.FrameworkBaseCase; +import org.apache.qpid.test.framework.LocalAMQPCircuitFactory; + +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; + +/** + * AMQPFeatureDecorator applies decorations to {@link FrameworkBaseCase} tests, so that they may use Qpid/AMQP specific + * features, not available through JMS. For example, the immediate and mandatory flags. This decorator replaces the + * standard test circuit factory on the base class with one that allows these features to be used. + * + *

CRC Card
+ *
CRC Card
Responsibilities Collaborations + *
Substitute the circuit factory with an AMQP/Qpid specific one. + *
+ * + * @todo This wrapper substitutes in a LocalAMQPCircuitFactory, which is fine for local tests. For distributed tests + * the Fanout or Interop factories are substituted in by their decorators instead. These actually use + * distributed circuit static create methods to build the circuits, which should actually be changed to a factory, + * so that static methods do not need to be used. The distributed circuit creater delegates the circuit + * construction to remote test nodes. This decorator should not be used with distributed tests, or should be made + * aware of them, in which case it might ensure that an AMQP feature (implied already by other properties) flag + * is passed out to the remote test nodes, and provide a mechansim for them to decorate their circuit creation + * with AMQP features too. Add factory substituion/decoration mechansim for test clients, here or in a seperate + * class. + */ +public class AMQPFeatureDecorator extends WrappedSuiteTestDecorator +{ + /** The test suite to run. */ + private Test test; + + /** + * Creates a wrapped test test decorator from another one. + * + * @param test The test test. + */ + public AMQPFeatureDecorator(WrappedSuiteTestDecorator test) + { + super(test); + this.test = test; + } + + /** + * Runs the tests with a LocalAMQPCircuitFactory. Only tests that extend FrameworkBaseCase are decorated. + * + * @param testResult The the results object to monitor the test results with. + */ + public void run(TestResult testResult) + { + for (Test test : getAllUnderlyingTests()) + { + if (test instanceof FrameworkBaseCase) + { + FrameworkBaseCase frameworkTest = (FrameworkBaseCase) test; + frameworkTest.setCircuitFactory(new LocalAMQPCircuitFactory()); + } + } + + // Run the test. + test.run(testResult); + } + + /** + * Prints the name of the test for debugging purposes. + * + * @return The name of the test. + */ + public String toString() + { + return "AMQPFeatureDecorator: [test = \"" + test + "\"]"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java new file mode 100644 index 0000000000..3a048ac042 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java @@ -0,0 +1,95 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework.qpid; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.apache.qpid.test.framework.BrokerLifecycleAware; +import org.apache.qpid.test.framework.CauseFailureUserPrompt; + +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; + +/** + * CauseFailureDecorator applies decorations to {@link BrokerLifecycleAware} tests, so that they may use different failure + * mechanisms. It is capable of detecting when a test case uses in-vm brokers, and setting up an automatic failure + * for those tests, so that the current live broker can be shut-down by test cases. For external brokers, automatic + * failure could be implemented, for example by having a kill script. At the moment this sets up the failure to prompt + * a user interactively to cause a failure, using {@link CauseFailureUserPrompt}. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Setup automatic failures for in-vm brokers. {@link CauseFailureInVM} + *
Setup user generated failures for external brokers. {@link CauseFailureUserPrompt}. + *
+ *
+ * + * @todo Slight problem in that CauseFailureInVM is Qpid specific, whereas CauseFailureUserPrompt is not. Would like the + * failure decorator to be non-qpid specific so that it can test failure of any JMS implementation too. Either pass + * in class name of failure mechanism, set it up in the in-vm decorator instead of here but with prompt user as the + * default for when the in-vm decorator is not used? + */ +public class CauseFailureDecorator extends WrappedSuiteTestDecorator +{ + /** The test suite to run. */ + private Test test; + + /** + * Creates a wrapped test test decorator from another one. + * + * @param test The test test. + */ + public CauseFailureDecorator(WrappedSuiteTestDecorator test) + { + super(test); + this.test = test; + } + + /** + * Runs the tests with a LocalAMQPCircuitFactory. Only tests that extend FrameworkBaseCase are decorated. + * + * @param testResult The the results object to monitor the test results with. + */ + public void run(TestResult testResult) + { + for (Test test : getAllUnderlyingTests()) + { + if (test instanceof BrokerLifecycleAware) + { + BrokerLifecycleAware failureTest = (BrokerLifecycleAware) test; + failureTest.setFailureMechanism(new CauseFailureUserPrompt()); + } + } + + // Run the test. + test.run(testResult); + } + + /** + * Prints the name of the test for debugging purposes. + * + * @return The name of the test. + */ + public String toString() + { + return "CauseFailureDecorator: [test = \"" + test + "\"]"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java new file mode 100644 index 0000000000..b63ac43601 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework.qpid; + +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.test.framework.CauseFailure; +import org.apache.qpid.test.framework.BrokerLifecycleAware; + +/** + *

+ *
CRC Card
Responsibilities Collaborations + *
Cause messaging broker failure on the active in-vm broker. + * {@link TransportConnection}, {@link ApplicationRegistry} + *
+ */ +public class CauseFailureInVM implements CauseFailure +{ + /** Holds the in-vm broker instrumented test case to create failures for. */ + private BrokerLifecycleAware inVMTest; + + /** + * Creates an automated failure mechanism for testing against in-vm brokers. The test to create the mechanism + * for is specified, and as this failure is for in-vm brokers, the test must be {@link org.apache.qpid.test.framework.BrokerLifecycleAware}. The test + * must also report that it is currently being run against an in-vm broker, and it is a runtime error if it is not, + * as the creator of this failure mechanism should already have checked that it is. + * + * @param inVMTest The test case to create an automated failure mechanism for. + */ + public CauseFailureInVM(BrokerLifecycleAware inVMTest) + { + // Check that the test is really using in-vm brokers. + if (!inVMTest.usingInVmBroker()) + { + throw new RuntimeException( + "Cannot create in-vm broker failure mechanism for a test that is not using in-vm brokers."); + } + + this.inVMTest = inVMTest; + } + + /** + * Causes the active message broker to fail. + */ + public void causeFailure() + { + int liveBroker = inVMTest.getLiveBroker(); + + TransportConnection.killVMBroker(liveBroker); + ApplicationRegistry.remove(liveBroker); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java new file mode 100644 index 0000000000..bcf052ea06 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java @@ -0,0 +1,135 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.framework.qpid; + +import junit.framework.Test; +import junit.framework.TestResult; + +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.test.framework.BrokerLifecycleAware; +import org.apache.qpid.test.framework.FrameworkBaseCase; + +import org.apache.qpid.junit.extensions.SetupTaskAware; +import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator; + +/** + * InVMBrokerDecorator is a test decorator, that is activated when running tests against an in-vm broker only. Its + * purpose is to automatically create, and close and delete an in-vm broker, during the set-up and tear-down of + * each test case. This decorator may only be used in conjunction with tests that extend {@link FrameworkBaseCase}. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Create/Destroy an in-vm broker on every test run. + *
+ * + * @todo May need to add a more fine grained injection point for the in-vm broker management, as this acts at the + * suite level, rather than the individual test level. + * + * @todo Management of in-vm brokers for failure testing. Failure test setups may need to set their connection url to + * use multiple broker (vm://:1;vm://:2), with fail-over between them. There is round-robin fail-over, but also + * retry? A test case using an in-vm broker needs to record which one it is using, so that it can be + * killed/restarted. + */ +public class InVMBrokerDecorator extends WrappedSuiteTestDecorator +{ + /** The test suite to run. */ + private Test test; + + /** + * Creates a wrapped test suite decorator from another one. + * + * @param test The test suite. + */ + public InVMBrokerDecorator(WrappedSuiteTestDecorator test) + { + super(test); + this.test = test; + } + + /** + * Runs the tests with in-vm broker creation and clean-up added to the tests task stack. + * + * @param testResult The the results object to monitor the test results with. + */ + public void run(TestResult testResult) + { + for (Test test : getAllUnderlyingTests()) + { + // Check that the test to have an in-vm broker setup/teardown task added to it, is actually a framework + // test that can handle setup tasks. + if ((test instanceof SetupTaskAware)) + { + SetupTaskAware frameworkTest = (SetupTaskAware) test; + + frameworkTest.chainSetupTask(new Runnable() + { + public void run() + { + // Ensure that the in-vm broker is created. + try + { + TransportConnection.createVMBroker(1); + } + catch (AMQVMBrokerCreationException e) + { + throw new RuntimeException("In-VM broker creation failed: " + e.getMessage(), e); + } + } + }); + + frameworkTest.chainTearDownTask(new Runnable() + { + public void run() + { + // Ensure that the in-vm broker is cleaned up so that the next test starts afresh. + TransportConnection.killVMBroker(1); + ApplicationRegistry.remove(1); + } + }); + + // Check if the test is aware whether or not it can control the broker life cycle, and if so provide + // additional instrumentation for it to control the in-vm broker through. + if (test instanceof BrokerLifecycleAware) + { + BrokerLifecycleAware inVMTest = (BrokerLifecycleAware) test; + inVMTest.setInVmBrokers(); + inVMTest.setLiveBroker(1); + inVMTest.setFailureMechanism(new CauseFailureInVM(inVMTest)); + } + } + } + + // Run the test. + test.run(testResult); + } + + /** + * Prints the name of the test for debugging purposes. + * + * @return The name of the test. + */ + public String toString() + { + return "InVMBrokerDecorator: [test = " + test + "]"; + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java index 4514fb836c..c7bde1ae03 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java @@ -31,13 +31,20 @@ import java.util.List; import java.util.Properties; /** + * BaseCircuitFactory provides some functionality common to all {@link CircuitFactory}s, such as the details of + * all {@link org.apache.qpid.test.framework.distributedtesting.TestClient}s that make up the end-points of + * the circuits that the factory creates, and an active {@link ConversationFactory} that can be used to generate + * control conversations with those circuit end-points. + * *

*
CRC Card
Responsibilities Collaborations - *
+ *
Hold the details of the sending and receiving end-points to create circuits from. + *
Provide a conversation factory to create control conversations with the end-points. *
*/ public abstract class BaseCircuitFactory implements CircuitFactory { + /** Used for debugging. */ private final Logger log = Logger.getLogger(BaseCircuitFactory.class); @@ -126,3 +133,4 @@ public abstract class BaseCircuitFactory implements CircuitFactory return conversationFactory; } } + diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java index bf7fedcffc..fff617c583 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java @@ -25,17 +25,13 @@ import org.apache.qpid.test.framework.Circuit; import org.apache.qpid.test.framework.TestClientDetails; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; - -import javax.jms.JMSException; -import javax.jms.Message; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import java.util.List; -import java.util.Map; import java.util.Properties; /** - * A TestCaseSequence is responsibile for creating test circuits appropriate to the context that a test case is + * A CircuitFactory is responsibile for creating test circuits appropriate to the context that a test case is * running in, and providing an implementation of a standard test procedure over a test circuit. * *

@@ -43,12 +39,6 @@ import java.util.Properties; *
CRC Card
Provide a standard test procedure over a test circuit. *
Construct test circuits appropriate to a tests context. *
- * - * @todo The sequence test method is deprecated, in favour of using test circuits instead. This interface might be - * better renamed to somethign like CircuitFactory, also the split between this interface and - * DistributedTestSequencer could be removed and DistributedTestCase functionality merged into FrameworkBaseCase. - * This is so that any test case written on top of the framework base case can be distributed, without having - * to extend a different base test class. */ public interface CircuitFactory { diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java index 2ff6725365..d1c39ff3ff 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java @@ -29,7 +29,7 @@ import org.apache.qpid.test.framework.TestUtils; import org.apache.qpid.test.framework.distributedcircuit.DistributedCircuitImpl; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.Destination; import javax.jms.JMSException; @@ -46,7 +46,7 @@ import java.util.Properties; * *

*
CRC Card
Responsibilities Collaborations - *
+ *
Create distributed circuits from one to many test nodes, for fanout style testing. *
* * @todo Adapt this to be an n*m topology circuit factory. Need to add circuit topology definitions to the test @@ -57,7 +57,7 @@ import java.util.Properties; * * @todo The createCircuit methods on this and InteropCircuitFactory are going to be identical. This is because the * partitioning into senders and receivers is already done by the test decorators. Either eliminate these factories - * as unnesesary, or move the partitioning functionaility into the factories, in which case the test decorators + * as unnesesary, or move the partitioning functionality into the factories, in which case the test decorators * can probably be merged or eliminated. There is confusion over the placement of responsibilities between the * factories and the test decorators... although the test decorators may well do more than just circuit creation * in the future. For example, there may have to be a special decorator for test repetition that does one circuit diff --git a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java index 75df5c65ea..feb87e7b9c 100644 --- a/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java @@ -29,7 +29,7 @@ import org.apache.qpid.test.framework.TestUtils; import org.apache.qpid.test.framework.distributedcircuit.DistributedCircuitImpl; import org.apache.qpid.util.ConversationFactory; -import uk.co.thebadgerset.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.ParsedProperties; import javax.jms.Destination; import javax.jms.JMSException; @@ -41,10 +41,20 @@ import java.util.List; import java.util.Properties; /** + * InteropCircuitFactory is a circuit factory that creates distributed test circuits. Given a set of participating + * test client nodes, it assigns one node to the SENDER role and one the RECEIVER role. + * *

*
CRC Card
Responsibilities Collaborations - *
+ *
Create distributed circuits from pairs of test nodes, for interop style testing. *
+ * + * @todo The partitioning of a set of nodes into sender and receiver roles is actually done by the interop test + * decorator. See the todo comment in FanOutCircuitFactory about merging the factories with the decorators, or + * more carefully dividing up responsibilities between them. + * + * @todo The squenceTest code is deprecated, but currently still used by the interop tests. It will be removed once it + * have been fully replaced by the default test procedure. */ public class InteropCircuitFactory extends BaseCircuitFactory { diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java new file mode 100644 index 0000000000..e7d874ffa9 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java @@ -0,0 +1,119 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.testcases; + +import org.apache.qpid.test.framework.*; +import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; +import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; +import org.apache.qpid.test.framework.sequencers.CircuitFactory; + +import javax.jms.JMSException; +import javax.jms.MessageProducer; +import javax.jms.Session; + +/** + * FailoverTest provides testing of fail-over over a local-circuit implementation. The circuit being tested may be + * against an in-vm broker or against an external broker, with the failure mechanism abstracted out of the test case. + * Automatic failures can be simulated against an in-vm broker. Currently the test must interact with the user to + * simulate failures on an external broker. + * + * Things to test: + * In tx, failure duing tx causes tx to error on subsequent sends/receives or commits/rollbacks. + * Outside of tx, reconnection allows msg flow to continue but there may be loss. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ * + * @todo This test is designed to be run over a local circuit only. For in-vm using automatic failures, for external + * brokers by prompting the user (or maybe using a script). Enforce the local-circuit only nature of the tests as + * well as thinking about how other local-circuit tests might be implemented. For example, could add a method + * to the framework base case for local only tests to call, that allows them access to the local-circuit + * implementation and so on. + * + * @todo More. Need to really expand the set of fail-over tests. + */ +public class FailoverTest extends FrameworkBaseCase +{ + /* Used for debugging purposes. */ + // private static final Logger log = Logger.getLogger(FailoverTest.class); + + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public FailoverTest(String name) + { + super(name); + } + + /** + * Checks that all messages sent within a transaction are receieved despite a fail-over occuring outside of + * the transaction. + * + * @throws JMSException Allowed to fall through and fail test. + */ + public void testTxP2PFailover() throws JMSException + { + // Set up the test properties to match the test cases requirements. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(ACK_MODE_PROPNAME, Session.AUTO_ACKNOWLEDGE); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // MessagingTestConfigProperties props = this.getTestParameters(); + + // Create the test circuit from the test configuration parameters. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Create an assertion that all messages are received. + Assertion allMessagesReceived = testCircuit.getReceiver().allMessagesReceivedAssertion(testProps); + + // This test case assumes it is using a local circuit. + LocalCircuitImpl localCircuit = (LocalCircuitImpl) testCircuit; + + Session producerSession = localCircuit.getLocalPublisherCircuitEnd().getSession(); + MessageProducer producer = localCircuit.getLocalPublisherCircuitEnd().getProducer(); + // MessageConsumer consumer = localCircuit.getLocalReceiverCircuitEnd().getConsumer(); + + // Send some test messages. + for (int i = 0; i < 100; i++) + { + producer.send(TestUtils.createTestMessageOfSize(producerSession, 10)); + producerSession.commit(); + + // Cause a failover. + if (i == 50) + { + failureMechanism.causeFailure(); + } + + // Wait for the reconnection to complete. + } + + // Check that trying to send within the original transaction fails. + + // Check that all messages sent were received. + assertTrue("All messages sent were not received back again.", allMessagesReceived.apply()); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java new file mode 100644 index 0000000000..845c3ed9c8 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java @@ -0,0 +1,303 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.testcases; + +import org.apache.qpid.test.framework.AMQPPublisher; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.FrameworkBaseCase; +import org.apache.qpid.test.framework.MessagingTestConfigProperties; +import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; +import org.apache.qpid.test.framework.sequencers.CircuitFactory; + +import org.apache.qpid.junit.extensions.util.TestContextProperties; + +/** + * ImmediateMessageTest tests for the desired behaviour of immediate messages. Immediate messages are a non-JMS + * feature. A message may be marked with an immediate delivery flag, which means that a consumer must be connected + * to receive the message, through a valid route, when it is sent, or when its transaction is committed in the case + * of transactional messaging. If this is not the case, the broker should return the message with a NO_CONSUMERS code. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Check that an immediate message is sent succesfully not using transactions when a consumer is connected. + *
Check that an immediate message is committed succesfully in a transaction when a consumer is connected. + *
Check that an immediate message results in no consumers code, not using transactions, when a consumer is + * disconnected. + *
Check that an immediate message results in no consumers code, in a transaction, when a consumer is + * disconnected. + *
Check that an immediate message results in no route code, not using transactions, when no outgoing route is + * connected. + *
Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is + * connected. + *
Check that an immediate message is sent succesfully not using transactions when a consumer is connected. + *
Check that an immediate message is committed succesfully in a transaction when a consumer is connected. + *
Check that an immediate message results in no consumers code, not using transactions, when a consumer is + * disconnected. + *
Check that an immediate message results in no consumers code, in a transaction, when a consumer is + * disconnected. + *
Check that an immediate message results in no route code, not using transactions, when no outgoing route is + * connected. + *
Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is + * connected. + *
+ * + * @todo All of these test cases will be generated by a test generator that thoroughly tests all combinations of test + * circuits. + */ +public class ImmediateMessageTest extends FrameworkBaseCase +{ + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public ImmediateMessageTest(String name) + { + super(name); + } + + /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */ + public void test_QPID_517_ImmediateOkNoTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps)))); + } + + /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */ + public void test_QPID_517_ImmediateOkTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Send one message with no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps)))); + } + + /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */ + public void test_QPID_517_ImmediateFailsConsumerDisconnectedNoTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message and get a linked no consumers exception. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps)))); + } + + /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */ + public void test_QPID_517_ImmediateFailsConsumerDisconnectedTxP2P() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message and get a linked no consumers exception. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps)))); + } + + /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */ + public void test_QPID_517_ImmediateFailsNoRouteNoTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */ + public void test_QPID_517_ImmediateFailsNoRouteTxP2P() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */ + public void test_QPID_517_ImmediateOkNoTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Send one message with no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */ + public void test_QPID_517_ImmediateOkTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Send one message with no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */ + public void test_QPID_517_ImmediateFailsConsumerDisconnectedNoTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Use durable subscriptions, so that the route remains open with no subscribers. + testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message and get a linked no consumers exception. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps)))); + } + + /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */ + public void test_QPID_517_ImmediateFailsConsumerDisconnectedTxPubSub() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Use durable subscriptions, so that the route remains open with no subscribers. + testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message and get a linked no consumers exception. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(testProps)))); + } + + /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */ + public void test_QPID_517_ImmediateFailsNoRouteNoTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */ + public void test_QPID_517_ImmediateFailsNoRouteTxPubSub() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + protected void setUp() throws Exception + { + super.setUp(); + + testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + /** All these tests should have the immediate flag on. */ + testProps.setProperty(IMMEDIATE_PROPNAME, true); + testProps.setProperty(MANDATORY_PROPNAME, false); + + /** Bind the receivers consumer by default. */ + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true); + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java new file mode 100644 index 0000000000..066b4e24ba --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java @@ -0,0 +1,321 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.testcases; + +import org.apache.qpid.test.framework.AMQPPublisher; +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.FrameworkBaseCase; +import org.apache.qpid.test.framework.MessagingTestConfigProperties; +import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; +import org.apache.qpid.test.framework.sequencers.CircuitFactory; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; + +/** + * MandatoryMessageTest tests for the desired behaviour of mandatory messages. Mandatory messages are a non-JMS + * feature. A message may be marked with a mandatory delivery flag, which means that a valid route for the message + * must exist, when it is sent, or when its transaction is committed in the case of transactional messaging. If this + * is not the case, the broker should return the message with a NO_CONSUMERS code. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. + *
Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. + *
Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected + * but the route exists. + *
Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but + * the route exists. + *
Check that an mandatory message results in no route code, not using transactions, when no consumer is + * connected. + *
Check that an mandatory message results in no route code, upon transaction commit, when a consumer is + * connected. + *
Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. + *
Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. + *
Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected + * but the route exists. + *
Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but + * the route exists. + *
Check that an mandatory message results in no route code, not using transactions, when no consumer is + * connected. + *
Check that an mandatory message results in no route code, upon transaction commit, when a consumer is + * connected. + *
+ * + * @todo All of these test cases will be generated by a test generator that thoroughly tests all combinations of test + * circuits. + */ +public class MandatoryMessageTest extends FrameworkBaseCase +{ + /** Used to read the tests configurable properties through. */ + ParsedProperties testProps; + + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public MandatoryMessageTest(String name) + { + super(name); + } + + /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */ + public void test_QPID_508_MandatoryOkNoTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */ + public void test_QPID_508_MandatoryOkTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** + * Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected but + * the route exists. + */ + public void test_QPID_517_MandatoryOkConsumerDisconnectedNoTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message with no errors. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** + * Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but + * the route exists. + */ + public void test_QPID_517_MandatoryOkConsumerDisconnectedTxP2P() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message with no errors. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */ + public void test_QPID_508_MandatoryFailsNoRouteNoTxP2P() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */ + public void test_QPID_508_MandatoryFailsNoRouteTxP2P() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */ + public void test_QPID_508_MandatoryOkNoTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */ + public void test_QPID_508_MandatoryOkTxPubSub() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** + * Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected but + * the route exists. + */ + public void test_QPID_517_MandatoryOkConsumerDisconnectedNoTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Use durable subscriptions, so that the route remains open with no subscribers. + testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message with no errors. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** + * Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but + * the route exists. + */ + public void test_QPID_517_MandatoryOkConsumerDisconnectedTxPubSub() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Use durable subscriptions, so that the route remains open with no subscribers. + testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true); + + // Disconnect the consumer. + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false); + + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // Send one message with no errors. + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps)))); + } + + /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */ + public void test_QPID_508_MandatoryFailsNoRouteNoTxPubSub() + { + // Ensure transactional sessions are off. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */ + public void test_QPID_508_MandatoryFailsNoRouteTxPubSub() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(PUBSUB_PROPNAME, true); + + // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to + // collect its messages). + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false); + + // Send one message and get a linked no route exception. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps)))); + } + + protected void setUp() throws Exception + { + super.setUp(); + + testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + /** All these tests should have the mandatory flag on. */ + testProps.setProperty(IMMEDIATE_PROPNAME, false); + testProps.setProperty(MANDATORY_PROPNAME, true); + + /** Bind the receivers consumer by default. */ + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true); + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java new file mode 100644 index 0000000000..f39d22bc67 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.testcases; + +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.FrameworkBaseCase; +import org.apache.qpid.test.framework.MessagingTestConfigProperties; +import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*; +import org.apache.qpid.test.framework.sequencers.CircuitFactory; + +import org.apache.qpid.junit.extensions.util.ParsedProperties; +import org.apache.qpid.junit.extensions.util.TestContextProperties; + +/** + * RollbackTest tests the rollback ability of transactional messaging. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Check messages sent but rolled back are never received. + *
Check messages received but rolled back are redelivered on subsequent receives. + *
Attempting to rollback outside of a transaction results in an IllegalStateException. + *
+ */ +public class RollbackTest extends FrameworkBaseCase +{ + /** Used to read the tests configurable properties through. */ + ParsedProperties testProps; + + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public RollbackTest(String name) + { + super(name); + } + + /** Check messages sent but rolled back are never received. */ + public void testRolledbackMessageNotDelivered() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true); + testProps.setProperty(ROLLBACK_PUBLISHER_PROPNAME, true); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps), + testCircuit.getReceiver().noMessagesReceivedAssertion(testProps)))); + } + + /** Check messages received but rolled back are redelivered on subsequent receives. */ + public void testRolledbackMessagesSubsequentlyReceived() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_RECEIVER_PROPNAME, true); + testProps.setProperty(ROLLBACK_RECEIVER_PROPNAME, true); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, + assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps), + testCircuit.getReceiver().allMessagesReceivedAssertion(testProps)))); + } + + /** Attempting to rollback outside of a transaction results in an IllegalStateException. */ + public void testRollbackUnavailableOutsideTransactionPublisher() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false); + testProps.setProperty(ROLLBACK_PUBLISHER_PROPNAME, true); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().channelClosedAssertion(testProps)))); + } + + /** Attempting to rollback outside of a transaction results in an IllegalStateException. */ + public void testRollbackUnavailableOutsideTransactionReceiver() + { + // Ensure transactional sessions are on. + testProps.setProperty(TRANSACTED_RECEIVER_PROPNAME, false); + testProps.setProperty(ROLLBACK_RECEIVER_PROPNAME, true); + + // Run the default test sequence over the test circuit checking for no errors. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getReceiver().channelClosedAssertion(testProps)))); + } + + /** + * Sets up all tests to have an active outward route and consumer by default. + * + * @throws Exception Any exceptions are allowed to fall through and fail the test. + */ + protected void setUp() throws Exception + { + super.setUp(); + + testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults); + + /** Bind the receivers consumer by default. */ + testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true); + testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java b/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java new file mode 100644 index 0000000000..f752ccba00 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java @@ -0,0 +1,151 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.test.testcases; + +import org.apache.qpid.test.framework.Circuit; +import org.apache.qpid.test.framework.FrameworkBaseCase; +import static org.apache.qpid.test.framework.MessagingTestConfigProperties.ACK_MODE_PROPNAME; +import static org.apache.qpid.test.framework.MessagingTestConfigProperties.PUBSUB_PROPNAME; +import org.apache.qpid.test.framework.TestUtils; +import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl; +import org.apache.qpid.test.framework.sequencers.CircuitFactory; + +import javax.jms.*; + +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +/** + * TTLTest checks that time-to-live is applied to messages. The test sends messages with a variety of TTL stamps on them + * then after a pause attempts to receive those messages. Only messages with a large enough TTL to have survived the pause + * should be receiveable. This test case also applies an additional assertion against the broker, that the message store + * is empty at the end of the test. + * + *

This test is designed to run over local circuits only, as it must control a timed pause between sending and receiving + * messages to that TTL can be applied to purge some of the messages. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
+ *
+ * + * @todo Use an interface or other method to mark this test as local only. + * + * @todo Implement the message store assertion for in-vm broker. Could also be done for external broker, for example + * by using diagnostic exchange. + * + * @todo Implement and add a queue depth assertion too. This might already be in another test to copy from. + * + * @todo Create variations on test theme, for different ack mode and tx and message sizes etc. + * + * @todo Add an allowable margin of error to the test, as ttl may not be precise. + */ +public class TTLTest extends FrameworkBaseCase +{ + /** + * Creates a new test case with the specified name. + * + * @param name The test case name. + */ + public TTLTest(String name) + { + super(name); + } + + /** + * Checks that all messages sent with a TTL shorter than a pause between sending them and attempting to receive them + * will fail to arrive. Once all messages have been purged by TTL or received, check that they no longer exist on + * the broker. + * + * @throws javax.jms.JMSException Allowed to fall through and fail test. + */ + public void testTTLP2P() throws JMSException + { + String errorMessages = ""; + Random r = new Random(); + + // Used to accumulate correctly received messages in. + List receivedMessages = new LinkedList(); + + // Set up the test properties to match the test case requirements. + testProps.setProperty(ACK_MODE_PROPNAME, Session.AUTO_ACKNOWLEDGE); + testProps.setProperty(PUBSUB_PROPNAME, false); + + // Create the test circuit from the test configuration parameters. + CircuitFactory circuitFactory = getCircuitFactory(); + Circuit testCircuit = circuitFactory.createCircuit(testProps); + + // This test case assumes it is using a local circuit. + LocalCircuitImpl localCircuit = (LocalCircuitImpl) testCircuit; + + Session producerSession = localCircuit.getLocalPublisherCircuitEnd().getSession(); + MessageProducer producer = localCircuit.getLocalPublisherCircuitEnd().getProducer(); + MessageConsumer consumer = localCircuit.getLocalReceiverCircuitEnd().getConsumer(); + + // Send some tests messages, with random TTLs, some shorter and some longer than the pause time. + for (int i = 0; i < 100; i++) + { + Message testMessage = TestUtils.createTestMessageOfSize(producerSession, 10); + + // Set the TTL on the message and record its value in the message headers. + long ttl = 500 + r.nextInt(1500); + producer.setTimeToLive(ttl); + testMessage.setLongProperty("testTTL", ttl); + + producer.send(testMessage); + // producerSession.commit(); + } + + // Inject a pause to allow some messages to be purged by TTL. + TestUtils.pause(1000); + + // Attempt to receive back all of the messages, confirming by the message time stamps and TTLs that only + // those received should have avoided being purged by the TTL. + boolean timedOut = false; + + while (!timedOut) + { + Message testMessage = consumer.receive(1000); + + long ttl = testMessage.getLongProperty("testTTL"); + long timeStamp = testMessage.getJMSTimestamp(); + long now = System.currentTimeMillis(); + + if ((timeStamp + ttl) < now) + { + errorMessages += + "Received message [sent: " + timeStamp + ", ttl: " + ttl + ", received: " + now + + "] which should have been purged by its TTL.\n"; + } + /*else + { + receivedMessages.add(testMessage); + }*/ + } + + // Check that the queue and message store on the broker are empty. + // assertTrue("Message store is not empty.", messageStoreEmpty.apply()); + // assertTrue("Queue is not empty.", queueEmpty.apply()); + + assertTrue(errorMessages, "".equals(errorMessages)); + } +} diff --git a/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java b/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java new file mode 100644 index 0000000000..8fddf651b4 --- /dev/null +++ b/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java @@ -0,0 +1,174 @@ +package org.apache.qpid.test.unit.ack; + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import javax.jms.Connection; +import javax.jms.ConnectionFactory; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Queue; +import javax.jms.Session; +import javax.jms.TextMessage; + +import org.apache.log4j.Logger; +import org.apache.qpid.client.AMQConnectionFactory; +import org.apache.qpid.client.AMQDestination; +import org.apache.qpid.client.AMQQueue; +import org.apache.qpid.client.AMQSession; +import org.apache.qpid.client.transport.TransportConnection; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.test.VMTestCase; + +public class AcknowledgeTest extends VMTestCase +{ + private static final int NUM_MESSAGES = 50; + private Connection _con; + private Queue _queue; + private MessageProducer _producer; + private Session _producerSession; + private Session _consumerSession; + private MessageConsumer _consumerA; + private MessageConsumer _consumerB; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _queue = (Queue) _context.lookup("queue"); + + //CreateQueue + ((ConnectionFactory) _context.lookup("connection")).createConnection().createSession(false, Session.AUTO_ACKNOWLEDGE).createConsumer(_queue).close(); + + //Create Producer put some messages on the queue + _con = ((ConnectionFactory) _context.lookup("connection")).createConnection(); + _con.start(); + } + + private void init(boolean transacted, int mode) throws JMSException { + _producerSession = _con.createSession(false, Session.AUTO_ACKNOWLEDGE); + _consumerSession = _con.createSession(transacted, mode); + _producer = _producerSession.createProducer(_queue); + _consumerA = _consumerSession.createConsumer(_queue); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + try + { + TransportConnection.killAllVMBrokers(); + ApplicationRegistry.removeAll(); + } + catch (Exception e) + { + fail("Unable to clean up"); + } + + } + + private void consumeMessages(int toConsume, MessageConsumer consumer) throws JMSException + { + Message msg; + for (int i = 0; i < toConsume; i++) + { + msg = consumer.receive(1000); + assertNotNull("Message " + i + " was null!", msg); + assertEquals("message " + i, ((TextMessage) msg).getText()); + } + } + + private void sendMessages(int totalMessages) throws JMSException + { + for (int i = 0; i < totalMessages; i++) + { + _producer.send(_producerSession.createTextMessage("message " + i)); + } + } + + private void testMessageAck(boolean transacted, int mode) throws Exception + { + init(transacted, mode); + sendMessages(NUM_MESSAGES/2); + Thread.sleep(1500); + _consumerB = _consumerSession.createConsumer(_queue); + sendMessages(NUM_MESSAGES/2); + int count = 0; + Message msg = _consumerB.receive(1500); + while (msg != null) + { + if (mode == Session.CLIENT_ACKNOWLEDGE) + { + msg.acknowledge(); + } + count++; + msg = _consumerB.receive(1500); + } + if (transacted) + { + _consumerSession.commit(); + } + _consumerA.close(); + _consumerB.close(); + _consumerSession.close(); + assertEquals("Wrong number of messages on queue", NUM_MESSAGES - count, + ((AMQSession) _producerSession).getQueueDepth((AMQDestination) _queue)); + + // Clean up messages that may be left on the queue + _consumerSession = _con.createSession(transacted, mode); + _consumerA = _consumerSession.createConsumer(_queue); + msg = _consumerA.receive(1500); + while (msg != null) + { + if (mode == Session.CLIENT_ACKNOWLEDGE) + { + msg.acknowledge(); + } + msg = _consumerA.receive(1500); + } + _consumerA.close(); + if (transacted) + { + _consumerSession.commit(); + } + _consumerSession.close(); + super.tearDown(); + } + + public void test2ConsumersAutoAck() throws Exception + { + testMessageAck(false, Session.AUTO_ACKNOWLEDGE); + } + + public void test2ConsumersClientAck() throws Exception + { + testMessageAck(true, Session.CLIENT_ACKNOWLEDGE); + } + + public void test2ConsumersTx() throws Exception + { + testMessageAck(true, Session.AUTO_ACKNOWLEDGE); + } + +} diff --git a/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java b/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java index 352cb80211..00cb458c86 100644 --- a/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java +++ b/java/systests/src/main/java/org/apache/qpid/util/ConversationFactory.java @@ -92,7 +92,7 @@ import java.util.concurrent.atomic.AtomicLong; * *

*
CRC Card
Responsibilities Collaborations - *
Associate messages to an ongoing conversation using correlation ids. + *
Associate messages to an ongoing conversation using correlation ids. *
Auto manage sessions for conversations. *
Store messages not in a conversation in dead letter box. *
-- cgit v1.2.1