summaryrefslogtreecommitdiff
path: root/qpid/java/client/src/main/java/org/apache/qpid
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/client/src/main/java/org/apache/qpid')
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQAnyDestination.java88
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java42
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java414
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java1485
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate.java66
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java460
-rwxr-xr-xqpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_9.java40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java325
-rwxr-xr-xqpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_9_1.java39
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java566
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java311
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java941
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java54
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoConsumersException.java40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoRouteException.java40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueue.java172
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java143
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueSessionAdaptor.java204
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java3489
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionAdapter.java26
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java42
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java1372
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java619
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryQueue.java69
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java72
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java224
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopicSessionAdaptor.java226
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/AMQUndefinedDestination.java40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java1079
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java529
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java95
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java601
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java269
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java229
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/ChannelToSessionMap.java167
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/Closeable.java101
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/ConnectionTuneParameters.java72
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java66
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/DispatcherCallback.java36
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/JMSAMQException.java67
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/JmsNotImplementedException.java31
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/MessageConsumerPair.java43
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/QpidConnectionMetaData.java97
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/QueueReceiverAdaptor.java115
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java227
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/SSLConfiguration.java61
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/TemporaryDestination.java38
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/TopicPublisherAdapter.java205
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java132
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/XAConnectionImpl.java78
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/XAResourceImpl.java522
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/XASessionImpl.java159
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverException.java49
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java268
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverNoopSupport.java75
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverProtectedOperation.java49
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java104
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverState.java64
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverSupport.java47
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java50
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java55
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java54
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java58
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java119
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java49
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java51
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java52
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java543
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java153
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_91.java158
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java85
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java113
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java48
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java71
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java70
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java239
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java85
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java57
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java57
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java140
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegateFactory.java54
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java980
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java576
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessage.java122
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageFactory.java46
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java206
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesMessage.java124
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesTypedMessage.java804
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java536
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java173
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/CloseConsumerMessage.java43
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/FieldTableSupport.java54
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessage.java386
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessageFactory.java44
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSHeaderAdapter.java553
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java512
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessageFactory.java42
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessage.java176
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessageFactory.java41
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessage.java206
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessageFactory.java40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessage.java189
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessageFactory.java42
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageConverter.java195
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactory.java49
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactoryRegistry.java173
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java47
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java58
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java53
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java163
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/AddressHelper.java332
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Link.java172
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Node.java148
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidExchangeOptions.java45
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidQueueOptions.java103
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java881
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java467
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java136
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatConfig.java61
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java121
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java115
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/AMQCallbackHandler.java30
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java231
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties22
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java210
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties21
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java72
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernameHashedPasswordCallbackHandler.java102
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java65
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java105
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java63
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClient.java52
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClientFactory.java52
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/security/crammd5hashed/CRAMMD5HashedSaslClientFactory.java72
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java32
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java60
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateChangedEvent.java48
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateListener.java26
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java221
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java38
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java128
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java41
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQNoTransportForProtocolException.java59
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQTransportConnectionException.java43
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/transport/ITransportConnection.java32
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java90
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java351
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java63
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser.java253
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser_0_10.java423
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java348
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java135
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/client/vmbroker/AMQVMBrokerCreationException.java60
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/collections/KeyValue.java46
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/collections/ReferenceMap.java957
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractKeyValue.java83
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractMapEntry.java96
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/DefaultMapEntry.java67
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/ArithmeticExpression.java268
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/BinaryExpression.java103
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/BooleanExpression.java33
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/ComparisonExpression.java589
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/ConstantExpression.java204
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/Expression.java35
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/JMSSelectorFilter.java70
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/LogicExpression.java108
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/MessageFilter.java27
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/PropertyExpression.java297
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/filter/UnaryExpression.java321
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java119
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/ChannelLimitReachedException.java46
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/Connection.java69
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionListener.java58
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java96
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java324
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/Message.java30
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/MessageConsumer.java27
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java57
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/Session.java101
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/TopicSubscriber.java32
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverExchangeMethod.java320
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverMethod.java79
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java253
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java166
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jms/failover/NoFailover.java62
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jndi/Example.properties40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jndi/NameParserImpl.java37
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java369
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/jndi/ReadOnlyContext.java527
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/naming/ReadOnlyContext.java509
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/naming/jndi.properties40
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/nclient/MessagePartListener.java46
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/nclient/util/ByteBufferMessage.java190
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessageListener.java34
-rw-r--r--qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessagePartListenerAdapter.java88
195 files changed, 39430 insertions, 0 deletions
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQAnyDestination.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQAnyDestination.java
new file mode 100644
index 0000000000..999b22299c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQAnyDestination.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.client;
+
+import java.net.URISyntaxException;
+
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.Topic;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.messaging.Address;
+import org.apache.qpid.url.BindingURL;
+
+/**
+ * In order to support JMS 1.0 the Qpid implementation maps the
+ * direct exchange to JMS Queue and topic exchange to JMS Topic.
+ *
+ * The JMS 1.1 spec provides a javax.Destination as an abstraction
+ * to represent any type of destination.
+ * The abstract class AMQDestination has most of the functionality
+ * to support any destination defined in AMQP 0-10 spec.
+ */
+public class AMQAnyDestination extends AMQDestination implements Queue, Topic
+{
+ public AMQAnyDestination(BindingURL binding)
+ {
+ super(binding);
+ }
+
+ public AMQAnyDestination(String str) throws URISyntaxException
+ {
+ super(str);
+ }
+
+ public AMQAnyDestination(Address addr) throws Exception
+ {
+ super(addr);
+ }
+
+ public AMQAnyDestination(AMQShortString exchangeName,AMQShortString exchangeClass,
+ AMQShortString routingKey,boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName,
+ boolean isDurable, AMQShortString[] bindingKeys)
+ {
+ super(exchangeName, exchangeClass, routingKey, isExclusive, isAutoDelete, queueName, isDurable, bindingKeys);
+ }
+
+ @Override
+ public boolean isNameRequired()
+ {
+ return getAMQQueueName() == null;
+ }
+
+ public String getTopicName() throws JMSException
+ {
+ if (getRoutingKey() != null)
+ {
+ return getRoutingKey().asString();
+ }
+ else if (getSubject() != null)
+ {
+ return getSubject();
+ }
+ else
+ {
+ return null;
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.java
new file mode 100644
index 0000000000..6bae0166d1
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQAuthenticationException.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;
+
+/**
+ * AMQAuthenticationException represents all failures to authenticate access to a broker.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to authenticate the client.
+ * </table>
+ *
+ * @todo Will this alwyas have the same status code, NOT_ALLOWED 530? Might set this up to always use that code.
+ */
+public class AMQAuthenticationException extends AMQException
+{
+ public AMQAuthenticationException(AMQConstant error, String msg, Throwable cause)
+ {
+ super(error, msg, cause);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
new file mode 100644
index 0000000000..b31dd2bc91
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQBrokerDetails.java
@@ -0,0 +1,414 @@
+/*
+ *
+ * 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 java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.url.URLHelper;
+import org.apache.qpid.url.URLSyntaxException;
+
+public class AMQBrokerDetails implements BrokerDetails
+{
+ private String _host;
+ private int _port;
+ private String _transport;
+
+ private Map<String, String> _options = new HashMap<String, String>();
+
+ private SSLConfiguration _sslConfiguration;
+
+ public AMQBrokerDetails(){}
+
+ public AMQBrokerDetails(String url) throws URLSyntaxException
+ {
+
+ // URL should be of format tcp://host:port?option='value',option='value'
+ try
+ {
+ URI connection = new URI(url);
+
+ String transport = connection.getScheme();
+
+ // Handles some defaults to minimise changes to existing broker URLS e.g. localhost
+ if (transport != null)
+ {
+ //todo this list of valid transports should be enumerated somewhere
+ if ((!(transport.equalsIgnoreCase(BrokerDetails.VM) ||
+ transport.equalsIgnoreCase(BrokerDetails.TCP) ||
+ transport.equalsIgnoreCase(BrokerDetails.SOCKET))))
+ {
+ if (transport.equalsIgnoreCase("localhost"))
+ {
+ connection = new URI(DEFAULT_TRANSPORT + "://" + url);
+ transport = connection.getScheme();
+ }
+ else
+ {
+ if (url.charAt(transport.length()) == ':' && url.charAt(transport.length() + 1) != '/')
+ {
+ //Then most likely we have a host:port value
+ connection = new URI(DEFAULT_TRANSPORT + "://" + url);
+ transport = connection.getScheme();
+ }
+ else
+ {
+ throw URLHelper.parseError(0, transport.length(), "Unknown transport", url);
+ }
+ }
+ }
+ else if (url.indexOf("//") == -1)
+ {
+ throw new URLSyntaxException(url, "Missing '//' after the transport In broker URL",transport.length()+1,1);
+ }
+ }
+ else
+ {
+ //Default the transport
+ connection = new URI(DEFAULT_TRANSPORT + "://" + url);
+ transport = connection.getScheme();
+ }
+
+ if (transport == null)
+ {
+ throw URLHelper.parseError(-1, "Unknown transport in broker URL:'"
+ + url + "' Format: " + URL_FORMAT_EXAMPLE, "");
+ }
+
+ setTransport(transport);
+
+ String host = connection.getHost();
+
+ // Fix for Java 1.5
+ if (host == null)
+ {
+ host = "";
+
+ String auth = connection.getAuthority();
+ if (auth != null)
+ {
+ // contains both host & port myhost:5672
+ if (auth.contains(":"))
+ {
+ host = auth.substring(0,auth.indexOf(":"));
+ }
+ else
+ {
+ host = auth;
+ }
+ }
+
+ }
+
+ setHost(host);
+
+ int port = connection.getPort();
+
+ if (port == -1)
+ {
+ // Fix for when there is port data but it is not automatically parseable by getPort().
+ String auth = connection.getAuthority();
+
+ if (auth != null && auth.contains(":"))
+ {
+ int start = auth.indexOf(":") + 1;
+ int end = start;
+ boolean looking = true;
+ boolean found = false;
+ // Throw an URL exception if the port number is not specified
+ if (start == auth.length())
+ {
+ throw URLHelper.parseError(connection.toString().indexOf(auth) + end - 1,
+ connection.toString().indexOf(auth) + end, "Port number must be specified",
+ connection.toString());
+ }
+ //Walk the authority looking for a port value.
+ while (looking)
+ {
+ try
+ {
+ end++;
+ Integer.parseInt(auth.substring(start, end));
+
+ if (end >= auth.length())
+ {
+ looking = false;
+ found = true;
+ }
+ }
+ catch (NumberFormatException nfe)
+ {
+ looking = false;
+ }
+
+ }
+ if (found)
+ {
+ setPort(Integer.parseInt(auth.substring(start, end)));
+ }
+ else
+ {
+ throw URLHelper.parseError(connection.toString().indexOf(connection.getAuthority()) + end - 1,
+ "Illegal character in port number", connection.toString());
+ }
+
+ }
+ else
+ {
+ setPort(DEFAULT_PORT);
+ }
+ }
+ else
+ {
+ if (!_transport.equalsIgnoreCase(SOCKET))
+ {
+ setPort(port);
+ }
+ }
+
+ String queryString = connection.getQuery();
+
+ URLHelper.parseOptions(_options, queryString);
+
+ //Fragment is #string (not used)
+ }
+ catch (URISyntaxException uris)
+ {
+ if (uris instanceof URLSyntaxException)
+ {
+ throw(URLSyntaxException) uris;
+ }
+
+ throw URLHelper.parseError(uris.getIndex(), uris.getReason(), uris.getInput());
+ }
+ }
+
+ public AMQBrokerDetails(String host, int port, SSLConfiguration sslConfiguration)
+ {
+ _host = host;
+ _port = port;
+ _sslConfiguration = sslConfiguration;
+ }
+
+ public String getHost()
+ {
+ return _host;
+ }
+
+ public void setHost(String _host)
+ {
+ this._host = _host;
+ }
+
+ public int getPort()
+ {
+ return _port;
+ }
+
+ public void setPort(int _port)
+ {
+ this._port = _port;
+ }
+
+ public String getTransport()
+ {
+ return _transport;
+ }
+
+ public void setTransport(String _transport)
+ {
+ this._transport = _transport;
+ }
+
+
+ public String getProperty(String key)
+ {
+ return _options.get(key);
+ }
+
+ public void setProperty(String key, String value)
+ {
+ _options.put(key, value);
+ }
+
+ public long getTimeout()
+ {
+ if (_options.containsKey(OPTIONS_CONNECT_TIMEOUT))
+ {
+ try
+ {
+ return Long.parseLong(_options.get(OPTIONS_CONNECT_TIMEOUT));
+ }
+ catch (NumberFormatException nfe)
+ {
+ //Do nothing as we will use the default below.
+ }
+ }
+
+ return BrokerDetails.DEFAULT_CONNECT_TIMEOUT;
+ }
+
+ public boolean getBooleanProperty(String propName)
+ {
+ if (_options.containsKey(propName))
+ {
+ return Boolean.parseBoolean(_options.get(propName));
+ }
+
+ return false;
+ }
+
+ public void setTimeout(long timeout)
+ {
+ setProperty(OPTIONS_CONNECT_TIMEOUT, Long.toString(timeout));
+ }
+
+ public SSLConfiguration getSSLConfiguration()
+ {
+ return _sslConfiguration;
+ }
+
+ public void setSSLConfiguration(SSLConfiguration sslConfig)
+ {
+ _sslConfiguration = sslConfig;
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(_transport);
+ sb.append("://");
+
+ if (!(_transport.equalsIgnoreCase(VM)))
+ {
+ sb.append(_host);
+ }
+
+ if (!(_transport.equalsIgnoreCase(SOCKET)))
+ {
+ sb.append(':');
+ sb.append(_port);
+ }
+
+ sb.append(printOptionsURL());
+
+ return sb.toString();
+ }
+
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof BrokerDetails))
+ {
+ return false;
+ }
+
+ BrokerDetails bd = (BrokerDetails) o;
+
+ return _host.equalsIgnoreCase(bd.getHost()) &&
+ (_port == bd.getPort()) &&
+ _transport.equalsIgnoreCase(bd.getTransport()) &&
+ compareSSLConfigurations(bd.getSSLConfiguration());
+ //todo do we need to compare all the options as well?
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = _host != null ? _host.hashCode() : 0;
+ result = 31 * result + _port;
+ result = 31 * result + (_transport != null ? _transport.hashCode() : 0);
+ return result;
+ }
+
+ private String printOptionsURL()
+ {
+ StringBuffer optionsURL = new StringBuffer();
+
+ optionsURL.append('?');
+
+ if (!(_options.isEmpty()))
+ {
+
+ for (String key : _options.keySet())
+ {
+ optionsURL.append(key);
+
+ optionsURL.append("='");
+
+ optionsURL.append(_options.get(key));
+
+ optionsURL.append("'");
+
+ optionsURL.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+ }
+
+ //removeKey the extra DEFAULT_OPTION_SEPERATOR or the '?' if there are no options
+ optionsURL.deleteCharAt(optionsURL.length() - 1);
+
+ return optionsURL.toString();
+ }
+
+ // Do we need to do a more in-depth comparison?
+ private boolean compareSSLConfigurations(SSLConfiguration other)
+ {
+ boolean retval = false;
+ if (_sslConfiguration == null &&
+ other == null)
+ {
+ retval = true;
+ }
+ else if (_sslConfiguration != null &&
+ other != null)
+ {
+ retval = true;
+ }
+
+ return retval;
+ }
+
+ public static String checkTransport(String broker)
+ {
+ if ((!broker.contains("://")))
+ {
+ return "tcp://" + broker;
+ }
+ else
+ {
+ return broker;
+ }
+ }
+
+ public Map<String, String> getProperties()
+ {
+ return _options;
+ }
+
+ public void setProperties(Map<String, String> props)
+ {
+ _options = props;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
new file mode 100644
index 0000000000..94a55ef52c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnection.java
@@ -0,0 +1,1485 @@
+/*
+*
+ * 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 java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.ConnectException;
+import java.net.UnknownHostException;
+import java.nio.channels.UnresolvedAddressException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.ConnectionConsumer;
+import javax.jms.ConnectionMetaData;
+import javax.jms.Destination;
+import javax.jms.ExceptionListener;
+import javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.QueueConnection;
+import javax.jms.QueueSession;
+import javax.jms.ServerSessionPool;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicSession;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.naming.StringRefAddr;
+
+import org.apache.qpid.AMQConnectionFailureException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQProtocolException;
+import org.apache.qpid.AMQUnresolvedAddressException;
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.failover.FailoverProtectedOperation;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicQosBody;
+import org.apache.qpid.framing.BasicQosOkBody;
+import org.apache.qpid.framing.ChannelOpenBody;
+import org.apache.qpid.framing.ChannelOpenOkBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.TxSelectBody;
+import org.apache.qpid.framing.TxSelectOkBody;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AMQConnection extends Closeable implements Connection, QueueConnection, TopicConnection, Referenceable
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AMQConnection.class);
+
+
+ /**
+ * This is the "root" mutex that must be held when doing anything that could be impacted by failover. This must be
+ * held by any child objects of this connection such as the session, producers and consumers.
+ */
+ private final Object _failoverMutex = new Object();
+
+ 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
+ * and we must prevent the client from opening too many.
+ */
+ private long _maximumChannelCount;
+
+ /** The maximum size of frame supported by the server */
+ private long _maximumFrameSize;
+
+ /**
+ * The protocol handler dispatches protocol events for this connection. For example, when the connection is dropped
+ * the handler deals with this. It also deals with the initial dispatch of any protocol frames to their appropriate
+ * handler.
+ */
+ protected AMQProtocolHandler _protocolHandler;
+
+ /** Maps from session id (Integer) to AMQSession instance */
+ private final ChannelToSessionMap _sessions = new ChannelToSessionMap();
+
+ private final String _clientName;
+
+ /** The user name to use for authentication */
+ private String _username;
+
+ /** The password to use for authentication */
+ private String _password;
+
+ /** The virtual path to connect to on the AMQ server */
+ private String _virtualHost;
+
+ protected ExceptionListener _exceptionListener;
+
+ private ConnectionListener _connectionListener;
+
+ private final ConnectionURL _connectionURL;
+
+ /**
+ * Whether this connection is started, i.e. whether messages are flowing to consumers. It has no meaning for message
+ * publication.
+ */
+ protected volatile boolean _started;
+
+ /** Policy dictating how to failover */
+ protected FailoverPolicy _failoverPolicy;
+
+ /*
+ * _Connected should be refactored with a suitable wait object.
+ */
+ protected boolean _connected;
+
+ /*
+ * The connection meta data
+ */
+ private QpidConnectionMetaData _connectionMetaData;
+
+ /** Configuration info for SSL */
+ private SSLConfiguration _sslConfiguration;
+
+ 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. */
+ private final ExecutorService _taskPool = Executors.newCachedThreadPool();
+ private static final long DEFAULT_TIMEOUT = 1000 * 30;
+
+ protected AMQConnectionDelegate _delegate;
+
+ // this connection maximum number of prefetched messages
+ private int _maxPrefetch;
+
+ //Indicates whether persistent messages are synchronized
+ private boolean _syncPersistence;
+
+ //Indicates whether we need to sync on every message ack
+ private boolean _syncAck;
+
+ //Indicates the sync publish options (persistent|all)
+ //By default it's async publish
+ private String _syncPublish = "";
+
+ // Indicates whether to use the old map message format or the
+ // new amqp-0-10 encoded format.
+ private boolean _useLegacyMapMessageFormat;
+
+ /**
+ * @param broker brokerdetails
+ * @param username username
+ * @param password password
+ * @param clientName clientid
+ * @param virtualHost virtualhost
+ *
+ * @throws AMQException
+ * @throws URLSyntaxException
+ */
+ public AMQConnection(String broker, String username, String password, String clientName, String virtualHost)
+ throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(
+ ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ + ((clientName == null) ? "" : clientName) + "/" + virtualHost + "?brokerlist='"
+ + AMQBrokerDetails.checkTransport(broker) + "'"), null);
+ }
+
+ /**
+ * @param broker brokerdetails
+ * @param username username
+ * @param password password
+ * @param clientName clientid
+ * @param virtualHost virtualhost
+ *
+ * @throws AMQException
+ * @throws URLSyntaxException
+ */
+ public AMQConnection(String broker, String username, String password, String clientName, String virtualHost,
+ SSLConfiguration sslConfig) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(
+ ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ + ((clientName == null) ? "" : clientName) + "/" + virtualHost + "?brokerlist='"
+ + AMQBrokerDetails.checkTransport(broker) + "'"), sslConfig);
+ }
+
+ public AMQConnection(String host, int port, String username, String password, String clientName, String virtualHost)
+ throws AMQException, URLSyntaxException
+ {
+ this(host, port, false, username, password, clientName, virtualHost, null);
+ }
+
+ public AMQConnection(String host, int port, String username, String password, String clientName, String virtualHost,
+ SSLConfiguration sslConfig) throws AMQException, URLSyntaxException
+ {
+ this(host, port, false, username, password, clientName, virtualHost, sslConfig);
+ }
+
+ public AMQConnection(String host, int port, boolean useSSL, String username, String password, String clientName,
+ String virtualHost, SSLConfiguration sslConfig) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(
+ useSSL
+ ? (ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ + ((clientName == null) ? "" : clientName) + virtualHost + "?brokerlist='tcp://" + host + ":" + port
+ + "'" + "," + BrokerDetails.OPTIONS_SSL + "='true'")
+ : (ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@"
+ + ((clientName == null) ? "" : clientName) + virtualHost + "?brokerlist='tcp://" + host + ":" + port
+ + "'" + "," + BrokerDetails.OPTIONS_SSL + "='false'")), sslConfig);
+ }
+
+ public AMQConnection(String connection) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(connection), null);
+ }
+
+ public AMQConnection(String connection, SSLConfiguration sslConfig) throws AMQException, URLSyntaxException
+ {
+ this(new AMQConnectionURL(connection), sslConfig);
+ }
+
+ /**
+ * @todo Some horrible stuff going on here with setting exceptions to be non-null to detect if an exception
+ * was thrown during the connection! Intention not clear. Use a flag anyway, not exceptions... Will fix soon.
+ */
+ public AMQConnection(ConnectionURL connectionURL, SSLConfiguration sslConfig) throws AMQException
+ {
+ if (connectionURL == null)
+ {
+ throw new IllegalArgumentException("Connection must be specified");
+ }
+
+ // set this connection maxPrefetch
+ if (connectionURL.getOption(ConnectionURL.OPTIONS_MAXPREFETCH) != null)
+ {
+ _maxPrefetch = Integer.parseInt(connectionURL.getOption(ConnectionURL.OPTIONS_MAXPREFETCH));
+ }
+ else
+ {
+ // use the default value set for all connections
+ _maxPrefetch = Integer.parseInt(System.getProperties().getProperty(ClientProperties.MAX_PREFETCH_PROP_NAME,
+ ClientProperties.MAX_PREFETCH_DEFAULT));
+ }
+
+ if (connectionURL.getOption(ConnectionURL.OPTIONS_SYNC_PERSISTENCE) != null)
+ {
+ _syncPersistence =
+ Boolean.parseBoolean(connectionURL.getOption(ConnectionURL.OPTIONS_SYNC_PERSISTENCE));
+ _logger.warn("sync_persistence is a deprecated property, " +
+ "please use sync_publish={persistent|all} instead");
+ }
+ else
+ {
+ // use the default value set for all connections
+ _syncPersistence = Boolean.getBoolean(ClientProperties.SYNC_PERSISTENT_PROP_NAME);
+ if (_syncPersistence)
+ {
+ _logger.warn("sync_persistence is a deprecated property, " +
+ "please use sync_publish={persistent|all} instead");
+ }
+ }
+
+ if (connectionURL.getOption(ConnectionURL.OPTIONS_SYNC_ACK) != null)
+ {
+ _syncAck = Boolean.parseBoolean(connectionURL.getOption(ConnectionURL.OPTIONS_SYNC_ACK));
+ }
+ else
+ {
+ // use the default value set for all connections
+ _syncAck = Boolean.getBoolean(ClientProperties.SYNC_ACK_PROP_NAME);
+ }
+
+ if (connectionURL.getOption(ConnectionURL.OPTIONS_SYNC_PUBLISH) != null)
+ {
+ _syncPublish = connectionURL.getOption(ConnectionURL.OPTIONS_SYNC_PUBLISH);
+ }
+ else
+ {
+ // use the default value set for all connections
+ _syncPublish = System.getProperty((ClientProperties.SYNC_PUBLISH_PROP_NAME),_syncPublish);
+ }
+
+ if (connectionURL.getOption(ConnectionURL.OPTIONS_USE_LEGACY_MAP_MESSAGE_FORMAT) != null)
+ {
+ _useLegacyMapMessageFormat = Boolean.parseBoolean(
+ connectionURL.getOption(ConnectionURL.OPTIONS_USE_LEGACY_MAP_MESSAGE_FORMAT));
+ }
+ else
+ {
+ // use the default value set for all connections
+ _useLegacyMapMessageFormat = Boolean.getBoolean(ClientProperties.USE_LEGACY_MAP_MESSAGE_FORMAT);
+ }
+
+ String amqpVersion = System.getProperty((ClientProperties.AMQP_VERSION), "0-10");
+ _logger.debug("AMQP version " + amqpVersion);
+
+ _failoverPolicy = new FailoverPolicy(connectionURL, this);
+ BrokerDetails brokerDetails = _failoverPolicy.getCurrentBrokerDetails();
+ if (brokerDetails.getTransport().equals(BrokerDetails.VM) || "0-8".equals(amqpVersion))
+ {
+ _delegate = new AMQConnectionDelegate_8_0(this);
+ }
+ else if ("0-9".equals(amqpVersion))
+ {
+ _delegate = new AMQConnectionDelegate_0_9(this);
+ }
+ else if ("0-91".equals(amqpVersion) || "0-9-1".equals(amqpVersion))
+ {
+ _delegate = new AMQConnectionDelegate_9_1(this);
+ }
+ else
+ {
+ _delegate = new AMQConnectionDelegate_0_10(this);
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Connection:" + connectionURL);
+ }
+
+ _sslConfiguration = sslConfig;
+ _connectionURL = connectionURL;
+
+ _clientName = connectionURL.getClientName();
+ _username = connectionURL.getUsername();
+ _password = connectionURL.getPassword();
+
+ setVirtualHost(connectionURL.getVirtualHost());
+
+ if (connectionURL.getDefaultQueueExchangeName() != null)
+ {
+ _defaultQueueExchangeName = connectionURL.getDefaultQueueExchangeName();
+ }
+
+ if (connectionURL.getDefaultTopicExchangeName() != null)
+ {
+ _defaultTopicExchangeName = connectionURL.getDefaultTopicExchangeName();
+ }
+
+ if (connectionURL.getTemporaryQueueExchangeName() != null)
+ {
+ _temporaryQueueExchangeName = connectionURL.getTemporaryQueueExchangeName();
+ }
+
+ if (connectionURL.getTemporaryTopicExchangeName() != null)
+ {
+ _temporaryTopicExchangeName = connectionURL.getTemporaryTopicExchangeName();
+ }
+
+ _protocolHandler = new AMQProtocolHandler(this);
+
+ _logger.info("Connecting with ProtocolHandler Version:"+_protocolHandler.getProtocolVersion());
+
+ // We are not currently connected
+ _connected = false;
+
+ boolean retryAllowed = true;
+ Exception connectionException = null;
+ while (!_connected && retryAllowed && brokerDetails != null)
+ {
+ ProtocolVersion pe = null;
+ try
+ {
+ pe = makeBrokerConnection(brokerDetails);
+ }
+ catch (Exception e)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Unable to connect to broker at " +
+ _failoverPolicy.getCurrentBrokerDetails(),
+ e);
+ }
+ connectionException = e;
+ }
+
+ if (pe != null)
+ {
+ // reset the delegate to the version returned by the
+ // broker
+ initDelegate(pe);
+ }
+ else if (!_connected)
+ {
+ retryAllowed = _failoverPolicy.failoverAllowed();
+ brokerDetails = _failoverPolicy.getNextBrokerDetails();
+ }
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Are we connected:" + _connected);
+ }
+
+ if (!_connected)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Last attempted ProtocolHandler Version:"+_protocolHandler.getProtocolVersion());
+ }
+
+ String message = null;
+
+ if (connectionException != null)
+ {
+ if (connectionException.getCause() != null)
+ {
+ message = connectionException.getCause().getMessage();
+ }
+ else
+ {
+ message = connectionException.getMessage();
+ }
+ }
+
+ if ((message == null) || message.equals(""))
+ {
+ if (message == null)
+ {
+ message = "Unable to Connect";
+ }
+ else // can only be "" if getMessage() returned it therfore lastException != null
+ {
+ message = "Unable to Connect:" + connectionException.getClass();
+ }
+ }
+
+ for (Throwable th = connectionException; th != null; th = th.getCause())
+ {
+ if (th instanceof UnresolvedAddressException ||
+ th instanceof UnknownHostException)
+ {
+ throw new AMQUnresolvedAddressException
+ (message,
+ _failoverPolicy.getCurrentBrokerDetails().toString(),
+ connectionException);
+ }
+ }
+
+ throw new AMQConnectionFailureException(message, connectionException);
+ }
+
+ _logger.info("Connected with ProtocolHandler Version:"+_protocolHandler.getProtocolVersion());
+
+ _sessions.setMaxChannelID(_delegate.getMaxChannelID());
+ _sessions.setMinChannelID(_delegate.getMinChannelID());
+
+ _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 initDelegate(ProtocolVersion pe) throws AMQProtocolException
+ {
+ try
+ {
+ String delegateClassName = String.format
+ ("org.apache.qpid.client.AMQConnectionDelegate_%s_%s",
+ pe.getMajorVersion(), pe.getMinorVersion());
+ _logger.info("Looking up delegate '" + delegateClassName + "' Based on PE:" + pe);
+ Class c = Class.forName(delegateClassName);
+ Class partypes[] = new Class[1];
+ partypes[0] = AMQConnection.class;
+ _delegate = (AMQConnectionDelegate) c.getConstructor(partypes).newInstance(this);
+ //Update our session to use this new protocol version
+ _protocolHandler.getProtocolSession().setProtocolVersion(_delegate.getProtocolVersion());
+
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new AMQProtocolException
+ (AMQConstant.UNSUPPORTED_CLIENT_PROTOCOL_ERROR,
+ String.format("Protocol: %s.%s is rquired by the broker but is not " +
+ "currently supported by this client library implementation",
+ pe.getMajorVersion(), pe.getMinorVersion()),
+ e);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new RuntimeException("unable to locate constructor for delegate", e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new RuntimeException("error instantiating delegate", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new RuntimeException("error accessing delegate", e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new RuntimeException("error invoking delegate", e);
+ }
+ }
+
+ private void setVirtualHost(String virtualHost)
+ {
+ if (virtualHost != null && virtualHost.startsWith("/"))
+ {
+ virtualHost = virtualHost.substring(1);
+ }
+
+ _virtualHost = virtualHost;
+ }
+
+ public boolean attemptReconnection(String host, int port)
+ {
+ BrokerDetails bd = new AMQBrokerDetails(host, port, _sslConfiguration);
+
+ _failoverPolicy.setBroker(bd);
+
+ try
+ {
+ makeBrokerConnection(bd);
+
+ return true;
+ }
+ catch (Exception e)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Unable to connect to broker at " + bd);
+ }
+
+ attemptReconnection();
+ }
+
+ return false;
+ }
+
+ public boolean attemptReconnection()
+ {
+ BrokerDetails broker = null;
+ while (_failoverPolicy.failoverAllowed() && (broker = _failoverPolicy.getNextBrokerDetails()) != null)
+ {
+ try
+ {
+ makeBrokerConnection(broker);
+ return true;
+ }
+ catch (Exception e)
+ {
+ if (!(e instanceof AMQException))
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Unable to connect to broker at " + _failoverPolicy.getCurrentBrokerDetails(), e);
+ }
+ }
+ else
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info(e.getMessage() + ":Unable to connect to broker at "
+ + _failoverPolicy.getCurrentBrokerDetails());
+ }
+ }
+ }
+ }
+
+ // connection unsuccessful
+ return false;
+ }
+
+ public ProtocolVersion makeBrokerConnection(BrokerDetails brokerDetail) throws IOException, AMQException
+ {
+ return _delegate.makeBrokerConnection(brokerDetail);
+ }
+
+ public <T, E extends Exception> T executeRetrySupport(FailoverProtectedOperation<T,E> operation) throws E
+ {
+ return _delegate.executeRetrySupport(operation);
+ }
+
+ /**
+ * Get the details of the currently active broker
+ *
+ * @return null if no broker is active (i.e. no successful connection has been made, or the BrokerDetail instance
+ * otherwise
+ */
+ public BrokerDetails getActiveBrokerDetails()
+ {
+ return _failoverPolicy.getCurrentBrokerDetails();
+ }
+
+ public boolean failoverAllowed()
+ {
+ if (!_connected)
+ {
+ return false;
+ }
+ else
+ {
+ return _failoverPolicy.failoverAllowed();
+ }
+ }
+
+ public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode) throws JMSException
+ {
+ return createSession(transacted, acknowledgeMode, _maxPrefetch);
+ }
+
+ public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode, final int prefetch)
+ throws JMSException
+ {
+ return createSession(transacted, acknowledgeMode, prefetch, prefetch);
+ }
+
+ 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);
+ }
+
+ TxSelectBody body = getProtocolHandler().getMethodRegistry().createTxSelectBody();
+
+ // TODO: Be aware of possible changes to parameter order as versions change.
+ _protocolHandler.syncWrite(body.generateFrame(channelId), TxSelectOkBody.class);
+ }
+ }
+
+ public void setFailoverPolicy(FailoverPolicy policy)
+ {
+ _failoverPolicy = policy;
+ }
+
+ public FailoverPolicy getFailoverPolicy()
+ {
+ return _failoverPolicy;
+ }
+
+ /**
+ * Returns an AMQQueueSessionAdaptor which wraps an AMQSession and throws IllegalStateExceptions where specified in
+ * the JMS spec
+ *
+ * @param transacted
+ * @param acknowledgeMode
+ *
+ * @return QueueSession
+ *
+ * @throws JMSException
+ */
+ public QueueSession createQueueSession(boolean transacted, int acknowledgeMode) throws JMSException
+ {
+ return new AMQQueueSessionAdaptor(createSession(transacted, acknowledgeMode));
+ }
+
+ /**
+ * Returns an AMQTopicSessionAdapter which wraps an AMQSession and throws IllegalStateExceptions where specified in
+ * the JMS spec
+ *
+ * @param transacted
+ * @param acknowledgeMode
+ *
+ * @return TopicSession
+ *
+ * @throws JMSException
+ */
+ public TopicSession createTopicSession(boolean transacted, int acknowledgeMode) throws JMSException
+ {
+ return new AMQTopicSessionAdaptor(createSession(transacted, acknowledgeMode));
+ }
+
+ public boolean channelLimitReached()
+ {
+ return _sessions.size() >= _maximumChannelCount;
+ }
+
+ public String getClientID() throws JMSException
+ {
+ checkNotClosed();
+
+ return _clientName;
+ }
+
+ public void setClientID(String clientID) throws JMSException
+ {
+ checkNotClosed();
+ // in AMQP it is not possible to change the client ID. If one is not specified
+ // upon connection construction, an id is generated automatically. Therefore
+ // we can always throw an exception.
+ if (!Boolean.getBoolean(ClientProperties.IGNORE_SET_CLIENTID_PROP_NAME))
+ {
+ throw new IllegalStateException("Client name cannot be changed after being set");
+ }
+ else
+ {
+ _logger.info("Operation setClientID is ignored using ID: " + getClientID());
+ }
+ }
+
+ public ConnectionMetaData getMetaData() throws JMSException
+ {
+ checkNotClosed();
+
+ return _connectionMetaData;
+
+ }
+
+ public ExceptionListener getExceptionListener() throws JMSException
+ {
+ checkNotClosed();
+
+ return _exceptionListener;
+ }
+
+ public void setExceptionListener(ExceptionListener listener) throws JMSException
+ {
+ checkNotClosed();
+ _exceptionListener = listener;
+ }
+
+ /**
+ * Start the connection, i.e. start flowing messages. Note that this method must be called only from a single thread
+ * and is not thread safe (which is legal according to the JMS specification).
+ *
+ * @throws JMSException
+ */
+ public void start() throws JMSException
+ {
+ checkNotClosed();
+ if (!_started)
+ {
+ _started = true;
+ final Iterator it = _sessions.values().iterator();
+ while (it.hasNext())
+ {
+ final AMQSession s = (AMQSession) (it.next());
+ try
+ {
+ s.start();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException(e);
+ }
+ }
+
+ }
+ }
+
+ public void stop() throws JMSException
+ {
+ checkNotClosed();
+ if (_started)
+ {
+ for (Iterator i = _sessions.values().iterator(); i.hasNext();)
+ {
+ try
+ {
+ ((AMQSession) i.next()).stop();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException(e);
+ }
+ }
+
+ _started = false;
+ }
+ }
+
+ public void close() throws JMSException
+ {
+ close(DEFAULT_TIMEOUT);
+ }
+
+ public void close(long timeout) throws JMSException
+ {
+ close(new ArrayList<AMQSession>(_sessions.values()), timeout);
+ }
+
+ public void close(List<AMQSession> sessions, long timeout) throws JMSException
+ {
+ if (!_closed.getAndSet(true))
+ {
+ _closing.set(true);
+ try{
+ doClose(sessions, timeout);
+ }finally{
+ _closing.set(false);
+ }
+ }
+ }
+
+ private void doClose(List<AMQSession> sessions, long timeout) throws JMSException
+ {
+ synchronized (_sessionCreationLock)
+ {
+ if (!sessions.isEmpty())
+ {
+ AMQSession session = sessions.remove(0);
+ synchronized (session.getMessageDeliveryLock())
+ {
+ doClose(sessions, timeout);
+ }
+ }
+ else
+ {
+ synchronized (getFailoverMutex())
+ {
+ try
+ {
+ long startCloseTime = System.currentTimeMillis();
+
+ closeAllSessions(null, timeout, startCloseTime);
+
+ //This MUST occur after we have successfully closed all Channels/Sessions
+ _taskPool.shutdown();
+
+ 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.closeConnection(timeout);
+
+ //If the taskpool hasn't shutdown by now then give it shutdownNow.
+ // This will interupt any running tasks.
+ if (!_taskPool.isTerminated())
+ {
+ List<Runnable> tasks = _taskPool.shutdownNow();
+ for (Runnable r : tasks)
+ {
+ _logger.warn("Connection close forced taskpool to prevent execution:" + r);
+ }
+ }
+ }
+ catch (AMQException e)
+ {
+ _logger.error("error:", e);
+ JMSException jmse = new JMSException("Error closing connection: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(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
+ * mark objects "visible" in userland as closed after failover or other significant event that impacts the
+ * connection. <p/> The caller must hold the failover mutex before calling this method.
+ */
+ private void markAllSessionsClosed()
+ {
+ final LinkedList sessionCopy = new LinkedList(_sessions.values());
+ final Iterator it = sessionCopy.iterator();
+ while (it.hasNext())
+ {
+ final AMQSession session = (AMQSession) it.next();
+
+ session.markClosed();
+ }
+
+ _sessions.clear();
+ }
+
+ /**
+ * Close all the sessions, either due to normal connection closure or due to an error occurring.
+ *
+ * @param cause if not null, the error that is causing this shutdown <p/> The caller must hold the failover mutex
+ * before calling this method.
+ */
+ private void closeAllSessions(Throwable cause, long timeout, long starttime) throws JMSException
+ {
+ final LinkedList sessionCopy = new LinkedList(_sessions.values());
+ final Iterator it = sessionCopy.iterator();
+ JMSException sessionException = null;
+ while (it.hasNext())
+ {
+ final AMQSession session = (AMQSession) it.next();
+ if (cause != null)
+ {
+ session.closed(cause);
+ }
+ else
+ {
+ try
+ {
+ if (starttime != -1)
+ {
+ timeout = adjustTimeout(timeout, starttime);
+ }
+
+ session.close(timeout);
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e);
+ sessionException = e;
+ }
+ }
+ }
+
+ _sessions.clear();
+ if (sessionException != null)
+ {
+ throw sessionException;
+ }
+ }
+
+ public ConnectionConsumer createConnectionConsumer(Destination destination, String messageSelector,
+ ServerSessionPool sessionPool, int maxMessages) throws JMSException
+ {
+ checkNotClosed();
+
+ return null;
+ }
+
+ public ConnectionConsumer createConnectionConsumer(Queue queue, String messageSelector, ServerSessionPool sessionPool,
+ int maxMessages) throws JMSException
+ {
+ checkNotClosed();
+
+ return null;
+ }
+
+ public ConnectionConsumer createConnectionConsumer(Topic topic, String messageSelector, ServerSessionPool sessionPool,
+ int maxMessages) throws JMSException
+ {
+ checkNotClosed();
+
+ return null;
+ }
+
+ public ConnectionConsumer createDurableConnectionConsumer(Topic topic, String subscriptionName, String messageSelector,
+ ServerSessionPool sessionPool, int maxMessages) throws JMSException
+ {
+ // TODO Auto-generated method stub
+ checkNotClosed();
+
+ return null;
+ }
+
+ public long getMaximumChannelCount() throws JMSException
+ {
+ checkNotClosed();
+
+ return _maximumChannelCount;
+ }
+
+ public void setConnectionListener(ConnectionListener listener)
+ {
+ _connectionListener = listener;
+ }
+
+ public ConnectionListener getConnectionListener()
+ {
+ return _connectionListener;
+ }
+
+ public void setMaximumChannelCount(long maximumChannelCount)
+ {
+ _maximumChannelCount = maximumChannelCount;
+ }
+
+ public void setMaximumFrameSize(long frameMax)
+ {
+ _maximumFrameSize = frameMax;
+ }
+
+ public long getMaximumFrameSize()
+ {
+ return _maximumFrameSize;
+ }
+
+ public ChannelToSessionMap getSessions()
+ {
+ return _sessions;
+ }
+
+ public String getUsername()
+ {
+ return _username;
+ }
+
+ public void setUsername(String id)
+ {
+ _username = id;
+ }
+
+ public String getPassword()
+ {
+ return _password;
+ }
+
+ public String getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public AMQProtocolHandler getProtocolHandler()
+ {
+ return _protocolHandler;
+ }
+
+ public boolean started()
+ {
+ return _started;
+ }
+
+ public void bytesSent(long writtenBytes)
+ {
+ if (_connectionListener != null)
+ {
+ _connectionListener.bytesSent(writtenBytes);
+ }
+ }
+
+ public void bytesReceived(long receivedBytes)
+ {
+ if (_connectionListener != null)
+ {
+ _connectionListener.bytesReceived(receivedBytes);
+ }
+ }
+
+ /**
+ * Fire the preFailover event to the registered connection listener (if any)
+ *
+ * @param redirect true if this is the result of a redirect request rather than a connection error
+ *
+ * @return true if no listener or listener does not veto change
+ */
+ public boolean firePreFailover(boolean redirect)
+ {
+ boolean proceed = true;
+ if (_connectionListener != null)
+ {
+ proceed = _connectionListener.preFailover(redirect);
+ }
+
+ return proceed;
+ }
+
+ /**
+ * Fire the preResubscribe event to the registered connection listener (if any). If the listener vetoes
+ * resubscription then all the sessions are closed.
+ *
+ * @return true if no listener or listener does not veto resubscription.
+ *
+ * @throws JMSException
+ */
+ public boolean firePreResubscribe() throws JMSException
+ {
+ if (_connectionListener != null)
+ {
+ boolean resubscribe = _connectionListener.preResubscribe();
+ if (!resubscribe)
+ {
+ markAllSessionsClosed();
+ }
+
+ return resubscribe;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /** Fires a failover complete event to the registered connection listener (if any). */
+ public void fireFailoverComplete()
+ {
+ if (_connectionListener != null)
+ {
+ _connectionListener.failoverComplete();
+ }
+ }
+
+ /**
+ * In order to protect the consistency of the connection and its child sessions, consumers and producers, the
+ * "failover mutex" must be held when doing any operations that could be corrupted during failover.
+ *
+ * @return a mutex. Guaranteed never to change for the lifetime of this connection even if failover occurs.
+ */
+ public final Object getFailoverMutex()
+ {
+ return _failoverMutex;
+ }
+
+ public void failoverPrep()
+ {
+ _delegate.failoverPrep();
+ }
+
+ public void resubscribeSessions() throws JMSException, AMQException, FailoverException
+ {
+ _delegate.resubscribeSessions();
+ }
+
+ /**
+ * If failover is taking place this will block until it has completed. If failover is not taking place it will
+ * return immediately.
+ *
+ * @throws InterruptedException
+ */
+ public void blockUntilNotFailingOver() throws InterruptedException
+ {
+ _protocolHandler.blockUntilNotFailingOver();
+ }
+
+ /**
+ * Invoked by the AMQProtocolSession when a protocol session exception has occurred. This method sends the exception
+ * to a JMS exception listener, if configured, and propagates the exception to sessions, which in turn will
+ * propagate to consumers. This allows synchronous consumers to have exceptions thrown to them.
+ *
+ * @param cause the exception
+ */
+ public void exceptionReceived(Throwable cause)
+ {
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("exceptionReceived done by:" + Thread.currentThread().getName(), cause);
+ }
+
+ final JMSException je;
+ if (cause instanceof JMSException)
+ {
+ je = (JMSException) cause;
+ }
+ else
+ {
+ AMQConstant code = null;
+
+ if (cause instanceof AMQException)
+ {
+ code = ((AMQException) cause).getErrorCode();
+ }
+
+ if (code != null)
+ {
+ je = new JMSException(Integer.toString(code.getCode()), "Exception thrown against " + toString() + ": " + cause);
+ }
+ else
+ {
+ //Should never get here as all AMQEs are required to have an ErrorCode!
+ // Other than AMQDisconnectedEx!
+
+ if (cause instanceof AMQDisconnectedException)
+ {
+ Exception last = _protocolHandler.getStateManager().getLastException();
+ if (last != null)
+ {
+ _logger.info("StateManager had an exception for us to use a cause of our Disconnected Exception");
+ cause = last;
+ }
+ }
+ je = new JMSException("Exception thrown against " + toString() + ": " + cause);
+ }
+
+ if (cause instanceof Exception)
+ {
+ je.setLinkedException((Exception) cause);
+ }
+
+ je.initCause(cause);
+ }
+
+ boolean closer = false;
+
+ // in the case of an IOException, MINA has closed the protocol session so we set _closed to true
+ // so that any generic client code that tries to close the connection will not mess up this error
+ // handling sequence
+ if (cause instanceof IOException || cause instanceof AMQDisconnectedException)
+ {
+ // If we have an IOE/AMQDisconnect there is no connection to close on.
+ _closing.set(false);
+ closer = !_closed.getAndSet(true);
+
+ _protocolHandler.getProtocolSession().notifyError(je);
+ }
+
+ // get the failover mutex before trying to close
+ synchronized (getFailoverMutex())
+ {
+ // decide if we are going to close the session
+ if (hardError(cause))
+ {
+ closer = (!_closed.getAndSet(true)) || closer;
+ {
+ _logger.info("Closing AMQConnection due to :" + cause);
+ }
+ }
+ else
+ {
+ _logger.info("Not a hard-error connection not closing: " + cause);
+ }
+
+ // deliver the exception if there is a listener
+ if (_exceptionListener != null)
+ {
+ _exceptionListener.onException(je);
+ }
+ else
+ {
+ _logger.error("Throwable Received but no listener set: " + cause);
+ }
+
+ // if we are closing the connection, close sessions first
+ if (closer)
+ {
+ try
+ {
+ closeAllSessions(cause, -1, -1); // FIXME: when doing this end up with RejectedExecutionException from executor.
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing all sessions: " + e, e);
+ }
+ }
+ }
+ }
+
+ 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);
+ }
+
+ public void deregisterSession(int channelId)
+ {
+ _sessions.remove(channelId);
+ }
+
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer("AMQConnection:\n");
+ if (_failoverPolicy.getCurrentBrokerDetails() == null)
+ {
+ buf.append("No active broker connection");
+ }
+ else
+ {
+ BrokerDetails bd = _failoverPolicy.getCurrentBrokerDetails();
+ buf.append("Host: ").append(String.valueOf(bd.getHost()));
+ buf.append("\nPort: ").append(String.valueOf(bd.getPort()));
+ }
+
+ buf.append("\nVirtual Host: ").append(String.valueOf(_virtualHost));
+ buf.append("\nClient ID: ").append(String.valueOf(_clientName));
+ buf.append("\nActive session count: ").append((_sessions == null) ? 0 : _sessions.size());
+
+ return buf.toString();
+ }
+
+ /**
+ * Returns connection url.
+ * @return connection url
+ */
+ public ConnectionURL getConnectionURL()
+ {
+ return _connectionURL;
+ }
+
+ /**
+ * Returns stringified connection url. This url is suitable only for display
+ * as {@link AMQConnectionURL#toString()} converts any password to asterisks.
+ * @return connection url
+ */
+ public String toURL()
+ {
+ return _connectionURL.toString();
+ }
+
+ public Reference getReference() throws NamingException
+ {
+ return new Reference(AMQConnection.class.getName(), new StringRefAddr(AMQConnection.class.getName(), toURL()),
+ AMQConnectionFactory.class.getName(), null); // factory location
+ }
+
+ public SSLConfiguration getSSLConfiguration()
+ {
+ return _sslConfiguration;
+ }
+
+ public AMQShortString getDefaultTopicExchangeName()
+ {
+ return _defaultTopicExchangeName;
+ }
+
+ public void setDefaultTopicExchangeName(AMQShortString defaultTopicExchangeName)
+ {
+ _defaultTopicExchangeName = defaultTopicExchangeName;
+ }
+
+ public AMQShortString getDefaultQueueExchangeName()
+ {
+ return _defaultQueueExchangeName;
+ }
+
+ public void setDefaultQueueExchangeName(AMQShortString defaultQueueExchangeName)
+ {
+ _defaultQueueExchangeName = defaultQueueExchangeName;
+ }
+
+ public AMQShortString getTemporaryTopicExchangeName()
+ {
+ return _temporaryTopicExchangeName;
+ }
+
+ public AMQShortString getTemporaryQueueExchangeName()
+ {
+ return _temporaryQueueExchangeName; // To change body of created methods use File | Settings | File Templates.
+ }
+
+ public void setTemporaryTopicExchangeName(AMQShortString temporaryTopicExchangeName)
+ {
+ _temporaryTopicExchangeName = temporaryTopicExchangeName;
+ }
+
+ public void setTemporaryQueueExchangeName(AMQShortString temporaryQueueExchangeName)
+ {
+ _temporaryQueueExchangeName = temporaryQueueExchangeName;
+ }
+
+ public void performConnectionTask(Runnable task)
+ {
+ _taskPool.execute(task);
+ }
+
+ public AMQSession getSession(int channelId)
+ {
+ return _sessions.get(channelId);
+ }
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return _delegate.getProtocolVersion();
+ }
+
+ public boolean isFailingOver()
+ {
+ return (_protocolHandler.getFailoverLatch() != null);
+ }
+
+ /**
+ * Get the maximum number of messages that this connection can pre-fetch.
+ *
+ * @return The maximum number of messages that this connection can pre-fetch.
+ */
+ public long getMaxPrefetch()
+ {
+ return _maxPrefetch;
+ }
+
+ /**
+ * Indicates whether persistent messages are synchronized
+ *
+ * @return true if persistent messages are synchronized false otherwise
+ */
+ public boolean getSyncPersistence()
+ {
+ return _syncPersistence;
+ }
+
+ /**
+ * Indicates whether we need to sync on every message ack
+ */
+ public boolean getSyncAck()
+ {
+ return _syncAck;
+ }
+
+ public String getSyncPublish()
+ {
+ return _syncPublish;
+ }
+
+ public int getNextChannelID()
+ {
+ return _sessions.getNextChannelId();
+ }
+
+ public boolean isUseLegacyMapMessageFormat()
+ {
+ return _useLegacyMapMessageFormat;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate.java
new file mode 100644
index 0000000000..9560bd5c7c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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 java.io.IOException;
+
+import javax.jms.JMSException;
+import javax.jms.XASession;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.failover.FailoverProtectedOperation;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.Session;
+
+public interface AMQConnectionDelegate
+{
+ ProtocolVersion makeBrokerConnection(BrokerDetails brokerDetail) throws IOException, AMQException;
+
+ Session createSession(final boolean transacted, final int acknowledgeMode,
+ final int prefetchHigh, final int prefetchLow) throws JMSException;
+
+ /**
+ * Create an XASession with default prefetch values of:
+ * High = MaxPrefetch
+ * Low = MaxPrefetch / 2
+ * @return XASession
+ * @throws JMSException thrown if there is a problem creating the session.
+ */
+ XASession createXASession() throws JMSException;
+
+ XASession createXASession(int prefetchHigh, int prefetchLow) throws JMSException;
+
+ void failoverPrep();
+
+ void resubscribeSessions() throws JMSException, AMQException, FailoverException;
+
+ void closeConnection(long timeout) throws JMSException, AMQException;
+
+ <T, E extends Exception> T executeRetrySupport(FailoverProtectedOperation<T,E> operation) throws E;
+
+ int getMaxChannelID();
+
+ int getMinChannelID();
+
+ ProtocolVersion getProtocolVersion();
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java
new file mode 100644
index 0000000000..d50c9e16fe
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_10.java
@@ -0,0 +1,460 @@
+package org.apache.qpid.client;
+/*
+ *
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.XASession;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.failover.FailoverProtectedOperation;
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ChannelLimitReachedException;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.transport.Connection;
+import org.apache.qpid.transport.ConnectionClose;
+import org.apache.qpid.transport.ConnectionException;
+import org.apache.qpid.transport.ConnectionListener;
+import org.apache.qpid.transport.ConnectionSettings;
+import org.apache.qpid.transport.ProtocolVersionException;
+import org.apache.qpid.transport.TransportException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AMQConnectionDelegate_0_10 implements AMQConnectionDelegate, ConnectionListener
+{
+ /**
+ * This class logger.
+ */
+ private static final Logger _logger = LoggerFactory.getLogger(AMQConnectionDelegate_0_10.class);
+
+ /**
+ * The AMQ Connection.
+ */
+ private AMQConnection _conn;
+
+ /**
+ * The QpidConeection instance that is mapped with thie JMS connection.
+ */
+ org.apache.qpid.transport.Connection _qpidConnection;
+ private ConnectionException exception = null;
+
+ static
+ {
+ // Register any configured SASL client factories.
+ org.apache.qpid.client.security.DynamicSaslRegistrar.registerSaslProviders();
+ }
+
+ //--- constructor
+ public AMQConnectionDelegate_0_10(AMQConnection conn)
+ {
+ _conn = conn;
+ _qpidConnection = new Connection();
+ _qpidConnection.addConnectionListener(this);
+ }
+
+ /**
+ * create a Session and start it if required.
+ */
+ public Session createSession(boolean transacted, int acknowledgeMode, int prefetchHigh, int prefetchLow)
+ throws JMSException
+ {
+ _conn.checkNotClosed();
+
+ if (_conn.channelLimitReached())
+ {
+ throw new ChannelLimitReachedException(_conn.getMaximumChannelCount());
+ }
+
+ int channelId = _conn.getNextChannelID();
+ AMQSession session;
+ try
+ {
+ session = new AMQSession_0_10(_qpidConnection, _conn, channelId, transacted, acknowledgeMode, prefetchHigh,
+ prefetchLow);
+ _conn.registerSession(channelId, session);
+ if (_conn._started)
+ {
+ session.start();
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("exception creating session:", e);
+ throw new JMSAMQException("cannot create session", e);
+ }
+ return session;
+ }
+
+ /**
+ * Create an XASession with default prefetch values of:
+ * High = MaxPrefetch
+ * Low = MaxPrefetch / 2
+ * @return XASession
+ * @throws JMSException
+ */
+ public XASession createXASession() throws JMSException
+ {
+ return createXASession((int) _conn.getMaxPrefetch(), (int) _conn.getMaxPrefetch() / 2);
+ }
+
+ /**
+ * create an XA Session and start it if required.
+ */
+ public XASession createXASession(int prefetchHigh, int prefetchLow) throws JMSException
+ {
+ _conn.checkNotClosed();
+
+ if (_conn.channelLimitReached())
+ {
+ throw new ChannelLimitReachedException(_conn.getMaximumChannelCount());
+ }
+
+ int channelId = _conn.getNextChannelID();
+ XASessionImpl session;
+ try
+ {
+ session = new XASessionImpl(_qpidConnection, _conn, channelId, prefetchHigh, prefetchLow);
+ _conn.registerSession(channelId, session);
+ if (_conn._started)
+ {
+ session.start();
+ }
+ }
+ catch (Exception e)
+ {
+ throw new JMSAMQException("cannot create session", e);
+ }
+ return session;
+ }
+
+
+ /**
+ * Make a connection with the broker
+ *
+ * @param brokerDetail The detail of the broker to connect to.
+ * @throws IOException
+ * @throws AMQException
+ */
+ public ProtocolVersion makeBrokerConnection(BrokerDetails brokerDetail) throws IOException, AMQException
+ {
+ try
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("connecting to host: " + brokerDetail.getHost()
+ + " port: " + brokerDetail.getPort() + " vhost: "
+ + _conn.getVirtualHost() + " username: "
+ + _conn.getUsername() + " password: "
+ + _conn.getPassword());
+ }
+
+ ConnectionSettings conSettings = new ConnectionSettings();
+ retriveConnectionSettings(conSettings,brokerDetail);
+ _qpidConnection.connect(conSettings);
+
+ _conn._connected = true;
+ _conn.setUsername(_qpidConnection.getUserID());
+ _conn.setMaximumChannelCount(_qpidConnection.getChannelMax());
+ _conn._failoverPolicy.attainedConnection();
+ }
+ catch (ProtocolVersionException pe)
+ {
+ return new ProtocolVersion(pe.getMajor(), pe.getMinor());
+ }
+ catch (ConnectionException ce)
+ {
+ AMQConstant code = AMQConstant.REPLY_SUCCESS;
+ if (ce.getClose() != null && ce.getClose().getReplyCode() != null)
+ {
+ code = AMQConstant.getConstant(ce.getClose().getReplyCode().getValue());
+ }
+ String msg = "Cannot connect to broker: " + ce.getMessage();
+ throw new AMQException(code, msg, ce);
+ }
+
+ return null;
+ }
+
+ public void failoverPrep()
+ {
+ List<AMQSession> sessions = new ArrayList<AMQSession>(_conn.getSessions().values());
+ for (AMQSession s : sessions)
+ {
+ s.failoverPrep();
+ }
+ }
+
+ public void resubscribeSessions() throws JMSException, AMQException, FailoverException
+ {
+ _logger.info("Resuming connection");
+ getQpidConnection().resume();
+ List<AMQSession> sessions = new ArrayList<AMQSession>(_conn.getSessions().values());
+ _logger.info(String.format("Resubscribing sessions = %s sessions.size=%d", sessions, sessions.size()));
+ for (AMQSession s : sessions)
+ {
+ s.resubscribe();
+ }
+ }
+
+ public void closeConnection(long timeout) throws JMSException, AMQException
+ {
+ try
+ {
+ _qpidConnection.close();
+ }
+ catch (TransportException e)
+ {
+ throw new AMQException(e.getMessage(), e);
+ }
+ }
+
+ public void opened(Connection conn) {}
+
+ public void exception(Connection conn, ConnectionException exc)
+ {
+ if (exception != null)
+ {
+ _logger.error("previous exception", exception);
+ }
+
+ exception = exc;
+ }
+
+ public void closed(Connection conn)
+ {
+ ConnectionException exc = exception;
+ exception = null;
+
+ if (exc == null)
+ {
+ return;
+ }
+
+ ConnectionClose close = exc.getClose();
+ if (close == null)
+ {
+ _conn.getProtocolHandler().setFailoverLatch(new CountDownLatch(1));
+
+ try
+ {
+ if (_conn.firePreFailover(false) && _conn.attemptReconnection())
+ {
+ _conn.failoverPrep();
+ _conn.resubscribeSessions();
+ _conn.fireFailoverComplete();
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("error during failover", e);
+ }
+ finally
+ {
+ _conn.getProtocolHandler().getFailoverLatch().countDown();
+ _conn.getProtocolHandler().setFailoverLatch(null);
+ }
+ }
+
+ ExceptionListener listener = _conn._exceptionListener;
+ if (listener == null)
+ {
+ _logger.error("connection exception: " + conn, exc);
+ }
+ else
+ {
+ String code = null;
+ if (close != null)
+ {
+ code = close.getReplyCode().toString();
+ }
+
+ JMSException ex = new JMSException(exc.getMessage(), code);
+ ex.setLinkedException(exc);
+ ex.initCause(exc);
+ listener.onException(ex);
+ }
+ }
+
+ public <T, E extends Exception> T executeRetrySupport(FailoverProtectedOperation<T,E> operation) throws E
+ {
+ try
+ {
+ return operation.execute();
+ }
+ catch (FailoverException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int getMaxChannelID()
+ {
+ //For a negotiated channelMax N, there are channels 0 to N-1 available.
+ return _qpidConnection.getChannelMax() - 1;
+ }
+
+ public int getMinChannelID()
+ {
+ return Connection.MIN_USABLE_CHANNEL_NUM;
+ }
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return ProtocolVersion.v0_10;
+ }
+
+ private void retriveConnectionSettings(ConnectionSettings conSettings, BrokerDetails brokerDetail)
+ {
+
+ conSettings.setHost(brokerDetail.getHost());
+ conSettings.setPort(brokerDetail.getPort());
+ conSettings.setVhost(_conn.getVirtualHost());
+ conSettings.setUsername(_conn.getUsername());
+ conSettings.setPassword(_conn.getPassword());
+
+ // ------------ sasl options ---------------
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_SASL_MECHS) != null)
+ {
+ conSettings.setSaslMechs(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_SASL_MECHS));
+ }
+
+ // Sun SASL Kerberos client uses the
+ // protocol + servername as the service key.
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_SASL_PROTOCOL_NAME) != null)
+ {
+ conSettings.setSaslProtocol(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_SASL_PROTOCOL_NAME));
+ }
+
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_SASL_SERVER_NAME) != null)
+ {
+ conSettings.setSaslServerName(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_SASL_SERVER_NAME));
+ }
+
+ conSettings.setUseSASLEncryption(
+ brokerDetail.getBooleanProperty(BrokerDetails.OPTIONS_SASL_ENCRYPTION));
+
+ // ------------- ssl options ---------------------
+ conSettings.setUseSSL(brokerDetail.getBooleanProperty(BrokerDetails.OPTIONS_SSL));
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_TRUST_STORE) != null)
+ {
+ conSettings.setTrustStorePath(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_TRUST_STORE));
+ }
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_TRUST_STORE_PASSWORD) != null)
+ {
+ conSettings.setTrustStorePassword(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_TRUST_STORE_PASSWORD));
+ }
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_KEY_STORE) != null)
+ {
+ conSettings.setKeyStorePath(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_KEY_STORE));
+ }
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_KEY_STORE_PASSWORD) != null)
+ {
+ conSettings.setKeyStorePassword(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_KEY_STORE_PASSWORD));
+ }
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_SSL_CERT_ALIAS) != null)
+ {
+ conSettings.setCertAlias(
+ brokerDetail.getProperty(BrokerDetails.OPTIONS_SSL_CERT_ALIAS));
+ }
+ // ----------------------------
+
+ conSettings.setVerifyHostname(brokerDetail.getBooleanProperty(BrokerDetails.OPTIONS_SSL_VERIFY_HOSTNAME));
+
+ // Pass client name from connection URL
+ Map<String, Object> clientProps = new HashMap<String, Object>();
+ try
+ {
+ clientProps.put("clientName", _conn.getClientID());
+ conSettings.setClientProperties(clientProps);
+ }
+ catch (JMSException e)
+ {
+ // Ignore
+ }
+
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_TCP_NO_DELAY) != null)
+ {
+ conSettings.setTcpNodelay(
+ brokerDetail.getBooleanProperty(BrokerDetails.OPTIONS_TCP_NO_DELAY));
+ }
+
+ conSettings.setHeartbeatInterval(getHeartbeatInterval(brokerDetail));
+ }
+
+ // The idle_timeout prop is in milisecs while
+ // the new heartbeat prop is in secs
+ private int getHeartbeatInterval(BrokerDetails brokerDetail)
+ {
+ int heartbeat = 0;
+ if (brokerDetail.getProperty(BrokerDetails.OPTIONS_IDLE_TIMEOUT) != null)
+ {
+ _logger.warn("Broker property idle_timeout=<mili_secs> is deprecated, please use heartbeat=<secs>");
+ heartbeat = Integer.parseInt(brokerDetail.getProperty(BrokerDetails.OPTIONS_IDLE_TIMEOUT))/1000;
+ }
+ else if (brokerDetail.getProperty(BrokerDetails.OPTIONS_HEARTBEAT) != null)
+ {
+ heartbeat = Integer.parseInt(brokerDetail.getProperty(BrokerDetails.OPTIONS_HEARTBEAT));
+ }
+ else if (Integer.getInteger(ClientProperties.IDLE_TIMEOUT_PROP_NAME) != null)
+ {
+ heartbeat = Integer.getInteger(ClientProperties.IDLE_TIMEOUT_PROP_NAME)/1000;
+ _logger.warn("JVM arg -Didle_timeout=<mili_secs> is deprecated, please use -Dqpid.heartbeat=<secs>");
+ }
+ else
+ {
+ heartbeat = Integer.getInteger(ClientProperties.HEARTBEAT,ClientProperties.HEARTBEAT_DEFAULT);
+ }
+ return heartbeat;
+ }
+
+ protected org.apache.qpid.transport.Connection getQpidConnection()
+ {
+ return _qpidConnection;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_9.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_9.java
new file mode 100755
index 0000000000..70ecedfd8b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_0_9.java
@@ -0,0 +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.client;
+
+import org.apache.qpid.framing.ProtocolVersion;
+
+
+public class AMQConnectionDelegate_0_9 extends AMQConnectionDelegate_8_0
+{
+
+ public AMQConnectionDelegate_0_9(AMQConnection conn)
+ {
+ super(conn);
+ }
+
+ @Override
+ public ProtocolVersion getProtocolVersion()
+ {
+ return ProtocolVersion.v0_9;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java
new file mode 100644
index 0000000000..40b332d216
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_8_0.java
@@ -0,0 +1,325 @@
+/*
+ *
+ * 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 java.io.IOException;
+import java.net.ConnectException;
+import java.nio.channels.UnresolvedAddressException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.jms.JMSException;
+import javax.jms.XASession;
+
+import org.apache.qpid.AMQException;
+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.AMQProtocolSession;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.StateWaiter;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.framing.BasicQosBody;
+import org.apache.qpid.framing.BasicQosOkBody;
+import org.apache.qpid.framing.ChannelOpenBody;
+import org.apache.qpid.framing.ChannelOpenOkBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.TxSelectBody;
+import org.apache.qpid.framing.TxSelectOkBody;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ChannelLimitReachedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AMQConnectionDelegate_8_0 implements AMQConnectionDelegate
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AMQConnectionDelegate_8_0.class);
+ private AMQConnection _conn;
+
+
+ public void closeConnection(long timeout) throws JMSException, AMQException
+ {
+ _conn.getProtocolHandler().closeConnection(timeout);
+
+ }
+
+ public AMQConnectionDelegate_8_0(AMQConnection conn)
+ {
+ _conn = conn;
+ }
+
+ protected boolean checkException(Throwable thrown)
+ {
+ Throwable cause = thrown.getCause();
+
+ if (cause == null)
+ {
+ cause = thrown;
+ }
+
+ return ((cause instanceof ConnectException) || (cause instanceof UnresolvedAddressException));
+ }
+
+ public ProtocolVersion makeBrokerConnection(BrokerDetails brokerDetail) throws AMQException, IOException
+ {
+ final Set<AMQState> openOrClosedStates =
+ EnumSet.of(AMQState.CONNECTION_OPEN, AMQState.CONNECTION_CLOSED);
+
+
+ StateWaiter waiter = _conn._protocolHandler.createWaiter(openOrClosedStates);
+
+ // TODO: use system property thingy for this
+ if (System.getProperty("UseTransportIo", "false").equals("false"))
+ {
+ TransportConnection.getInstance(brokerDetail).connect(_conn._protocolHandler, brokerDetail);
+ }
+ else
+ {
+ _conn.getProtocolHandler().createIoTransportSession(brokerDetail);
+ }
+ _conn._protocolHandler.getProtocolSession().init();
+ // this blocks until the connection has been set up or when an error
+ // has prevented the connection being set up
+
+ AMQState state = waiter.await();
+
+ if(state == AMQState.CONNECTION_OPEN)
+ {
+ _conn._failoverPolicy.attainedConnection();
+ _conn._connected = true;
+ return null;
+ }
+ else
+ {
+ return _conn._protocolHandler.getSuggestedProtocolVersion();
+ }
+
+ }
+
+ public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode, final int prefetch)
+ throws JMSException
+ {
+ return createSession(transacted, acknowledgeMode, prefetch, prefetch);
+ }
+
+ public XASession createXASession(int prefetchHigh, int prefetchLow) throws JMSException
+ {
+ throw new UnsupportedOperationException("0_8 version does not provide XA support");
+ }
+
+ public org.apache.qpid.jms.Session createSession(final boolean transacted, final int acknowledgeMode,
+ final int prefetchHigh, final int prefetchLow) throws JMSException
+ {
+ _conn.checkNotClosed();
+
+ if (_conn.channelLimitReached())
+ {
+ throw new ChannelLimitReachedException(_conn.getMaximumChannelCount());
+ }
+
+ return new FailoverRetrySupport<org.apache.qpid.jms.Session, JMSException>(
+ new FailoverProtectedOperation<org.apache.qpid.jms.Session, JMSException>()
+ {
+ public org.apache.qpid.jms.Session execute() throws JMSException, FailoverException
+ {
+ int channelId = _conn.getNextChannelID();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Write channel open frame for channel id " + channelId);
+ }
+
+ // We must create the session and register it before actually sending the frame to the server to
+ // open it, so that there is no window where we could receive data on the channel and not be set
+ // up to handle it appropriately.
+ AMQSession session =
+ new AMQSession_0_8(_conn, channelId, transacted, acknowledgeMode, prefetchHigh,
+ prefetchLow);
+ // _protocolHandler.addSessionByChannel(channelId, session);
+ _conn.registerSession(channelId, session);
+
+ boolean success = false;
+ try
+ {
+ createChannelOverWire(channelId, prefetchHigh, prefetchLow, transacted);
+ success = true;
+ }
+ catch (AMQException e)
+ {
+ JMSException jmse = new JMSException("Error creating session: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ finally
+ {
+ if (!success)
+ {
+ _conn.deregisterSession(channelId);
+ }
+ }
+
+ if (_conn._started)
+ {
+ try
+ {
+ session.start();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException(e);
+ }
+ }
+
+ return session;
+ }
+ }, _conn).execute();
+ }
+
+ /**
+ * Create an XASession with default prefetch values of:
+ * High = MaxPrefetch
+ * Low = MaxPrefetch / 2
+ * @return XASession
+ * @throws JMSException thrown if there is a problem creating the session.
+ */
+ public XASession createXASession() throws JMSException
+ {
+ return createXASession((int) _conn.getMaxPrefetch(), (int) _conn.getMaxPrefetch() / 2);
+ }
+
+ 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.generateFrame(channelId), ChannelOpenOkBody.class);
+
+ // todo send low water mark when protocol allows.
+ // todo Be aware of possible changes to parameter order as versions change.
+ 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(body.generateFrame(channelId), TxSelectOkBody.class);
+ }
+ }
+
+ public void failoverPrep()
+ {
+ // do nothing
+ }
+
+ /**
+ * For all sessions, and for all consumers in those sessions, resubscribe. This is called during failover handling.
+ * The caller must hold the failover mutex before calling this method.
+ */
+ public void resubscribeSessions() throws JMSException, AMQException, FailoverException
+ {
+ 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();)
+ {
+ AMQSession s = (AMQSession) it.next();
+ // _protocolHandler.addSessionByChannel(s.getChannelId(), s);
+ reopenChannel(s.getChannelId(), s.getDefaultPrefetchHigh(), s.getDefaultPrefetchLow(), s.getTransacted());
+ s.resubscribe();
+ }
+ }
+
+ private void reopenChannel(int channelId, int prefetchHigh, int prefetchLow, boolean transacted)
+ throws AMQException, FailoverException
+ {
+ try
+ {
+ createChannelOverWire(channelId, prefetchHigh, prefetchLow, transacted);
+ }
+ catch (AMQException e)
+ {
+ _conn.deregisterSession(channelId);
+ throw new AMQException(null, "Error reopening channel " + channelId + " after failover: " + e, e);
+ }
+ }
+
+ public <T, E extends Exception> T executeRetrySupport(FailoverProtectedOperation<T,E> operation) throws E
+ {
+ while (true)
+ {
+ try
+ {
+ _conn.blockUntilNotFailingOver();
+ }
+ catch (InterruptedException e)
+ {
+ _logger.debug("Interrupted: " + e, e);
+
+ return null;
+ }
+
+ synchronized (_conn.getFailoverMutex())
+ {
+ try
+ {
+ return operation.execute();
+ }
+ catch (FailoverException e)
+ {
+ _logger.debug("Failover exception caught during operation: " + e, e);
+ }
+ catch (IllegalStateException e)
+ {
+ if (!(e.getMessage().startsWith("Fail-over interupted no-op failover support")))
+ {
+ throw e;
+ }
+ }
+ }
+ }
+ }
+
+ public int getMaxChannelID()
+ {
+ ConnectionTuneParameters params = _conn.getProtocolHandler().getProtocolSession().getConnectionTuneParameters();
+
+ return params == null ? AMQProtocolSession.MAX_CHANNEL_MAX : params.getChannelMax();
+ }
+
+ public int getMinChannelID()
+ {
+ return AMQProtocolSession.MIN_USABLE_CHANNEL_NUM;
+ }
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return ProtocolVersion.v8_0;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_9_1.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_9_1.java
new file mode 100755
index 0000000000..442dd7b286
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionDelegate_9_1.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * 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.framing.ProtocolVersion;
+
+
+public class AMQConnectionDelegate_9_1 extends AMQConnectionDelegate_8_0
+{
+
+ public AMQConnectionDelegate_9_1(AMQConnection conn)
+ {
+ super(conn);
+ }
+
+ @Override
+ public ProtocolVersion getProtocolVersion()
+ {
+ return ProtocolVersion.v0_91;
+ }
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java
new file mode 100644
index 0000000000..ec4c668d7e
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionFactory.java
@@ -0,0 +1,566 @@
+/*
+ *
+ * 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 java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Hashtable;
+import java.util.UUID;
+
+import javax.jms.*;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.naming.StringRefAddr;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.URLSyntaxException;
+
+
+public class AMQConnectionFactory implements ConnectionFactory, QueueConnectionFactory, TopicConnectionFactory,
+ ObjectFactory, Referenceable, XATopicConnectionFactory,
+ XAQueueConnectionFactory, XAConnectionFactory
+{
+ private String _host;
+ private int _port;
+ private String _defaultUsername;
+ private String _defaultPassword;
+ private String _virtualPath;
+
+ private ConnectionURL _connectionDetails;
+ private SSLConfiguration _sslConfig;
+
+ public AMQConnectionFactory()
+ {
+ }
+
+ /**
+ * This is the Only constructor used!
+ * It is used form the context and from the JNDI objects.
+ */
+ public AMQConnectionFactory(String url) throws URLSyntaxException
+ {
+ _connectionDetails = new AMQConnectionURL(url);
+ }
+
+ /**
+ * This constructor is never used!
+ */
+ public AMQConnectionFactory(ConnectionURL url)
+ {
+ _connectionDetails = url;
+ }
+
+ /**
+ * This constructor is never used!
+ */
+ public AMQConnectionFactory(String broker, String username, String password, String clientName, String virtualHost)
+ throws URLSyntaxException
+ {
+ this(new AMQConnectionURL(
+ ConnectionURL.AMQ_PROTOCOL + "://" + username + ":" + password + "@" + clientName + "/" + virtualHost + "?brokerlist='" + broker + "'"));
+ }
+
+ /**
+ * This constructor is never used!
+ */
+ public AMQConnectionFactory(String host, int port, String virtualPath)
+ {
+ this(host, port, "guest", "guest", virtualPath);
+ }
+
+ /**
+ * This constructor is never used!
+ */
+ public AMQConnectionFactory(String host, int port, String defaultUsername, String defaultPassword,
+ String virtualPath)
+ {
+ _host = host;
+ _port = port;
+ _defaultUsername = defaultUsername;
+ _defaultPassword = defaultPassword;
+ _virtualPath = virtualPath;
+
+//todo when setting Host/Port has been resolved then we can use this otherwise those methods won't work with the following line.
+// _connectionDetails = new AMQConnectionURL(
+// ConnectionURL.AMQ_PROTOCOL + "://" +
+// _defaultUsername + ":" + _defaultPassword + "@" +
+// virtualPath + "?brokerlist='tcp://" + host + ":" + port + "'");
+ }
+
+ /**
+ * @return The _defaultPassword.
+ */
+ public final String getDefaultPassword(String password)
+ {
+ if (_connectionDetails != null)
+ {
+ return _connectionDetails.getPassword();
+ }
+ else
+ {
+ return _defaultPassword;
+ }
+ }
+
+ /**
+ * @param password The _defaultPassword to set.
+ */
+ public final void setDefaultPassword(String password)
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setPassword(password);
+ }
+ _defaultPassword = password;
+ }
+
+ /**
+ * Getter for SSLConfiguration
+ *
+ * @return SSLConfiguration if set, otherwise null
+ */
+ public final SSLConfiguration getSSLConfiguration()
+ {
+ return _sslConfig;
+ }
+
+ /**
+ * Setter for SSLConfiguration
+ *
+ * @param sslConfig config to store
+ */
+ public final void setSSLConfiguration(SSLConfiguration sslConfig)
+ {
+ _sslConfig = sslConfig;
+ }
+
+ /**
+ * @return The _defaultPassword.
+ */
+ public final String getDefaultUsername(String password)
+ {
+ if (_connectionDetails != null)
+ {
+ return _connectionDetails.getUsername();
+ }
+ else
+ {
+ return _defaultUsername;
+ }
+ }
+
+ /**
+ * @param username The _defaultUsername to set.
+ */
+ public final void setDefaultUsername(String username)
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setUsername(username);
+ }
+ _defaultUsername = username;
+ }
+
+ /**
+ * @return The _host .
+ */
+ public final String getHost()
+ {
+ //todo this doesn't make sense in a multi broker URL as we have no current as that is done by AMQConnection
+ return _host;
+ }
+
+ /**
+ * @param host The _host to set.
+ */
+ public final void setHost(String host)
+ {
+ //todo if _connectionDetails is set then run _connectionDetails.addBrokerDetails()
+ // Should perhaps have this method changed to setBroker(host,port)
+ _host = host;
+ }
+
+ /**
+ * @return _port The _port to set.
+ */
+ public final int getPort()
+ {
+ //todo see getHost
+ return _port;
+ }
+
+ /**
+ * @param port The port to set.
+ */
+ public final void setPort(int port)
+ {
+ //todo see setHost
+ _port = port;
+ }
+
+ /**
+ * @return he _virtualPath.
+ */
+ public final String getVirtualPath()
+ {
+ if (_connectionDetails != null)
+ {
+ return _connectionDetails.getVirtualHost();
+ }
+ else
+ {
+ return _virtualPath;
+ }
+ }
+
+ /**
+ * @param path The _virtualPath to set.
+ */
+ public final void setVirtualPath(String path)
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setVirtualHost(path);
+ }
+
+ _virtualPath = path;
+ }
+
+ public static String getUniqueClientID()
+ {
+ try
+ {
+ InetAddress addr = InetAddress.getLocalHost();
+ return addr.getHostName() + System.currentTimeMillis();
+ }
+ catch (UnknownHostException e)
+ {
+ return "UnknownHost" + UUID.randomUUID();
+ }
+ }
+
+ public Connection createConnection() throws JMSException
+ {
+ try
+ {
+ if (_connectionDetails != null)
+ {
+ if (_connectionDetails.getClientName() == null || _connectionDetails.getClientName().equals(""))
+ {
+ _connectionDetails.setClientName(getUniqueClientID());
+ }
+ return new AMQConnection(_connectionDetails, _sslConfig);
+ }
+ else
+ {
+ return new AMQConnection(_host, _port, _defaultUsername, _defaultPassword, getUniqueClientID(),
+ _virtualPath);
+ }
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Error creating connection: " + e.getMessage());
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+
+
+ }
+
+ public Connection createConnection(String userName, String password) throws JMSException
+ {
+ return createConnection(userName, password, null);
+ }
+
+ public Connection createConnection(String userName, String password, String id) throws JMSException
+ {
+ try
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setUsername(userName);
+ _connectionDetails.setPassword(password);
+
+ if (id != null && !id.equals(""))
+ {
+ _connectionDetails.setClientName(id);
+ }
+ else if (_connectionDetails.getClientName() == null || _connectionDetails.getClientName().equals(""))
+ {
+ _connectionDetails.setClientName(getUniqueClientID());
+ }
+ return new AMQConnection(_connectionDetails, _sslConfig);
+ }
+ else
+ {
+ return new AMQConnection(_host, _port, userName, password, (id != null ? id : getUniqueClientID()), _virtualPath);
+ }
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Error creating connection: " + e.getMessage());
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ public QueueConnection createQueueConnection() throws JMSException
+ {
+ return (QueueConnection) createConnection();
+ }
+
+ public QueueConnection createQueueConnection(String username, String password) throws JMSException
+ {
+ return (QueueConnection) createConnection(username, password);
+ }
+
+ public TopicConnection createTopicConnection() throws JMSException
+ {
+ return (TopicConnection) createConnection();
+ }
+
+ public TopicConnection createTopicConnection(String username, String password) throws JMSException
+ {
+ return (TopicConnection) createConnection(username, password);
+ }
+
+
+ public ConnectionURL getConnectionURL()
+ {
+ return _connectionDetails;
+ }
+
+ public String getConnectionURLString()
+ {
+ return _connectionDetails.toString();
+ }
+
+
+ public final void setConnectionURLString(String url) throws URLSyntaxException
+ {
+ _connectionDetails = new AMQConnectionURL(url);
+ }
+
+ /**
+ * JNDI interface to create objects from References.
+ *
+ * @param obj The Reference from JNDI
+ * @param name
+ * @param ctx
+ * @param env
+ *
+ * @return AMQConnection,AMQTopic,AMQQueue, or AMQConnectionFactory.
+ *
+ * @throws Exception
+ */
+ public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable env) throws Exception
+ {
+ if (obj instanceof Reference)
+ {
+ Reference ref = (Reference) obj;
+
+ if (ref.getClassName().equals(AMQConnection.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQConnection.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQConnection((String) addr.getContent());
+ }
+ }
+
+ if (ref.getClassName().equals(AMQQueue.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQQueue.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQQueue(new AMQBindingURL((String) addr.getContent()));
+ }
+ }
+
+ if (ref.getClassName().equals(AMQTopic.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQTopic.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQTopic(new AMQBindingURL((String) addr.getContent()));
+ }
+ }
+
+ if (ref.getClassName().equals(AMQConnectionFactory.class.getName()))
+ {
+ RefAddr addr = ref.get(AMQConnectionFactory.class.getName());
+
+ if (addr != null)
+ {
+ return new AMQConnectionFactory((String) addr.getContent());
+ }
+ }
+
+ }
+ return null;
+ }
+
+
+ public Reference getReference() throws NamingException
+ {
+ return new Reference(
+ AMQConnectionFactory.class.getName(),
+ new StringRefAddr(AMQConnectionFactory.class.getName(), _connectionDetails.getURL()),
+ AMQConnectionFactory.class.getName(), null); // factory location
+ }
+
+ // ---------------------------------------------------------------------------------------------------
+ // the following methods are provided for XA compatibility
+ // Those methods are only supported by 0_10 and above
+ // ---------------------------------------------------------------------------------------------------
+
+ /**
+ * Creates a XAConnection with the default user identity.
+ * <p> The XAConnection is created in stopped mode. No messages
+ * will be delivered until the <code>Connection.start</code> method
+ * is explicitly called.
+ *
+ * @return A newly created XAConnection
+ * @throws JMSException If creating the XAConnection fails due to some internal error.
+ * @throws JMSSecurityException If client authentication fails due to an invalid user name or password.
+ */
+ public XAConnection createXAConnection() throws JMSException
+ {
+ try
+ {
+ return new XAConnectionImpl(_connectionDetails, _sslConfig);
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Error creating connection: " + e.getMessage());
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ /**
+ * Creates a XAConnection with the specified user identity.
+ * <p> The XAConnection is created in stopped mode. No messages
+ * will be delivered until the <code>Connection.start</code> method
+ * is explicitly called.
+ *
+ * @param username the caller's user name
+ * @param password the caller's password
+ * @return A newly created XAConnection.
+ * @throws JMSException If creating the XAConnection fails due to some internal error.
+ * @throws JMSSecurityException If client authentication fails due to an invalid user name or password.
+ */
+ public XAConnection createXAConnection(String username, String password) throws JMSException
+ {
+ if (_connectionDetails != null)
+ {
+ _connectionDetails.setUsername(username);
+ _connectionDetails.setPassword(password);
+
+ if (_connectionDetails.getClientName() == null || _connectionDetails.getClientName().equals(""))
+ {
+ _connectionDetails.setClientName(getUniqueClientID());
+ }
+ }
+ else
+ {
+ throw new JMSException("A URL must be specified to access XA connections");
+ }
+ return createXAConnection();
+ }
+
+
+ /**
+ * Creates a XATopicConnection with the default user identity.
+ * <p> The XATopicConnection is created in stopped mode. No messages
+ * will be delivered until the <code>Connection.start</code> method
+ * is explicitly called.
+ *
+ * @return A newly created XATopicConnection
+ * @throws JMSException If creating the XATopicConnection fails due to some internal error.
+ * @throws JMSSecurityException If client authentication fails due to an invalid user name or password.
+ */
+ public XATopicConnection createXATopicConnection() throws JMSException
+ {
+ return (XATopicConnection) createXAConnection();
+ }
+
+ /**
+ * Creates a XATopicConnection with the specified user identity.
+ * <p> The XATopicConnection is created in stopped mode. No messages
+ * will be delivered until the <code>Connection.start</code> method
+ * is explicitly called.
+ *
+ * @param username the caller's user name
+ * @param password the caller's password
+ * @return A newly created XATopicConnection.
+ * @throws JMSException If creating the XATopicConnection fails due to some internal error.
+ * @throws JMSSecurityException If client authentication fails due to an invalid user name or password.
+ */
+ public XATopicConnection createXATopicConnection(String username, String password) throws JMSException
+ {
+ return (XATopicConnection) createXAConnection(username, password);
+ }
+
+ /**
+ * Creates a XAQueueConnection with the default user identity.
+ * <p> The XAQueueConnection is created in stopped mode. No messages
+ * will be delivered until the <code>Connection.start</code> method
+ * is explicitly called.
+ *
+ * @return A newly created XAQueueConnection
+ * @throws JMSException If creating the XAQueueConnection fails due to some internal error.
+ * @throws JMSSecurityException If client authentication fails due to an invalid user name or password.
+ */
+ public XAQueueConnection createXAQueueConnection() throws JMSException
+ {
+ return (XAQueueConnection) createXAConnection();
+ }
+
+ /**
+ * Creates a XAQueueConnection with the specified user identity.
+ * <p> The XAQueueConnection is created in stopped mode. No messages
+ * will be delivered until the <code>Connection.start</code> method
+ * is explicitly called.
+ *
+ * @param username the caller's user name
+ * @param password the caller's password
+ * @return A newly created XAQueueConnection.
+ * @throws JMSException If creating the XAQueueConnection fails due to some internal error.
+ * @throws JMSSecurityException If client authentication fails due to an invalid user name or password.
+ */
+ public XAQueueConnection createXAQueueConnection(String username, String password) throws JMSException
+ {
+ return (XAQueueConnection) createXAConnection(username, password);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java
new file mode 100644
index 0000000000..93b4c51a8f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQConnectionURL.java
@@ -0,0 +1,311 @@
+/*
+ *
+ * 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 java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+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;
+import org.apache.qpid.url.URLSyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AMQConnectionURL implements ConnectionURL
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AMQConnectionURL.class);
+
+ private String _url;
+ private String _failoverMethod;
+ private Map<String, String> _failoverOptions;
+ private Map<String, String> _options;
+ private List<BrokerDetails> _brokers;
+ private String _clientName;
+ private String _username;
+ private String _password;
+ private String _virtualHost;
+ private AMQShortString _defaultQueueExchangeName;
+ private AMQShortString _defaultTopicExchangeName;
+ private AMQShortString _temporaryTopicExchangeName;
+ private AMQShortString _temporaryQueueExchangeName;
+
+ public AMQConnectionURL(String fullURL) throws URLSyntaxException
+ {
+ if (fullURL == null) throw new IllegalArgumentException("URL cannot be null");
+ _url = fullURL;
+ _options = new HashMap<String, String>();
+ _brokers = new LinkedList<BrokerDetails>();
+ _failoverOptions = new HashMap<String, String>();
+ new URLParser(this);
+ }
+
+ public String getURL()
+ {
+ return _url;
+ }
+
+ public Map<String,String> getOptions()
+ {
+ return _options;
+ }
+
+ public String getFailoverMethod()
+ {
+ return _failoverMethod;
+ }
+
+ public void setFailoverMethod(String failoverMethod)
+ {
+ _failoverMethod = failoverMethod;
+ }
+
+ public Map<String,String> getFailoverOptions()
+ {
+ return _failoverOptions;
+ }
+
+ public String getFailoverOption(String key)
+ {
+ return _failoverOptions.get(key);
+ }
+
+ public void setFailoverOption(String key, String value)
+ {
+ _failoverOptions.put(key, value);
+ }
+
+ public int getBrokerCount()
+ {
+ return _brokers.size();
+ }
+
+ public BrokerDetails getBrokerDetails(int index)
+ {
+ if (index < _brokers.size())
+ {
+ return _brokers.get(index);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public void addBrokerDetails(BrokerDetails broker)
+ {
+ if (!(_brokers.contains(broker)))
+ {
+ _brokers.add(broker);
+ }
+ }
+
+ public void setBrokerDetails(List<BrokerDetails> brokers)
+ {
+ _brokers = brokers;
+ }
+
+ public List<BrokerDetails> getAllBrokerDetails()
+ {
+ return _brokers;
+ }
+
+ public String getClientName()
+ {
+ return _clientName;
+ }
+
+ public void setClientName(String clientName)
+ {
+ _clientName = clientName;
+ }
+
+ public String getUsername()
+ {
+ return _username;
+ }
+
+ public void setUsername(String username)
+ {
+ _username = username;
+ }
+
+ public String getPassword()
+ {
+ return _password;
+ }
+
+ public void setPassword(String password)
+ {
+ _password = password;
+ }
+
+ public String getVirtualHost()
+ {
+ return _virtualHost;
+ }
+
+ public void setVirtualHost(String virtuaHost)
+ {
+ _virtualHost = virtuaHost;
+ }
+
+ public String getOption(String key)
+ {
+ return _options.get(key);
+ }
+
+ public void setOption(String key, String value)
+ {
+ _options.put(key, value);
+ }
+
+ public AMQShortString getDefaultQueueExchangeName()
+ {
+ return _defaultQueueExchangeName;
+ }
+
+ public void setDefaultQueueExchangeName(AMQShortString defaultQueueExchangeName)
+ {
+ _defaultQueueExchangeName = defaultQueueExchangeName;
+ }
+
+ public AMQShortString getDefaultTopicExchangeName()
+ {
+ return _defaultTopicExchangeName;
+ }
+
+ public void setDefaultTopicExchangeName(AMQShortString defaultTopicExchangeName)
+ {
+ _defaultTopicExchangeName = defaultTopicExchangeName;
+ }
+
+ public AMQShortString getTemporaryQueueExchangeName()
+ {
+ return _temporaryQueueExchangeName;
+ }
+
+ public void setTemporaryQueueExchangeName(AMQShortString temporaryQueueExchangeName)
+ {
+ _temporaryQueueExchangeName = temporaryQueueExchangeName;
+ }
+
+ public AMQShortString getTemporaryTopicExchangeName()
+ {
+ return _temporaryTopicExchangeName;
+ }
+
+ public void setTemporaryTopicExchangeName(AMQShortString temporaryTopicExchangeName)
+ {
+ _temporaryTopicExchangeName = temporaryTopicExchangeName;
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(AMQ_PROTOCOL);
+ sb.append("://");
+
+ if (_username != null)
+ {
+ sb.append(_username);
+
+ if (_password != null)
+ {
+ sb.append(':');
+ sb.append("********");
+ }
+
+ sb.append('@');
+ }
+
+ sb.append(_clientName);
+
+ sb.append(_virtualHost);
+
+ sb.append(optionsToString());
+
+ return sb.toString();
+ }
+
+ private String optionsToString()
+ {
+ StringBuffer sb = new StringBuffer("?");
+
+ if (!_options.isEmpty())
+ {
+ for (Map.Entry<String, String> option : _options.entrySet())
+ {
+ sb.append(option.getKey()).append("='").append(option.getValue()).append("'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+ }
+
+ sb.append(OPTIONS_BROKERLIST).append("='");
+ for (BrokerDetails service : _brokers)
+ {
+ sb.append(service.toString());
+ sb.append(URLHelper.BROKER_SEPARATOR);
+ }
+
+ sb.deleteCharAt(sb.length() - 1);
+ sb.append("'");
+
+ if (_failoverMethod != null)
+ {
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ sb.append(OPTIONS_FAILOVER + "='");
+ sb.append(_failoverMethod);
+ sb.append(URLHelper.printOptions(_failoverOptions));
+ sb.append("'");
+ }
+
+ for (String key : _options.keySet())
+ {
+ if (!key.equals(OPTIONS_FAILOVER) || !key.equals(OPTIONS_BROKERLIST))
+ {
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR).append(key).append("='");
+ sb.append(_options.get(key)).append("'");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static void main(String[] args) throws URLSyntaxException
+ {
+ String url2 =
+ "amqp://ritchiem:bob@temp/testHost?brokerlist='tcp://localhost:5672;tcp://fancyserver:3000/',failover='roundrobin'";
+ // "amqp://user:pass@clientid/virtualhost?brokerlist='tcp://host:1?option1=\'value\',option2=\'value\';vm://:3?option1=\'value\'',failover='method?option1=\'value\',option2='value''";
+
+ ConnectionURL connectionurl2 = new AMQConnectionURL(url2);
+
+ System.out.println(url2);
+ System.out.println(connectionurl2);
+
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java
new file mode 100644
index 0000000000..eb9682a3cf
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQDestination.java
@@ -0,0 +1,941 @@
+/*
+ *
+ * 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 java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.jms.Destination;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.naming.StringRefAddr;
+
+import org.apache.qpid.client.messaging.address.AddressHelper;
+import org.apache.qpid.client.messaging.address.Link;
+import org.apache.qpid.client.messaging.address.Node;
+import org.apache.qpid.client.messaging.address.QpidExchangeOptions;
+import org.apache.qpid.client.messaging.address.QpidQueueOptions;
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.messaging.Address;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.url.URLHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class AMQDestination implements Destination, Referenceable
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AMQDestination.class);
+
+ protected AMQShortString _exchangeName;
+
+ protected AMQShortString _exchangeClass;
+
+ protected boolean _isDurable;
+
+ protected boolean _isExclusive;
+
+ protected boolean _isAutoDelete;
+
+ private boolean _browseOnly;
+
+ private boolean _isAddressResolved;
+
+ private AMQShortString _queueName;
+
+ private AMQShortString _routingKey;
+
+ private AMQShortString[] _bindingKeys;
+
+ private String _url;
+ private AMQShortString _urlAsShortString;
+
+ private boolean _checkedForQueueBinding;
+
+ private boolean _exchangeExistsChecked;
+
+ private byte[] _byteEncoding;
+ private static final int IS_DURABLE_MASK = 0x1;
+ private static final int IS_EXCLUSIVE_MASK = 0x2;
+ private static final int IS_AUTODELETE_MASK = 0x4;
+
+ public static final int QUEUE_TYPE = 1;
+ public static final int TOPIC_TYPE = 2;
+ public static final int UNKNOWN_TYPE = 3;
+
+ // ----- Fields required to support new address syntax -------
+
+ public enum DestSyntax {
+ BURL,ADDR;
+
+ public static DestSyntax getSyntaxType(String s)
+ {
+ if (("BURL").equals(s))
+ {
+ return BURL;
+ }
+ else if (("ADDR").equals(s))
+ {
+ return ADDR;
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid Destination Syntax Type" +
+ " should be one of {BURL|ADDR}");
+ }
+ }
+ }
+
+ public enum AddressOption {
+ ALWAYS, NEVER, SENDER, RECEIVER;
+
+ public static AddressOption getOption(String str)
+ {
+ if ("always".equals(str)) return ALWAYS;
+ else if ("never".equals(str)) return NEVER;
+ else if ("sender".equals(str)) return SENDER;
+ else if ("receiver".equals(str)) return RECEIVER;
+ else throw new IllegalArgumentException(str + " is not an allowed value");
+ }
+ }
+
+ protected final static DestSyntax defaultDestSyntax;
+
+ protected DestSyntax _destSyntax = DestSyntax.ADDR;
+
+ protected AddressHelper _addrHelper;
+ protected Address _address;
+ protected int _addressType = AMQDestination.UNKNOWN_TYPE;
+ protected String _name;
+ protected String _subject;
+ protected AddressOption _create = AddressOption.NEVER;
+ protected AddressOption _assert = AddressOption.NEVER;
+ protected AddressOption _delete = AddressOption.NEVER;
+
+ protected Node _targetNode;
+ protected Node _sourceNode;
+ protected Link _targetLink;
+ protected Link _link;
+
+ // ----- / Fields required to support new address syntax -------
+
+ static
+ {
+ defaultDestSyntax = DestSyntax.getSyntaxType(
+ System.getProperty(ClientProperties.DEST_SYNTAX,
+ DestSyntax.ADDR.toString()));
+
+
+ }
+
+ public static DestSyntax getDefaultDestSyntax()
+ {
+ return defaultDestSyntax;
+ }
+
+ protected AMQDestination(Address address) throws Exception
+ {
+ this._address = address;
+ getInfoFromAddress();
+ _destSyntax = DestSyntax.ADDR;
+ _logger.debug("Based on " + address + " the selected destination syntax is " + _destSyntax);
+ }
+
+ public static DestSyntax getDestType(String str)
+ {
+ if (str.startsWith("BURL:") ||
+ (!str.startsWith("ADDR:") && defaultDestSyntax == DestSyntax.BURL))
+ {
+ return DestSyntax.BURL;
+ }
+ else
+ {
+ return DestSyntax.ADDR;
+ }
+ }
+
+ public static String stripSyntaxPrefix(String str)
+ {
+ if (str.startsWith("BURL:") || str.startsWith("ADDR:"))
+ {
+ return str.substring(5,str.length());
+ }
+ else
+ {
+ return str;
+ }
+ }
+
+ protected AMQDestination(String str) throws URISyntaxException
+ {
+ _destSyntax = getDestType(str);
+ str = stripSyntaxPrefix(str);
+ if (_destSyntax == DestSyntax.BURL)
+ {
+ getInfoFromBindingURL(new AMQBindingURL(str));
+ }
+ else
+ {
+ this._address = createAddressFromString(str);
+ try
+ {
+ getInfoFromAddress();
+ }
+ catch(Exception e)
+ {
+ URISyntaxException ex = new URISyntaxException(str,"Error parsing address");
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+ _logger.debug("Based on " + str + " the selected destination syntax is " + _destSyntax);
+ }
+
+ //retained for legacy support
+ protected AMQDestination(BindingURL binding)
+ {
+ getInfoFromBindingURL(binding);
+ _destSyntax = DestSyntax.BURL;
+ _logger.debug("Based on " + binding + " the selected destination syntax is " + _destSyntax);
+ }
+
+ protected void getInfoFromBindingURL(BindingURL binding)
+ {
+ _exchangeName = binding.getExchangeName();
+ _exchangeClass = binding.getExchangeClass();
+
+ _isExclusive = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_EXCLUSIVE));
+ _isAutoDelete = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_AUTODELETE));
+ _isDurable = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_DURABLE));
+ _browseOnly = Boolean.parseBoolean(binding.getOption(BindingURL.OPTION_BROWSE));
+ _queueName = binding.getQueueName() == null ? null : binding.getQueueName();
+ _routingKey = binding.getRoutingKey() == null ? null : binding.getRoutingKey();
+ _bindingKeys = binding.getBindingKeys() == null || binding.getBindingKeys().length == 0 ? new AMQShortString[0] : binding.getBindingKeys();
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, AMQShortString queueName)
+ {
+ this(exchangeName, exchangeClass, routingKey, false, false, queueName, null);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, AMQShortString queueName, AMQShortString[] bindingKeys)
+ {
+ this(exchangeName, exchangeClass, routingKey, false, false, queueName,bindingKeys);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString destinationName)
+ {
+ this(exchangeName, exchangeClass, destinationName, false, false, null,null);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName)
+ {
+ this(exchangeName, exchangeClass, routingKey, isExclusive, isAutoDelete, queueName, false,null);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName,AMQShortString[] bindingKeys)
+ {
+ this(exchangeName, exchangeClass, routingKey, isExclusive, isAutoDelete, queueName, false,bindingKeys);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName, boolean isDurable){
+ this (exchangeName, exchangeClass, routingKey, isExclusive,isAutoDelete,queueName,isDurable,null);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName, boolean isDurable,AMQShortString[] bindingKeys)
+ {
+ this (exchangeName, exchangeClass, routingKey, isExclusive,isAutoDelete,queueName,isDurable,bindingKeys, false);
+ }
+
+ protected AMQDestination(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName, boolean isDurable,AMQShortString[] bindingKeys, boolean browseOnly)
+ {
+ if ( (ExchangeDefaults.DIRECT_EXCHANGE_CLASS.equals(exchangeClass) ||
+ ExchangeDefaults.TOPIC_EXCHANGE_CLASS.equals(exchangeClass))
+ && routingKey == null)
+ {
+ throw new IllegalArgumentException("routing/binding key must not be null");
+ }
+ if (exchangeName == null)
+ {
+ throw new IllegalArgumentException("Exchange name must not be null");
+ }
+ if (exchangeClass == null)
+ {
+ throw new IllegalArgumentException("Exchange class must not be null");
+ }
+ _exchangeName = exchangeName;
+ _exchangeClass = exchangeClass;
+ _routingKey = routingKey;
+ _isExclusive = isExclusive;
+ _isAutoDelete = isAutoDelete;
+ _queueName = queueName;
+ _isDurable = isDurable;
+ _bindingKeys = bindingKeys == null || bindingKeys.length == 0 ? new AMQShortString[0] : bindingKeys;
+ _destSyntax = DestSyntax.BURL;
+ _browseOnly = browseOnly;
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Based on " + toString() + " the selected destination syntax is " + _destSyntax);
+ }
+ }
+
+ public DestSyntax getDestSyntax()
+ {
+ return _destSyntax;
+ }
+
+ protected void setDestSyntax(DestSyntax syntax)
+ {
+ _destSyntax = syntax;
+ }
+
+ public AMQShortString getEncodedName()
+ {
+ if(_urlAsShortString == null)
+ {
+ toURL();
+ }
+ return _urlAsShortString;
+ }
+
+ public boolean isDurable()
+ {
+ return _isDurable;
+ }
+
+ public AMQShortString getExchangeName()
+ {
+ return _exchangeName;
+ }
+
+ public AMQShortString getExchangeClass()
+ {
+ return _exchangeClass;
+ }
+
+ public boolean isTopic()
+ {
+ return ExchangeDefaults.TOPIC_EXCHANGE_CLASS.equals(_exchangeClass);
+ }
+
+ public boolean isQueue()
+ {
+ return ExchangeDefaults.DIRECT_EXCHANGE_CLASS.equals(_exchangeClass);
+ }
+
+ public String getQueueName()
+ {
+ return _queueName == null ? null : _queueName.toString();
+ }
+
+ public AMQShortString getAMQQueueName()
+ {
+ return _queueName;
+ }
+
+ public void setQueueName(AMQShortString queueName)
+ {
+
+ _queueName = queueName;
+ // calculated URL now out of date
+ _url = null;
+ _urlAsShortString = null;
+ _byteEncoding = null;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+
+ public AMQShortString[] getBindingKeys()
+ {
+ if (_bindingKeys != null && _bindingKeys.length > 0)
+ {
+ return _bindingKeys;
+ }
+ else
+ {
+ // catering to the common use case where the
+ //routingKey is the same as the bindingKey.
+ return new AMQShortString[]{_routingKey};
+ }
+ }
+
+ public boolean isExclusive()
+ {
+ return _isExclusive;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _isAutoDelete;
+ }
+
+ public abstract boolean isNameRequired();
+
+ public String toString()
+ {
+ if (_destSyntax == DestSyntax.BURL)
+ {
+ return toURL();
+ }
+ else
+ {
+ return _address.toString();
+ }
+
+ }
+
+ public boolean isCheckedForQueueBinding()
+ {
+ return _checkedForQueueBinding;
+ }
+
+ public void setCheckedForQueueBinding(boolean checkedForQueueBinding)
+ {
+ _checkedForQueueBinding = checkedForQueueBinding;
+ }
+
+
+ public boolean isExchangeExistsChecked()
+ {
+ return _exchangeExistsChecked;
+ }
+
+ public void setExchangeExistsChecked(final boolean exchangeExistsChecked)
+ {
+ _exchangeExistsChecked = exchangeExistsChecked;
+ }
+
+ public String toURL()
+ {
+ String url = _url;
+ if(url == null)
+ {
+
+
+ StringBuffer sb = new StringBuffer();
+
+ sb.append(_exchangeClass);
+ sb.append("://");
+ sb.append(_exchangeName);
+
+ sb.append("/"+_routingKey+"/");
+
+ if (_queueName != null)
+ {
+ sb.append(_queueName);
+ }
+
+ sb.append('?');
+
+ if (_routingKey != null)
+ {
+ sb.append(BindingURL.OPTION_ROUTING_KEY);
+ sb.append("='");
+ sb.append(_routingKey).append("'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ // We can't allow both routingKey and bindingKey
+ if (_routingKey == null && _bindingKeys != null && _bindingKeys.length>0)
+ {
+
+ for (AMQShortString bindingKey:_bindingKeys)
+ {
+ sb.append(BindingURL.OPTION_BINDING_KEY);
+ sb.append("='");
+ sb.append(bindingKey);
+ sb.append("'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+
+ }
+ }
+
+ if (_isDurable)
+ {
+ sb.append(BindingURL.OPTION_DURABLE);
+ sb.append("='true'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ if (_isExclusive)
+ {
+ sb.append(BindingURL.OPTION_EXCLUSIVE);
+ sb.append("='true'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ if (_isAutoDelete)
+ {
+ sb.append(BindingURL.OPTION_AUTODELETE);
+ sb.append("='true'");
+ sb.append(URLHelper.DEFAULT_OPTION_SEPERATOR);
+ }
+
+ //removeKey the last char '?' if there is no options , ',' if there are.
+ sb.deleteCharAt(sb.length() - 1);
+ url = sb.toString();
+ _url = url;
+ _urlAsShortString = new AMQShortString(url);
+ }
+ return url;
+ }
+
+ public byte[] toByteEncoding()
+ {
+ byte[] encoding = _byteEncoding;
+ if(encoding == null)
+ {
+ int size = _exchangeClass.length() + 1 +
+ _exchangeName.length() + 1 +
+ 0 + // in place of the destination name
+ (_queueName == null ? 0 : _queueName.length()) + 1 +
+ 1;
+ encoding = new byte[size];
+ int pos = 0;
+
+ pos = _exchangeClass.writeToByteArray(encoding, pos);
+ pos = _exchangeName.writeToByteArray(encoding, pos);
+
+ encoding[pos++] = (byte)0;
+
+ if(_queueName == null)
+ {
+ encoding[pos++] = (byte)0;
+ }
+ else
+ {
+ pos = _queueName.writeToByteArray(encoding,pos);
+ }
+ byte options = 0;
+ if(_isDurable)
+ {
+ options |= IS_DURABLE_MASK;
+ }
+ if(_isExclusive)
+ {
+ options |= IS_EXCLUSIVE_MASK;
+ }
+ if(_isAutoDelete)
+ {
+ options |= IS_AUTODELETE_MASK;
+ }
+ encoding[pos] = options;
+
+
+ _byteEncoding = encoding;
+
+ }
+ return encoding;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+
+ final AMQDestination that = (AMQDestination) o;
+
+ if (!_exchangeClass.equals(that._exchangeClass))
+ {
+ return false;
+ }
+ if (!_exchangeName.equals(that._exchangeName))
+ {
+ return false;
+ }
+ if ((_queueName == null && that._queueName != null) ||
+ (_queueName != null && !_queueName.equals(that._queueName)))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = _exchangeName == null ? "".hashCode() : _exchangeName.hashCode();
+ result = 29 * result + (_exchangeClass == null ? "".hashCode() :_exchangeClass.hashCode());
+ //result = 29 * result + _destinationName.hashCode();
+ if (_queueName != null)
+ {
+ result = 29 * result + _queueName.hashCode();
+ }
+
+ return result;
+ }
+
+ public Reference getReference() throws NamingException
+ {
+ return new Reference(
+ this.getClass().getName(),
+ new StringRefAddr(this.getClass().getName(), toURL()),
+ AMQConnectionFactory.class.getName(),
+ null); // factory location
+ }
+
+
+ public static Destination createDestination(byte[] byteEncodedDestination)
+ {
+ AMQShortString exchangeClass;
+ AMQShortString exchangeName;
+ AMQShortString routingKey;
+ AMQShortString queueName;
+ boolean isDurable;
+ boolean isExclusive;
+ boolean isAutoDelete;
+
+ int pos = 0;
+ exchangeClass = AMQShortString.readFromByteArray(byteEncodedDestination, pos);
+ pos+= exchangeClass.length() + 1;
+ exchangeName = AMQShortString.readFromByteArray(byteEncodedDestination, pos);
+ pos+= exchangeName.length() + 1;
+ routingKey = AMQShortString.readFromByteArray(byteEncodedDestination, pos);
+ pos+= (routingKey == null ? 0 : routingKey.length()) + 1;
+ queueName = AMQShortString.readFromByteArray(byteEncodedDestination, pos);
+ pos+= (queueName == null ? 0 : queueName.length()) + 1;
+ int options = byteEncodedDestination[pos];
+ isDurable = (options & IS_DURABLE_MASK) != 0;
+ isExclusive = (options & IS_EXCLUSIVE_MASK) != 0;
+ isAutoDelete = (options & IS_AUTODELETE_MASK) != 0;
+
+ if (exchangeClass.equals(ExchangeDefaults.DIRECT_EXCHANGE_CLASS))
+ {
+ return new AMQQueue(exchangeName,routingKey,queueName,isExclusive,isAutoDelete,isDurable);
+ }
+ else if (exchangeClass.equals(ExchangeDefaults.TOPIC_EXCHANGE_CLASS))
+ {
+ return new AMQTopic(exchangeName,routingKey,isAutoDelete,queueName,isDurable);
+ }
+ else if (exchangeClass.equals(ExchangeDefaults.HEADERS_EXCHANGE_CLASS))
+ {
+ return new AMQHeadersExchange(routingKey);
+ }
+ else
+ {
+ return new AMQAnyDestination(exchangeName,exchangeClass,
+ routingKey,isExclusive,
+ isAutoDelete,queueName,
+ isDurable, new AMQShortString[0]);
+ }
+
+ }
+
+ public static Destination createDestination(BindingURL binding)
+ {
+ AMQShortString type = binding.getExchangeClass();
+
+ if (type.equals(ExchangeDefaults.DIRECT_EXCHANGE_CLASS))
+ {
+ return new AMQQueue(binding);
+ }
+ else if (type.equals(ExchangeDefaults.TOPIC_EXCHANGE_CLASS))
+ {
+ return new AMQTopic(binding);
+ }
+ else if (type.equals(ExchangeDefaults.HEADERS_EXCHANGE_CLASS))
+ {
+ return new AMQHeadersExchange(binding);
+ }
+ else
+ {
+ return new AMQAnyDestination(binding);
+ }
+ }
+
+ public static Destination createDestination(String str) throws Exception
+ {
+ DestSyntax syntax = getDestType(str);
+ str = stripSyntaxPrefix(str);
+ if (syntax == DestSyntax.BURL)
+ {
+ return createDestination(new AMQBindingURL(str));
+ }
+ else
+ {
+ Address address = createAddressFromString(str);
+ return new AMQAnyDestination(address);
+ }
+ }
+
+ // ----- new address syntax -----------
+
+ public static class Binding
+ {
+ String exchange;
+ String bindingKey;
+ String queue;
+ Map<String,Object> args;
+
+ public Binding(String exchange,
+ String queue,
+ String bindingKey,
+ Map<String,Object> args)
+ {
+ this.exchange = exchange;
+ this.queue = queue;
+ this.bindingKey = bindingKey;
+ this.args = args;
+ }
+
+ public String getExchange()
+ {
+ return exchange;
+ }
+
+ public String getQueue()
+ {
+ return queue;
+ }
+
+ public String getBindingKey()
+ {
+ return bindingKey;
+ }
+
+ public Map<String, Object> getArgs()
+ {
+ return args;
+ }
+ }
+
+ public Address getAddress() {
+ return _address;
+ }
+
+ protected void setAddress(Address addr) {
+ _address = addr;
+ }
+
+ public int getAddressType(){
+ return _addressType;
+ }
+
+ public void setAddressType(int addressType){
+ _addressType = addressType;
+ }
+
+ public String getAddressName() {
+ return _name;
+ }
+
+ public void setAddressName(String name){
+ _name = name;
+ }
+
+ public String getSubject() {
+ return _subject;
+ }
+
+ public void setSubject(String subject) {
+ _subject = subject;
+ }
+
+ public AddressOption getCreate() {
+ return _create;
+ }
+
+ public void setCreate(AddressOption option) {
+ _create = option;
+ }
+
+ public AddressOption getAssert() {
+ return _assert;
+ }
+
+ public void setAssert(AddressOption option) {
+ _assert = option;
+ }
+
+ public AddressOption getDelete() {
+ return _delete;
+ }
+
+ public void setDelete(AddressOption option) {
+ _delete = option;
+ }
+
+ public Node getTargetNode()
+ {
+ return _targetNode;
+ }
+
+ public void setTargetNode(Node node)
+ {
+ _targetNode = node;
+ }
+
+ public Node getSourceNode()
+ {
+ return _sourceNode;
+ }
+
+ public void setSourceNode(Node node)
+ {
+ _sourceNode = node;
+ }
+
+ public Link getLink()
+ {
+ return _link;
+ }
+
+ public void setLink(Link link)
+ {
+ _link = link;
+ }
+
+ public void setExchangeName(AMQShortString name)
+ {
+ this._exchangeName = name;
+ }
+
+ public void setExchangeClass(AMQShortString type)
+ {
+ this._exchangeClass = type;
+ }
+
+ public void setRoutingKey(AMQShortString rk)
+ {
+ this._routingKey = rk;
+ }
+
+ public boolean isAddressResolved()
+ {
+ return _isAddressResolved;
+ }
+
+ public void setAddressResolved(boolean addressResolved)
+ {
+ _isAddressResolved = addressResolved;
+ }
+
+ private static Address createAddressFromString(String str)
+ {
+ return Address.parse(str);
+ }
+
+ private void getInfoFromAddress() throws Exception
+ {
+ _name = _address.getName();
+ _subject = _address.getSubject();
+
+ _addrHelper = new AddressHelper(_address);
+
+ _create = _addrHelper.getCreate() != null ?
+ AddressOption.getOption(_addrHelper.getCreate()):AddressOption.NEVER;
+
+ _assert = _addrHelper.getAssert() != null ?
+ AddressOption.getOption(_addrHelper.getAssert()):AddressOption.NEVER;
+
+ _delete = _addrHelper.getDelete() != null ?
+ AddressOption.getOption(_addrHelper.getDelete()):AddressOption.NEVER;
+
+ _browseOnly = _addrHelper.isBrowseOnly();
+
+ _addressType = _addrHelper.getTargetNodeType();
+ _targetNode = _addrHelper.getTargetNode(_addressType);
+ _sourceNode = _addrHelper.getSourceNode(_addressType);
+ _link = _addrHelper.getLink();
+ }
+
+ // This method is needed if we didn't know the node type at the beginning.
+ // Therefore we have to query the broker to figure out the type.
+ // Once the type is known we look for the necessary properties.
+ public void rebuildTargetAndSourceNodes(int addressType)
+ {
+ _targetNode = _addrHelper.getTargetNode(addressType);
+ _sourceNode = _addrHelper.getSourceNode(addressType);
+ }
+
+ // ----- / new address syntax -----------
+
+ public boolean isBrowseOnly()
+ {
+ return _browseOnly;
+ }
+
+ public void setBrowseOnly(boolean b)
+ {
+ _browseOnly = b;
+ }
+
+ public AMQDestination copyDestination()
+ {
+ AMQDestination dest =
+ new AMQAnyDestination(_exchangeName,
+ _exchangeClass,
+ _routingKey,
+ _isExclusive,
+ _isAutoDelete,
+ _queueName,
+ _isDurable,
+ _bindingKeys
+ );
+
+ dest.setDestSyntax(_destSyntax);
+ dest.setAddress(_address);
+ dest.setAddressName(_name);
+ dest.setSubject(_subject);
+ dest.setCreate(_create);
+ dest.setAssert(_assert);
+ dest.setDelete(_create);
+ dest.setBrowseOnly(_browseOnly);
+ dest.setAddressType(_addressType);
+ dest.setTargetNode(_targetNode);
+ dest.setSourceNode(_sourceNode);
+ dest.setLink(_link);
+ dest.setAddressResolved(_isAddressResolved);
+ return dest;
+ }
+
+ protected void setAutoDelete(boolean b)
+ {
+ _isAutoDelete = b;
+ }
+
+ protected void setDurable(boolean b)
+ {
+ _isDurable = b;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.java
new file mode 100644
index 0000000000..b9e9a33cd6
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQHeadersExchange.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.client;
+
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.url.BindingURL;
+
+/**
+ * A destination backed by a headers exchange
+ */
+public class AMQHeadersExchange extends AMQDestination
+{
+ public AMQHeadersExchange(BindingURL binding)
+ {
+ this(binding.getExchangeName());
+ }
+
+ public AMQHeadersExchange(String name)
+ {
+ this(new AMQShortString(name));
+ }
+
+ public AMQHeadersExchange(AMQShortString queueName)
+ {
+ super(queueName, ExchangeDefaults.HEADERS_EXCHANGE_CLASS, queueName, true, true, null);
+ }
+
+ public boolean isNameRequired()
+ {
+ //Not sure what the best approach is here, probably to treat this like a topic
+ //and allow server to generate names. As it is AMQ specific it doesn't need to
+ //fit the JMS API expectations so this is not as yet critical.
+ return getAMQQueueName() == null;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoConsumersException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoConsumersException.java
new file mode 100644
index 0000000000..08867b5de7
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoConsumersException.java
@@ -0,0 +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.client;
+
+import org.apache.qpid.AMQUndeliveredException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * AMQNoConsumersException indicates failure to pass an immediate message to a consumer.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to pass an immediate message to a consumer.
+ * <tr><td>
+ */
+public class AMQNoConsumersException extends AMQUndeliveredException
+{
+ public AMQNoConsumersException(String msg, Object bounced, Throwable cause)
+ {
+ super(AMQConstant.NO_CONSUMERS, msg, bounced, cause);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoRouteException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoRouteException.java
new file mode 100644
index 0000000000..42ed9c3df7
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQNoRouteException.java
@@ -0,0 +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.client;
+
+import org.apache.qpid.AMQUndeliveredException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * AMQNoRouteException indicates that a mandatory message could not be routed.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to route a mandatory message.
+ * <tr><td>
+ */
+public class AMQNoRouteException extends AMQUndeliveredException
+{
+ public AMQNoRouteException(String msg, Object bounced, Throwable cause)
+ {
+ super(AMQConstant.NO_ROUTE, msg, bounced, cause);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueue.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueue.java
new file mode 100644
index 0000000000..5bd1bd629a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueue.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.client;
+
+import java.net.URISyntaxException;
+
+import javax.jms.Queue;
+
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.url.BindingURL;
+
+public class AMQQueue extends AMQDestination implements Queue
+{
+
+ public AMQQueue(String address) throws URISyntaxException
+ {
+ super(address);
+ }
+
+ /**
+ * Create a reference to a non temporary queue using a BindingURL object.
+ * Note this does not actually imply the queue exists.
+ * @param binding a BindingURL object
+ */
+ public AMQQueue(BindingURL binding)
+ {
+ super(binding);
+ }
+
+ /**
+ * Create a reference to a non temporary queue. Note this does not actually imply the queue exists.
+ * @param name the name of the queue
+ */
+ public AMQQueue(AMQShortString exchangeName, String name)
+ {
+ this(exchangeName, new AMQShortString(name));
+ }
+
+
+ /**
+ * Create a reference to a non temporary queue. Note this does not actually imply the queue exists.
+ * @param name the name of the queue
+ */
+ public AMQQueue(AMQShortString exchangeName, AMQShortString name)
+ {
+ this(exchangeName, name, false);
+ }
+
+ public AMQQueue(AMQShortString exchangeName, AMQShortString routingKey, AMQShortString queueName)
+ {
+ super(exchangeName, ExchangeDefaults.DIRECT_EXCHANGE_CLASS, routingKey, false,
+ false, queueName, false);
+ }
+
+ public AMQQueue(AMQShortString exchangeName, AMQShortString routingKey, AMQShortString queueName,AMQShortString[] bindingKeys)
+ {
+ super(exchangeName, ExchangeDefaults.DIRECT_EXCHANGE_CLASS, routingKey, false,
+ false, queueName, false,bindingKeys);
+ }
+
+ /**
+ * Create a reference to a non temporary queue. Note this does not actually imply the queue exists.
+ * @param name the name of the queue
+ */
+ public AMQQueue(String exchangeName, String name)
+ {
+ this(new AMQShortString(exchangeName), new AMQShortString(name), false);
+ }
+
+
+ public AMQQueue(AMQConnection connection, String name)
+ {
+ this(connection.getDefaultQueueExchangeName(),name);
+ }
+
+ public AMQQueue(AMQConnection connection, String name, boolean temporary)
+ {
+ this(connection.getDefaultQueueExchangeName(), new AMQShortString(name),temporary);
+ }
+
+
+ /**
+ * Create a queue with a specified name.
+ *
+ * @param name the destination name (used in the routing key)
+ * @param temporary if true the broker will generate a queue name, also if true then the queue is autodeleted
+ * and exclusive
+ */
+ public AMQQueue(String exchangeName, String name, boolean temporary)
+ {
+ this(new AMQShortString(exchangeName), new AMQShortString(name),temporary);
+ }
+
+
+ /**
+ * Create a queue with a specified name.
+ *
+ * @param name the destination name (used in the routing key)
+ * @param temporary if true the broker will generate a queue name, also if true then the queue is autodeleted
+ * and exclusive
+ */
+ public AMQQueue(AMQShortString exchangeName, AMQShortString name, boolean temporary)
+ {
+ // queue name is set to null indicating that the broker assigns a name in the case of temporary queues
+ // temporary queues are typically used as response queues
+ this(exchangeName, name, temporary?null:name, temporary, temporary, !temporary);
+
+ }
+
+ /**
+ * Create a reference to a queue. Note this does not actually imply the queue exists.
+ * @param exchangeName the exchange name we want to send the message to
+ * @param routingKey the routing key
+ * @param queueName the queue name
+ * @param exclusive true if the queue should only permit a single consumer
+ * @param autoDelete true if the queue should be deleted automatically when the last consumers detaches
+ */
+ public AMQQueue(AMQShortString exchangeName, AMQShortString routingKey, AMQShortString queueName, boolean exclusive, boolean autoDelete)
+ {
+ this(exchangeName, routingKey, queueName, exclusive, autoDelete, false);
+ }
+
+ public AMQQueue(AMQShortString exchangeName, AMQShortString routingKey, AMQShortString queueName, boolean exclusive, boolean autoDelete, boolean durable)
+ {
+ this(exchangeName,routingKey,queueName,exclusive,autoDelete,durable,null);
+ }
+
+ public AMQQueue(AMQShortString exchangeName, AMQShortString routingKey, AMQShortString queueName, boolean exclusive, boolean autoDelete, boolean durable,AMQShortString[] bindingKeys)
+ {
+ super(exchangeName, ExchangeDefaults.DIRECT_EXCHANGE_CLASS, routingKey, exclusive,
+ autoDelete, queueName, durable, bindingKeys);
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ //return getAMQQueueName();
+ if (getAMQQueueName() != null && getAMQQueueName().equals(super.getRoutingKey()))
+ {
+ return getAMQQueueName();
+ }
+ else
+ {
+ return super.getRoutingKey();
+ }
+ }
+
+ public boolean isNameRequired()
+ {
+ //If the name is null, we require one to be generated by the client so that it will#
+ //remain valid if we failover (see BLZ-24)
+ return getQueueName() == null;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java
new file mode 100644
index 0000000000..d96544adf8
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueBrowser.java
@@ -0,0 +1,143 @@
+/*
+ *
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class AMQQueueBrowser implements QueueBrowser
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AMQQueueBrowser.class);
+
+ private AtomicBoolean _isClosed = new AtomicBoolean();
+ private final AMQSession _session;
+ private final AMQQueue _queue;
+ private final ArrayList<BasicMessageConsumer> _consumers = new ArrayList<BasicMessageConsumer>();
+ private final String _messageSelector;
+
+ AMQQueueBrowser(AMQSession session, AMQQueue queue, String messageSelector) throws JMSException
+ {
+ _session = session;
+ _queue = queue;
+ _messageSelector = ((messageSelector == null) || (messageSelector.trim().length() == 0)) ? null : messageSelector;
+ // Create Consumer to verify message selector.
+ BasicMessageConsumer consumer =
+ (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();
+ }
+
+ public Queue getQueue() throws JMSException
+ {
+ checkState();
+
+ return _queue;
+ }
+
+ private void checkState() throws JMSException
+ {
+ if (_isClosed.get())
+ {
+ throw new IllegalStateException("Queue Browser");
+ }
+
+ if (_session.isClosed())
+ {
+ throw new IllegalStateException("Session is closed");
+ }
+
+ }
+
+ public String getMessageSelector() throws JMSException
+ {
+
+ checkState();
+
+ return _messageSelector;
+ }
+
+ public Enumeration getEnumeration() throws JMSException
+ {
+ checkState();
+ final BasicMessageConsumer consumer =
+ (BasicMessageConsumer) _session.createBrowserConsumer(_queue, _messageSelector, false);
+
+ _consumers.add(consumer);
+
+ return new QueueBrowserEnumeration(consumer);
+ }
+
+ public void close() throws JMSException
+ {
+ for (BasicMessageConsumer consumer : _consumers)
+ {
+ consumer.close();
+ }
+
+ _consumers.clear();
+ }
+
+ private class QueueBrowserEnumeration implements Enumeration
+ {
+ Message _nextMessage;
+ private BasicMessageConsumer _consumer;
+
+ public QueueBrowserEnumeration(BasicMessageConsumer consumer) throws JMSException
+ {
+ _nextMessage = consumer == null ? null : consumer.receiveBrowse();
+ _logger.info("QB:created with first element:" + _nextMessage);
+ _consumer = consumer;
+ }
+
+ public boolean hasMoreElements()
+ {
+ _logger.info("QB:hasMoreElements:" + (_nextMessage != null));
+ return (_nextMessage != null);
+ }
+
+ public Object nextElement()
+ {
+ Message msg = _nextMessage;
+ try
+ {
+ _logger.info("QB:nextElement about to receive");
+ _nextMessage = _consumer.receiveBrowse();
+ _logger.info("QB:nextElement received:" + _nextMessage);
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Exception caught while queue browsing", e);
+ _nextMessage = null;
+ }
+ return msg;
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueSessionAdaptor.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueSessionAdaptor.java
new file mode 100644
index 0000000000..a8c83d8868
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQQueueSessionAdaptor.java
@@ -0,0 +1,204 @@
+/*
+ *
+ * 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 java.io.Serializable;
+
+import javax.jms.BytesMessage;
+import javax.jms.Destination;
+import javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.QueueReceiver;
+import javax.jms.QueueSender;
+import javax.jms.QueueSession;
+import javax.jms.Session;
+import javax.jms.StreamMessage;
+import javax.jms.TemporaryQueue;
+import javax.jms.TemporaryTopic;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+
+/**
+ * Need this adaptor class to conform to JMS spec and throw IllegalStateException
+ * from createDurableSubscriber, unsubscribe, createTopic & createTemporaryTopic
+ */
+public class AMQQueueSessionAdaptor implements QueueSession, AMQSessionAdapter
+{
+ //holds a session for delegation
+ protected final AMQSession _session;
+
+ /**
+ * Construct an adaptor with a session to wrap
+ * @param session
+ */
+ public AMQQueueSessionAdaptor(Session session)
+ {
+ _session = (AMQSession) session;
+ }
+
+ public TemporaryQueue createTemporaryQueue() throws JMSException {
+ return _session.createTemporaryQueue();
+ }
+
+ public Queue createQueue(String string) throws JMSException {
+ return _session.createQueue(string);
+ }
+
+ public QueueReceiver createReceiver(Queue queue) throws JMSException {
+ return _session.createReceiver(queue);
+ }
+
+ public QueueReceiver createReceiver(Queue queue, String string) throws JMSException {
+ return _session.createReceiver(queue, string);
+ }
+
+ public QueueSender createSender(Queue queue) throws JMSException {
+ return _session.createSender(queue);
+ }
+
+ public QueueBrowser createBrowser(Queue queue) throws JMSException {
+ return _session.createBrowser(queue);
+ }
+
+ public QueueBrowser createBrowser(Queue queue, String string) throws JMSException {
+ return _session.createBrowser(queue, string);
+ }
+
+ public BytesMessage createBytesMessage() throws JMSException {
+ return _session.createBytesMessage();
+ }
+
+ public MapMessage createMapMessage() throws JMSException {
+ return _session.createMapMessage();
+ }
+
+ public Message createMessage() throws JMSException {
+ return _session.createMessage();
+ }
+
+ public ObjectMessage createObjectMessage() throws JMSException {
+ return _session.createObjectMessage();
+ }
+
+ public ObjectMessage createObjectMessage(Serializable serializable) throws JMSException {
+ return _session.createObjectMessage(serializable);
+ }
+
+ public StreamMessage createStreamMessage() throws JMSException {
+ return _session.createStreamMessage();
+ }
+
+ public TextMessage createTextMessage() throws JMSException {
+ return _session.createTextMessage();
+ }
+
+ public TextMessage createTextMessage(String string) throws JMSException {
+ return _session.createTextMessage(string);
+ }
+
+ public boolean getTransacted() throws JMSException {
+ return _session.getTransacted();
+ }
+
+ public int getAcknowledgeMode() throws JMSException {
+ return _session.getAcknowledgeMode();
+ }
+
+ public void commit() throws JMSException {
+ _session.commit();
+ }
+
+ public void rollback() throws JMSException {
+ _session.rollback();
+ }
+
+ public void close() throws JMSException {
+ _session.close();
+ }
+
+ public void recover() throws JMSException {
+ _session.recover();
+ }
+
+ public MessageListener getMessageListener() throws JMSException {
+ return _session.getMessageListener();
+ }
+
+ public void setMessageListener(MessageListener messageListener) throws JMSException {
+ _session.setMessageListener(messageListener);
+ }
+
+ public void run() {
+ _session.run();
+ }
+
+ public MessageProducer createProducer(Destination destination) throws JMSException {
+ return _session.createProducer(destination);
+ }
+
+ public MessageConsumer createConsumer(Destination destination) throws JMSException {
+ return _session.createConsumer(destination);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String string) throws JMSException {
+ return _session.createConsumer(destination,string);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String string, boolean b) throws JMSException {
+ return _session.createConsumer(destination,string,b);
+ }
+
+ //The following methods cannot be called from a QueueSession as per JMS spec
+
+ public Topic createTopic(String string) throws JMSException {
+ throw new IllegalStateException("Cannot call createTopic from QueueSession");
+ }
+
+ public TopicSubscriber createDurableSubscriber(Topic topic, String string) throws JMSException {
+ throw new IllegalStateException("Cannot call createDurableSubscriber from QueueSession");
+ }
+
+ public TopicSubscriber createDurableSubscriber(Topic topic, String string, String string1, boolean b) throws JMSException {
+ throw new IllegalStateException("Cannot call createDurableSubscriber from QueueSession");
+ }
+
+ public TemporaryTopic createTemporaryTopic() throws JMSException {
+ throw new IllegalStateException("Cannot call createTemporaryTopic from QueueSession");
+ }
+
+ public void unsubscribe(String string) throws JMSException {
+ throw new IllegalStateException("Cannot call unsubscribe from QueueSession");
+ }
+
+ public AMQSession getSession()
+ {
+ return _session;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
new file mode 100644
index 0000000000..25562cfff7
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession.java
@@ -0,0 +1,3489 @@
+/*
+ *
+ * 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 java.io.Serializable;
+import java.net.URISyntaxException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jms.BytesMessage;
+import javax.jms.Destination;
+import javax.jms.IllegalStateException;
+import javax.jms.InvalidDestinationException;
+import javax.jms.InvalidSelectorException;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.QueueReceiver;
+import javax.jms.QueueSender;
+import javax.jms.QueueSession;
+import javax.jms.StreamMessage;
+import javax.jms.TemporaryQueue;
+import javax.jms.TemporaryTopic;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+import javax.jms.TransactionRolledBackException;
+
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInvalidArgumentException;
+import org.apache.qpid.AMQInvalidRoutingKeyException;
+import org.apache.qpid.client.AMQDestination.AddressOption;
+import org.apache.qpid.client.AMQDestination.DestSyntax;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.failover.FailoverNoopSupport;
+import org.apache.qpid.client.failover.FailoverProtectedOperation;
+import org.apache.qpid.client.failover.FailoverRetrySupport;
+import org.apache.qpid.client.message.AMQMessageDelegateFactory;
+import org.apache.qpid.client.message.AMQPEncodedMapMessage;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.CloseConsumerMessage;
+import org.apache.qpid.client.message.JMSBytesMessage;
+import org.apache.qpid.client.message.JMSMapMessage;
+import org.apache.qpid.client.message.JMSObjectMessage;
+import org.apache.qpid.client.message.JMSStreamMessage;
+import org.apache.qpid.client.message.JMSTextMessage;
+import org.apache.qpid.client.message.MessageFactoryRegistry;
+import org.apache.qpid.client.message.UnprocessedMessage;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.util.FlowControllingBlockingQueue;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.thread.Threading;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ *
+ * @todo Different FailoverSupport implementation are needed on the same method call, in different situations. For
+ * example, when failing-over and reestablishing the bindings, the bind cannot be interrupted by a second
+ * fail-over, if it fails with an exception, the fail-over process should also fail. When binding outside of
+ * the fail-over process, the retry handler could be used to automatically retry the operation once the connection
+ * has been reestablished. All fail-over protected operations should be placed in private methods, with
+ * FailoverSupport passed in by the caller to provide the correct support for the calling context. Sometimes the
+ * fail-over process sets a nowait flag and uses an async method call instead.
+ * @todo Two new objects created on every failover supported method call. Consider more efficient ways of doing this,
+ * after looking at worse bottlenecks first.
+ */
+public abstract class AMQSession<C extends BasicMessageConsumer, P extends BasicMessageProducer> extends Closeable implements Session, QueueSession, TopicSession
+{
+ public static final class IdToConsumerMap<C extends BasicMessageConsumer>
+ {
+ private final BasicMessageConsumer[] _fastAccessConsumers = new BasicMessageConsumer[16];
+ private final ConcurrentHashMap<Integer, C> _slowAccessConsumers = new ConcurrentHashMap<Integer, C>();
+
+ public C get(int id)
+ {
+ if ((id & 0xFFFFFFF0) == 0)
+ {
+ return (C) _fastAccessConsumers[id];
+ }
+ else
+ {
+ return _slowAccessConsumers.get(id);
+ }
+ }
+
+ public C put(int id, C consumer)
+ {
+ C oldVal;
+ if ((id & 0xFFFFFFF0) == 0)
+ {
+ oldVal = (C) _fastAccessConsumers[id];
+ _fastAccessConsumers[id] = consumer;
+ }
+ else
+ {
+ oldVal = _slowAccessConsumers.put(id, consumer);
+ }
+
+ return oldVal;
+
+ }
+
+ public C remove(int id)
+ {
+ C consumer;
+ if ((id & 0xFFFFFFF0) == 0)
+ {
+ consumer = (C) _fastAccessConsumers[id];
+ _fastAccessConsumers[id] = null;
+ }
+ else
+ {
+ consumer = _slowAccessConsumers.remove(id);
+ }
+
+ return consumer;
+
+ }
+
+ public Collection<C> values()
+ {
+ ArrayList<C> values = new ArrayList<C>();
+
+ for (int i = 0; i < 16; i++)
+ {
+ if (_fastAccessConsumers[i] != null)
+ {
+ values.add((C) _fastAccessConsumers[i]);
+ }
+ }
+ values.addAll(_slowAccessConsumers.values());
+
+ return values;
+ }
+
+ public void clear()
+ {
+ _slowAccessConsumers.clear();
+ for (int i = 0; i < 16; i++)
+ {
+ _fastAccessConsumers[i] = null;
+ }
+ }
+ }
+
+ final AMQSession<C, P> _thisSession = this;
+
+ /** Used for debugging. */
+ private static final Logger _logger = LoggerFactory.getLogger(AMQSession.class);
+
+ /**
+ * The default value for immediate flag used by producers created by this session is false. That is, a consumer does
+ * not need to be attached to a queue.
+ */
+ protected final boolean DEFAULT_IMMEDIATE = Boolean.parseBoolean(System.getProperty("qpid.default_immediate", "false"));
+
+ /**
+ * The default value for mandatory flag used by producers created by this session is true. That is, server will not
+ * silently drop messages where no queue is connected to the exchange for the message.
+ */
+ protected final boolean DEFAULT_MANDATORY = Boolean.parseBoolean(System.getProperty("qpid.default_mandatory", "true"));
+
+ protected final boolean DEFAULT_WAIT_ON_SEND = Boolean.parseBoolean(System.getProperty("qpid.default_wait_on_send", "false"));
+
+ /**
+ * The period to wait while flow controlled before sending a log message confirming that the session is still
+ * waiting on flow control being revoked
+ */
+ protected final long FLOW_CONTROL_WAIT_PERIOD = Long.getLong("qpid.flow_control_wait_notify_period",5000L);
+
+ /**
+ * The period to wait while flow controlled before declaring a failure
+ */
+ public static final long DEFAULT_FLOW_CONTROL_WAIT_FAILURE = 120000L;
+ protected final long FLOW_CONTROL_WAIT_FAILURE = Long.getLong("qpid.flow_control_wait_failure",
+ DEFAULT_FLOW_CONTROL_WAIT_FAILURE);
+
+ protected final boolean DECLARE_QUEUES =
+ Boolean.parseBoolean(System.getProperty("qpid.declare_queues", "true"));
+
+ protected final boolean DECLARE_EXCHANGES =
+ Boolean.parseBoolean(System.getProperty("qpid.declare_exchanges", "true"));
+
+ protected final boolean USE_AMQP_ENCODED_MAP_MESSAGE;
+
+ /** System property to enable strict AMQP compliance. */
+ public static final String STRICT_AMQP = "STRICT_AMQP";
+
+ /** Strict AMQP default setting. */
+ public static final String STRICT_AMQP_DEFAULT = "false";
+
+ /** System property to enable failure if strict AMQP compliance is violated. */
+ public static final String STRICT_AMQP_FATAL = "STRICT_AMQP_FATAL";
+
+ /** Strickt AMQP failure default. */
+ public static final String STRICT_AMQP_FATAL_DEFAULT = "true";
+
+ /** System property to enable immediate message prefetching. */
+ public static final String IMMEDIATE_PREFETCH = "IMMEDIATE_PREFETCH";
+
+ /** Immediate message prefetch default. */
+ public static final String IMMEDIATE_PREFETCH_DEFAULT = "false";
+
+ /** The connection to which this session belongs. */
+ protected AMQConnection _connection;
+
+ /** Used to indicate whether or not this is a transactional session. */
+ protected boolean _transacted;
+
+ /** Holds the sessions acknowledgement mode. */
+ protected final int _acknowledgeMode;
+
+ /** Holds this session unique identifier, used to distinguish it from other sessions. */
+ protected int _channelId;
+
+ private int _ticket;
+
+ /** Holds the high mark for prefetched message, at which the session is suspended. */
+ private int _prefetchHighMark;
+
+ /** Holds the low mark for prefetched messages, below which the session is resumed. */
+ private int _prefetchLowMark;
+
+ /** Holds the message listener, if any, which is attached to this session. */
+ private MessageListener _messageListener = null;
+
+ /** Used to indicate that this session has been started at least once. */
+ private AtomicBoolean _startedAtLeastOnce = new AtomicBoolean(false);
+
+ /**
+ * Used to reference durable subscribers so that requests for unsubscribe can be handled correctly. Note this only
+ * keeps a record of subscriptions which have been created in the current instance. It does not remember
+ * subscriptions between executions of the client.
+ */
+ protected final ConcurrentHashMap<String, TopicSubscriberAdaptor<C>> _subscriptions =
+ new ConcurrentHashMap<String, TopicSubscriberAdaptor<C>>();
+
+ /**
+ * Holds a mapping from message consumers to their identifying names, so that their subscriptions may be looked
+ * up in the {@link #_subscriptions} map.
+ */
+ protected final ConcurrentHashMap<C, String> _reverseSubscriptionMap = new ConcurrentHashMap<C, String>();
+
+ /**
+ * Locks to keep access to subscriber details atomic.
+ * <p>
+ * Added for QPID2418
+ */
+ protected final Lock _subscriberDetails = new ReentrantLock(true);
+ protected final Lock _subscriberAccess = new ReentrantLock(true);
+
+ /**
+ * Used to hold incoming messages.
+ *
+ * @todo Weaken the type once {@link FlowControllingBlockingQueue} implements Queue.
+ */
+ protected final FlowControllingBlockingQueue _queue;
+
+ /** Holds the highest received delivery tag. */
+ private final AtomicLong _highestDeliveryTag = new AtomicLong(-1);
+ private final AtomicLong _rollbackMark = new AtomicLong(-1);
+
+ /** All the not yet acknowledged message tags */
+ protected ConcurrentLinkedQueue<Long> _unacknowledgedMessageTags = new ConcurrentLinkedQueue<Long>();
+
+ /** All the delivered message tags */
+ protected ConcurrentLinkedQueue<Long> _deliveredMessageTags = new ConcurrentLinkedQueue<Long>();
+
+ /** Holds the dispatcher thread for this session. */
+ protected Dispatcher _dispatcher;
+
+ protected Thread _dispatcherThread;
+
+ /** Holds the message factory factory for this session. */
+ protected MessageFactoryRegistry _messageFactoryRegistry;
+
+ /** Holds all of the producers created by this session, keyed by their unique identifiers. */
+ private Map<Long, MessageProducer> _producers = new ConcurrentHashMap<Long, MessageProducer>();
+
+ /**
+ * Used as a source of unique identifiers so that the consumers can be tagged to match them to BasicConsume
+ * methods.
+ */
+ private int _nextTag = 1;
+
+ /**
+ * Maps from identifying tags to message consumers, in order to pass dispatch incoming messages to the right
+ * consumer.
+ */
+ protected final IdToConsumerMap<C> _consumers = new IdToConsumerMap<C>();
+
+ /**
+ * 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<C> _removedConsumers = new CopyOnWriteArrayList<C>();
+
+ /** Provides a count of consumers on destinations, in order to be able to know if a destination has consumers. */
+ private ConcurrentHashMap<Destination, AtomicInteger> _destinationConsumerCount =
+ new ConcurrentHashMap<Destination, AtomicInteger>();
+
+ /**
+ * Used as a source of unique identifiers for producers within the session.
+ *
+ * <p/> Access to this id does not require to be synchronized since according to the JMS specification only one
+ * thread of control is allowed to create producers for any given session instance.
+ */
+ private long _nextProducerId;
+
+ /**
+ * Set when recover is called. This is to handle the case where recover() is called by application code during
+ * onMessage() processing to ensure that an auto ack is not sent.
+ */
+ private boolean _inRecovery;
+
+ /** Used to indicates that the connection to which this session belongs, has been stopped. */
+ private boolean _connectionStopped;
+
+ /** Used to indicate that this session has a message listener attached to it. */
+ private boolean _hasMessageListeners;
+
+ /** Used to indicate that this session has been suspended. */
+ private boolean _suspended;
+
+ /**
+ * Used to protect the suspension of this session, so that critical code can be executed during suspension,
+ * without the session being resumed by other threads.
+ */
+ private final Object _suspensionLock = new Object();
+
+ /**
+ * Used to ensure that only the first call to start the dispatcher can unsuspend the channel.
+ *
+ * @todo This is accessed only within a synchronized method, so does not need to be atomic.
+ */
+ protected final AtomicBoolean _firstDispatcher = new AtomicBoolean(true);
+
+ /** Used to indicate that the session should start pre-fetching messages as soon as it is started. */
+ protected final boolean _immediatePrefetch;
+
+ /** Indicates that warnings should be generated on violations of the strict AMQP. */
+ protected final boolean _strictAMQP;
+
+ /** Indicates that runtime exceptions should be generated on vilations of the strict AMQP. */
+ 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 with outstanding actions to commit? */
+ private boolean _failedOverDirty;
+
+ 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.
+ *
+ * @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 acknowledgement mode for the session.
+ * @param messageFactoryRegistry The message factory factory for the session.
+ * @param defaultPrefetchHighMark The maximum number of messages to prefetched before suspending the session.
+ * @param defaultPrefetchLowMark The number of prefetched messages at which to resume the session.
+ */
+ protected AMQSession(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
+ MessageFactoryRegistry messageFactoryRegistry, int defaultPrefetchHighMark, int defaultPrefetchLowMark)
+ {
+ USE_AMQP_ENCODED_MAP_MESSAGE = con == null ? true : !con.isUseLegacyMapMessageFormat();
+ _strictAMQP = Boolean.parseBoolean(System.getProperties().getProperty(STRICT_AMQP, STRICT_AMQP_DEFAULT));
+ _strictAMQPFATAL =
+ Boolean.parseBoolean(System.getProperties().getProperty(STRICT_AMQP_FATAL, STRICT_AMQP_FATAL_DEFAULT));
+ _immediatePrefetch =
+ _strictAMQP
+ || Boolean.parseBoolean(System.getProperties().getProperty(IMMEDIATE_PREFETCH, IMMEDIATE_PREFETCH_DEFAULT));
+
+ _connection = con;
+ _transacted = transacted;
+ if (transacted)
+ {
+ _acknowledgeMode = javax.jms.Session.SESSION_TRANSACTED;
+ }
+ else
+ {
+ _acknowledgeMode = acknowledgeMode;
+ }
+
+ _channelId = channelId;
+ _messageFactoryRegistry = messageFactoryRegistry;
+ _prefetchHighMark = defaultPrefetchHighMark;
+ _prefetchLowMark = defaultPrefetchLowMark;
+
+ if (_acknowledgeMode == NO_ACKNOWLEDGE)
+ {
+ _queue =
+ new FlowControllingBlockingQueue(_prefetchHighMark, _prefetchLowMark,
+ new FlowControllingBlockingQueue.ThresholdListener()
+ {
+ private final AtomicBoolean _suspendState = new AtomicBoolean();
+
+ public void aboveThreshold(int currentValue)
+ {
+ // If the session has been closed don't waste time creating a thread to do
+ // flow control
+ if (!(_thisSession.isClosed() || _thisSession.isClosing()))
+ {
+ // Only execute change if previous state
+ // was False
+ if (!_suspendState.getAndSet(true))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(
+ "Above threshold(" + _prefetchHighMark
+ + ") so suspending channel. Current value is " + currentValue);
+ }
+ try
+ {
+ Threading.getThreadFactory().createThread(new SuspenderRunner(_suspendState)).start();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Failed to create thread", e);
+ }
+ }
+ }
+ }
+
+ public void underThreshold(int currentValue)
+ {
+ // If the session has been closed don't waste time creating a thread to do
+ // flow control
+ if (!(_thisSession.isClosed() || _thisSession.isClosing()))
+ {
+ // Only execute change if previous state
+ // was true
+ if (_suspendState.getAndSet(false))
+ {
+ if (_logger.isDebugEnabled())
+ {
+
+ _logger.debug(
+ "Below threshold(" + _prefetchLowMark
+ + ") so unsuspending channel. Current value is " + currentValue);
+ }
+ try
+ {
+ Threading.getThreadFactory().createThread(new SuspenderRunner(_suspendState)).start();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Failed to create thread", e);
+ }
+ }
+ }
+ }
+ });
+ }
+ else
+ {
+ _queue = new FlowControllingBlockingQueue(_prefetchHighMark, null);
+ }
+
+ // Add creation logging to tie in with the existing close logging
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Created session:" + this);
+ }
+ }
+
+ /**
+ * 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 acknowledgement 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.
+
+ /**
+ * Closes the session with no timeout.
+ *
+ * @throws JMSException If the JMS provider fails to close the session due to some internal error.
+ */
+ public void close() throws JMSException
+ {
+ close(-1);
+ }
+
+ public abstract AMQException getLastException();
+
+ public void checkNotClosed() throws JMSException
+ {
+ try
+ {
+ super.checkNotClosed();
+ }
+ catch (IllegalStateException ise)
+ {
+ AMQException ex = getLastException();
+ if (ex != null)
+ {
+ IllegalStateException ssnClosed = new IllegalStateException(
+ "Session has been closed", ex.getErrorCode().toString());
+
+ ssnClosed.setLinkedException(ex);
+ ssnClosed.initCause(ex);
+ throw ssnClosed;
+ }
+ else
+ {
+ throw ise;
+ }
+ }
+ }
+
+ public BytesMessage createBytesMessage() throws JMSException
+ {
+ checkNotClosed();
+ JMSBytesMessage msg = new JMSBytesMessage(getMessageDelegateFactory());
+ msg.setAMQSession(this);
+ return msg;
+ }
+
+ /**
+ * Acknowledges all unacknowledged messages on the session, for all message consumers on the session.
+ *
+ * @throws IllegalStateException If the session is closed.
+ */
+ public void acknowledge() throws IllegalStateException
+ {
+ if (isClosed())
+ {
+ throw new IllegalStateException("Session is already closed");
+ }
+ else if (hasFailedOver())
+ {
+ throw new IllegalStateException("has failed over");
+ }
+
+ while (true)
+ {
+ Long tag = _unacknowledgedMessageTags.poll();
+ if (tag == null)
+ {
+ break;
+ }
+ acknowledgeMessage(tag, false);
+ }
+ }
+
+ /**
+ * Acknowledge one or many messages.
+ *
+ * @param deliveryTag The tag of the last message to be acknowledged.
+ * @param multiple <tt>true</tt> to acknowledge all messages up to and including the one specified by the
+ * delivery tag, <tt>false</tt> to just acknowledge that message.
+ *
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ 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.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param queueName The name of the queue to bind.
+ * @param routingKey The routing key to bind the queue with.
+ * @param arguments Additional arguments.
+ * @param exchangeName The exchange to bind the queue on.
+ *
+ * @throws AMQException If the queue cannot be bound for any reason.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ * @todo Document the additional arguments that may be passed in the field table. Are these for headers exchanges?
+ */
+ public void bindQueue(final AMQShortString queueName, final AMQShortString routingKey, final FieldTable arguments,
+ final AMQShortString exchangeName, final AMQDestination destination) throws AMQException
+ {
+ bindQueue(queueName, routingKey, arguments, exchangeName, destination, false);
+ }
+
+ public void bindQueue(final AMQShortString queueName, final AMQShortString routingKey, final FieldTable arguments,
+ final AMQShortString exchangeName, final AMQDestination destination,
+ final boolean nowait) throws AMQException
+ {
+ /*new FailoverRetrySupport<Object, AMQException>(new FailoverProtectedOperation<Object, AMQException>()*/
+ new FailoverNoopSupport<Object, AMQException>(new FailoverProtectedOperation<Object, AMQException>()
+ {
+ public Object execute() throws AMQException, FailoverException
+ {
+ sendQueueBind(queueName, routingKey, arguments, exchangeName, destination, nowait);
+ return null;
+ }
+ }, _connection).execute();
+ }
+
+ public void addBindingKey(C consumer, AMQDestination amqd, String routingKey) throws AMQException
+ {
+ if (consumer.getQueuename() != null)
+ {
+ bindQueue(consumer.getQueuename(), new AMQShortString(routingKey), new FieldTable(), amqd.getExchangeName(), amqd);
+ }
+ }
+
+ public abstract void sendQueueBind(final AMQShortString queueName, final AMQShortString routingKey, final FieldTable arguments,
+ final AMQShortString exchangeName, AMQDestination destination,
+ final boolean nowait) throws AMQException, FailoverException;
+
+ /**
+ * Closes the session.
+ *
+ * <p/>Note that this operation succeeds automatically if a fail-over interrupts the synchronous request to close
+ * the channel. This is because the channel is marked as closed before the request to close it is made, so the
+ * fail-over should not re-open it.
+ *
+ * @param timeout The timeout in milliseconds to wait for the session close acknowledgement from the broker.
+ *
+ * @throws JMSException If the JMS provider fails to close the session due to some internal error.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ * @todo Not certain about the logic of ignoring the failover exception, because the channel won't be
+ * re-opened. May need to examine this more carefully.
+ * @todo Note that taking the failover mutex doesn't prevent this operation being interrupted by a failover,
+ * because the failover process sends the failover event before acquiring the mutex itself.
+ */
+ public void close(long timeout) throws JMSException
+ {
+ close(timeout, true);
+ }
+
+ private void close(long timeout, boolean sendClose) throws JMSException
+ {
+ if (_logger.isInfoEnabled())
+ {
+ // StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ _logger.info("Closing session: " + this); // + ":"
+ // Arrays.asList(stackTrace).subList(3, stackTrace.length - 1));
+ }
+
+ // Ensure we only try and close an open session.
+ if (!_closed.getAndSet(true))
+ {
+ _closing.set(true);
+ synchronized (getFailoverMutex())
+ {
+ // 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
+ {
+ // If the connection is open or we are in the process
+ // of closing the connection then send a cance
+ // no point otherwise as the connection will be gone
+ if (!_connection.isClosed() || _connection.isClosing())
+ {
+ if (sendClose)
+ {
+ sendClose(timeout);
+ }
+ }
+ }
+ catch (AMQException e)
+ {
+ JMSException jmse = new JMSException("Error closing session: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ // This is ignored because the channel is already marked as closed so the fail-over process will
+ // not re-open it.
+ catch (FailoverException e)
+ {
+ _logger.debug(
+ "Got FailoverException during channel close, ignored as channel already marked as closed.");
+ }
+ finally
+ {
+ _connection.deregisterSession(_channelId);
+ }
+ }
+ }
+ }
+ }
+
+ public abstract void sendClose(long timeout) throws AMQException, FailoverException;
+
+ /**
+ * Called when the server initiates the closure of the session unilaterally.
+ *
+ * @param e the exception that caused this session to be closed. Null causes the
+ */
+ public void closed(Throwable e) throws JMSException
+ {
+ // 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.
+ _dispatcherThread.interrupt();
+ }
+
+ }
+
+ //if we don't have an exception then we can perform closing operations
+ _closing.set(e == null);
+
+ if (!_closed.getAndSet(true))
+ {
+ synchronized (_messageDeliveryLock)
+ {
+ // 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);
+ }
+ }
+ }
+
+ /**
+ * Commits all messages done in this transaction and releases any locks currently held.
+ *
+ * <p/>If the commit fails, because the commit itself is interrupted by a fail-over between requesting that the
+ * commit be done, and receiving an acknowledgement that it has been done, then a JMSException will be thrown.
+ * The client will be unable to determine whether or not the commit actually happened on the broker in this case.
+ *
+ * @throws JMSException If the JMS provider fails to commit the transaction due to some internal error. This does
+ * not mean that the commit is known to have failed, merely that it is not known whether it
+ * failed or not.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ public void commit() throws JMSException
+ {
+ checkTransacted();
+
+ try
+ {
+ //Check that we are clean to commit.
+ if (_failedOverDirty)
+ {
+ rollback();
+
+ throw new TransactionRolledBackException("Connection failover has occured since last send. " +
+ "Forced rollback");
+ }
+
+
+ // Acknowledge all delivered messages
+ while (true)
+ {
+ Long tag = _deliveredMessageTags.poll();
+ if (tag == null)
+ {
+ break;
+ }
+
+ acknowledgeMessage(tag, false);
+ }
+ // Commits outstanding messages and acknowledgments
+ sendCommit();
+ markClean();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("Failed to commit: " + e.getMessage() + ":" + e.getCause(), e);
+ }
+ catch (FailoverException e)
+ {
+ throw new JMSAMQException("Fail-over interrupted commit. Status of the commit is uncertain.", e);
+ }
+ }
+
+ public abstract void sendCommit() throws AMQException, FailoverException;
+
+
+ public void confirmConsumerCancelled(int consumerTag)
+ {
+
+ // Remove the consumer from the map
+ C consumer = _consumers.get(consumerTag);
+ if (consumer != null)
+ {
+ if (!consumer.isNoConsume()) // Normal Consumer
+ {
+ // Clean the Maps up first
+ // Flush any pending messages for this consumerTag
+ if (_dispatcher != null)
+ {
+ _logger.info("Dispatcher is not null");
+ }
+ else
+ {
+ _logger.info("Dispatcher is null so created stopped dispatcher");
+ startDispatcherIfNecessary(true);
+ }
+
+ _dispatcher.rejectPending(consumer);
+ }
+ else // Queue Browser
+ {
+ // Just close the consumer
+ // fixme the CancelOK is being processed before the arriving messages..
+ // The dispatcher is still to process them so the server sent in order but the client
+ // 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
+ {
+ _queue.add(new CloseConsumerMessage(consumer));
+ }
+ }
+ }
+ }
+ }
+
+ public QueueBrowser createBrowser(Queue queue) throws JMSException
+ {
+ if (isStrictAMQP())
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ return createBrowser(queue, null);
+ }
+
+ public QueueBrowser createBrowser(Queue queue, String messageSelector) throws JMSException
+ {
+ if (isStrictAMQP())
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ checkNotClosed();
+ checkValidQueue(queue);
+
+ return new AMQQueueBrowser(this, (AMQQueue) queue, messageSelector);
+ }
+
+ public MessageConsumer createBrowserConsumer(Destination destination, String messageSelector, boolean noLocal)
+ throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, noLocal, false,
+ messageSelector, null, true, true);
+ }
+
+ public MessageConsumer createConsumer(Destination destination) throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, false, (destination instanceof Topic), null, null,
+ ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public C createExclusiveConsumer(Destination destination) throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, false, true, null, null,
+ ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String messageSelector) throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, false, (destination instanceof Topic),
+ messageSelector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal)
+ throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, noLocal, (destination instanceof Topic),
+ messageSelector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public MessageConsumer createExclusiveConsumer(Destination destination, String messageSelector, boolean noLocal)
+ throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, _prefetchHighMark, _prefetchLowMark, 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 / 2, noLocal, exclusive, selector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal,
+ boolean exclusive, String selector) throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, null, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, int prefetch, boolean noLocal, boolean exclusive,
+ String selector, FieldTable rawSelector) throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, prefetch, prefetch / 2, noLocal, exclusive, selector, rawSelector, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()), false);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, int prefetchHigh, int prefetchLow, boolean noLocal,
+ boolean exclusive, String selector, FieldTable rawSelector) throws JMSException
+ {
+ checkValidDestination(destination);
+
+ return createConsumerImpl(destination, prefetchHigh, prefetchLow, noLocal, exclusive, selector, rawSelector, ((destination instanceof AMQDestination) && ((AMQDestination)destination).isBrowseOnly()),
+ false);
+ }
+
+ public TopicSubscriber createDurableSubscriber(Topic topic, String name) throws JMSException
+ {
+ // Delegate the work to the {@link #createDurableSubscriber(Topic, String, String, boolean)} method
+ return createDurableSubscriber(topic, name, null, false);
+ }
+
+ public TopicSubscriber createDurableSubscriber(Topic topic, String name, String selector, boolean noLocal)
+ throws JMSException
+ {
+ checkNotClosed();
+ Topic origTopic = checkValidTopic(topic, true);
+
+ AMQTopic dest = AMQTopic.createDurableTopic(origTopic, name, _connection);
+ if (dest.getDestSyntax() == DestSyntax.ADDR &&
+ !dest.isAddressResolved())
+ {
+ try
+ {
+ handleAddressBasedDestination(dest,false,true);
+ if (dest.getAddressType() != AMQDestination.TOPIC_TYPE)
+ {
+ throw new JMSException("Durable subscribers can only be created for Topics");
+ }
+ dest.getSourceNode().setDurable(true);
+ }
+ catch(AMQException e)
+ {
+ JMSException ex = new JMSException("Error when verifying destination");
+ ex.initCause(e);
+ ex.setLinkedException(e);
+ throw ex;
+ }
+ }
+
+ String messageSelector = ((selector == null) || (selector.trim().length() == 0)) ? null : selector;
+
+ _subscriberDetails.lock();
+ try
+ {
+ TopicSubscriberAdaptor<C> subscriber = _subscriptions.get(name);
+
+ // Not subscribed to this name in the current session
+ if (subscriber == null)
+ {
+ // After the address is resolved routing key will not be null.
+ AMQShortString topicName = dest.getRoutingKey();
+
+ 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
+ {
+ Map<String,Object> args = new HashMap<String,Object>();
+
+ // We must always send the selector argument even if empty, so that we can tell when a selector is removed from a
+ // durable topic subscription that the broker arguments don't match any more. This is because it is not otherwise
+ // possible to determine when querying the broker whether there are no arguments or just a non-matching selector
+ // argument, as specifying null for the arguments when querying means they should not be checked at all
+ args.put(AMQPFilterTypes.JMS_SELECTOR.getValue().toString(), messageSelector == null ? "" : messageSelector);
+
+ // if the queue is bound to the exchange but NOT for this topic and selector, then the JMS spec
+ // says we must trash the subscription.
+ boolean isQueueBound = isQueueBound(dest.getExchangeName(), dest.getAMQQueueName());
+ boolean isQueueBoundForTopicAndSelector =
+ isQueueBound(dest.getExchangeName().asString(), dest.getAMQQueueName().asString(), topicName.asString(), args);
+
+ if (isQueueBound && !isQueueBoundForTopicAndSelector)
+ {
+ deleteQueue(dest.getAMQQueueName());
+ }
+ }
+ }
+ else
+ {
+ // Subscribed with the same topic and no current / previous or same selector
+ if (subscriber.getTopic().equals(topic)
+ && ((messageSelector == null && subscriber.getMessageSelector() == null)
+ || (messageSelector != null && messageSelector.equals(subscriber.getMessageSelector()))))
+ {
+ throw new IllegalStateException("Already subscribed to topic " + topic + " with subscription name " + name
+ + (messageSelector != null ? " and selector " + messageSelector : ""));
+ }
+ else
+ {
+ unsubscribe(name, true);
+ }
+
+ }
+
+ _subscriberAccess.lock();
+ try
+ {
+ C consumer = (C) createConsumer(dest, messageSelector, noLocal);
+ subscriber = new TopicSubscriberAdaptor<C>(dest, consumer);
+
+ // Save subscription information
+ _subscriptions.put(name, subscriber);
+ _reverseSubscriptionMap.put(subscriber.getMessageConsumer(), name);
+ }
+ finally
+ {
+ _subscriberAccess.unlock();
+ }
+
+ return subscriber;
+ }
+ finally
+ {
+ _subscriberDetails.unlock();
+ }
+ }
+
+ public MapMessage createMapMessage() throws JMSException
+ {
+ checkNotClosed();
+ if (USE_AMQP_ENCODED_MAP_MESSAGE)
+ {
+ AMQPEncodedMapMessage msg = new AMQPEncodedMapMessage(getMessageDelegateFactory());
+ msg.setAMQSession(this);
+ return msg;
+ }
+ else
+ {
+ JMSMapMessage msg = new JMSMapMessage(getMessageDelegateFactory());
+ msg.setAMQSession(this);
+ return msg;
+ }
+ }
+
+ public javax.jms.Message createMessage() throws JMSException
+ {
+ return createBytesMessage();
+ }
+
+ public ObjectMessage createObjectMessage() throws JMSException
+ {
+ checkNotClosed();
+ JMSObjectMessage msg = new JMSObjectMessage(getMessageDelegateFactory());
+ msg.setAMQSession(this);
+ return msg;
+ }
+
+ public ObjectMessage createObjectMessage(Serializable object) throws JMSException
+ {
+ ObjectMessage msg = createObjectMessage();
+ msg.setObject(object);
+
+ return msg;
+ }
+
+ public P createProducer(Destination destination) throws JMSException
+ {
+ return createProducerImpl(destination, DEFAULT_MANDATORY, DEFAULT_IMMEDIATE);
+ }
+
+ public P createProducer(Destination destination, boolean immediate) throws JMSException
+ {
+ return createProducerImpl(destination, DEFAULT_MANDATORY, immediate);
+ }
+
+ public P createProducer(Destination destination, boolean mandatory, boolean immediate)
+ throws JMSException
+ {
+ return createProducerImpl(destination, mandatory, immediate);
+ }
+
+ public P createProducer(Destination destination, boolean mandatory, boolean immediate,
+ boolean waitUntilSent) throws JMSException
+ {
+ return createProducerImpl(destination, mandatory, immediate, waitUntilSent);
+ }
+
+ public TopicPublisher createPublisher(Topic topic) throws JMSException
+ {
+ checkNotClosed();
+
+ return new TopicPublisherAdapter((P) createProducer(topic, false, false), topic);
+ }
+
+ public Queue createQueue(String queueName) throws JMSException
+ {
+ checkNotClosed();
+ try
+ {
+ if (queueName.indexOf('/') == -1 && queueName.indexOf(';') == -1)
+ {
+ DestSyntax syntax = AMQDestination.getDestType(queueName);
+ if (syntax == AMQDestination.DestSyntax.BURL)
+ {
+ // For testing we may want to use the prefix
+ return new AMQQueue(getDefaultQueueExchangeName(),
+ new AMQShortString(AMQDestination.stripSyntaxPrefix(queueName)));
+ }
+ else
+ {
+ AMQQueue queue = new AMQQueue(queueName);
+ return queue;
+
+ }
+ }
+ else
+ {
+ return new AMQQueue(queueName);
+ }
+ }
+ catch (URISyntaxException urlse)
+ {
+ _logger.error("", urlse);
+ JMSException jmse = new JMSException(urlse.getReason());
+ jmse.setLinkedException(urlse);
+ jmse.initCause(urlse);
+ throw jmse;
+ }
+
+ }
+
+ /**
+ * Declares the named queue.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param name The name of the queue to declare.
+ * @param autoDelete
+ * @param durable Flag to indicate that the queue is durable.
+ * @param exclusive Flag to indicate that the queue is exclusive to this client.
+ *
+ * @throws AMQException If the queue cannot be declared for any reason.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ public void createQueue(final AMQShortString name, final boolean autoDelete, final boolean durable,
+ final boolean exclusive) throws AMQException
+ {
+ createQueue(name, autoDelete, durable, exclusive, null);
+ }
+
+ /**
+ * Declares the named queue.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param name The name of the queue to declare.
+ * @param autoDelete
+ * @param durable Flag to indicate that the queue is durable.
+ * @param exclusive Flag to indicate that the queue is exclusive to this client.
+ * @param arguments Arguments used to set special properties of the queue
+ *
+ * @throws AMQException If the queue cannot be declared for any reason.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ public void createQueue(final AMQShortString name, final boolean autoDelete, final boolean durable,
+ final boolean exclusive, final Map<String, Object> arguments) throws AMQException
+ {
+ new FailoverRetrySupport<Object, AMQException>(new FailoverProtectedOperation<Object, AMQException>()
+ {
+ public Object execute() throws AMQException, FailoverException
+ {
+ sendCreateQueue(name, autoDelete, durable, exclusive, arguments);
+ return null;
+ }
+ }, _connection).execute();
+ }
+
+ public abstract void sendCreateQueue(AMQShortString name, final boolean autoDelete, final boolean durable,
+ final boolean exclusive, final Map<String, Object> arguments) throws AMQException, FailoverException;
+
+ /**
+ * Creates a QueueReceiver
+ *
+ * @param destination
+ *
+ * @return QueueReceiver - a wrapper around our MessageConsumer
+ *
+ * @throws JMSException
+ */
+ public QueueReceiver createQueueReceiver(Destination destination) throws JMSException
+ {
+ checkValidDestination(destination);
+ Queue dest = validateQueue(destination);
+ C consumer = (C) createConsumer(dest);
+
+ return new QueueReceiverAdaptor(dest, consumer);
+ }
+
+ /**
+ * Creates a QueueReceiver using a message selector
+ *
+ * @param destination
+ * @param messageSelector
+ *
+ * @return QueueReceiver - a wrapper around our MessageConsumer
+ *
+ * @throws JMSException
+ */
+ public QueueReceiver createQueueReceiver(Destination destination, String messageSelector) throws JMSException
+ {
+ checkValidDestination(destination);
+ Queue dest = validateQueue(destination);
+ C consumer = (C) createConsumer(dest, messageSelector);
+
+ return new QueueReceiverAdaptor(dest, consumer);
+ }
+
+ /**
+ * Creates a QueueReceiver wrapping a MessageConsumer
+ *
+ * @param queue
+ *
+ * @return QueueReceiver
+ *
+ * @throws JMSException
+ */
+ public QueueReceiver createReceiver(Queue queue) throws JMSException
+ {
+ checkNotClosed();
+ Queue dest = validateQueue(queue);
+ C consumer = (C) createConsumer(dest);
+
+ return new QueueReceiverAdaptor(dest, consumer);
+ }
+
+ /**
+ * Creates a QueueReceiver wrapping a MessageConsumer using a message selector
+ *
+ * @param queue
+ * @param messageSelector
+ *
+ * @return QueueReceiver
+ *
+ * @throws JMSException
+ */
+ public QueueReceiver createReceiver(Queue queue, String messageSelector) throws JMSException
+ {
+ checkNotClosed();
+ Queue dest = validateQueue(queue);
+ C consumer = (C) createConsumer(dest, messageSelector);
+
+ return new QueueReceiverAdaptor(dest, consumer);
+ }
+
+ private Queue validateQueue(Destination dest) throws InvalidDestinationException
+ {
+ if (dest instanceof AMQDestination && dest instanceof javax.jms.Queue)
+ {
+ return (Queue)dest;
+ }
+ else
+ {
+ throw new InvalidDestinationException("The destination object used is not from this provider or of type javax.jms.Queue");
+ }
+ }
+
+ public QueueSender createSender(Queue queue) throws JMSException
+ {
+ checkNotClosed();
+
+ // return (QueueSender) createProducer(queue);
+ return new QueueSenderAdapter(createProducer(queue), queue);
+ }
+
+ public StreamMessage createStreamMessage() throws JMSException
+ {
+ // 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
+
+ synchronized (getFailoverMutex())
+ {
+ checkNotClosed();
+
+ JMSStreamMessage msg = new JMSStreamMessage(getMessageDelegateFactory());
+ msg.setAMQSession(this);
+ return msg;
+ }
+ }
+
+ /**
+ * Creates a non-durable subscriber
+ *
+ * @param topic
+ *
+ * @return TopicSubscriber - a wrapper round our MessageConsumer
+ *
+ * @throws JMSException
+ */
+ public TopicSubscriber createSubscriber(Topic topic) throws JMSException
+ {
+ checkNotClosed();
+ Topic dest = checkValidTopic(topic);
+
+ // AMQTopic dest = new AMQTopic(topic.getTopicName());
+ return new TopicSubscriberAdaptor(dest, (C) createExclusiveConsumer(dest));
+ }
+
+ /**
+ * Creates a non-durable subscriber with a message selector
+ *
+ * @param topic
+ * @param messageSelector
+ * @param noLocal
+ *
+ * @return TopicSubscriber - a wrapper round our MessageConsumer
+ *
+ * @throws JMSException
+ */
+ public TopicSubscriber createSubscriber(Topic topic, String messageSelector, boolean noLocal) throws JMSException
+ {
+ checkNotClosed();
+ Topic dest = checkValidTopic(topic);
+
+ // AMQTopic dest = new AMQTopic(topic.getTopicName());
+ return new TopicSubscriberAdaptor(dest, (C) createExclusiveConsumer(dest, messageSelector, noLocal));
+ }
+
+ public TemporaryQueue createTemporaryQueue() throws JMSException
+ {
+ checkNotClosed();
+ try
+ {
+ AMQTemporaryQueue result = new AMQTemporaryQueue(this);
+
+ // this is done so that we can produce to a temporary queue before we create a consumer
+ result.setQueueName(result.getRoutingKey());
+ createQueue(result.getAMQQueueName(), result.isAutoDelete(),
+ result.isDurable(), result.isExclusive());
+ bindQueue(result.getAMQQueueName(), result.getRoutingKey(),
+ new FieldTable(), result.getExchangeName(), result);
+ return result;
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Cannot create temporary queue");
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ public TemporaryTopic createTemporaryTopic() throws JMSException
+ {
+ checkNotClosed();
+
+ return new AMQTemporaryTopic(this);
+ }
+
+ public TextMessage createTextMessage() throws JMSException
+ {
+ synchronized (getFailoverMutex())
+ {
+ checkNotClosed();
+
+ JMSTextMessage msg = new JMSTextMessage(getMessageDelegateFactory());
+ msg.setAMQSession(this);
+ return msg;
+ }
+ }
+
+ protected Object getFailoverMutex()
+ {
+ return _connection.getFailoverMutex();
+ }
+
+ public TextMessage createTextMessage(String text) throws JMSException
+ {
+
+ TextMessage msg = createTextMessage();
+ msg.setText(text);
+
+ return msg;
+ }
+
+ public Topic createTopic(String topicName) throws JMSException
+ {
+ checkNotClosed();
+ try
+ {
+ if (topicName.indexOf('/') == -1 && topicName.indexOf(';') == -1)
+ {
+ DestSyntax syntax = AMQDestination.getDestType(topicName);
+ // for testing we may want to use the prefix to indicate our choice.
+ topicName = AMQDestination.stripSyntaxPrefix(topicName);
+ if (syntax == AMQDestination.DestSyntax.BURL)
+ {
+ return new AMQTopic(getDefaultTopicExchangeName(), new AMQShortString(topicName));
+ }
+ else
+ {
+ return new AMQTopic("ADDR:" + getDefaultTopicExchangeName() + "/" + topicName);
+ }
+ }
+ else
+ {
+ return new AMQTopic(topicName);
+ }
+
+ }
+ catch (URISyntaxException urlse)
+ {
+ _logger.error("", urlse);
+ JMSException jmse = new JMSException(urlse.getReason());
+ jmse.setLinkedException(urlse);
+ jmse.initCause(urlse);
+ throw jmse;
+ }
+ }
+
+ public void declareExchange(AMQShortString name, AMQShortString type, boolean nowait) throws AMQException
+ {
+ declareExchange(name, type, getProtocolHandler(), nowait);
+ }
+
+ abstract public void sync() throws AMQException;
+
+ public int getAcknowledgeMode() throws JMSException
+ {
+ checkNotClosed();
+
+ return _acknowledgeMode;
+ }
+
+ public AMQConnection getAMQConnection()
+ {
+ return _connection;
+ }
+
+ public int getChannelId()
+ {
+ return _channelId;
+ }
+
+ public int getDefaultPrefetch()
+ {
+ return _prefetchHighMark;
+ }
+
+ public int getDefaultPrefetchHigh()
+ {
+ return _prefetchHighMark;
+ }
+
+ public int getDefaultPrefetchLow()
+ {
+ return _prefetchLowMark;
+ }
+
+ public AMQShortString getDefaultQueueExchangeName()
+ {
+ return _connection.getDefaultQueueExchangeName();
+ }
+
+ public AMQShortString getDefaultTopicExchangeName()
+ {
+ return _connection.getDefaultTopicExchangeName();
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ // checkNotClosed();
+ return _messageListener;
+ }
+
+ public AMQShortString getTemporaryQueueExchangeName()
+ {
+ return _connection.getTemporaryQueueExchangeName();
+ }
+
+ public AMQShortString getTemporaryTopicExchangeName()
+ {
+ return _connection.getTemporaryTopicExchangeName();
+ }
+
+ public int getTicket()
+ {
+ return _ticket;
+ }
+
+ public boolean getTransacted() throws JMSException
+ {
+ checkNotClosed();
+
+ return _transacted;
+ }
+
+ public boolean hasConsumer(Destination destination)
+ {
+ AtomicInteger counter = _destinationConsumerCount.get(destination);
+
+ return (counter != null) && (counter.get() != 0);
+ }
+
+ public boolean isStrictAMQP()
+ {
+ return _strictAMQP;
+ }
+
+ public boolean isSuspended()
+ {
+ return _suspended;
+ }
+
+ protected void addUnacknowledgedMessage(long id)
+ {
+ _unacknowledgedMessageTags.add(id);
+ }
+
+ protected void addDeliveredMessage(long id)
+ {
+ _deliveredMessageTags.add(id);
+ }
+
+ /**
+ * Invoked by the MINA IO thread (indirectly) when a message is received from the transport. Puts the message onto
+ * the queue read by the dispatcher.
+ *
+ * @param message the message that has been received
+ */
+ public void messageReceived(UnprocessedMessage message)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Message[" + message.toString() + "] received in session");
+ }
+ _highestDeliveryTag.set(message.getDeliveryTag());
+ _queue.add(message);
+ }
+
+ public void declareAndBind(AMQDestination amqd)
+ throws
+ AMQException
+ {
+ AMQProtocolHandler protocolHandler = getProtocolHandler();
+ declareExchange(amqd, protocolHandler, false);
+ AMQShortString queueName = declareQueue(amqd, protocolHandler, false);
+ bindQueue(queueName, amqd.getRoutingKey(), new FieldTable(), amqd.getExchangeName(), amqd);
+ }
+
+ /**
+ * Stops message delivery in this session, and restarts message delivery with the oldest unacknowledged message.
+ *
+ * <p/>All consumers deliver messages in a serial order. Acknowledging a received message automatically acknowledges
+ * all messages that have been delivered to the client.
+ *
+ * <p/>Restarting a session causes it to take the following actions:
+ *
+ * <ul>
+ * <li>Stop message delivery.</li>
+ * <li>Mark all messages that might have been delivered but not acknowledged as "redelivered".
+ * <li>Restart the delivery sequence including all unacknowledged messages that had been previously delivered.
+ * Redelivered messages do not have to be delivered in exactly their original delivery order.</li>
+ * </ul>
+ *
+ * <p/>If the recover operation is interrupted by a fail-over, between asking that the broker begin recovery and
+ * receiving acknowledgment that it has then a JMSException will be thrown. In this case it will not be possible
+ * for the client to determine whether the broker is going to recover the session or not.
+ *
+ * @throws JMSException If the JMS provider fails to stop and restart message delivery due to some internal error.
+ * Not that this does not necessarily mean that the recovery has failed, but simply that it is
+ * not possible to tell if it has or not.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ *
+ * Strategy for handling recover.
+ * Flush any acks not yet sent.
+ * Stop the message flow.
+ * Clear the dispatch queue and the consumer queues.
+ * Release/Reject all messages received but not yet acknowledged.
+ * Start the message flow.
+ */
+ public void recover() throws JMSException
+ {
+ // Ensure that the session is open.
+ checkNotClosed();
+
+ // Ensure that the session is not transacted.
+ checkNotTransacted();
+
+ // flush any acks we are holding in the buffer.
+ flushAcknowledgments();
+
+ // this is set only here, and the before the consumer's onMessage is called it is set to false
+ _inRecovery = true;
+ try
+ {
+
+ boolean isSuspended = isSuspended();
+
+ if (!isSuspended)
+ {
+ suspendChannel(true);
+ }
+
+ syncDispatchQueue();
+
+ if (_dispatcher != null)
+ {
+ _dispatcher.recover();
+ }
+
+ sendRecover();
+
+ markClean();
+
+ // Set inRecovery to false before you start message flow again again.
+ _inRecovery = false;
+
+ if (!isSuspended)
+ {
+ suspendChannel(false);
+ }
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("Recover failed: " + e.getMessage(), e);
+ }
+ catch (FailoverException e)
+ {
+ throw new JMSAMQException("Recovery was interrupted by fail-over. Recovery status is not known.", e);
+ }
+
+ }
+
+ protected abstract void sendRecover() throws AMQException, FailoverException;
+
+ protected abstract void flushAcknowledgments();
+
+ public void rejectMessage(UnprocessedMessage message, boolean requeue)
+ {
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejecting Unacked message:" + message.getDeliveryTag());
+ }
+
+ rejectMessage(message.getDeliveryTag(), requeue);
+ }
+
+ public void rejectMessage(AbstractJMSMessage message, boolean requeue)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejecting Abstract message:" + message.getDeliveryTag());
+ }
+
+ rejectMessage(message.getDeliveryTag(), requeue);
+
+ }
+
+ public abstract void rejectMessage(long deliveryTag, boolean requeue);
+
+ /**
+ * Commits all messages done in this transaction and releases any locks currently held.
+ *
+ * <p/>If the rollback fails, because the rollback itself is interrupted by a fail-over between requesting that the
+ * rollback be done, and receiving an acknowledgement that it has been done, then a JMSException will be thrown.
+ * The client will be unable to determine whether or not the rollback actually happened on the broker in this case.
+ *
+ * @throws JMSException If the JMS provider fails to rollback the transaction due to some internal error. This does
+ * not mean that the rollback is known to have failed, merely that it is not known whether it
+ * failed or not.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ public void rollback() throws JMSException
+ {
+ synchronized (_suspensionLock)
+ {
+ checkTransacted();
+
+ try
+ {
+ boolean isSuspended = isSuspended();
+
+ if (!isSuspended)
+ {
+ suspendChannel(true);
+ }
+
+ // Let the dispatcher know that all the incomming messages
+ // should be rolled back(reject/release)
+ _rollbackMark.set(_highestDeliveryTag.get());
+
+ syncDispatchQueue();
+
+ _dispatcher.rollback();
+
+ releaseForRollback();
+
+ sendRollback();
+
+ markClean();
+
+ if (!isSuspended)
+ {
+ suspendChannel(false);
+ }
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("Failed to rollback: " + e, e);
+ }
+ catch (FailoverException e)
+ {
+ throw new JMSAMQException("Fail-over interrupted rollback. Status of the rollback is uncertain.", e);
+ }
+ }
+ }
+
+ public abstract void releaseForRollback();
+
+ public abstract void sendRollback() throws AMQException, FailoverException;
+
+ public void run()
+ {
+ throw new java.lang.UnsupportedOperationException();
+ }
+
+ public void setMessageListener(MessageListener listener) throws JMSException
+ {
+ // checkNotClosed();
+ //
+ // if (_dispatcher != null && !_dispatcher.connectionStopped())
+ // {
+ // throw new javax.njms.IllegalStateException("Attempt to set listener while session is started.");
+ // }
+ //
+ // // We are stopped
+ // for (Iterator<BasicMessageConsumer> i = _consumers.values().iterator(); i.hasNext();)
+ // {
+ // BasicMessageConsumer consumer = i.next();
+ //
+ // if (consumer.isReceiving())
+ // {
+ // throw new javax.njms.IllegalStateException("Another thread is already receiving synchronously.");
+ // }
+ // }
+ //
+ // _messageListener = listener;
+ //
+ // for (Iterator<BasicMessageConsumer> i = _consumers.values().iterator(); i.hasNext();)
+ // {
+ // i.next().setMessageListener(_messageListener);
+ // }
+
+ }
+
+ /**
+ * @see #unsubscribe(String, boolean)
+ */
+ public void unsubscribe(String name) throws JMSException
+ {
+ unsubscribe(name, false);
+ }
+
+ /**
+ * Unsubscribe from a subscription.
+ *
+ * @param name the name of the subscription to unsubscribe
+ * @param safe allows safe unsubscribe operation that will not throw an {@link InvalidDestinationException} if the
+ * queue is not bound, possibly due to the subscription being closed.
+ * @throws JMSException on
+ * @throws InvalidDestinationException
+ */
+ private void unsubscribe(String name, boolean safe) throws JMSException
+ {
+ TopicSubscriberAdaptor<C> subscriber;
+
+ _subscriberDetails.lock();
+ try
+ {
+ checkNotClosed();
+ subscriber = _subscriptions.get(name);
+ if (subscriber != null)
+ {
+ // Remove saved subscription information
+ _subscriptions.remove(name);
+ _reverseSubscriptionMap.remove(subscriber.getMessageConsumer());
+ }
+ }
+ finally
+ {
+ _subscriberDetails.unlock();
+ }
+
+ if (subscriber != null)
+ {
+ subscriber.close();
+
+ // send a queue.delete for the subscription
+ deleteQueue(AMQTopic.getDurableTopicQueueName(name, _connection));
+ }
+ else
+ {
+ 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 '" + name + "' for unsubscribe."
+ + " Requesting queue deletion regardless.");
+ }
+
+ deleteQueue(AMQTopic.getDurableTopicQueueName(name, _connection));
+ }
+ else // Queue Browser
+ {
+
+ if (isQueueBound(getDefaultTopicExchangeName(), AMQTopic.getDurableTopicQueueName(name, _connection)))
+ {
+ deleteQueue(AMQTopic.getDurableTopicQueueName(name, _connection));
+ }
+ else if (!safe)
+ {
+ throw new InvalidDestinationException("Unknown subscription name: " + name);
+ }
+ }
+ }
+ }
+
+ protected C createConsumerImpl(final Destination destination, final int prefetchHigh,
+ final int prefetchLow, final boolean noLocal, final boolean exclusive, String selector, final FieldTable rawSelector,
+ final boolean noConsume, final boolean autoClose) throws JMSException
+ {
+ checkTemporaryDestination(destination);
+
+ final String messageSelector;
+
+ if (_strictAMQP && !((selector == null) || selector.equals("")))
+ {
+ if (_strictAMQPFATAL)
+ {
+ throw new UnsupportedOperationException("Selectors not currently supported by AMQP.");
+ }
+ else
+ {
+ messageSelector = null;
+ }
+ }
+ else
+ {
+ messageSelector = selector;
+ }
+
+ return new FailoverRetrySupport<C, JMSException>(
+ new FailoverProtectedOperation<C, JMSException>()
+ {
+ public C execute() throws JMSException, FailoverException
+ {
+ checkNotClosed();
+
+ AMQDestination amqd = (AMQDestination) destination;
+
+ // TODO: Define selectors in AMQP
+ // TODO: construct the rawSelector from the selector string if rawSelector == null
+ final FieldTable ft = FieldTableFactory.newFieldTable();
+ // if (rawSelector != null)
+ // ft.put("headers", rawSelector.getDataAsBytes());
+ // rawSelector is used by HeadersExchange and is not a JMS Selector
+ if (rawSelector != null)
+ {
+ ft.addAll(rawSelector);
+ }
+
+ // We must always send the selector argument even if empty, so that we can tell when a selector is removed from a
+ // durable topic subscription that the broker arguments don't match any more. This is because it is not otherwise
+ // possible to determine when querying the broker whether there are no arguments or just a non-matching selector
+ // argument, as specifying null for the arguments when querying means they should not be checked at all
+ ft.put(AMQPFilterTypes.JMS_SELECTOR.getValue(), messageSelector == null ? "" : messageSelector);
+
+ C consumer = createMessageConsumer(amqd, prefetchHigh, prefetchLow,
+ noLocal, exclusive, messageSelector, ft, noConsume, autoClose);
+
+ if (_messageListener != null)
+ {
+ consumer.setMessageListener(_messageListener);
+ }
+
+ try
+ {
+ registerConsumer(consumer, false);
+ }
+ catch (AMQInvalidArgumentException ise)
+ {
+ JMSException jmse = new InvalidSelectorException(ise.getMessage());
+ jmse.setLinkedException(ise);
+ jmse.initCause(ise);
+ throw jmse;
+ }
+ catch (AMQInvalidRoutingKeyException e)
+ {
+ JMSException jmse = new InvalidDestinationException("Invalid routing key:" + amqd.getRoutingKey().toString());
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ catch (AMQException e)
+ {
+ if (e instanceof AMQChannelClosedException)
+ {
+ close(-1, false);
+ }
+
+ JMSException ex = new JMSException("Error registering consumer: " + e);
+ ex.setLinkedException(e);
+ ex.initCause(e);
+ throw ex;
+ }
+
+ return consumer;
+ }
+ }, _connection).execute();
+ }
+
+ public abstract C createMessageConsumer(final AMQDestination destination, final int prefetchHigh,
+ final int prefetchLow, final boolean noLocal, final boolean exclusive, String selector, final FieldTable arguments,
+ final boolean noConsume, final boolean autoClose) throws JMSException;
+
+ /**
+ * Called by the MessageConsumer when closing, to deregister the consumer from the map from consumerTag to consumer
+ * instance.
+ *
+ * @param consumer the consum
+ */
+ void deregisterConsumer(C consumer)
+ {
+ if (_consumers.remove(consumer.getConsumerTag()) != null)
+ {
+ _subscriberAccess.lock();
+ try
+ {
+ String subscriptionName = _reverseSubscriptionMap.remove(consumer);
+ if (subscriptionName != null)
+ {
+ _subscriptions.remove(subscriptionName);
+ }
+ }
+ finally
+ {
+ _subscriberAccess.unlock();
+ }
+
+ Destination dest = consumer.getDestination();
+ synchronized (dest)
+ {
+ // Provide additional NPE check
+ // This would occur if the consumer was closed before it was
+ // fully opened.
+ if (_destinationConsumerCount.get(dest) != null)
+ {
+ if (_destinationConsumerCount.get(dest).decrementAndGet() == 0)
+ {
+ _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);
+ }
+ }
+ }
+
+ void deregisterProducer(long producerId)
+ {
+ _producers.remove(new Long(producerId));
+ }
+
+ boolean isInRecovery()
+ {
+ return _inRecovery;
+ }
+
+ boolean isQueueBound(AMQShortString exchangeName, AMQShortString queueName) throws JMSException
+ {
+ return isQueueBound(exchangeName, queueName, null);
+ }
+
+ /**
+ * Tests whether or not the specified queue is bound to the specified exchange under a particular routing key.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param exchangeName The exchange name to test for binding against.
+ * @param queueName The queue name to check if bound.
+ * @param routingKey The routing key to check if the queue is bound under.
+ *
+ * @return <tt>true</tt> if the queue is bound to the exchange and routing key, <tt>false</tt> if not.
+ *
+ * @throws JMSException If the query fails for any reason.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ public abstract boolean isQueueBound(final AMQShortString exchangeName, final AMQShortString queueName, final AMQShortString routingKey)
+ throws JMSException;
+
+ public abstract boolean isQueueBound(final AMQDestination destination) throws JMSException;
+
+ public abstract boolean isQueueBound(String exchangeName, String queueName, String bindingKey, Map<String,Object> args) throws JMSException;
+
+ /**
+ * Called to mark the session as being closed. Useful when the session needs to be made invalid, e.g. after failover
+ * when the client has veoted resubscription. <p/> The caller of this method must already hold the failover mutex.
+ */
+ void markClosed()
+ {
+ _closed.set(true);
+ _connection.deregisterSession(_channelId);
+ markClosedProducersAndConsumers();
+
+ }
+
+ void failoverPrep()
+ {
+ syncDispatchQueue();
+ }
+
+ void syncDispatchQueue()
+ {
+ if (Thread.currentThread() == _dispatcherThread)
+ {
+ while (!_closed.get() && !_queue.isEmpty())
+ {
+ Dispatchable disp;
+ try
+ {
+ disp = (Dispatchable) _queue.take();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ // Check just in case _queue becomes empty, it shouldn't but
+ // better than an NPE.
+ if (disp == null)
+ {
+ _logger.debug("_queue became empty during sync.");
+ break;
+ }
+
+ disp.dispatch(AMQSession.this);
+ }
+ }
+ else
+ {
+ startDispatcherIfNecessary();
+
+ final CountDownLatch signal = new CountDownLatch(1);
+
+ _queue.add(new Dispatchable()
+ {
+ public void dispatch(AMQSession ssn)
+ {
+ signal.countDown();
+ }
+ });
+
+ try
+ {
+ signal.await();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Resubscribes all producers and consumers. This is called when performing failover.
+ *
+ * @throws AMQException
+ */
+ void resubscribe() throws AMQException
+ {
+ if (_dirty)
+ {
+ _failedOverDirty = true;
+ }
+
+ _rollbackMark.set(-1);
+ resubscribeProducers();
+ resubscribeConsumers();
+ }
+
+ void setHasMessageListeners()
+ {
+ _hasMessageListeners = true;
+ }
+
+ void setInRecovery(boolean inRecovery)
+ {
+ _inRecovery = inRecovery;
+ }
+
+ boolean isStarted()
+ {
+ return _startedAtLeastOnce.get();
+ }
+
+ /**
+ * Starts the session, which ensures that it is not suspended and that its event dispatcher is running.
+ *
+ * @throws AMQException If the session cannot be started for any reason.
+ * @todo This should be controlled by _stopped as it pairs with the stop method fixme or check the
+ * FlowControlledBlockingQueue _queue to see if we have flow controlled. will result in sending Flow messages
+ * for each subsequent call to flow.. only need to do this if we have called stop.
+ */
+ void start() throws AMQException
+ {
+ // Check if the session has perviously been started and suspended, in which case it must be unsuspended.
+ if (_startedAtLeastOnce.getAndSet(true))
+ {
+ suspendChannel(false);
+ }
+
+ // If the event dispatcher is not running then start it too.
+ if (hasMessageListeners())
+ {
+ startDispatcherIfNecessary();
+ }
+ }
+
+ void startDispatcherIfNecessary()
+ {
+ //If we are the dispatcher then we don't need to check we are started
+ if (Thread.currentThread() == _dispatcherThread)
+ {
+ 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
+ if (isSuspended() && _startedAtLeastOnce.get() && _firstDispatcher.getAndSet(false))
+ {
+ try
+ {
+ suspendChannel(false);
+ }
+ catch (AMQException e)
+ {
+ _logger.info("Unsuspending channel threw an exception:" + e);
+ }
+ }
+ }
+
+ startDispatcherIfNecessary(false);
+ }
+
+ synchronized void startDispatcherIfNecessary(boolean initiallyStopped)
+ {
+ if (_dispatcher == null)
+ {
+ _dispatcher = new Dispatcher();
+ try
+ {
+ _dispatcherThread = Threading.getThreadFactory().createThread(_dispatcher);
+
+ }
+ catch(Exception e)
+ {
+ throw new Error("Error creating Dispatcher thread",e);
+ }
+ _dispatcherThread.setName("Dispatcher-Channel-" + _channelId);
+ _dispatcherThread.setDaemon(true);
+ _dispatcher.setConnectionStopped(initiallyStopped);
+ _dispatcherThread.start();
+ if (_dispatcherLogger.isInfoEnabled())
+ {
+ _dispatcherLogger.info(_dispatcherThread.getName() + " created");
+ }
+ }
+ else
+ {
+ _dispatcher.setConnectionStopped(initiallyStopped);
+ }
+ }
+
+ void stop() throws AMQException
+ {
+ // Stop the server delivering messages to this session.
+ suspendChannel(true);
+
+ if (_dispatcher != null)
+ {
+ _dispatcher.setConnectionStopped(true);
+ }
+ }
+
+ /*
+ * Binds the named queue, with the specified routing key, to the named exchange.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param queueName The name of the queue to bind.
+ * @param routingKey The routing key to bind the queue with.
+ * @param arguments Additional arguments.
+ * @param exchangeName The exchange to bind the queue on.
+ *
+ * @throws AMQException If the queue cannot be bound for any reason.
+ */
+ /*private void bindQueue(AMQDestination amqd, AMQShortString queueName, AMQProtocolHandler protocolHandler, FieldTable ft)
+ throws AMQException, FailoverException
+ {
+ AMQFrame queueBind =
+ QueueBindBody.createAMQFrame(_channelId, getProtocolMajorVersion(), getProtocolMinorVersion(), ft, // arguments
+ amqd.getExchangeName(), // exchange
+ false, // nowait
+ queueName, // queue
+ amqd.getRoutingKey(), // routingKey
+ getTicket()); // ticket
+
+ protocolHandler.syncWrite(queueBind, QueueBindOkBody.class);
+ }*/
+
+ private void checkNotTransacted() throws JMSException
+ {
+ if (getTransacted())
+ {
+ throw new IllegalStateException("Session is transacted");
+ }
+ }
+
+ private void checkTemporaryDestination(Destination destination) throws JMSException
+ {
+ if ((destination instanceof TemporaryDestination))
+ {
+ _logger.debug("destination is temporary");
+ final TemporaryDestination tempDest = (TemporaryDestination) destination;
+ if (tempDest.getSession() != this)
+ {
+ _logger.debug("destination is on different session");
+ throw new JMSException("Cannot consume from a temporary destination created on another session");
+ }
+
+ if (tempDest.isDeleted())
+ {
+ _logger.debug("destination is deleted");
+ throw new JMSException("Cannot consume from a deleted destination");
+ }
+ }
+ }
+
+ protected void checkTransacted() throws JMSException
+ {
+ if (!getTransacted())
+ {
+ throw new IllegalStateException("Session is not transacted");
+ }
+ }
+
+ private void checkValidDestination(Destination destination) throws InvalidDestinationException
+ {
+ if (destination == null)
+ {
+ throw new javax.jms.InvalidDestinationException("Invalid Queue");
+ }
+ }
+
+ private void checkValidQueue(Queue queue) throws InvalidDestinationException
+ {
+ if (queue == null)
+ {
+ throw new javax.jms.InvalidDestinationException("Invalid Queue");
+ }
+ }
+
+ /*
+ * I could have combined the last 3 methods, but this way it improves readability
+ */
+ protected Topic checkValidTopic(Topic topic, boolean durable) throws JMSException
+ {
+ if (topic == null)
+ {
+ throw new javax.jms.InvalidDestinationException("Invalid Topic");
+ }
+
+ if ((topic instanceof TemporaryDestination) && (((TemporaryDestination) topic).getSession() != this))
+ {
+ throw new javax.jms.InvalidDestinationException(
+ "Cannot create a subscription on a temporary topic created in another session");
+ }
+
+ if ((topic instanceof TemporaryDestination) && durable)
+ {
+ throw new javax.jms.InvalidDestinationException
+ ("Cannot create a durable subscription with a temporary topic: " + topic);
+ }
+
+ if (!(topic instanceof AMQDestination && topic instanceof javax.jms.Topic))
+ {
+ throw new javax.jms.InvalidDestinationException(
+ "Cannot create a subscription on topic created for another JMS Provider, class of topic provided is: "
+ + topic.getClass().getName());
+ }
+
+ return topic;
+ }
+
+ protected Topic checkValidTopic(Topic topic) throws JMSException
+ {
+ return checkValidTopic(topic, false);
+ }
+
+ /**
+ * Called to close message consumers cleanly. This may or may <b>not</b> be as a result of an error.
+ *
+ * @param error not null if this is a result of an error occurring at the connection level
+ */
+ private void closeConsumers(Throwable error) throws JMSException
+ {
+ // we need to clone the list of consumers since the close() method updates the _consumers collection
+ // which would result in a concurrent modification exception
+ final ArrayList<C> clonedConsumers = new ArrayList<C>(_consumers.values());
+
+ final Iterator<C> it = clonedConsumers.iterator();
+ while (it.hasNext())
+ {
+ final C con = it.next();
+ if (error != null)
+ {
+ con.notifyError(error);
+ }
+ else
+ {
+ con.close(false);
+ }
+ }
+ // at this point the _consumers map will be empty
+ if (_dispatcher != null)
+ {
+ _dispatcher.close();
+ _dispatcher = null;
+ }
+ }
+
+ /**
+ * Called to close message producers cleanly. This may or may <b>not</b> be as a result of an error. There is
+ * currently no way of propagating errors to message producers (this is a JMS limitation).
+ */
+ private void closeProducers() throws JMSException
+ {
+ // we need to clone the list of producers since the close() method updates the _producers collection
+ // which would result in a concurrent modification exception
+ final ArrayList clonedProducers = new ArrayList(_producers.values());
+
+ final Iterator it = clonedProducers.iterator();
+ while (it.hasNext())
+ {
+ final P prod = (P) it.next();
+ prod.close();
+ }
+ // at this point the _producers map is empty
+ }
+
+ /**
+ * Close all producers or consumers. This is called either in the error case or when closing the session normally.
+ *
+ * @param amqe the exception, may be null to indicate no error has occurred
+ */
+ private void closeProducersAndConsumers(AMQException amqe) throws JMSException
+ {
+ JMSException jmse = null;
+ try
+ {
+ closeProducers();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ jmse = e;
+ }
+
+ try
+ {
+ closeConsumers(amqe);
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ if (jmse == null)
+ {
+ jmse = e;
+ }
+ }
+
+ if (jmse != null)
+ {
+ throw jmse;
+ }
+ }
+
+ /**
+ * Register to consume from the queue.
+ *
+ * @param queueName
+ */
+ private void consumeFromQueue(C consumer, AMQShortString queueName,
+ AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector) throws AMQException, FailoverException
+ {
+ int tagId = _nextTag++;
+
+ consumer.setConsumerTag(tagId);
+ // we must register the consumer in the map before we actually start listening
+ _consumers.put(tagId, consumer);
+
+ synchronized (consumer.getDestination())
+ {
+ _destinationConsumerCount.putIfAbsent(consumer.getDestination(), new AtomicInteger());
+ _destinationConsumerCount.get(consumer.getDestination()).incrementAndGet();
+ }
+
+
+ try
+ {
+ sendConsume(consumer, queueName, protocolHandler, nowait, messageSelector, tagId);
+ }
+ catch (AMQException e)
+ {
+ // clean-up the map in the event of an error
+ _consumers.remove(tagId);
+ throw e;
+ }
+ }
+
+ public abstract void sendConsume(C consumer, AMQShortString queueName,
+ AMQProtocolHandler protocolHandler, boolean nowait, String messageSelector, int tag) throws AMQException, FailoverException;
+
+ private P createProducerImpl(Destination destination, boolean mandatory, boolean immediate)
+ throws JMSException
+ {
+ return createProducerImpl(destination, mandatory, immediate, DEFAULT_WAIT_ON_SEND);
+ }
+
+ private P createProducerImpl(final Destination destination, final boolean mandatory,
+ final boolean immediate, final boolean waitUntilSent) throws JMSException
+ {
+ return new FailoverRetrySupport<P, JMSException>(
+ new FailoverProtectedOperation<P, JMSException>()
+ {
+ public P execute() throws JMSException, FailoverException
+ {
+ checkNotClosed();
+ long producerId = getNextProducerId();
+ P producer = createMessageProducer(destination, mandatory,
+ immediate, waitUntilSent, producerId);
+ registerProducer(producerId, producer);
+
+ return producer;
+ }
+ }, _connection).execute();
+ }
+
+ public abstract P createMessageProducer(final Destination destination, final boolean mandatory,
+ final boolean immediate, final boolean waitUntilSent, long producerId) throws JMSException;
+
+ private void declareExchange(AMQDestination amqd, AMQProtocolHandler protocolHandler, boolean nowait) throws AMQException
+ {
+ declareExchange(amqd.getExchangeName(), amqd.getExchangeClass(), protocolHandler, nowait);
+ }
+
+ /**
+ * Returns the number of messages currently queued for the given destination.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param amqd The destination to be checked
+ *
+ * @return the number of queued messages.
+ *
+ * @throws AMQException If the queue cannot be declared for any reason.
+ */
+ public long getQueueDepth(final AMQDestination amqd)
+ throws AMQException
+ {
+ return new FailoverNoopSupport<Long, AMQException>(
+ new FailoverProtectedOperation<Long, AMQException>()
+ {
+ public Long execute() throws AMQException, FailoverException
+ {
+ return requestQueueDepth(amqd);
+ }
+ }, _connection).execute();
+
+ }
+
+ protected abstract Long requestQueueDepth(AMQDestination amqd) throws AMQException, FailoverException;
+
+ /**
+ * Declares the named exchange and type of exchange.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param name The name of the exchange to declare.
+ * @param type The type of the exchange to declare.
+ * @param protocolHandler The protocol handler to process the communication through.
+ * @param nowait
+ *
+ * @throws AMQException If the exchange cannot be declared for any reason.
+ * @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
+ {
+ new FailoverNoopSupport<Object, AMQException>(new FailoverProtectedOperation<Object, AMQException>()
+ {
+ public Object execute() throws AMQException, FailoverException
+ {
+ sendExchangeDeclare(name, type, protocolHandler, nowait);
+ return null;
+ }
+ }, _connection).execute();
+ }
+
+ public abstract void sendExchangeDeclare(final AMQShortString name, final AMQShortString type, final AMQProtocolHandler protocolHandler,
+ final boolean nowait) throws AMQException, FailoverException;
+
+ /**
+ * Declares a queue for a JMS destination.
+ *
+ * <p/>Note that for queues but not topics the name is generated in the client rather than the server. This allows
+ * the name to be reused on failover if required. In general, the destination indicates whether it wants a name
+ * generated or not.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param amqd The destination to declare as a queue.
+ * @param protocolHandler The protocol handler to communicate through.
+ *
+ * @return The name of the decalred queue. This is useful where the broker is generating a queue name on behalf of
+ * the client.
+ *
+ * @throws AMQException If the queue cannot be declared for any reason.
+ * @todo Verify the destiation is valid or throw an exception.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ protected AMQShortString declareQueue(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean noLocal) throws AMQException
+ {
+ return declareQueue(amqd, protocolHandler, noLocal, false);
+ }
+
+ protected AMQShortString declareQueue(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean noLocal, final boolean nowait)
+ throws AMQException
+ {
+ /*return new FailoverRetrySupport<AMQShortString, AMQException>(*/
+ return new FailoverNoopSupport<AMQShortString, AMQException>(
+ new FailoverProtectedOperation<AMQShortString, AMQException>()
+ {
+ public AMQShortString execute() throws AMQException, FailoverException
+ {
+ // Generate the queue name if the destination indicates that a client generated name is to be used.
+ if (amqd.isNameRequired())
+ {
+ amqd.setQueueName(protocolHandler.generateQueueName());
+ }
+
+ sendQueueDeclare(amqd, protocolHandler, nowait);
+
+ return amqd.getAMQQueueName();
+ }
+ }, _connection).execute();
+ }
+
+ public abstract void sendQueueDeclare(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean nowait) throws AMQException, FailoverException;
+
+ /**
+ * Undeclares the specified queue.
+ *
+ * <p/>Note that this operation automatically retries in the event of fail-over.
+ *
+ * @param queueName The name of the queue to delete.
+ *
+ * @throws JMSException If the queue could not be deleted for any reason.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ protected void deleteQueue(final AMQShortString queueName) throws JMSException
+ {
+ try
+ {
+ new FailoverRetrySupport<Object, AMQException>(new FailoverProtectedOperation<Object, AMQException>()
+ {
+ public Object execute() throws AMQException, FailoverException
+ {
+ sendQueueDelete(queueName);
+ return null;
+ }
+ }, _connection).execute();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("The queue deletion failed: " + e.getMessage(), e);
+ }
+ }
+
+ public abstract void sendQueueDelete(final AMQShortString queueName) throws AMQException, FailoverException;
+
+ private long getNextProducerId()
+ {
+ return ++_nextProducerId;
+ }
+
+ protected AMQProtocolHandler getProtocolHandler()
+ {
+ return _connection.getProtocolHandler();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolHandler().getProtocolMajorVersion();
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolHandler().getProtocolMinorVersion();
+ }
+
+ protected boolean hasMessageListeners()
+ {
+ return _hasMessageListeners;
+ }
+
+ private void markClosedConsumers() throws JMSException
+ {
+ if (_dispatcher != null)
+ {
+ _dispatcher.close();
+ _dispatcher = null;
+ }
+ // we need to clone the list of consumers since the close() method updates the _consumers collection
+ // which would result in a concurrent modification exception
+ final ArrayList<C> clonedConsumers = new ArrayList<C>(_consumers.values());
+
+ final Iterator<C> it = clonedConsumers.iterator();
+ while (it.hasNext())
+ {
+ final C con = it.next();
+ con.markClosed();
+ }
+ // at this point the _consumers map will be empty
+ }
+
+ private void markClosedProducersAndConsumers()
+ {
+ try
+ {
+ // no need for a markClosed* method in this case since there is no protocol traffic closing a producer
+ closeProducers();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ }
+
+ try
+ {
+ markClosedConsumers();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error closing session: " + e, e);
+ }
+ }
+
+ /**
+ * Callers must hold the failover mutex before calling this method.
+ *
+ * @param consumer
+ *
+ * @throws AMQException
+ */
+ private void registerConsumer(C consumer, boolean nowait) throws AMQException // , FailoverException
+ {
+ AMQDestination amqd = consumer.getDestination();
+
+ AMQProtocolHandler protocolHandler = getProtocolHandler();
+
+ if (amqd.getDestSyntax() == DestSyntax.ADDR)
+ {
+ handleAddressBasedDestination(amqd,true,nowait);
+ }
+ else
+ {
+ if (DECLARE_EXCHANGES)
+ {
+ declareExchange(amqd, protocolHandler, nowait);
+ }
+
+ if (DECLARE_QUEUES || amqd.isNameRequired())
+ {
+ declareQueue(amqd, protocolHandler, consumer.isNoLocal(), nowait);
+ }
+ bindQueue(amqd.getAMQQueueName(), amqd.getRoutingKey(), consumer.getArguments(), amqd.getExchangeName(), amqd, nowait);
+ }
+
+ AMQShortString queueName = amqd.getAMQQueueName();
+
+ // store the consumer queue name
+ consumer.setQueuename(queueName);
+
+ // If IMMEDIATE_PREFETCH is not required then suspsend the channel to delay prefetch
+ if (!_immediatePrefetch)
+ {
+ // The dispatcher will be null if we have just created this session
+ // so suspend the channel before we register our consumer so that we don't
+ // start prefetching until a receive/mListener is set.
+ if (_dispatcher == null)
+ {
+ if (!isSuspended())
+ {
+ try
+ {
+ suspendChannel(true);
+ _logger.info(
+ "Prefetching delayed existing messages will not flow until requested via receive*() or setML().");
+ }
+ catch (AMQException e)
+ {
+ _logger.info("Suspending channel threw an exception:" + e);
+ }
+ }
+ }
+ }
+ else
+ {
+ _logger.info("Immediately prefetching existing messages to new consumer.");
+ }
+
+ try
+ {
+ consumeFromQueue(consumer, queueName, protocolHandler, nowait, consumer._messageSelector);
+ }
+ catch (FailoverException e)
+ {
+ throw new AMQException(null, "Fail-over exception interrupted basic consume.", e);
+ }
+ }
+
+ public abstract void handleAddressBasedDestination(AMQDestination dest,
+ boolean isConsumer,
+ boolean noWait) throws AMQException;
+
+ private void registerProducer(long producerId, MessageProducer producer)
+ {
+ _producers.put(new Long(producerId), producer);
+ }
+
+ private void rejectAllMessages(boolean requeue)
+ {
+ rejectMessagesForConsumerTag(0, requeue, true);
+ }
+
+ /**
+ * @param consumerTag The consumerTag to prune from queue or all if null
+ * @param requeue Should the removed messages be requeued (or discarded. Possibly to DLQ)
+ * @param rejectAllConsumers
+ */
+
+ private void rejectMessagesForConsumerTag(int consumerTag, boolean requeue, boolean rejectAllConsumers)
+ {
+ Iterator messages = _queue.iterator();
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Rejecting messages from _queue for Consumer tag(" + consumerTag + ") (PDispatchQ) requeue:"
+ + requeue);
+
+ if (messages.hasNext())
+ {
+ _logger.info("Checking all messages in _queue for Consumer tag(" + consumerTag + ")");
+ }
+ else
+ {
+ _logger.info("No messages in _queue to reject");
+ }
+ }
+ while (messages.hasNext())
+ {
+ UnprocessedMessage message = (UnprocessedMessage) messages.next();
+
+ if (rejectAllConsumers || (message.getConsumerTag() == consumerTag))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Removing message(" + System.identityHashCode(message) + ") from _queue DT:"
+ + message.getDeliveryTag());
+ }
+
+ messages.remove();
+
+ rejectMessage(message, requeue);
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejected the message(" + message.toString() + ") for consumer :" + consumerTag);
+ }
+ }
+ }
+ }
+
+ private void resubscribeConsumers() throws AMQException
+ {
+ ArrayList<C> consumers = new ArrayList<C>(_consumers.values());
+ _consumers.clear();
+
+ for (C consumer : consumers)
+ {
+ consumer.failedOverPre();
+ registerConsumer(consumer, true);
+ consumer.failedOverPost();
+ }
+ }
+
+ private void resubscribeProducers() throws AMQException
+ {
+ ArrayList producers = new ArrayList(_producers.values());
+ _logger.info(MessageFormat.format("Resubscribing producers = {0} producers.size={1}", producers, producers.size())); // FIXME: removeKey
+ for (Iterator it = producers.iterator(); it.hasNext();)
+ {
+ P producer = (P) it.next();
+ producer.resubscribe();
+ }
+ }
+
+ /**
+ * Suspends or unsuspends this session.
+ *
+ * @param suspend <tt>true</tt> indicates that the session should be suspended, <tt>false<tt> indicates that it
+ * should be unsuspended.
+ *
+ * @throws AMQException If the session cannot be suspended for any reason.
+ * @todo Be aware of possible changes to parameter order as versions change.
+ */
+ protected void suspendChannel(boolean suspend) throws AMQException // , FailoverException
+ {
+ synchronized (_suspensionLock)
+ {
+ try
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Setting channel flow : " + (suspend ? "suspended" : "unsuspended"));
+ }
+
+ _suspended = suspend;
+ sendSuspendChannel(suspend);
+ }
+ catch (FailoverException e)
+ {
+ throw new AMQException(null, "Fail-over interrupted suspend/unsuspend channel.", e);
+ }
+ }
+ }
+
+ public abstract void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException;
+
+ Object getMessageDeliveryLock()
+ {
+ return _messageDeliveryLock;
+ }
+
+ /**
+ * Indicates whether this session consumers pre-fetche messages
+ *
+ * @return true if this session consumers pre-fetche messages false otherwise
+ */
+ public boolean prefetch()
+ {
+ return getAMQConnection().getMaxPrefetch() > 0;
+ }
+
+ /** 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;
+ _failedOverDirty = 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 _failedOverDirty;
+ }
+
+ /**
+ * 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);
+ _logger.warn("Broker enforced flow control " + (active ? "no longer in effect" : "has been enforced"));
+ }
+
+ public void checkFlowControl() throws InterruptedException, JMSException
+ {
+ long expiryTime = 0L;
+ synchronized (_flowControl)
+ {
+ while (!_flowControl.getFlowControl() &&
+ (expiryTime == 0L ? (expiryTime = System.currentTimeMillis() + FLOW_CONTROL_WAIT_FAILURE)
+ : expiryTime) >= System.currentTimeMillis() )
+ {
+
+ _flowControl.wait(FLOW_CONTROL_WAIT_PERIOD);
+ _logger.warn("Message send delayed by " + (System.currentTimeMillis() + FLOW_CONTROL_WAIT_FAILURE - expiryTime)/1000 + "s due to broker enforced flow control");
+ }
+ if(!_flowControl.getFlowControl())
+ {
+ _logger.error("Message send failed due to timeout waiting on broker enforced flow control");
+ throw new JMSException("Unable to send message for " + FLOW_CONTROL_WAIT_FAILURE/1000 + " seconds due to broker enforced flow control");
+ }
+ }
+
+ }
+
+ public interface Dispatchable
+ {
+ void dispatch(AMQSession ssn);
+ }
+
+ public void dispatch(UnprocessedMessage message)
+ {
+ if (_dispatcher == null)
+ {
+ throw new java.lang.IllegalStateException("dispatcher is not started");
+ }
+
+ _dispatcher.dispatchMessage(message);
+ }
+
+ /** Used for debugging in the dispatcher. */
+ private static final Logger _dispatcherLogger = LoggerFactory.getLogger("org.apache.qpid.client.AMQSession.Dispatcher");
+
+ /** Responsible for decoding a message fragment and passing it to the appropriate message consumer. */
+ class Dispatcher implements Runnable
+ {
+
+ /** Track the 'stopped' state of the dispatcher, a session starts in the stopped state. */
+ private final AtomicBoolean _closed = new AtomicBoolean(false);
+
+ private final Object _lock = new Object();
+ private String dispatcherID = "" + System.identityHashCode(this);
+
+ public Dispatcher()
+ {
+ }
+
+ public void close()
+ {
+ _closed.set(true);
+ _dispatcherThread.interrupt();
+
+ // fixme awaitTermination
+
+ }
+
+ public void rejectPending(C consumer)
+ {
+ synchronized (_lock)
+ {
+ boolean stopped = _dispatcher.connectionStopped();
+
+ if (!stopped)
+ {
+ _dispatcher.setConnectionStopped(true);
+ }
+
+ // Reject messages on pre-receive queue
+ consumer.rollbackPendingMessages();
+
+ // Reject messages on pre-dispatch queue
+ rejectMessagesForConsumerTag(consumer.getConsumerTag(), true, false);
+ //Let the dispatcher deal with this when it gets to them.
+
+ // closeConsumer
+ consumer.markClosed();
+
+ _dispatcher.setConnectionStopped(stopped);
+
+ }
+ }
+
+ public void rollback()
+ {
+
+ synchronized (_lock)
+ {
+ boolean isStopped = connectionStopped();
+
+ if (!isStopped)
+ {
+ setConnectionStopped(true);
+ }
+
+ _rollbackMark.set(_highestDeliveryTag.get());
+
+ _dispatcherLogger.debug("Session Pre Dispatch Queue cleared");
+
+ for (C consumer : _consumers.values())
+ {
+ if (!consumer.isNoConsume())
+ {
+ consumer.rollback();
+ }
+ else
+ {
+ // should perhaps clear the _SQ here.
+ // consumer._synchronousQueue.clear();
+ consumer.clearReceiveQueue();
+ }
+
+ }
+
+ for (int i = 0; i < _removedConsumers.size(); i++)
+ {
+ // Sends acknowledgement to server
+ _removedConsumers.get(i).rollback();
+ _removedConsumers.remove(i);
+ }
+
+ setConnectionStopped(isStopped);
+ }
+
+ }
+
+ public void recover()
+ {
+
+ synchronized (_lock)
+ {
+ boolean isStopped = connectionStopped();
+
+ if (!isStopped)
+ {
+ setConnectionStopped(true);
+ }
+
+ _dispatcherLogger.debug("Session clearing the consumer queues");
+
+ for (C consumer : _consumers.values())
+ {
+ List<Long> tags = consumer.drainReceiverQueueAndRetrieveDeliveryTags();
+ _unacknowledgedMessageTags.addAll(tags);
+ }
+
+ setConnectionStopped(isStopped);
+ }
+
+ }
+
+
+ public void run()
+ {
+ if (_dispatcherLogger.isInfoEnabled())
+ {
+ _dispatcherLogger.info(_dispatcherThread.getName() + " started");
+ }
+
+ UnprocessedMessage message;
+
+ // Allow disptacher to start stopped
+ synchronized (_lock)
+ {
+ while (!_closed.get() && connectionStopped())
+ {
+ try
+ {
+ _lock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ // ignore
+ }
+ }
+ }
+
+ try
+ {
+ Dispatchable disp;
+ while (!_closed.get() && ((disp = (Dispatchable) _queue.take()) != null))
+ {
+ disp.dispatch(AMQSession.this);
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // ignore
+ }
+
+ if (_dispatcherLogger.isInfoEnabled())
+ {
+ _dispatcherLogger.info(_dispatcherThread.getName() + " thread terminating for channel " + _channelId + ":" + _thisSession);
+ }
+
+ }
+
+ // only call while holding lock
+ final boolean connectionStopped()
+ {
+ return _connectionStopped;
+ }
+
+ boolean setConnectionStopped(boolean connectionStopped)
+ {
+ boolean currently;
+ synchronized (_lock)
+ {
+ currently = _connectionStopped;
+ _connectionStopped = connectionStopped;
+ _lock.notify();
+
+ if (_dispatcherLogger.isDebugEnabled())
+ {
+ _dispatcherLogger.debug("Set Dispatcher Connection " + (connectionStopped ? "Stopped" : "Started")
+ + ": Currently " + (currently ? "Stopped" : "Started"));
+ }
+ }
+
+ return currently;
+ }
+
+ private void dispatchMessage(UnprocessedMessage message)
+ {
+ long deliveryTag = message.getDeliveryTag();
+
+ synchronized (_lock)
+ {
+
+ try
+ {
+ while (connectionStopped())
+ {
+ _lock.wait();
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // pass
+ }
+
+ if (!(message instanceof CloseConsumerMessage)
+ && tagLE(deliveryTag, _rollbackMark.get()))
+ {
+ rejectMessage(message, true);
+ }
+ else if (isInRecovery())
+ {
+ _unacknowledgedMessageTags.add(deliveryTag);
+ }
+ else
+ {
+ synchronized (_messageDeliveryLock)
+ {
+ notifyConsumer(message);
+ }
+ }
+ }
+
+ long current = _rollbackMark.get();
+ if (updateRollbackMark(current, deliveryTag))
+ {
+ _rollbackMark.compareAndSet(current, deliveryTag);
+ }
+ }
+
+ private void notifyConsumer(UnprocessedMessage message)
+ {
+ final C consumer = _consumers.get(message.getConsumerTag());
+
+ if ((consumer == null) || consumer.isClosed())
+ {
+ if (_dispatcherLogger.isInfoEnabled())
+ {
+ if (consumer == null)
+ {
+ _dispatcherLogger.info("Dispatcher(" + dispatcherID + ")Received a message("
+ + System.identityHashCode(message) + ")" + "["
+ + message.getDeliveryTag() + "] from queue "
+ + message.getConsumerTag() + " )without a handler - rejecting(requeue)...");
+ }
+ else
+ {
+ if (consumer.isNoConsume())
+ {
+ _dispatcherLogger.info("Received a message("
+ + System.identityHashCode(message) + ")" + "["
+ + message.getDeliveryTag() + "] from queue " + " consumer("
+ + message.getConsumerTag() + ") is closed and a browser so dropping...");
+ //DROP MESSAGE
+ return;
+
+ }
+ else
+ {
+ _dispatcherLogger.info("Received a message("
+ + System.identityHashCode(message) + ")" + "["
+ + message.getDeliveryTag() + "] from queue " + " consumer("
+ + message.getConsumerTag() + ") is closed rejecting(requeue)...");
+ }
+ }
+ }
+ // Don't reject if we're already closing
+ if (!_closed.get())
+ {
+ rejectMessage(message, true);
+ }
+ }
+ else
+ {
+ consumer.notifyMessage(message);
+ }
+ }
+ }
+
+ protected abstract boolean tagLE(long tag1, long tag2);
+
+ protected abstract boolean updateRollbackMark(long current, long deliveryTag);
+
+ public abstract AMQMessageDelegateFactory getMessageDelegateFactory();
+
+ /*public void requestAccess(AMQShortString realm, boolean exclusive, boolean passive, boolean active, boolean write,
+ boolean read) throws AMQException
+ {
+ getProtocolHandler().writeCommandFrameAndWaitForReply(AccessRequestBody.createAMQFrame(getChannelId(),
+ getProtocolMajorVersion(), getProtocolMinorVersion(), active, exclusive, passive, read, realm, write),
+ new BlockingMethodFrameListener(_channelId)
+ {
+
+ public boolean processMethod(int channelId, AMQMethodBody frame) // throws AMQException
+ {
+ if (frame instanceof AccessRequestOkBody)
+ {
+ setTicket(((AccessRequestOkBody) frame).getTicket());
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ });
+ }*/
+
+ private class SuspenderRunner implements Runnable
+ {
+ private AtomicBoolean _suspend;
+
+ public SuspenderRunner(AtomicBoolean suspend)
+ {
+ _suspend = suspend;
+ }
+
+ public void run()
+ {
+ try
+ {
+ synchronized (_suspensionLock)
+ {
+ // If the session has closed by the time we get here
+ // then we should not attempt to write to the sesion/channel.
+ if (!(_thisSession.isClosed() || _thisSession.isClosing()))
+ {
+ suspendChannel(_suspend.get());
+ }
+ }
+ }
+ catch (AMQException e)
+ {
+ _logger.warn("Unable to " + (_suspend.get() ? "suspend" : "unsuspend") + " session " + _thisSession + " due to: " + e);
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Is the _queue empty?" + _queue.isEmpty());
+ _logger.debug("Is the dispatcher closed?" + (_dispatcher == null ? "it's Null" : _dispatcher._closed));
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the Session and its parent connection are closed
+ *
+ * @return <tt>true</tt> if this is closed, <tt>false</tt> otherwise.
+ */
+ @Override
+ public boolean isClosed()
+ {
+ return _closed.get() || _connection.isClosed();
+ }
+
+ /**
+ * Checks if the Session and its parent connection are capable of performing
+ * closing operations
+ *
+ * @return <tt>true</tt> if we are closing, <tt>false</tt> otherwise.
+ */
+ @Override
+ public boolean isClosing()
+ {
+ return _closing.get()|| _connection.isClosing();
+ }
+
+ public boolean isDeclareExchanges()
+ {
+ return DECLARE_EXCHANGES;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionAdapter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionAdapter.java
new file mode 100644
index 0000000000..7e257e0c20
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionAdapter.java
@@ -0,0 +1,26 @@
+/*
+ *
+ * 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;
+
+public interface AMQSessionAdapter
+{
+ public AMQSession getSession();
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java
new file mode 100644
index 0000000000..a1b240ed54
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSessionDirtyException.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * AMQSessionDirtyException represents all failures to send data on a transacted session that is
+ * no longer in a state that the client expects. i.e. failover has occured so previously sent messages
+ * will not be part of the transaction.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent attempt to perform additional sends on a dirty session.
+ * </table>
+ */
+public class AMQSessionDirtyException extends AMQException
+{
+ public AMQSessionDirtyException(String msg)
+ {
+ super(AMQConstant.RESOURCE_ERROR, msg, null);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java
new file mode 100644
index 0000000000..1ea92c67f7
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_10.java
@@ -0,0 +1,1372 @@
+/* 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 static org.apache.qpid.transport.Option.BATCH;
+import static org.apache.qpid.transport.Option.NONE;
+import static org.apache.qpid.transport.Option.SYNC;
+import static org.apache.qpid.transport.Option.UNRELIABLE;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.UUID;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQDestination.AddressOption;
+import org.apache.qpid.client.AMQDestination.Binding;
+import org.apache.qpid.client.AMQDestination.DestSyntax;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.failover.FailoverNoopSupport;
+import org.apache.qpid.client.failover.FailoverProtectedOperation;
+import org.apache.qpid.client.message.AMQMessageDelegateFactory;
+import org.apache.qpid.client.message.FieldTableSupport;
+import org.apache.qpid.client.message.MessageFactoryRegistry;
+import org.apache.qpid.client.message.UnprocessedMessage_0_10;
+import org.apache.qpid.client.messaging.address.Link;
+import org.apache.qpid.client.messaging.address.Link.Reliability;
+import org.apache.qpid.client.messaging.address.Node.ExchangeNode;
+import org.apache.qpid.client.messaging.address.Node.QueueNode;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.transport.ExchangeBoundResult;
+import org.apache.qpid.transport.ExchangeQueryResult;
+import org.apache.qpid.transport.ExecutionErrorCode;
+import org.apache.qpid.transport.ExecutionException;
+import org.apache.qpid.transport.MessageAcceptMode;
+import org.apache.qpid.transport.MessageAcquireMode;
+import org.apache.qpid.transport.MessageCreditUnit;
+import org.apache.qpid.transport.MessageFlowMode;
+import org.apache.qpid.transport.MessageTransfer;
+import org.apache.qpid.transport.Option;
+import org.apache.qpid.transport.QueueQueryResult;
+import org.apache.qpid.transport.Range;
+import org.apache.qpid.transport.RangeSet;
+import org.apache.qpid.transport.Session;
+import org.apache.qpid.transport.SessionException;
+import org.apache.qpid.transport.SessionListener;
+import org.apache.qpid.util.Serial;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a 0.10 Session
+ */
+public class AMQSession_0_10 extends AMQSession<BasicMessageConsumer_0_10, BasicMessageProducer_0_10>
+ implements SessionListener
+{
+
+ /**
+ * This class logger
+ */
+ private static final Logger _logger = LoggerFactory.getLogger(AMQSession_0_10.class);
+
+ private static Timer timer = new Timer("ack-flusher", true);
+ private static class Flusher extends TimerTask
+ {
+
+ private WeakReference<AMQSession_0_10> session;
+ public Flusher(AMQSession_0_10 session)
+ {
+ this.session = new WeakReference<AMQSession_0_10>(session);
+ }
+
+ public void run() {
+ AMQSession_0_10 ssn = session.get();
+ if (ssn == null)
+ {
+ cancel();
+ }
+ else
+ {
+ try
+ {
+ ssn.flushAcknowledgments(true);
+ }
+ catch (Throwable t)
+ {
+ _logger.error("error flushing acks", t);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * The underlying QpidSession
+ */
+ private Session _qpidSession;
+
+ /**
+ * The latest qpid Exception that has been raised.
+ */
+ private Object _currentExceptionLock = new Object();
+ private AMQException _currentException;
+
+ // a ref on the qpid connection
+ protected org.apache.qpid.transport.Connection _qpidConnection;
+
+ private long maxAckDelay = Long.getLong("qpid.session.max_ack_delay", 1000);
+ private TimerTask flushTask = null;
+ private RangeSet unacked = new RangeSet();
+ private int unackedCount = 0;
+
+ /**
+ * USed to store the range of in tx messages
+ */
+ private RangeSet _txRangeSet = new RangeSet();
+ private int _txSize = 0;
+ //--- constructors
+
+ /**
+ * Creates a new session on a connection.
+ *
+ * @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 acknowledgement mode for the session.
+ * @param messageFactoryRegistry The message factory factory for the session.
+ * @param defaultPrefetchHighMark The maximum number of messages to prefetched before suspending the session.
+ * @param defaultPrefetchLowMark The number of prefetched messages at which to resume the session.
+ * @param qpidConnection The qpid connection
+ */
+ AMQSession_0_10(org.apache.qpid.transport.Connection qpidConnection, AMQConnection con, int channelId,
+ boolean transacted, int acknowledgeMode, MessageFactoryRegistry messageFactoryRegistry,
+ int defaultPrefetchHighMark, int defaultPrefetchLowMark)
+ {
+
+ super(con, channelId, transacted, acknowledgeMode, messageFactoryRegistry, defaultPrefetchHighMark,
+ defaultPrefetchLowMark);
+ _qpidConnection = qpidConnection;
+ _qpidSession = _qpidConnection.createSession(1);
+ _qpidSession.setSessionListener(this);
+ if (_transacted)
+ {
+ _qpidSession.txSelect();
+ _qpidSession.setTransacted(true);
+ }
+
+ if (maxAckDelay > 0)
+ {
+ flushTask = new Flusher(this);
+ timer.schedule(flushTask, new Date(), maxAckDelay);
+ }
+ }
+
+ /**
+ * Creates a new session on a connection with the default 0-10 message 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 acknowledgement 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.
+ * @param qpidConnection The connection
+ */
+ AMQSession_0_10(org.apache.qpid.transport.Connection qpidConnection, AMQConnection con, int channelId,
+ boolean transacted, int acknowledgeMode, int defaultPrefetchHigh, int defaultPrefetchLow)
+ {
+
+ this(qpidConnection, con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(),
+ defaultPrefetchHigh, defaultPrefetchLow);
+ }
+
+ private void addUnacked(int id)
+ {
+ synchronized (unacked)
+ {
+ unacked.add(id);
+ unackedCount++;
+ }
+ }
+
+ private void clearUnacked()
+ {
+ synchronized (unacked)
+ {
+ unacked.clear();
+ unackedCount = 0;
+ }
+ }
+
+ //------- overwritten methods of class AMQSession
+
+ void failoverPrep()
+ {
+ super.failoverPrep();
+ clearUnacked();
+ }
+
+ /**
+ * Acknowledge one or many messages.
+ *
+ * @param deliveryTag The tag of the last message to be acknowledged.
+ * @param multiple <tt>true</tt> to acknowledge all messages up to and including the one specified by the
+ * delivery tag, <tt>false</tt> to just acknowledge that message.
+ */
+
+ public void acknowledgeMessage(long deliveryTag, boolean multiple)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Sending ack for delivery tag " + deliveryTag + " on session " + _channelId);
+ }
+ // acknowledge this message
+ if (multiple)
+ {
+ for (Long messageTag : _unacknowledgedMessageTags)
+ {
+ if( messageTag <= deliveryTag )
+ {
+ addUnacked(messageTag.intValue());
+ _unacknowledgedMessageTags.remove(messageTag);
+ }
+ }
+ //empty the list of unack messages
+
+ }
+ else
+ {
+ addUnacked((int) deliveryTag);
+ _unacknowledgedMessageTags.remove(deliveryTag);
+ }
+
+ long prefetch = getAMQConnection().getMaxPrefetch();
+
+ if (unackedCount >= prefetch/2 || maxAckDelay <= 0)
+ {
+ flushAcknowledgments();
+ }
+ }
+
+ protected void flushAcknowledgments()
+ {
+ flushAcknowledgments(false);
+ }
+
+ void flushAcknowledgments(boolean setSyncBit)
+ {
+ synchronized (unacked)
+ {
+ if (unackedCount > 0)
+ {
+ messageAcknowledge
+ (unacked, _acknowledgeMode != org.apache.qpid.jms.Session.NO_ACKNOWLEDGE,setSyncBit);
+ clearUnacked();
+ }
+ }
+ }
+
+ void messageAcknowledge(RangeSet ranges, boolean accept)
+ {
+ messageAcknowledge(ranges,accept,false);
+ }
+
+ void messageAcknowledge(RangeSet ranges, boolean accept,boolean setSyncBit)
+ {
+ Session ssn = getQpidSession();
+ for (Range range : ranges)
+ {
+ ssn.processed(range);
+ }
+ ssn.flushProcessed(accept ? BATCH : NONE);
+ if (accept)
+ {
+ ssn.messageAccept(ranges, UNRELIABLE,setSyncBit? SYNC : NONE);
+ }
+ }
+
+ /**
+ * Bind a queue with an exchange.
+ *
+ * @param queueName Specifies the name of the queue to bind. If the queue name is empty,
+ * refers to the current
+ * queue for the session, which is the last declared queue.
+ * @param exchangeName The exchange name.
+ * @param routingKey Specifies the routing key for the binding.
+ * @param arguments 0_8 specific
+ */
+ public void sendQueueBind(final AMQShortString queueName, final AMQShortString routingKey,
+ final FieldTable arguments, final AMQShortString exchangeName,
+ final AMQDestination destination, final boolean nowait)
+ throws AMQException
+ {
+ if (destination.getDestSyntax() == DestSyntax.BURL)
+ {
+ Map args = FieldTableSupport.convertToMap(arguments);
+
+ for (AMQShortString rk: destination.getBindingKeys())
+ {
+ _logger.debug("Binding queue : " + queueName.toString() +
+ " exchange: " + exchangeName.toString() +
+ " using binding key " + rk.asString());
+ getQpidSession().exchangeBind(queueName.toString(),
+ exchangeName.toString(),
+ rk.toString(),
+ args);
+ }
+ }
+ else
+ {
+ List<Binding> bindings = new ArrayList<Binding>();
+ bindings.addAll(destination.getSourceNode().getBindings());
+ bindings.addAll(destination.getTargetNode().getBindings());
+
+ String defaultExchange = destination.getAddressType() == AMQDestination.TOPIC_TYPE ?
+ destination.getAddressName(): "amq.topic";
+
+ for (Binding binding: bindings)
+ {
+ String queue = binding.getQueue() == null?
+ queueName.asString(): binding.getQueue();
+
+ String exchange = binding.getExchange() == null ?
+ defaultExchange :
+ binding.getExchange();
+
+ _logger.debug("Binding queue : " + queue +
+ " exchange: " + exchange +
+ " using binding key " + binding.getBindingKey() +
+ " with args " + printMap(binding.getArgs()));
+ getQpidSession().exchangeBind(queue,
+ exchange,
+ binding.getBindingKey(),
+ binding.getArgs());
+ }
+ }
+
+ if (!nowait)
+ {
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+ }
+
+
+ /**
+ * Close this session.
+ *
+ * @param timeout no used / 0_8 specific
+ * @throws AMQException
+ * @throws FailoverException
+ */
+ public void sendClose(long timeout) throws AMQException, FailoverException
+ {
+ if (flushTask != null)
+ {
+ flushTask.cancel();
+ flushTask = null;
+ }
+ flushAcknowledgments();
+ try
+ {
+ getQpidSession().sync();
+ getQpidSession().close();
+ }
+ catch (SessionException se)
+ {
+ setCurrentException(se);
+ }
+
+ AMQException amqe = getCurrentException();
+ if (amqe != null)
+ {
+ throw amqe;
+ }
+ }
+
+
+ /**
+ * Commit the receipt and the delivery of all messages exchanged by this session resources.
+ */
+ public void sendCommit() throws AMQException, FailoverException
+ {
+ getQpidSession().setAutoSync(true);
+ try
+ {
+ getQpidSession().txCommit();
+ }
+ finally
+ {
+ getQpidSession().setAutoSync(false);
+ }
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+
+ /**
+ * Create a queue with a given name.
+ *
+ * @param name The queue name
+ * @param autoDelete If this field is set and the exclusive field is also set,
+ * then the queue is deleted when the connection closes.
+ * @param durable If set when creating a new queue,
+ * the queue will be marked as durable.
+ * @param exclusive Exclusive queues can only be used from one connection at a time.
+ * @param arguments Exclusive queues can only be used from one connection at a time.
+ * @throws AMQException
+ * @throws FailoverException
+ */
+ public void sendCreateQueue(AMQShortString name, final boolean autoDelete, final boolean durable,
+ final boolean exclusive, Map<String, Object> arguments) throws AMQException, FailoverException
+ {
+ getQpidSession().queueDeclare(name.toString(), null, arguments, durable ? Option.DURABLE : Option.NONE,
+ autoDelete ? Option.AUTO_DELETE : Option.NONE,
+ exclusive ? Option.EXCLUSIVE : Option.NONE);
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+
+ /**
+ * This method asks the broker to redeliver all unacknowledged messages
+ *
+ * @throws AMQException
+ * @throws FailoverException
+ */
+ public void sendRecover() throws AMQException, FailoverException
+ {
+ // release all unacked messages
+ RangeSet ranges = new RangeSet();
+ while (true)
+ {
+ Long tag = _unacknowledgedMessageTags.poll();
+ if (tag == null)
+ {
+ break;
+ }
+ ranges.add((int) (long) tag);
+ }
+ getQpidSession().messageRelease(ranges, Option.SET_REDELIVERED);
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+
+
+ public void releaseForRollback()
+ {
+ getQpidSession().messageRelease(_txRangeSet, Option.SET_REDELIVERED);
+ _txRangeSet.clear();
+ _txSize = 0;
+ }
+
+ /**
+ * Release (0_8 notion of Reject) an acquired message
+ *
+ * @param deliveryTag the message ID
+ * @param requeue always true
+ */
+ public void rejectMessage(long deliveryTag, boolean requeue)
+ {
+ // The value of requeue is always true
+ RangeSet ranges = new RangeSet();
+ ranges.add((int) deliveryTag);
+ getQpidSession().messageRelease(ranges, Option.SET_REDELIVERED);
+ //I don't think we need to sync
+ }
+
+ /**
+ * Create an 0_10 message consumer
+ */
+ public BasicMessageConsumer_0_10 createMessageConsumer(final AMQDestination destination, final int prefetchHigh,
+ final int prefetchLow, final boolean noLocal,
+ final boolean exclusive, String messageSelector,
+ final FieldTable ft, final boolean noConsume,
+ final boolean autoClose) throws JMSException
+ {
+
+ final AMQProtocolHandler protocolHandler = getProtocolHandler();
+ return new BasicMessageConsumer_0_10(_channelId, _connection, destination, messageSelector, noLocal,
+ _messageFactoryRegistry, this, protocolHandler, ft, prefetchHigh,
+ prefetchLow, exclusive, _acknowledgeMode, noConsume, autoClose);
+ }
+
+ /**
+ * Bind a queue with an exchange.
+ */
+
+ public boolean isQueueBound(final AMQShortString exchangeName, final AMQShortString queueName, final AMQShortString routingKey)
+ throws JMSException
+ {
+ return isQueueBound(exchangeName,queueName,routingKey,null);
+ }
+
+ public boolean isQueueBound(final AMQDestination destination) throws JMSException
+ {
+ return isQueueBound(destination.getExchangeName(),destination.getAMQQueueName(),destination.getRoutingKey(),destination.getBindingKeys());
+ }
+
+ public boolean isQueueBound(final AMQShortString exchangeName, final AMQShortString queueName, final AMQShortString routingKey,AMQShortString[] bindingKeys)
+ throws JMSException
+ {
+ String rk = null;
+ if (bindingKeys != null && bindingKeys.length>0)
+ {
+ rk = bindingKeys[0].toString();
+ }
+ else if (routingKey != null)
+ {
+ rk = routingKey.toString();
+ }
+
+ return isQueueBound(exchangeName.toString(),queueName.toString(),rk,null);
+ }
+
+ public boolean isQueueBound(final String exchangeName, final String queueName, final String bindingKey,Map<String,Object> args)
+ throws JMSException
+ {
+ boolean res;
+ ExchangeBoundResult bindingQueryResult =
+ getQpidSession().exchangeBound(exchangeName,queueName, bindingKey, args).get();
+
+ if (bindingKey == null)
+ {
+ res = !(bindingQueryResult.getExchangeNotFound() || bindingQueryResult.getQueueNotFound());
+ }
+ else
+ {
+ if (args == null)
+ {
+ res = !(bindingQueryResult.getKeyNotMatched() || bindingQueryResult.getQueueNotFound() || bindingQueryResult
+ .getQueueNotMatched());
+ }
+ else
+ {
+ res = !(bindingQueryResult.getKeyNotMatched() || bindingQueryResult.getQueueNotFound() || bindingQueryResult
+ .getQueueNotMatched() || bindingQueryResult.getArgsNotMatched());
+ }
+ }
+ return res;
+ }
+
+ /**
+ * This method is invoked when a consumer is created
+ * Registers the consumer with the broker
+ */
+ public void sendConsume(BasicMessageConsumer_0_10 consumer, AMQShortString queueName, AMQProtocolHandler protocolHandler,
+ boolean nowait, String messageSelector, int tag)
+ throws AMQException, FailoverException
+ {
+ boolean preAcquire;
+
+ long capacity = getCapacity(consumer.getDestination());
+
+ try
+ {
+ boolean isTopic;
+ Map<String, Object> arguments = FieldTable.convertToMap(consumer.getArguments());
+
+ if (consumer.getDestination().getDestSyntax() == AMQDestination.DestSyntax.BURL)
+ {
+ isTopic = consumer.getDestination() instanceof AMQTopic ||
+ consumer.getDestination().getExchangeClass().equals(ExchangeDefaults.TOPIC_EXCHANGE_CLASS) ;
+
+ preAcquire = isTopic || (!consumer.isNoConsume() &&
+ (consumer.getMessageSelector() == null || consumer.getMessageSelector().equals("")));
+ }
+ else
+ {
+ isTopic = consumer.getDestination().getAddressType() == AMQDestination.TOPIC_TYPE;
+
+ preAcquire = !consumer.isNoConsume() &&
+ (isTopic || consumer.getMessageSelector() == null ||
+ consumer.getMessageSelector().equals(""));
+
+ arguments.putAll(
+ (Map<? extends String, ? extends Object>) consumer.getDestination().getLink().getSubscription().getArgs());
+ }
+
+ boolean acceptModeNone = getAcknowledgeMode() == NO_ACKNOWLEDGE;
+
+ if (consumer.getDestination().getLink() != null)
+ {
+ acceptModeNone = consumer.getDestination().getLink().getReliability() == Link.Reliability.UNRELIABLE;
+ }
+
+ getQpidSession().messageSubscribe
+ (queueName.toString(), String.valueOf(tag),
+ acceptModeNone ? MessageAcceptMode.NONE : MessageAcceptMode.EXPLICIT,
+ preAcquire ? MessageAcquireMode.PRE_ACQUIRED : MessageAcquireMode.NOT_ACQUIRED, null, 0, arguments,
+ consumer.isExclusive() ? Option.EXCLUSIVE : Option.NONE);
+ }
+ catch (JMSException e)
+ {
+ throw new AMQException(AMQConstant.INTERNAL_ERROR, "problem when registering consumer", e);
+ }
+
+ String consumerTag = ((BasicMessageConsumer_0_10)consumer).getConsumerTagString();
+
+ if (capacity == 0)
+ {
+ getQpidSession().messageSetFlowMode(consumerTag, MessageFlowMode.CREDIT);
+ }
+ else
+ {
+ getQpidSession().messageSetFlowMode(consumerTag, MessageFlowMode.WINDOW);
+ }
+ getQpidSession().messageFlow(consumerTag, MessageCreditUnit.BYTE, 0xFFFFFFFF,
+ Option.UNRELIABLE);
+
+ if(capacity > 0 && _dispatcher != null && (isStarted() || _immediatePrefetch))
+ {
+ // set the flow
+ getQpidSession().messageFlow(consumerTag,
+ MessageCreditUnit.MESSAGE,
+ capacity,
+ Option.UNRELIABLE);
+ }
+
+ if (!nowait)
+ {
+ sync();
+ }
+ }
+
+ private long getCapacity(AMQDestination destination)
+ {
+ long capacity = 0;
+ if (destination.getDestSyntax() == DestSyntax.ADDR &&
+ destination.getLink().getConsumerCapacity() > 0)
+ {
+ capacity = destination.getLink().getConsumerCapacity();
+ }
+ else if (prefetch())
+ {
+ capacity = getAMQConnection().getMaxPrefetch();
+ }
+ return capacity;
+ }
+
+ /**
+ * Create an 0_10 message producer
+ */
+ public BasicMessageProducer_0_10 createMessageProducer(final Destination destination, final boolean mandatory,
+ final boolean immediate, final boolean waitUntilSent,
+ long producerId) throws JMSException
+ {
+ try
+ {
+ return new BasicMessageProducer_0_10(_connection, (AMQDestination) destination, _transacted, _channelId, this,
+ getProtocolHandler(), producerId, immediate, mandatory, waitUntilSent);
+ }
+ catch (AMQException e)
+ {
+ JMSException ex = new JMSException("Error creating producer");
+ ex.initCause(e);
+ ex.setLinkedException(e);
+
+ throw ex;
+ }
+
+ }
+
+ /**
+ * creates an exchange if it does not already exist
+ */
+ public void sendExchangeDeclare(final AMQShortString name,
+ final AMQShortString type,
+ final AMQProtocolHandler protocolHandler, final boolean nowait)
+ throws AMQException, FailoverException
+ {
+ sendExchangeDeclare(name.asString(), type.asString(), null, null,
+ nowait);
+ }
+
+ public void sendExchangeDeclare(final String name, final String type,
+ final String alternateExchange, final Map<String, Object> args,
+ final boolean nowait) throws AMQException
+ {
+ getQpidSession().exchangeDeclare(
+ name,
+ type,
+ alternateExchange,
+ args,
+ name.toString().startsWith("amq.") ? Option.PASSIVE
+ : Option.NONE);
+ // We need to sync so that we get notify of an error.
+ if (!nowait)
+ {
+ sync();
+ }
+ }
+
+ /**
+ * deletes an exchange
+ */
+ public void sendExchangeDelete(final String name, final boolean nowait)
+ throws AMQException, FailoverException
+ {
+ getQpidSession().exchangeDelete(name);
+ // We need to sync so that we get notify of an error.
+ if (!nowait)
+ {
+ sync();
+ }
+ }
+
+ /**
+ * Declare a queue with the given queueName
+ */
+ public void sendQueueDeclare(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean nowait)
+ throws AMQException, FailoverException
+ {
+ // do nothing this is only used by 0_8
+ }
+
+ /**
+ * Declare a queue with the given queueName
+ */
+ public AMQShortString send0_10QueueDeclare(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean noLocal, final boolean nowait)
+ throws AMQException
+ {
+ AMQShortString queueName;
+ if (amqd.getAMQQueueName() == null)
+ {
+ // generate a name for this queue
+ queueName = new AMQShortString("TempQueue" + UUID.randomUUID());
+ amqd.setQueueName(queueName);
+ }
+ else
+ {
+ queueName = amqd.getAMQQueueName();
+ }
+
+ if (amqd.getDestSyntax() == DestSyntax.BURL)
+ {
+ Map<String,Object> arguments = new HashMap<String,Object>();
+ if (noLocal)
+ {
+ arguments.put("no-local", true);
+ }
+
+ getQpidSession().queueDeclare(queueName.toString(), "" , arguments,
+ amqd.isAutoDelete() ? Option.AUTO_DELETE : Option.NONE,
+ amqd.isDurable() ? Option.DURABLE : Option.NONE,
+ amqd.isExclusive() ? Option.EXCLUSIVE : Option.NONE);
+ }
+ else
+ {
+ QueueNode node = (QueueNode)amqd.getSourceNode();
+ getQpidSession().queueDeclare(queueName.toString(), node.getAlternateExchange() ,
+ node.getDeclareArgs(),
+ node.isAutoDelete() ? Option.AUTO_DELETE : Option.NONE,
+ node.isDurable() ? Option.DURABLE : Option.NONE,
+ node.isExclusive() ? Option.EXCLUSIVE : Option.NONE);
+ }
+
+ // passive --> false
+ if (!nowait)
+ {
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+ return queueName;
+ }
+
+ /**
+ * deletes a queue
+ */
+ public void sendQueueDelete(final AMQShortString queueName) throws AMQException, FailoverException
+ {
+ getQpidSession().queueDelete(queueName.toString());
+ // ifEmpty --> false
+ // ifUnused --> false
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+
+ /**
+ * Activate/deactivate the message flow for all the consumers of this session.
+ */
+ public void sendSuspendChannel(boolean suspend) throws AMQException, FailoverException
+ {
+ if (suspend)
+ {
+ for (BasicMessageConsumer consumer : _consumers.values())
+ {
+ getQpidSession().messageStop(String.valueOf(consumer.getConsumerTag()),
+ Option.UNRELIABLE);
+ }
+ }
+ else
+ {
+ for (BasicMessageConsumer_0_10 consumer : _consumers.values())
+ {
+ String consumerTag = String.valueOf(consumer.getConsumerTag());
+ //only set if msg list is null
+ try
+ {
+ long capacity = getCapacity(consumer.getDestination());
+
+ if (capacity == 0)
+ {
+ if (consumer.getMessageListener() != null)
+ {
+ getQpidSession().messageFlow(consumerTag,
+ MessageCreditUnit.MESSAGE, 1,
+ Option.UNRELIABLE);
+ }
+ }
+ else
+ {
+ getQpidSession()
+ .messageFlow(consumerTag, MessageCreditUnit.MESSAGE,
+ capacity,
+ Option.UNRELIABLE);
+ }
+ getQpidSession()
+ .messageFlow(consumerTag, MessageCreditUnit.BYTE, 0xFFFFFFFF,
+ Option.UNRELIABLE);
+ }
+ catch (Exception e)
+ {
+ throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error while trying to get the listener", e);
+ }
+ }
+ }
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+
+
+ public void sendRollback() throws AMQException, FailoverException
+ {
+ getQpidSession().txRollback();
+ // We need to sync so that we get notify of an error.
+ sync();
+ }
+
+ //------ Private methods
+ /**
+ * Access to the underlying Qpid Session
+ *
+ * @return The associated Qpid Session.
+ */
+ protected Session getQpidSession()
+ {
+ return _qpidSession;
+ }
+
+
+ /**
+ * Get the latest thrown exception.
+ *
+ * @throws SessionException get the latest thrown error.
+ */
+ public AMQException getCurrentException()
+ {
+ AMQException amqe = null;
+ synchronized (_currentExceptionLock)
+ {
+ if (_currentException != null)
+ {
+ amqe = _currentException;
+ _currentException = null;
+ }
+ }
+ return amqe;
+ }
+
+ public void opened(Session ssn) {}
+
+ public void resumed(Session ssn)
+ {
+ _qpidConnection = ssn.getConnection();
+ }
+
+ public void message(Session ssn, MessageTransfer xfr)
+ {
+ messageReceived(new UnprocessedMessage_0_10(xfr));
+ }
+
+ public void exception(Session ssn, SessionException exc)
+ {
+ setCurrentException(exc);
+ }
+
+ public void closed(Session ssn)
+ {
+ try
+ {
+ super.closed(null);
+ if (flushTask != null)
+ {
+ flushTask.cancel();
+ flushTask = null;
+ }
+ } catch (Exception e)
+ {
+ _logger.error("Error closing JMS session", e);
+ }
+ }
+
+ public AMQException getLastException()
+ {
+ return getCurrentException();
+ }
+
+ protected AMQShortString declareQueue(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean noLocal, final boolean nowait)
+ throws AMQException
+ {
+ /*return new FailoverRetrySupport<AMQShortString, AMQException>(*/
+ return new FailoverNoopSupport<AMQShortString, AMQException>(
+ new FailoverProtectedOperation<AMQShortString, AMQException>()
+ {
+ public AMQShortString execute() throws AMQException, FailoverException
+ {
+ // Generate the queue name if the destination indicates that a client generated name is to be used.
+ if (amqd.isNameRequired())
+ {
+ String binddingKey = "";
+ for(AMQShortString key : amqd.getBindingKeys())
+ {
+ binddingKey = binddingKey + "_" + key.toString();
+ }
+ amqd.setQueueName(new AMQShortString( binddingKey + "@"
+ + amqd.getExchangeName().toString() + "_" + UUID.randomUUID()));
+ }
+ return send0_10QueueDeclare(amqd, protocolHandler, noLocal, nowait);
+ }
+ }, _connection).execute();
+ }
+
+ protected Long requestQueueDepth(AMQDestination amqd)
+ {
+ flushAcknowledgments();
+ return getQpidSession().queueQuery(amqd.getQueueName()).get().getMessageCount();
+ }
+
+
+ /**
+ * Store non committed messages for this session
+ * With 0.10 messages are consumed with window mode, we must send a completion
+ * before the window size is reached so credits don't dry up.
+ * @param id
+ */
+ @Override protected void addDeliveredMessage(long id)
+ {
+ _txRangeSet.add((int) id);
+ _txSize++;
+ // this is a heuristic, we may want to have that configurable
+ if (_connection.getMaxPrefetch() == 1 ||
+ _connection.getMaxPrefetch() != 0 && _txSize % (_connection.getMaxPrefetch() / 2) == 0)
+ {
+ // send completed so consumer credits don't dry up
+ messageAcknowledge(_txRangeSet, false);
+ }
+ }
+
+ @Override public void commit() throws JMSException
+ {
+ checkTransacted();
+ try
+ {
+ if( _txSize > 0 )
+ {
+ messageAcknowledge(_txRangeSet, true);
+ _txRangeSet.clear();
+ _txSize = 0;
+ }
+ sendCommit();
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("Failed to commit: " + e.getMessage(), e);
+ }
+ catch (FailoverException e)
+ {
+ throw new JMSAMQException("Fail-over interrupted commit. Status of the commit is uncertain.", e);
+ }
+ }
+
+ protected final boolean tagLE(long tag1, long tag2)
+ {
+ return Serial.le((int) tag1, (int) tag2);
+ }
+
+ protected final boolean updateRollbackMark(long currentMark, long deliveryTag)
+ {
+ return Serial.lt((int) currentMark, (int) deliveryTag);
+ }
+
+ public void sync() throws AMQException
+ {
+ try
+ {
+ getQpidSession().sync();
+ }
+ catch (SessionException se)
+ {
+ setCurrentException(se);
+ }
+
+ AMQException amqe = getCurrentException();
+ if (amqe != null)
+ {
+ throw amqe;
+ }
+ }
+
+ public void setCurrentException(SessionException se)
+ {
+ synchronized (_currentExceptionLock)
+ {
+ ExecutionException ee = se.getException();
+ int code = AMQConstant.INTERNAL_ERROR.getCode();
+ if (ee != null)
+ {
+ code = ee.getErrorCode().getValue();
+ }
+ AMQException amqe = new AMQException(AMQConstant.getConstant(code), se.getMessage(), se.getCause());
+ _currentException = amqe;
+ }
+ _connection.exceptionReceived(_currentException);
+ }
+
+ public AMQMessageDelegateFactory getMessageDelegateFactory()
+ {
+ return AMQMessageDelegateFactory.FACTORY_0_10;
+ }
+
+ public boolean isExchangeExist(AMQDestination dest,ExchangeNode node,boolean assertNode)
+ {
+ boolean match = true;
+ ExchangeQueryResult result = getQpidSession().exchangeQuery(dest.getAddressName(), Option.NONE).get();
+ match = !result.getNotFound();
+
+ if (match)
+ {
+ if (assertNode)
+ {
+ match = (result.getDurable() == node.isDurable()) &&
+ (node.getExchangeType() != null &&
+ node.getExchangeType().equals(result.getType())) &&
+ (matchProps(result.getArguments(),node.getDeclareArgs()));
+ }
+ else if (node.getExchangeType() != null)
+ {
+ // even if assert is false, better to verify this
+ match = node.getExchangeType().equals(result.getType());
+ if (!match)
+ {
+ _logger.debug("Exchange type doesn't match. Expected : " + node.getExchangeType() +
+ " actual " + result.getType());
+ }
+ }
+ else
+ {
+ _logger.debug("Setting Exchange type " + result.getType());
+ node.setExchangeType(result.getType());
+ dest.setExchangeClass(new AMQShortString(result.getType()));
+ }
+ }
+
+ return match;
+ }
+
+ public boolean isQueueExist(AMQDestination dest,QueueNode node,boolean assertNode) throws AMQException
+ {
+ boolean match = true;
+ try
+ {
+ QueueQueryResult result = getQpidSession().queueQuery(dest.getAddressName(), Option.NONE).get();
+ match = dest.getAddressName().equals(result.getQueue());
+
+ if (match && assertNode)
+ {
+ match = (result.getDurable() == node.isDurable()) &&
+ (result.getAutoDelete() == node.isAutoDelete()) &&
+ (result.getExclusive() == node.isExclusive()) &&
+ (matchProps(result.getArguments(),node.getDeclareArgs()));
+ }
+ else if (match)
+ {
+ // should I use the queried details to update the local data structure.
+ }
+ }
+ catch(SessionException e)
+ {
+ if (e.getException().getErrorCode() == ExecutionErrorCode.RESOURCE_DELETED)
+ {
+ match = false;
+ }
+ else
+ {
+ throw new AMQException(AMQConstant.getConstant(e.getException().getErrorCode().getValue()),
+ "Error querying queue",e);
+ }
+ }
+
+ return match;
+ }
+
+ private boolean matchProps(Map<String,Object> target,Map<String,Object> source)
+ {
+ boolean match = true;
+ for (String key: source.keySet())
+ {
+ match = target.containsKey(key) &&
+ target.get(key).equals(source.get(key));
+
+ if (!match)
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append("Property given in address did not match with the args sent by the broker.");
+ buf.append(" Expected { ").append(key).append(" : ").append(source.get(key)).append(" }, ");
+ buf.append(" Actual { ").append(key).append(" : ").append(target.get(key)).append(" }");
+ _logger.debug(buf.toString());
+ return match;
+ }
+ }
+
+ return match;
+ }
+
+ /**
+ * 1. Try to resolve the address type (queue or exchange)
+ * 2. if type == queue,
+ * 2.1 verify queue exists or create if create == true
+ * 2.2 If not throw exception
+ *
+ * 3. if type == exchange,
+ * 3.1 verify exchange exists or create if create == true
+ * 3.2 if not throw exception
+ * 3.3 if exchange exists (or created) create subscription queue.
+ */
+
+ @SuppressWarnings("deprecation")
+ public void handleAddressBasedDestination(AMQDestination dest,
+ boolean isConsumer,
+ boolean noWait) throws AMQException
+ {
+ if (dest.isAddressResolved())
+ {
+ if (isConsumer && AMQDestination.TOPIC_TYPE == dest.getAddressType())
+ {
+ createSubscriptionQueue(dest);
+ }
+ }
+ else
+ {
+ boolean assertNode = (dest.getAssert() == AddressOption.ALWAYS) ||
+ (isConsumer && dest.getAssert() == AddressOption.RECEIVER) ||
+ (!isConsumer && dest.getAssert() == AddressOption.SENDER);
+
+ boolean createNode = (dest.getCreate() == AddressOption.ALWAYS) ||
+ (isConsumer && dest.getCreate() == AddressOption.RECEIVER) ||
+ (!isConsumer && dest.getCreate() == AddressOption.SENDER);
+
+
+
+ int type = resolveAddressType(dest);
+
+ if (type == AMQDestination.QUEUE_TYPE &&
+ dest.getLink().getReliability() == Reliability.UNSPECIFIED)
+ {
+ dest.getLink().setReliability(Reliability.AT_LEAST_ONCE);
+ }
+ else if (type == AMQDestination.TOPIC_TYPE &&
+ dest.getLink().getReliability() == Reliability.UNSPECIFIED)
+ {
+ dest.getLink().setReliability(Reliability.UNRELIABLE);
+ }
+ else if (type == AMQDestination.TOPIC_TYPE &&
+ dest.getLink().getReliability() == Reliability.AT_LEAST_ONCE)
+ {
+ throw new AMQException("AT-LEAST-ONCE is not yet supported for Topics");
+ }
+
+ switch (type)
+ {
+ case AMQDestination.QUEUE_TYPE:
+ {
+ if (isQueueExist(dest,(QueueNode)dest.getSourceNode(),assertNode))
+ {
+ setLegacyFiledsForQueueType(dest);
+ break;
+ }
+ else if(createNode)
+ {
+ setLegacyFiledsForQueueType(dest);
+ send0_10QueueDeclare(dest,null,false,noWait);
+ sendQueueBind(dest.getAMQQueueName(), dest.getRoutingKey(),
+ null,dest.getExchangeName(),dest, false);
+ break;
+ }
+ }
+
+ case AMQDestination.TOPIC_TYPE:
+ {
+ if (isExchangeExist(dest,(ExchangeNode)dest.getTargetNode(),assertNode))
+ {
+ setLegacyFiledsForTopicType(dest);
+ verifySubject(dest);
+ if (isConsumer && !isQueueExist(dest,(QueueNode)dest.getSourceNode(),true))
+ {
+ createSubscriptionQueue(dest);
+ }
+ break;
+ }
+ else if(createNode)
+ {
+ setLegacyFiledsForTopicType(dest);
+ verifySubject(dest);
+ sendExchangeDeclare(dest.getAddressName(),
+ dest.getExchangeClass().asString(),
+ dest.getTargetNode().getAlternateExchange(),
+ dest.getTargetNode().getDeclareArgs(),
+ false);
+ if (isConsumer && !isQueueExist(dest,(QueueNode)dest.getSourceNode(),true))
+ {
+ createSubscriptionQueue(dest);
+ }
+ break;
+ }
+ }
+
+ default:
+ throw new AMQException(
+ "The name '" + dest.getAddressName() +
+ "' supplied in the address doesn't resolve to an exchange or a queue");
+ }
+ dest.setAddressResolved(true);
+ }
+ }
+
+ public int resolveAddressType(AMQDestination dest) throws AMQException
+ {
+ int type = dest.getAddressType();
+ String name = dest.getAddressName();
+ if (type != AMQDestination.UNKNOWN_TYPE)
+ {
+ return type;
+ }
+ else
+ {
+ ExchangeBoundResult result = getQpidSession().exchangeBound(name,name,null,null).get();
+ if (result.getQueueNotFound() && result.getExchangeNotFound()) {
+ //neither a queue nor an exchange exists with that name; treat it as a queue
+ type = AMQDestination.QUEUE_TYPE;
+ } else if (result.getExchangeNotFound()) {
+ //name refers to a queue
+ type = AMQDestination.QUEUE_TYPE;
+ } else if (result.getQueueNotFound()) {
+ //name refers to an exchange
+ type = AMQDestination.TOPIC_TYPE;
+ } else {
+ //both a queue and exchange exist for that name
+ throw new AMQException("Ambiguous address, please specify queue or topic as node type");
+ }
+ dest.setAddressType(type);
+ dest.rebuildTargetAndSourceNodes(type);
+ return type;
+ }
+ }
+
+ private void verifySubject(AMQDestination dest) throws AMQException
+ {
+ if (dest.getSubject() == null || dest.getSubject().trim().equals(""))
+ {
+
+ if ("topic".equals(dest.getExchangeClass().toString()))
+ {
+ dest.setRoutingKey(new AMQShortString("#"));
+ dest.setSubject(dest.getRoutingKey().toString());
+ }
+ else
+ {
+ dest.setRoutingKey(new AMQShortString(""));
+ dest.setSubject("");
+ }
+ }
+ }
+
+ private void createSubscriptionQueue(AMQDestination dest) throws AMQException
+ {
+ QueueNode node = (QueueNode)dest.getSourceNode(); // source node is never null
+
+ if (dest.getQueueName() == null)
+ {
+ if (dest.getLink() != null && dest.getLink().getName() != null)
+ {
+ dest.setQueueName(new AMQShortString(dest.getLink().getName()));
+ }
+ }
+ node.setExclusive(true);
+ node.setAutoDelete(!node.isDurable());
+ send0_10QueueDeclare(dest,null,false,true);
+ node.addBinding(new Binding(dest.getAddressName(),
+ dest.getQueueName(),// should have one by now
+ dest.getSubject(),
+ Collections.<String,Object>emptyMap()));
+ sendQueueBind(dest.getAMQQueueName(), dest.getRoutingKey(),
+ null,dest.getExchangeName(),dest, false);
+ }
+
+ public void setLegacyFiledsForQueueType(AMQDestination dest)
+ {
+ // legacy support
+ dest.setQueueName(new AMQShortString(dest.getAddressName()));
+ dest.setExchangeName(new AMQShortString(""));
+ dest.setExchangeClass(new AMQShortString(""));
+ dest.setRoutingKey(dest.getAMQQueueName());
+ }
+
+ public void setLegacyFiledsForTopicType(AMQDestination dest)
+ {
+ // legacy support
+ dest.setExchangeName(new AMQShortString(dest.getAddressName()));
+ ExchangeNode node = (ExchangeNode)dest.getTargetNode();
+ dest.setExchangeClass(node.getExchangeType() == null?
+ ExchangeDefaults.TOPIC_EXCHANGE_CLASS:
+ new AMQShortString(node.getExchangeType()));
+ dest.setRoutingKey(new AMQShortString(dest.getSubject()));
+ }
+
+ /** This should be moved to a suitable utility class */
+ private String printMap(Map<String,Object> map)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<");
+ if (map != null)
+ {
+ for(String key : map.keySet())
+ {
+ sb.append(key).append(" = ").append(map.get(key)).append(" ");
+ }
+ }
+ sb.append(">");
+ return sb.toString();
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java
new file mode 100644
index 0000000000..c010e4c7ed
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQSession_0_8.java
@@ -0,0 +1,619 @@
+/*
+ *
+ * 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 java.util.Map;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUndeliveredException;
+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.message.AMQMessageDelegateFactory;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.MessageFactoryRegistry;
+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.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.listener.SpecificMethodFrameListener;
+import org.apache.qpid.common.AMQPFilterTypes;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicAckBody;
+import org.apache.qpid.framing.BasicConsumeBody;
+import org.apache.qpid.framing.BasicConsumeOkBody;
+import org.apache.qpid.framing.BasicQosBody;
+import org.apache.qpid.framing.BasicQosOkBody;
+import org.apache.qpid.framing.BasicRecoverBody;
+import org.apache.qpid.framing.BasicRecoverOkBody;
+import org.apache.qpid.framing.BasicRecoverSyncBody;
+import org.apache.qpid.framing.BasicRecoverSyncOkBody;
+import org.apache.qpid.framing.BasicRejectBody;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.framing.ChannelFlowBody;
+import org.apache.qpid.framing.ChannelFlowOkBody;
+import org.apache.qpid.framing.ExchangeBoundOkBody;
+import org.apache.qpid.framing.ExchangeDeclareBody;
+import org.apache.qpid.framing.ExchangeDeclareOkBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.QueueBindOkBody;
+import org.apache.qpid.framing.QueueDeclareBody;
+import org.apache.qpid.framing.QueueDeclareOkBody;
+import org.apache.qpid.framing.QueueDeleteBody;
+import org.apache.qpid.framing.QueueDeleteOkBody;
+import org.apache.qpid.framing.TxCommitOkBody;
+import org.apache.qpid.framing.TxRollbackBody;
+import org.apache.qpid.framing.TxRollbackOkBody;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class AMQSession_0_8 extends AMQSession<BasicMessageConsumer_0_8, BasicMessageProducer_0_8>
+{
+
+ /** Used for debugging. */
+ private static final Logger _logger = LoggerFactory.getLogger(AMQSession.class);
+
+ /**
+ * Creates a new session on a connection.
+ *
+ * @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 messageFactoryRegistry The message factory factory for the session.
+ * @param defaultPrefetchHighMark The maximum number of messages to prefetched before suspending the session.
+ * @param defaultPrefetchLowMark The number of prefetched messages at which to resume the session.
+ */
+ AMQSession_0_8(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode,
+ MessageFactoryRegistry messageFactoryRegistry, int defaultPrefetchHighMark, int defaultPrefetchLowMark)
+ {
+
+ super(con,channelId,transacted,acknowledgeMode,messageFactoryRegistry,defaultPrefetchHighMark,defaultPrefetchLowMark);
+ }
+
+ /**
+ * 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_0_8(AMQConnection con, int channelId, boolean transacted, int acknowledgeMode, int defaultPrefetchHigh,
+ int defaultPrefetchLow)
+ {
+ this(con, channelId, transacted, acknowledgeMode, MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetchHigh,
+ defaultPrefetchLow);
+ }
+
+ private ProtocolVersion getProtocolVersion()
+ {
+ return getProtocolHandler().getProtocolVersion();
+ }
+
+ public void acknowledgeMessage(long deliveryTag, boolean multiple)
+ {
+ BasicAckBody body = getMethodRegistry().createBasicAckBody(deliveryTag, multiple);
+
+ final AMQFrame ackFrame = body.generateFrame(_channelId);
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Sending ack for delivery tag " + deliveryTag + " on channel " + _channelId);
+ }
+
+ getProtocolHandler().writeFrame(ackFrame);
+ _unacknowledgedMessageTags.remove(deliveryTag);
+ }
+
+ public void sendQueueBind(final AMQShortString queueName, final AMQShortString routingKey, final FieldTable arguments,
+ final AMQShortString exchangeName, final AMQDestination dest,
+ final boolean nowait) throws AMQException, FailoverException
+ {
+ getProtocolHandler().syncWrite(getProtocolHandler().getMethodRegistry().createQueueBindBody
+ (getTicket(),queueName,exchangeName,routingKey,false,arguments).
+ generateFrame(_channelId), QueueBindOkBody.class);
+ }
+
+ public void sendClose(long timeout) throws AMQException, FailoverException
+ {
+ // we also need to check the state manager for 08/09 as the
+ // _connection variable may not be updated in time by the error receiving
+ // thread.
+ // We can't close the session if we are alreadying in the process of
+ // closing/closed the connection.
+
+ if (!(getProtocolHandler().getStateManager().getCurrentState().equals(AMQState.CONNECTION_CLOSED)
+ || getProtocolHandler().getStateManager().getCurrentState().equals(AMQState.CONNECTION_CLOSING)))
+ {
+
+ getProtocolHandler().closeSession(this);
+ 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.
+ }
+ }
+
+ public void sendCommit() throws AMQException, FailoverException
+ {
+ final AMQProtocolHandler handler = getProtocolHandler();
+
+ handler.syncWrite(getProtocolHandler().getMethodRegistry().createTxCommitBody().generateFrame(_channelId), TxCommitOkBody.class);
+ }
+
+ public void sendCreateQueue(AMQShortString name, final boolean autoDelete, final boolean durable, final boolean exclusive, final Map<String, Object> arguments) throws AMQException,
+ FailoverException
+ {
+ FieldTable table = null;
+ if(arguments != null && !arguments.isEmpty())
+ {
+ table = new FieldTable();
+ for(Map.Entry<String, Object> entry : arguments.entrySet())
+ {
+ table.setObject(entry.getKey(), entry.getValue());
+ }
+ }
+ QueueDeclareBody body = getMethodRegistry().createQueueDeclareBody(getTicket(),name,false,durable,exclusive,autoDelete,false,table);
+ AMQFrame queueDeclare = body.generateFrame(_channelId);
+ getProtocolHandler().syncWrite(queueDeclare, QueueDeclareOkBody.class);
+ }
+
+ public void sendRecover() throws AMQException, FailoverException
+ {
+ _unacknowledgedMessageTags.clear();
+
+ if (isStrictAMQP())
+ {
+ // We can't use the BasicRecoverBody-OK method as it isn't part of the spec.
+
+ 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
+ {
+ // 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 if(getProtocolVersion().equals(ProtocolVersion.v0_91))
+ {
+ BasicRecoverSyncBody body = ((MethodRegistry_0_91)getMethodRegistry()).createBasicRecoverSyncBody(false);
+ _connection.getProtocolHandler().syncWrite(body.generateFrame(_channelId), BasicRecoverSyncOkBody.class);
+ }
+ else
+ {
+ throw new RuntimeException("Unsupported version of the AMQP Protocol: " + getProtocolVersion());
+ }
+ }
+ }
+
+ public void releaseForRollback()
+ {
+ // Reject all the messages that have been received in this session and
+ // have not yet been acknowledged. Should look to remove
+ // _deliveredMessageTags and use _txRangeSet as used by 0-10.
+ // Otherwise messages will be able to arrive out of order to a second
+ // consumer on the queue. Whilst this is within the JMS spec it is not
+ // user friendly and avoidable.
+ while (true)
+ {
+ Long tag = _deliveredMessageTags.poll();
+ if (tag == null)
+ {
+ break;
+ }
+
+ rejectMessage(tag, true);
+ }
+ }
+
+ public void rejectMessage(long deliveryTag, boolean requeue)
+ {
+ if ((_acknowledgeMode == CLIENT_ACKNOWLEDGE) || (_acknowledgeMode == SESSION_TRANSACTED))
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejecting delivery tag:" + deliveryTag + ":SessionHC:" + this.hashCode());
+ }
+
+ BasicRejectBody body = getMethodRegistry().createBasicRejectBody(deliveryTag, requeue);
+ AMQFrame frame = body.generateFrame(_channelId);
+
+ _connection.getProtocolHandler().writeFrame(frame);
+ }
+ }
+
+ public boolean isQueueBound(final AMQDestination destination) throws JMSException
+ {
+ return isQueueBound(destination.getExchangeName(),destination.getAMQQueueName(),destination.getAMQQueueName());
+ }
+
+
+ public boolean isQueueBound(final AMQShortString exchangeName, final AMQShortString queueName, final AMQShortString routingKey)
+ throws JMSException
+ {
+ try
+ {
+ AMQMethodEvent response = new FailoverRetrySupport<AMQMethodEvent, AMQException>(
+ new FailoverProtectedOperation<AMQMethodEvent, AMQException>()
+ {
+ public AMQMethodEvent execute() throws AMQException, FailoverException
+ {
+ AMQFrame boundFrame = getProtocolHandler().getMethodRegistry().createExchangeBoundBody
+ (exchangeName, routingKey, queueName).generateFrame(_channelId);
+
+ return getProtocolHandler().syncWrite(boundFrame, ExchangeBoundOkBody.class);
+
+ }
+ }, _connection).execute();
+
+ // Extract and return the response code from the query.
+ ExchangeBoundOkBody responseBody = (ExchangeBoundOkBody) response.getMethod();
+
+ return (responseBody.getReplyCode() == 0);
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("Queue bound query failed: " + e.getMessage(), e);
+ }
+ }
+
+ @Override public void sendConsume(BasicMessageConsumer_0_8 consumer,
+ AMQShortString queueName,
+ AMQProtocolHandler protocolHandler,
+ boolean nowait,
+ String messageSelector,
+ int tag) throws AMQException, FailoverException
+ {
+ FieldTable arguments = FieldTableFactory.newFieldTable();
+ if ((messageSelector != null) && !messageSelector.equals(""))
+ {
+ arguments.put(AMQPFilterTypes.JMS_SELECTOR.getValue(), messageSelector);
+ }
+
+ if (consumer.isAutoClose())
+ {
+ arguments.put(AMQPFilterTypes.AUTO_CLOSE.getValue(), Boolean.TRUE);
+ }
+
+ if (consumer.isNoConsume())
+ {
+ arguments.put(AMQPFilterTypes.NO_CONSUME.getValue(), Boolean.TRUE);
+ }
+
+ BasicConsumeBody body = getMethodRegistry().createBasicConsumeBody(getTicket(),
+ queueName,
+ new AMQShortString(String.valueOf(tag)),
+ consumer.isNoLocal(),
+ consumer.getAcknowledgeMode() == Session.NO_ACKNOWLEDGE,
+ consumer.isExclusive(),
+ nowait,
+ arguments);
+
+
+ AMQFrame jmsConsume = body.generateFrame(_channelId);
+
+ if (nowait)
+ {
+ protocolHandler.writeFrame(jmsConsume);
+ }
+ else
+ {
+ protocolHandler.syncWrite(jmsConsume, BasicConsumeOkBody.class);
+ }
+ }
+
+ public void sendExchangeDeclare(final AMQShortString name, final AMQShortString type, final AMQProtocolHandler protocolHandler,
+ final boolean nowait) throws AMQException, FailoverException
+ {
+ ExchangeDeclareBody body = getMethodRegistry().createExchangeDeclareBody(getTicket(),name,type,
+ name.toString().startsWith("amq."),
+ false,false,false,false,null);
+ AMQFrame exchangeDeclare = body.generateFrame(_channelId);
+
+ protocolHandler.syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class);
+ }
+
+ public void sendQueueDeclare(final AMQDestination amqd, final AMQProtocolHandler protocolHandler,
+ final boolean nowait) throws AMQException, FailoverException
+ {
+ 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
+ {
+ 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
+ {
+ ChannelFlowBody body = getMethodRegistry().createChannelFlowBody(!suspend);
+ AMQFrame channelFlowFrame = body.generateFrame(_channelId);
+ _connection.getProtocolHandler().syncWrite(channelFlowFrame, ChannelFlowOkBody.class);
+ }
+
+ public BasicMessageConsumer_0_8 createMessageConsumer(final AMQDestination destination, final int prefetchHigh,
+ final int prefetchLow, final boolean noLocal, final boolean exclusive, String messageSelector, final FieldTable arguments,
+ final boolean noConsume, final boolean autoClose) throws JMSException
+ {
+
+ final AMQProtocolHandler protocolHandler = getProtocolHandler();
+ return new BasicMessageConsumer_0_8(_channelId, _connection, destination, messageSelector, noLocal,
+ _messageFactoryRegistry,this, protocolHandler, arguments, prefetchHigh, prefetchLow,
+ exclusive, _acknowledgeMode, noConsume, autoClose);
+ }
+
+
+ public BasicMessageProducer_0_8 createMessageProducer(final Destination destination, final boolean mandatory,
+ final boolean immediate, final boolean waitUntilSent, long producerId) throws JMSException
+ {
+ try
+ {
+ return new BasicMessageProducer_0_8(_connection, (AMQDestination) destination, _transacted, _channelId,
+ this, getProtocolHandler(), producerId, immediate, mandatory, waitUntilSent);
+ }
+ catch (AMQException e)
+ {
+ JMSException ex = new JMSException("Error creating producer");
+ ex.initCause(e);
+ ex.setLinkedException(e);
+
+ throw ex;
+ }
+ }
+
+
+ @Override public void messageReceived(UnprocessedMessage message)
+ {
+
+ if (message instanceof ReturnMessage)
+ {
+ // Return of the bounced message.
+ returnBouncedMessage((ReturnMessage) message);
+ }
+ else
+ {
+ super.messageReceived(message);
+ }
+ }
+
+ private void returnBouncedMessage(final ReturnMessage msg)
+ {
+ _connection.performConnectionTask(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ // Bounced message is processed here, away from the mina thread
+ AbstractJMSMessage bouncedMessage =
+ _messageFactoryRegistry.createMessage(0, false, msg.getExchange(),
+ 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)
+ {
+ _connection.exceptionReceived(new AMQNoConsumersException("Error: " + reason, bouncedMessage, null));
+ }
+ else if (errorCode == AMQConstant.NO_ROUTE)
+ {
+ _connection.exceptionReceived(new AMQNoRouteException("Error: " + reason, bouncedMessage, null));
+ }
+ else
+ {
+ _connection.exceptionReceived(
+ new AMQUndeliveredException(errorCode, "Error: " + reason, bouncedMessage, null));
+ }
+
+ }
+ catch (Exception e)
+ {
+ _logger.error(
+ "Caught exception trying to raise undelivered message exception (dump follows) - ignoring...",
+ e);
+ }
+ }
+ });
+ }
+
+
+
+
+ public void sendRollback() throws AMQException, FailoverException
+ {
+ TxRollbackBody body = getMethodRegistry().createTxRollbackBody();
+ AMQFrame frame = body.generateFrame(getChannelId());
+ getProtocolHandler().syncWrite(frame, TxRollbackOkBody.class);
+ }
+
+ public void setPrefetchLimits(final int messagePrefetch, final long sizePrefetch) throws AMQException
+ {
+ new FailoverRetrySupport<Object, AMQException>(
+ new FailoverProtectedOperation<Object, AMQException>()
+ {
+ public Object execute() throws AMQException, FailoverException
+ {
+
+ BasicQosBody basicQosBody = getProtocolHandler().getMethodRegistry().createBasicQosBody(sizePrefetch, messagePrefetch, false);
+
+ // todo send low water mark when protocol allows.
+ // todo Be aware of possible changes to parameter order as versions change.
+ getProtocolHandler().syncWrite(basicQosBody.generateFrame(getChannelId()), BasicQosOkBody.class);
+
+ return null;
+ }
+ }, _connection).execute();
+ }
+
+ 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;
+ }
+
+ }
+
+ protected 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;
+ }
+
+ protected final boolean tagLE(long tag1, long tag2)
+ {
+ return tag1 <= tag2;
+ }
+
+ protected final boolean updateRollbackMark(long currentMark, long deliveryTag)
+ {
+ return false;
+ }
+
+ public AMQMessageDelegateFactory getMessageDelegateFactory()
+ {
+ return AMQMessageDelegateFactory.FACTORY_0_8;
+ }
+
+ public void sync() throws AMQException
+ {
+ declareExchange(new AMQShortString("amq.direct"), new AMQShortString("direct"), false);
+ }
+
+ public void handleAddressBasedDestination(AMQDestination dest,
+ boolean isConsumer,
+ boolean noWait) throws AMQException
+ {
+ throw new UnsupportedOperationException("The new addressing based sytanx is "
+ + "not supported for AMQP 0-8/0-9 versions");
+ }
+
+ protected void flushAcknowledgments()
+ {
+
+ }
+
+ public boolean isQueueBound(String exchangeName, String queueName,
+ String bindingKey, Map<String, Object> args) throws JMSException
+ {
+ return isQueueBound(exchangeName == null ? null : new AMQShortString(exchangeName),
+ queueName == null ? null : new AMQShortString(queueName),
+ bindingKey == null ? null : new AMQShortString(bindingKey));
+ }
+
+
+ public AMQException getLastException()
+ {
+ // if the Connection has closed then we should throw any exception that
+ // has occurred that we were not waiting for
+ AMQStateManager manager = _connection.getProtocolHandler()
+ .getStateManager();
+
+ Exception e = manager.getLastException();
+ if (manager.getCurrentState().equals(AMQState.CONNECTION_CLOSED)
+ && e != null)
+ {
+ if (e instanceof AMQException)
+ {
+ return (AMQException) e;
+ }
+ else
+ {
+ AMQException amqe = new AMQException(AMQConstant
+ .getConstant(AMQConstant.INTERNAL_ERROR.getCode()),
+ e.getMessage(), e.getCause());
+ return amqe;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryQueue.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryQueue.java
new file mode 100644
index 0000000000..f54cb782c8
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryQueue.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * 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 javax.jms.JMSException;
+import javax.jms.TemporaryQueue;
+
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.Random;
+import java.util.UUID;
+
+/** AMQ implementation of a TemporaryQueue. */
+final class AMQTemporaryQueue extends AMQQueue implements TemporaryQueue, TemporaryDestination
+{
+
+
+ private final AMQSession _session;
+ private boolean _deleted;
+
+ /** Create a new instance of an AMQTemporaryQueue */
+ public AMQTemporaryQueue(AMQSession session)
+ {
+ super(session.getTemporaryQueueExchangeName(), new AMQShortString("TempQueue" + UUID.randomUUID()), true);
+ _session = session;
+ }
+
+ /** @see javax.jms.TemporaryQueue#delete() */
+ public synchronized void delete() throws JMSException
+ {
+ if (_session.hasConsumer(this))
+ {
+ throw new JMSException("Temporary Queue has consumers so cannot be deleted");
+ }
+ _deleted = true;
+
+ // Currently TemporaryQueue is set to be auto-delete which means that the queue will be deleted
+ // by the server when there are no more subscriptions to that queue. This is probably not
+ // quite right for JMSCompliance.
+ }
+
+ public AMQSession getSession()
+ {
+ return _session;
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java
new file mode 100644
index 0000000000..7b5781530b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTemporaryTopic.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * 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.framing.AMQShortString;
+
+import javax.jms.JMSException;
+import javax.jms.TemporaryTopic;
+import java.util.UUID;
+
+/**
+ * AMQ implementation of TemporaryTopic.
+ */
+class AMQTemporaryTopic extends AMQTopic implements TemporaryTopic, TemporaryDestination
+{
+
+ private final AMQSession _session;
+ private boolean _deleted;
+ /**
+ * Create new temporary topic.
+ */
+ public AMQTemporaryTopic(AMQSession session)
+ {
+ super(session.getTemporaryTopicExchangeName(),new AMQShortString("tmp_" + UUID.randomUUID()));
+ _session = session;
+ }
+
+ /**
+ * @see javax.jms.TemporaryTopic#delete()
+ */
+ public void delete() throws JMSException
+ {
+ if(_session.hasConsumer(this))
+ {
+ throw new JMSException("Temporary Topic has consumers so cannot be deleted");
+ }
+
+ _deleted = true;
+ // Currently TemporaryQueue is set to be auto-delete which means that the queue will be deleted
+ // by the server when there are no more subscriptions to that queue. This is probably not
+ // quite right for JMSCompliance.
+ }
+
+ public AMQSession getSession()
+ {
+ return _session;
+ }
+
+ public boolean isDeleted()
+ {
+ return _deleted;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java
new file mode 100644
index 0000000000..780dbcafc2
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopic.java
@@ -0,0 +1,224 @@
+/*
+ *
+ * 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 java.net.URISyntaxException;
+
+import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
+import javax.jms.Topic;
+
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.messaging.Address;
+import org.apache.qpid.url.BindingURL;
+
+public class AMQTopic extends AMQDestination implements Topic
+{
+ public AMQTopic(String address) throws URISyntaxException
+ {
+ super(address);
+ }
+
+ public AMQTopic(Address address) throws Exception
+ {
+ super(address);
+ }
+
+ /**
+ * Constructor for use in creating a topic using a BindingURL.
+ *
+ * @param binding The binding url object.
+ */
+ public AMQTopic(BindingURL binding)
+ {
+ super(binding);
+ }
+
+ public AMQTopic(AMQShortString exchange, AMQShortString routingKey, AMQShortString queueName)
+ {
+ super(exchange, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, routingKey, true, true, queueName, false);
+ }
+
+ public AMQTopic(AMQShortString exchange, AMQShortString routingKey, AMQShortString queueName,AMQShortString[] bindingKeys)
+ {
+ super(exchange, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, routingKey, true, true, queueName, false,bindingKeys);
+ }
+
+ public AMQTopic(AMQConnection conn, String routingKey)
+ {
+ this(conn.getDefaultTopicExchangeName(), new AMQShortString(routingKey));
+ }
+
+
+ public AMQTopic(AMQShortString exchangeName, String routingKey)
+ {
+ this(exchangeName, new AMQShortString(routingKey));
+ }
+
+ public AMQTopic(AMQShortString exchangeName, AMQShortString routingKey)
+ {
+ this(exchangeName, routingKey, null);
+ }
+
+ public AMQTopic(AMQShortString exchangeName, AMQShortString name, boolean isAutoDelete, AMQShortString queueName, boolean isDurable)
+ {
+ super(exchangeName, ExchangeDefaults.TOPIC_EXCHANGE_CLASS, name, true, isAutoDelete, queueName, isDurable);
+ }
+
+ protected AMQTopic(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName, boolean isDurable)
+ {
+ super(exchangeName, exchangeClass, routingKey, isExclusive, isAutoDelete, queueName, isDurable );
+ }
+
+ protected AMQTopic(AMQShortString exchangeName, AMQShortString exchangeClass, AMQShortString routingKey, boolean isExclusive,
+ boolean isAutoDelete, AMQShortString queueName, boolean isDurable,AMQShortString[] bindingKeys)
+ {
+ super(exchangeName, exchangeClass, routingKey, isExclusive, isAutoDelete, queueName, isDurable,bindingKeys);
+ }
+
+ public static AMQTopic createDurableTopic(Topic topic, String subscriptionName, AMQConnection connection)
+ throws JMSException
+ {
+ if (topic instanceof AMQDestination && topic instanceof javax.jms.Topic)
+ {
+ AMQDestination qpidTopic = (AMQDestination)topic;
+ if (qpidTopic.getDestSyntax() == DestSyntax.ADDR)
+ {
+ try
+ {
+ AMQTopic t = new AMQTopic(qpidTopic.getAddress());
+ AMQShortString queueName = getDurableTopicQueueName(subscriptionName, connection);
+ // link is never null if dest was created using an address string.
+ t.getLink().setName(queueName.asString());
+ t.getSourceNode().setAutoDelete(false);
+ t.getSourceNode().setDurable(true);
+
+ // The legacy fields are also populated just in case.
+ t.setQueueName(queueName);
+ t.setAutoDelete(false);
+ t.setDurable(true);
+ return t;
+ }
+ catch(Exception e)
+ {
+ JMSException ex = new JMSException("Error creating durable topic");
+ ex.initCause(e);
+ ex.setLinkedException(e);
+ throw ex;
+ }
+ }
+ else
+ {
+ return new AMQTopic(qpidTopic.getExchangeName(), qpidTopic.getRoutingKey(), false,
+ getDurableTopicQueueName(subscriptionName, connection),
+ true);
+ }
+ }
+ else
+ {
+ throw new InvalidDestinationException("The destination object used is not from this provider or of type javax.jms.Topic");
+ }
+ }
+
+ public static AMQShortString getDurableTopicQueueName(String subscriptionName, AMQConnection connection) throws JMSException
+ {
+ return new AMQShortString(connection.getClientID() + ":" + subscriptionName);
+ }
+
+ public String getTopicName() throws JMSException
+ {
+ if (getRoutingKey() != null)
+ {
+ return getRoutingKey().asString();
+ }
+ else if (getSubject() != null)
+ {
+ return getSubject();
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public AMQShortString getExchangeName()
+ {
+ if (super.getExchangeName() == null && super.getAddressName() != null)
+ {
+ return new AMQShortString(super.getAddressName());
+ }
+ else
+ {
+ return _exchangeName;
+ }
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ if (super.getRoutingKey() != null)
+ {
+ return super.getRoutingKey();
+ }
+ else if (getSubject() != null)
+ {
+ return new AMQShortString(getSubject());
+ }
+ else
+ {
+ setRoutingKey(new AMQShortString(""));
+ setSubject("");
+ return super.getRoutingKey();
+ }
+ }
+
+ public boolean isNameRequired()
+ {
+ return !isDurable();
+ }
+
+ /**
+ * Override since the queue is always private and we must ensure it remains null. If not,
+ * reuse of the topic when registering consumers will make all consumers listen on the same (private) queue rather
+ * than getting their own (private) queue.
+ * <p/>
+ * This is relatively nasty but it is difficult to come up with a more elegant solution, given
+ * the requirement in the case on AMQQueue and possibly other AMQDestination subclasses to
+ * use the underlying queue name even where it is server generated.
+ */
+ public void setQueueName(String queueName)
+ {
+ }
+
+ 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/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopicSessionAdaptor.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopicSessionAdaptor.java
new file mode 100644
index 0000000000..ec482a8f79
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQTopicSessionAdaptor.java
@@ -0,0 +1,226 @@
+/*
+ *
+ * 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 java.io.Serializable;
+
+import javax.jms.BytesMessage;
+import javax.jms.Destination;
+import javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.Session;
+import javax.jms.StreamMessage;
+import javax.jms.TemporaryQueue;
+import javax.jms.TemporaryTopic;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+
+public class AMQTopicSessionAdaptor implements TopicSession, AMQSessionAdapter
+{
+ protected final AMQSession _session;
+
+ public AMQTopicSessionAdaptor(Session session)
+ {
+ _session = (AMQSession) session;
+ }
+
+ public Topic createTopic(String string) throws JMSException
+ {
+ return _session.createTopic(string);
+ }
+
+ public TopicSubscriber createSubscriber(Topic topic) throws JMSException
+ {
+ return _session.createSubscriber(topic);
+ }
+
+ public TopicSubscriber createSubscriber(Topic topic, String string, boolean b) throws JMSException
+ {
+ return _session.createSubscriber(topic, string, b);
+ }
+
+ public TopicSubscriber createDurableSubscriber(Topic topic, String string) throws JMSException
+ {
+ return _session.createDurableSubscriber(topic, string);
+ }
+
+ public TopicSubscriber createDurableSubscriber(Topic topic, String string, String string1, boolean b) throws JMSException
+ {
+ return _session.createDurableSubscriber(topic, string, string1, b);
+ }
+
+ public TopicPublisher createPublisher(Topic topic) throws JMSException
+ {
+ return _session.createPublisher(topic);
+ }
+
+ public TemporaryTopic createTemporaryTopic() throws JMSException
+ {
+ return _session.createTemporaryTopic();
+ }
+
+ public void unsubscribe(String string) throws JMSException
+ {
+ _session.unsubscribe(string);
+ }
+
+ public BytesMessage createBytesMessage() throws JMSException
+ {
+ return _session.createBytesMessage();
+ }
+
+ public MapMessage createMapMessage() throws JMSException
+ {
+ return _session.createMapMessage();
+ }
+
+ public Message createMessage() throws JMSException
+ {
+ return _session.createMessage();
+ }
+
+ public ObjectMessage createObjectMessage() throws JMSException
+ {
+ return _session.createObjectMessage();
+ }
+
+ public ObjectMessage createObjectMessage(Serializable serializable) throws JMSException
+ {
+ return _session.createObjectMessage(serializable);
+ }
+
+ public StreamMessage createStreamMessage() throws JMSException
+ {
+ return _session.createStreamMessage();
+ }
+
+ public TextMessage createTextMessage() throws JMSException
+ {
+ return _session.createTextMessage();
+ }
+
+ public TextMessage createTextMessage(String string) throws JMSException
+ {
+ return _session.createTextMessage(string);
+ }
+
+ public boolean getTransacted() throws JMSException
+ {
+ return _session.getTransacted();
+ }
+
+ public int getAcknowledgeMode() throws JMSException
+ {
+ return _session.getAcknowledgeMode();
+ }
+
+ public void commit() throws JMSException
+ {
+ _session.commit();
+ }
+
+ public void rollback() throws JMSException
+ {
+ _session.rollback();
+ }
+
+ public void close() throws JMSException
+ {
+ _session.close();
+ }
+
+ public void recover() throws JMSException
+ {
+ _session.recover();
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ return _session.getMessageListener();
+ }
+
+ public void setMessageListener(MessageListener messageListener) throws JMSException
+ {
+ _session.setMessageListener(messageListener);
+ }
+
+ public void run()
+ {
+ _session.run();
+ }
+
+ public MessageProducer createProducer(Destination destination) throws JMSException
+ {
+ return _session.createProducer(destination);
+ }
+
+ public MessageConsumer createConsumer(Destination destination) throws JMSException
+ {
+ return _session.createConsumer(destination);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String string) throws JMSException
+ {
+ return _session.createConsumer(destination, string);
+ }
+
+ public MessageConsumer createConsumer(Destination destination, String string, boolean b) throws JMSException
+ {
+ return _session.createConsumer(destination, string, b);
+ }
+
+ //The following methods cannot be called from a TopicSession as per JMS spec
+ public Queue createQueue(String string) throws JMSException
+ {
+ throw new IllegalStateException("Cannot call createQueue from TopicSession");
+ }
+
+ public QueueBrowser createBrowser(Queue queue) throws JMSException
+ {
+ throw new IllegalStateException("Cannot call createBrowser from TopicSession");
+ }
+
+ public QueueBrowser createBrowser(Queue queue, String string) throws JMSException
+ {
+ throw new IllegalStateException("Cannot call createBrowser from TopicSession");
+ }
+
+ public TemporaryQueue createTemporaryQueue() throws JMSException
+ {
+ throw new IllegalStateException("Cannot call createTemporaryQueue from TopicSession");
+ }
+
+ public AMQSession getSession()
+ {
+ return _session;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/AMQUndefinedDestination.java b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQUndefinedDestination.java
new file mode 100644
index 0000000000..fa2afb3ee4
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/AMQUndefinedDestination.java
@@ -0,0 +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.client;
+
+import org.apache.qpid.framing.AMQShortString;
+
+public class AMQUndefinedDestination extends AMQDestination
+{
+
+ private static final AMQShortString UNKNOWN_EXCHANGE_CLASS = new AMQShortString("unknown");
+
+
+ public AMQUndefinedDestination(AMQShortString exchange, AMQShortString routingKey, AMQShortString queueName)
+ {
+ super(exchange, UNKNOWN_EXCHANGE_CLASS, routingKey, queueName);
+ }
+
+ public boolean isNameRequired()
+ {
+ return getAMQQueueName() == null;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java
new file mode 100644
index 0000000000..5d32863f2f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer.java
@@ -0,0 +1,1079 @@
+/*
+ *
+ * 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.client.failover.FailoverException;
+import org.apache.qpid.client.message.*;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.jms.MessageConsumer;
+import org.apache.qpid.jms.Session;
+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.SortedSet;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.TreeSet;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+public abstract class BasicMessageConsumer<U> extends Closeable implements MessageConsumer
+{
+ private static final Logger _logger = LoggerFactory.getLogger(BasicMessageConsumer.class);
+
+ /** The connection being used by this consumer */
+ protected final AMQConnection _connection;
+
+ protected final String _messageSelector;
+
+ private final boolean _noLocal;
+
+ protected AMQDestination _destination;
+
+ /**
+ * When true indicates that a blocking receive call is in progress
+ */
+ private final AtomicBoolean _receiving = new AtomicBoolean(false);
+ /**
+ * Holds an atomic reference to the listener installed.
+ */
+ private final AtomicReference<MessageListener> _messageListener = new AtomicReference<MessageListener>();
+
+ /** The consumer tag allows us to close the consumer by sending a jmsCancel method to the broker */
+ protected int _consumerTag;
+
+ /** We need to know the channel id when constructing frames */
+ protected final int _channelId;
+
+ /**
+ * Used in the blocking receive methods to receive a message from the Session thread. <p/> Or to notify of errors
+ * <p/> Argument true indicates we want strict FIFO semantics
+ */
+ protected final BlockingQueue _synchronousQueue;
+
+ protected final MessageFactoryRegistry _messageFactory;
+
+ protected final AMQSession _session;
+
+ 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 final FieldTable _arguments;
+
+ /**
+ * We store the high water prefetch field in order to be able to reuse it when resubscribing in the event of
+ * failover
+ */
+ 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 final int _prefetchLow;
+
+ /**
+ * We store the exclusive field in order to be able to reuse it when resubscribing in the event of failover
+ */
+ protected 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.
+ */
+ protected final int _acknowledgeMode;
+
+ /**
+ * Number of messages unacknowledged in DUPS_OK_ACKNOWLEDGE mode
+ */
+ private int _outstanding;
+
+ /**
+ * Switch to enable sending of acknowledgements when using DUPS_OK_ACKNOWLEDGE mode. Enabled when _outstannding
+ * number of msgs >= _prefetchHigh and disabled at < _prefetchLow
+ */
+ private boolean _dups_ok_acknowledge_send;
+
+ /**
+ * List of tags delievered, The last of which which should be acknowledged on commit in transaction mode.
+ */
+ private ConcurrentLinkedQueue<Long> _receivedDeliveryTags = new ConcurrentLinkedQueue<Long>();
+
+ /** The last tag that was "multiple" acknowledged on this session (if transacted) */
+ private long _lastAcked;
+
+ /** set of tags which have previously been acked; but not part of the multiple ack (transacted mode only) */
+ private final SortedSet<Long> _previouslyAcked = new TreeSet<Long>();
+
+ private final Object _commitLock = new Object();
+
+ /**
+ * The thread that was used to call receive(). This is important for being able to interrupt that thread if a
+ * receive() is in progress.
+ */
+ private Thread _receivingThread;
+
+
+ /**
+ * Used to store this consumer queue name
+ * Usefull when more than binding key should be used
+ */
+ private AMQShortString _queuename;
+
+ /**
+ * 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 final boolean _autoClose;
+
+ private final boolean _noConsume;
+ private List<StackTraceElement> _closedStack = null;
+
+
+
+ protected BasicMessageConsumer(int channelId, AMQConnection connection, AMQDestination destination,
+ String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory,
+ AMQSession session, AMQProtocolHandler protocolHandler,
+ FieldTable arguments, int prefetchHigh, int prefetchLow,
+ boolean exclusive, int acknowledgeMode, boolean noConsume, boolean autoClose)
+ {
+ _channelId = channelId;
+ _connection = connection;
+ _messageSelector = messageSelector;
+ _noLocal = noLocal;
+ _destination = destination;
+ _messageFactory = messageFactory;
+ _session = session;
+ _protocolHandler = protocolHandler;
+ _arguments = arguments;
+ _prefetchHigh = prefetchHigh;
+ _prefetchLow = prefetchLow;
+ _exclusive = exclusive;
+
+ _synchronousQueue = new LinkedBlockingQueue();
+ _autoClose = autoClose;
+ _noConsume = noConsume;
+
+ // Force queue browsers not to use acknowledge modes.
+ if (_noConsume)
+ {
+ _acknowledgeMode = Session.NO_ACKNOWLEDGE;
+ }
+ else
+ {
+ _acknowledgeMode = acknowledgeMode;
+ }
+ }
+
+ public AMQDestination getDestination()
+ {
+ return _destination;
+ }
+
+ public String getMessageSelector() throws JMSException
+ {
+ checkPreConditions();
+
+ return _messageSelector;
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ checkPreConditions();
+
+ return _messageListener.get();
+ }
+
+ public int getAcknowledgeMode()
+ {
+ return _acknowledgeMode;
+ }
+
+ protected boolean isMessageListenerSet()
+ {
+ return _messageListener.get() != null;
+ }
+
+ public void setMessageListener(final MessageListener messageListener) throws JMSException
+ {
+ checkPreConditions();
+
+ // if the current listener is non-null and the session is not stopped, then
+ // it is an error to call this method.
+
+ // i.e. it is only valid to call this method if
+ //
+ // (a) the connection is stopped, in which case the dispatcher is not running
+ // OR
+ // (b) the listener is null AND we are not receiving synchronously at present
+ //
+
+ if (!_session.getAMQConnection().started())
+ {
+ _messageListener.set(messageListener);
+ _session.setHasMessageListeners();
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(
+ "Session stopped : Message listener(" + messageListener + ") set for destination " + _destination);
+ }
+ }
+ else
+ {
+ if (_receiving.get())
+ {
+ throw new javax.jms.IllegalStateException("Another thread is already receiving synchronously.");
+ }
+
+ if (!_messageListener.compareAndSet(null, messageListener))
+ {
+ throw new javax.jms.IllegalStateException("Attempt to alter listener while session is started.");
+ }
+
+ _logger.debug("Message listener set for destination " + _destination);
+
+ if (messageListener != null)
+ {
+ //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.startDispatcherIfNecessary();
+
+ // If we already have messages on the queue, deliver them to the listener
+ Object o = _synchronousQueue.poll();
+ while (o != null)
+ {
+ notifyMessage((AbstractJMSMessage) o);
+ o = _synchronousQueue.poll();
+ }
+ }
+ }
+ }
+ }
+
+ protected void preApplicationProcessing(AbstractJMSMessage jmsMsg) throws JMSException
+ {
+ if (_session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+ {
+ _session.addUnacknowledgedMessage(jmsMsg.getDeliveryTag());
+ }
+
+ _session.setInRecovery(false);
+ preDeliver(jmsMsg);
+ }
+
+ /**
+ * @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.");
+ }
+
+ if (isMessageListenerSet())
+ {
+ throw new javax.jms.IllegalStateException("A listener has already been set.");
+ }
+
+ _receivingThread = Thread.currentThread();
+ return true;
+ }
+
+ private void releaseReceiving()
+ {
+ _receiving.set(false);
+ _receivingThread = null;
+ }
+
+ public FieldTable getArguments()
+ {
+ return _arguments;
+ }
+
+ public int getPrefetch()
+ {
+ return _prefetchHigh;
+ }
+
+ public int getPrefetchHigh()
+ {
+ return _prefetchHigh;
+ }
+
+ public int getPrefetchLow()
+ {
+ return _prefetchLow;
+ }
+
+ public boolean isNoLocal()
+ {
+ return _noLocal;
+ }
+
+ public boolean isExclusive()
+ {
+ return _exclusive;
+ }
+
+ public boolean isReceiving()
+ {
+ return _receiving.get();
+ }
+
+ public Message receive() throws JMSException
+ {
+ return receive(0);
+ }
+
+ public Message receive(long l) throws JMSException
+ {
+
+ checkPreConditions();
+
+ try
+ {
+ acquireReceiving(false);
+ }
+ catch (InterruptedException e)
+ {
+ _logger.warn("Interrupted acquire: " + e);
+ if (isClosed())
+ {
+ return null;
+ }
+ }
+
+ _session.startDispatcherIfNecessary();
+
+ try
+ {
+ Object o = getMessageFromQueue(l);
+ final AbstractJMSMessage m = returnMessageOrThrow(o);
+ if (m != null)
+ {
+ preApplicationProcessing(m);
+ postDeliver(m);
+ }
+ return m;
+ }
+ catch (InterruptedException e)
+ {
+ _logger.warn("Interrupted: " + e);
+
+ return null;
+ }
+ finally
+ {
+ releaseReceiving();
+ }
+ }
+
+ public Object getMessageFromQueue(long l) throws InterruptedException
+ {
+ Object o;
+ if (l > 0)
+ {
+ o = _synchronousQueue.poll(l, TimeUnit.MILLISECONDS);
+ }
+ else if (l < 0)
+ {
+ o = _synchronousQueue.poll();
+ }
+ else
+ {
+ o = _synchronousQueue.take();
+ }
+ return o;
+ }
+
+ abstract Message receiveBrowse() throws JMSException;
+
+ public Message receiveNoWait() throws JMSException
+ {
+ checkPreConditions();
+
+ try
+ {
+ 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)
+ {
+ preApplicationProcessing(m);
+ postDeliver(m);
+ }
+
+ return m;
+ }
+ catch (InterruptedException e)
+ {
+ _logger.warn("Interrupted: " + e);
+
+ return null;
+ }
+ finally
+ {
+ releaseReceiving();
+ }
+ }
+
+ /**
+ * We can get back either a Message or an exception from the queue. This method examines the argument and deals with
+ * it by throwing it (if an exception) or returning it (in any other case).
+ *
+ * @param o 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
+ */
+ private AbstractJMSMessage returnMessageOrThrow(Object o) throws JMSException
+ {
+ // errors are passed via the queue too since there is no way of interrupting the poll() via the API.
+ if (o instanceof Throwable)
+ {
+ JMSException e = new JMSException("Message consumer forcibly closed due to error: " + o);
+ e.initCause((Throwable) o);
+ if (o instanceof Exception)
+ {
+ e.setLinkedException((Exception) o);
+ }
+
+ throw e;
+ }
+ else if (o instanceof CloseConsumerMessage)
+ {
+ _closed.set(true);
+ deregisterConsumer();
+ return null;
+ }
+ else
+ {
+ return (AbstractJMSMessage) o;
+ }
+ }
+
+ public void close() throws JMSException
+ {
+ close(true);
+ }
+
+ public void close(boolean sendClose) throws JMSException
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Closing consumer:" + debugIdentity());
+ }
+
+ if (!_closed.getAndSet(true))
+ {
+ _closing.set(true);
+ if (_logger.isDebugEnabled())
+ {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ if (_closedStack != null)
+ {
+ _logger.debug(_consumerTag + " previously:" + _closedStack.toString());
+ }
+ else
+ {
+ _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1);
+ }
+ }
+
+ if (sendClose)
+ {
+ // The Synchronized block only needs to protect network traffic.
+ synchronized (_connection.getFailoverMutex())
+ {
+ try
+ {
+ // If the session is open or we are in the process
+ // of closing the session then send a cance
+ // no point otherwise as the connection will be gone
+ if (!_session.isClosed() || _session.isClosing())
+ {
+ sendCancel();
+ cleanupQueue();
+ }
+ }
+ catch (AMQException e)
+ {
+ throw new JMSAMQException("Error closing consumer: " + e, e);
+ }
+ catch (FailoverException e)
+ {
+ throw new JMSAMQException("FailoverException interrupted basic cancel.", e);
+ }
+ }
+ }
+ else
+ {
+ // FIXME: wow this is ugly
+ // //fixme this probably is not right
+ // if (!isNoConsume())
+ { // done in BasicCancelOK Handler but not sending one so just deregister.
+ deregisterConsumer();
+ }
+ }
+
+ // 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();
+ }
+ }
+ }
+
+ abstract void sendCancel() throws AMQException, FailoverException;
+
+ abstract void cleanupQueue() throws AMQException, FailoverException;
+
+ /**
+ * Called when you need to invalidate a consumer. Used for example when failover has occurred and the client has
+ * vetoed automatic resubscription. The caller must hold the failover mutex.
+ */
+ void markClosed()
+ {
+ // synchronized (_closed)
+ {
+ _closed.set(true);
+
+ if (_logger.isDebugEnabled())
+ {
+ if (_closedStack != null)
+ {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ _logger.debug(_consumerTag + " markClosed():"
+ + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1));
+ _logger.debug(_consumerTag + " previously:" + _closedStack.toString());
+ }
+ else
+ {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1);
+ }
+ }
+ }
+
+ deregisterConsumer();
+ }
+
+ /**
+ * @param closeMessage
+ * this message signals that we should close the browser
+ */
+ public void notifyCloseMessage(CloseConsumerMessage closeMessage)
+ {
+ if (isMessageListenerSet())
+ {
+ // Currently only possible to get this msg type with a browser.
+ // If we get the message here then we should probably just close
+ // this consumer.
+ // Though an AutoClose consumer with message listener is quite odd..
+ // Just log out the fact so we know where we are
+ _logger.warn("Using an AutoCloseconsumer with message listener is not supported.");
+ }
+ else
+ {
+ try
+ {
+ _synchronousQueue.put(closeMessage);
+ }
+ catch (InterruptedException e)
+ {
+ _logger.info(" SynchronousQueue.put interupted. Usually result of connection closing,"
+ + "but we shouldn't have close yet");
+ }
+ }
+ }
+
+
+ /**
+ * Called from the AMQSession when a message has arrived for this consumer. This methods handles both the case of a
+ * message listener or a synchronous receive() caller.
+ *
+ * @param messageFrame the raw unprocessed mesage
+ */
+ void notifyMessage(U messageFrame)
+ {
+ if (messageFrame instanceof CloseConsumerMessage)
+ {
+ notifyCloseMessage((CloseConsumerMessage) messageFrame);
+ return;
+ }
+
+
+
+ try
+ {
+ AbstractJMSMessage jmsMessage = createJMSMessageFromUnprocessedMessage(_session.getMessageDelegateFactory(), messageFrame);
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Message is of type: " + jmsMessage.getClass().getName());
+ }
+ notifyMessage(jmsMessage);
+ }
+ catch (Exception e)
+ {
+ if (e instanceof InterruptedException)
+ {
+ _logger.info("SynchronousQueue.put interupted. Usually result of connection closing");
+ }
+ else
+ {
+ _logger.error("Caught exception (dump follows) - ignoring...", e);
+ }
+ }
+ }
+
+ public abstract AbstractJMSMessage createJMSMessageFromUnprocessedMessage(AMQMessageDelegateFactory delegateFactory, U messageFrame)
+ throws Exception;
+
+ /** @param jmsMessage this message has already been processed so can't redo preDeliver */
+ public void notifyMessage(AbstractJMSMessage jmsMessage)
+ {
+ try
+ {
+ if (isMessageListenerSet())
+ {
+ preApplicationProcessing(jmsMessage);
+ getMessageListener().onMessage(jmsMessage);
+ postDeliver(jmsMessage);
+ }
+ else
+ {
+ // we should not be allowed to add a message is the
+ // consumer is closed
+ _synchronousQueue.put(jmsMessage);
+ }
+ }
+ catch (Exception e)
+ {
+ if (e instanceof InterruptedException)
+ {
+ _logger.info("reNotification : SynchronousQueue.put interupted. Usually result of connection closing");
+ }
+ else
+ {
+ _logger.error("reNotification : Caught exception (dump follows) - ignoring...", e);
+ }
+ }
+ }
+
+ void preDeliver(AbstractJMSMessage msg)
+ {
+ switch (_acknowledgeMode)
+ {
+
+ case Session.PRE_ACKNOWLEDGE:
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ break;
+
+ case Session.CLIENT_ACKNOWLEDGE:
+ // we set the session so that when the user calls acknowledge() it can call the method on session
+ // to send out the appropriate frame
+ msg.setAMQSession(_session);
+ break;
+ case Session.SESSION_TRANSACTED:
+ if (isNoConsume())
+ {
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ }
+ else
+ {
+ _session.addDeliveredMessage(msg.getDeliveryTag());
+ _session.markDirty();
+ }
+
+ break;
+ }
+
+ }
+
+ void postDeliver(AbstractJMSMessage msg) throws JMSException
+ {
+ switch (_acknowledgeMode)
+ {
+
+ case Session.CLIENT_ACKNOWLEDGE:
+ if (isNoConsume())
+ {
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ }
+ _session.markDirty();
+ break;
+
+ case Session.DUPS_OK_ACKNOWLEDGE:
+ case Session.AUTO_ACKNOWLEDGE:
+ // we do not auto ack a message if the application code called recover()
+ if (!_session.isInRecovery())
+ {
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ }
+
+ break;
+ }
+ }
+
+
+ /**
+ * Acknowledge up to last message delivered (if any). Used when commiting.
+ *
+ * @return the lastDeliveryTag to acknowledge
+ */
+ Long getLastDelivered()
+ {
+ if (!_receivedDeliveryTags.isEmpty())
+ {
+ Long lastDeliveryTag = _receivedDeliveryTags.poll();
+
+ while (!_receivedDeliveryTags.isEmpty())
+ {
+ lastDeliveryTag = _receivedDeliveryTags.poll();
+ }
+
+ assert _receivedDeliveryTags.isEmpty();
+
+ return lastDeliveryTag;
+ }
+
+ return null;
+ }
+
+ /**
+ * Acknowledge up to last message delivered (if any). Used when commiting.
+ */
+ void acknowledgeDelivered()
+ {
+ synchronized(_commitLock)
+ {
+ ArrayList<Long> tagsToAck = new ArrayList<Long>();
+
+ while (!_receivedDeliveryTags.isEmpty())
+ {
+ tagsToAck.add(_receivedDeliveryTags.poll());
+ }
+
+ Collections.sort(tagsToAck);
+
+ long prevAcked = _lastAcked;
+ long oldAckPoint = -1;
+
+ while(oldAckPoint != prevAcked)
+ {
+ oldAckPoint = prevAcked;
+
+ Iterator<Long> tagsToAckIterator = tagsToAck.iterator();
+
+ while(tagsToAckIterator.hasNext() && tagsToAckIterator.next() == prevAcked+1)
+ {
+ tagsToAckIterator.remove();
+ prevAcked++;
+ }
+
+ Iterator<Long> previousAckIterator = _previouslyAcked.iterator();
+ while(previousAckIterator.hasNext() && previousAckIterator.next() == prevAcked+1)
+ {
+ previousAckIterator.remove();
+ prevAcked++;
+ }
+
+ }
+ if(prevAcked != _lastAcked)
+ {
+ _session.acknowledgeMessage(prevAcked, true);
+ _lastAcked = prevAcked;
+ }
+
+ Iterator<Long> tagsToAckIterator = tagsToAck.iterator();
+
+ while(tagsToAckIterator.hasNext())
+ {
+ Long tag = tagsToAckIterator.next();
+ _session.acknowledgeMessage(tag, false);
+ _previouslyAcked.add(tag);
+ }
+ }
+ }
+
+
+ void notifyError(Throwable cause)
+ {
+ // synchronized (_closed)
+ {
+ _closed.set(true);
+ if (_logger.isDebugEnabled())
+ {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ if (_closedStack != null)
+ {
+ _logger.debug(_consumerTag + " notifyError():"
+ + Arrays.asList(stackTrace).subList(3, stackTrace.length - 1));
+ _logger.debug(_consumerTag + " previously" + _closedStack.toString());
+ }
+ else
+ {
+ _closedStack = Arrays.asList(stackTrace).subList(3, stackTrace.length - 1);
+ }
+ }
+ }
+ // QPID-293 can "request redelivery of this error through dispatcher"
+
+ // we have no way of propagating the exception to a message listener - a JMS limitation - so we
+ // deal with the case where we have a synchronous receive() waiting for a message to arrive
+ if (!isMessageListenerSet())
+ {
+ // offer only succeeds if there is a thread waiting for an item from the queue
+ if (_synchronousQueue.offer(cause))
+ {
+ _logger.debug("Passed exception to synchronous queue for propagation to receive()");
+ }
+ }
+
+ deregisterConsumer();
+ }
+
+ /**
+ * Perform cleanup to deregister this consumer. This occurs when closing the consumer in both the clean case and in
+ * the case of an error occurring.
+ */
+ private void deregisterConsumer()
+ {
+ _session.deregisterConsumer(this);
+ }
+
+ public int getConsumerTag()
+ {
+ return _consumerTag;
+ }
+
+ public void setConsumerTag(int consumerTag)
+ {
+ _consumerTag = consumerTag;
+ }
+
+ public AMQSession getSession()
+ {
+ return _session;
+ }
+
+ private void checkPreConditions() throws JMSException
+ {
+
+ this.checkNotClosed();
+
+ if ((_session == null) || _session.isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Invalid Session");
+ }
+ }
+
+ public boolean isAutoClose()
+ {
+ return _autoClose;
+ }
+
+ public boolean isNoConsume()
+ {
+ return _noConsume || _destination.isBrowseOnly() ;
+ }
+
+ public void rollback()
+ {
+ rollbackPendingMessages();
+ }
+
+ public void rollbackPendingMessages()
+ {
+ if (_synchronousQueue.size() > 0)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Rejecting the messages(" + _synchronousQueue
+ .size() + ") in _syncQueue (PRQ)" + "for consumer with tag:" + _consumerTag);
+ }
+
+ Iterator iterator = _synchronousQueue.iterator();
+
+ int initialSize = _synchronousQueue.size();
+
+ boolean removed = false;
+ while (iterator.hasNext())
+ {
+
+ Object o = iterator.next();
+ if (o instanceof AbstractJMSMessage)
+ {
+ _session.rejectMessage(((AbstractJMSMessage) o), true);
+
+ if (_logger.isDebugEnabled())
+ {
+ _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");
+ 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());
+ rollback();
+ }
+
+ clearReceiveQueue();
+ }
+ }
+
+ public String debugIdentity()
+ {
+ return String.valueOf(_consumerTag) + "[" + System.identityHashCode(this) + "]";
+ }
+
+ public void clearReceiveQueue()
+ {
+ _synchronousQueue.clear();
+ }
+
+
+ public List<Long> drainReceiverQueueAndRetrieveDeliveryTags()
+ {
+ Iterator<AbstractJMSMessage> iterator = _synchronousQueue.iterator();
+ List<Long> tags = new ArrayList<Long>(_synchronousQueue.size());
+
+ while (iterator.hasNext())
+ {
+
+ AbstractJMSMessage msg = iterator.next();
+ tags.add(msg.getDeliveryTag());
+ iterator.remove();
+ }
+ return tags;
+ }
+
+ public AMQShortString getQueuename()
+ {
+ return _queuename;
+ }
+
+ public void setQueuename(AMQShortString queuename)
+ {
+ this._queuename = queuename;
+ }
+
+ public void addBindingKey(AMQDestination amqd, String routingKey) throws AMQException
+ {
+ _session.addBindingKey(this,amqd,routingKey);
+ }
+
+ /** to be called when a failover has occured */
+ public void failedOverPre()
+ {
+ clearReceiveQueue();
+ // TGM FIXME: think this should just be removed
+ // clearUnackedMessages();
+ }
+
+ public void failedOverPost() {}
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java
new file mode 100644
index 0000000000..964c238946
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_10.java
@@ -0,0 +1,529 @@
+/* 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.qpid.client.AMQDestination.AddressOption;
+import org.apache.qpid.client.AMQDestination.DestSyntax;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.message.*;
+import org.apache.qpid.client.messaging.address.Node.QueueNode;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.transport.*;
+import org.apache.qpid.filter.MessageFilter;
+import org.apache.qpid.filter.JMSSelectorFilter;
+
+import javax.jms.InvalidSelectorException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This is a 0.10 message consumer.
+ */
+public class BasicMessageConsumer_0_10 extends BasicMessageConsumer<UnprocessedMessage_0_10>
+{
+
+ /**
+ * This class logger
+ */
+ protected final Logger _logger = LoggerFactory.getLogger(getClass());
+
+ /**
+ * The message selector filter associated with this consumer message selector
+ */
+ private MessageFilter _filter = null;
+
+ /**
+ * The underlying QpidSession
+ */
+ private AMQSession_0_10 _0_10session;
+
+ /**
+ * Indicates whether this consumer receives pre-acquired messages
+ */
+ private boolean _preAcquire = true;
+
+ /**
+ * Indicate whether this consumer is started.
+ */
+ private boolean _isStarted = false;
+
+ /**
+ * Specify whether this consumer is performing a sync receive
+ */
+ private final AtomicBoolean _syncReceive = new AtomicBoolean(false);
+ private String _consumerTagString;
+
+ private long capacity = 0;
+
+ //--- constructor
+ protected BasicMessageConsumer_0_10(int channelId, AMQConnection connection, AMQDestination destination,
+ String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory,
+ AMQSession session, AMQProtocolHandler protocolHandler,
+ FieldTable arguments, int prefetchHigh, int prefetchLow,
+ boolean exclusive, int acknowledgeMode, boolean noConsume, boolean autoClose)
+ throws JMSException
+ {
+ super(channelId, connection, destination, messageSelector, noLocal, messageFactory, session, protocolHandler,
+ arguments, prefetchHigh, prefetchLow, exclusive, acknowledgeMode, noConsume, autoClose);
+ _0_10session = (AMQSession_0_10) session;
+ if (messageSelector != null && !messageSelector.equals(""))
+ {
+ try
+ {
+ _filter = new JMSSelectorFilter(messageSelector);
+ }
+ catch (AMQInternalException e)
+ {
+ throw new InvalidSelectorException("cannot create consumer because of selector issue");
+ }
+ if (destination instanceof AMQQueue)
+ {
+ _preAcquire = false;
+ }
+ }
+ _isStarted = connection.started();
+
+ // Destination setting overrides connection defaults
+ if (destination.getDestSyntax() == DestSyntax.ADDR &&
+ destination.getLink().getConsumerCapacity() > 0)
+ {
+ capacity = destination.getLink().getConsumerCapacity();
+ }
+ else if (getSession().prefetch())
+ {
+ capacity = _0_10session.getAMQConnection().getMaxPrefetch();
+ }
+
+ if (destination.isAddressResolved() && AMQDestination.TOPIC_TYPE == destination.getAddressType())
+ {
+ boolean namedQueue = destination.getLink() != null && destination.getLink().getName() != null ;
+
+ if (!namedQueue)
+ {
+ _destination = destination.copyDestination();
+ _destination.setQueueName(null);
+ }
+ }
+ }
+
+
+ @Override public void setConsumerTag(int consumerTag)
+ {
+ super.setConsumerTag(consumerTag);
+ _consumerTagString = String.valueOf(consumerTag);
+ }
+
+ public String getConsumerTagString()
+ {
+ return _consumerTagString;
+ }
+
+ /**
+ *
+ * This is invoked by the session thread when emptying the session message queue.
+ * We first check if the message is valid (match the selector) and then deliver it to the
+ * message listener or to the sync consumer queue.
+ *
+ * @param jmsMessage this message has already been processed so can't redo preDeliver
+ */
+ @Override public void notifyMessage(AbstractJMSMessage jmsMessage)
+ {
+ try
+ {
+ if (checkPreConditions(jmsMessage))
+ {
+ if (isMessageListenerSet() && capacity == 0)
+ {
+ _0_10session.getQpidSession().messageFlow(getConsumerTagString(),
+ MessageCreditUnit.MESSAGE, 1,
+ Option.UNRELIABLE);
+ }
+ _logger.debug("messageOk, trying to notify");
+ super.notifyMessage(jmsMessage);
+ }
+ }
+ catch (AMQException e)
+ {
+ _logger.error("Receivecd an Exception when receiving message",e);
+ getSession().getAMQConnection().exceptionReceived(e);
+ }
+ }
+
+ //----- overwritten methods
+
+ /**
+ * This method is invoked when this consumer is stopped.
+ * It tells the broker to stop delivering messages to this consumer.
+ */
+ @Override void sendCancel() throws AMQException
+ {
+ _0_10session.getQpidSession().messageCancel(getConsumerTagString());
+ try
+ {
+ _0_10session.getQpidSession().sync();
+ getSession().confirmConsumerCancelled(getConsumerTag()); // confirm cancel
+ }
+ catch (SessionException se)
+ {
+ _0_10session.setCurrentException(se);
+ }
+
+ AMQException amqe = _0_10session.getCurrentException();
+ if (amqe != null)
+ {
+ throw amqe;
+ }
+ }
+
+ @Override void notifyMessage(UnprocessedMessage_0_10 messageFrame)
+ {
+ super.notifyMessage(messageFrame);
+ }
+
+ @Override protected void preApplicationProcessing(AbstractJMSMessage jmsMsg) throws JMSException
+ {
+ super.preApplicationProcessing(jmsMsg);
+ if (!_session.getTransacted() && _session.getAcknowledgeMode() != org.apache.qpid.jms.Session.CLIENT_ACKNOWLEDGE)
+ {
+ _session.addUnacknowledgedMessage(jmsMsg.getDeliveryTag());
+ }
+ }
+
+ @Override public AbstractJMSMessage createJMSMessageFromUnprocessedMessage(
+ AMQMessageDelegateFactory delegateFactory, UnprocessedMessage_0_10 msg) throws Exception
+ {
+ AMQMessageDelegate_0_10.updateExchangeTypeMapping(msg.getMessageTransfer().getHeader(), ((AMQSession_0_10)getSession()).getQpidSession());
+ return _messageFactory.createMessage(msg.getMessageTransfer());
+ }
+
+ // private methods
+ /**
+ * Check whether a message can be delivered to this consumer.
+ *
+ * @param message The message to be checked.
+ * @return true if the message matches the selector and can be acquired, false otherwise.
+ * @throws AMQException If the message preConditions cannot be checked due to some internal error.
+ */
+ private boolean checkPreConditions(AbstractJMSMessage message) throws AMQException
+ {
+ boolean messageOk = true;
+ // TODO Use a tag for fiding out if message filtering is done here or by the broker.
+ try
+ {
+ if (_messageSelector != null && !_messageSelector.equals(""))
+ {
+ messageOk = _filter.matches(message);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new AMQException(AMQConstant.INTERNAL_ERROR, "Error when evaluating message selector", e);
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("messageOk " + messageOk);
+ _logger.debug("_preAcquire " + _preAcquire);
+ }
+ if (!messageOk)
+ {
+ if (_preAcquire)
+ {
+ // this is the case for topics
+ // We need to ack this message
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("filterMessage - trying to ack message");
+ }
+ acknowledgeMessage(message);
+ }
+ else
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Message not OK, releasing");
+ }
+ releaseMessage(message);
+ }
+ // if we are syncrhonously waiting for a message
+ // and messages are not prefetched we then need to request another one
+ if(capacity == 0)
+ {
+ _0_10session.getQpidSession().messageFlow(getConsumerTagString(),
+ MessageCreditUnit.MESSAGE, 1,
+ Option.UNRELIABLE);
+ }
+ }
+ // now we need to acquire this message if needed
+ // this is the case of queue with a message selector set
+ if (!_preAcquire && messageOk && !isNoConsume())
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("filterMessage - trying to acquire message");
+ }
+ messageOk = acquireMessage(message);
+ _logger.debug("filterMessage - message acquire status : " + messageOk);
+ }
+ return messageOk;
+ }
+
+
+ /**
+ * Acknowledge a message
+ *
+ * @param message The message to be acknowledged
+ * @throws AMQException If the message cannot be acquired due to some internal error.
+ */
+ private void acknowledgeMessage(AbstractJMSMessage message) throws AMQException
+ {
+ if (!_preAcquire)
+ {
+ RangeSet ranges = new RangeSet();
+ ranges.add((int) message.getDeliveryTag());
+ _0_10session.messageAcknowledge
+ (ranges,
+ _acknowledgeMode != org.apache.qpid.jms.Session.NO_ACKNOWLEDGE);
+
+ AMQException amqe = _0_10session.getCurrentException();
+ if (amqe != null)
+ {
+ throw amqe;
+ }
+ }
+ }
+
+ /**
+ * Release a message
+ *
+ * @param message The message to be released
+ * @throws AMQException If the message cannot be released due to some internal error.
+ */
+ private void releaseMessage(AbstractJMSMessage message) throws AMQException
+ {
+ if (_preAcquire)
+ {
+ RangeSet ranges = new RangeSet();
+ ranges.add((int) message.getDeliveryTag());
+ _0_10session.getQpidSession().messageRelease(ranges);
+ _0_10session.sync();
+ }
+ }
+
+ /**
+ * Acquire a message
+ *
+ * @param message The message to be acquired
+ * @return true if the message has been acquired, false otherwise.
+ * @throws AMQException If the message cannot be acquired due to some internal error.
+ */
+ private boolean acquireMessage(AbstractJMSMessage message) throws AMQException
+ {
+ boolean result = false;
+ if (!_preAcquire)
+ {
+ RangeSet ranges = new RangeSet();
+ ranges.add((int) message.getDeliveryTag());
+
+ Acquired acq = _0_10session.getQpidSession().messageAcquire(ranges).get();
+
+ RangeSet acquired = acq.getTransfers();
+ if (acquired != null && acquired.size() > 0)
+ {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+
+ public void setMessageListener(final MessageListener messageListener) throws JMSException
+ {
+ super.setMessageListener(messageListener);
+ if (messageListener != null && capacity == 0)
+ {
+ _0_10session.getQpidSession().messageFlow(getConsumerTagString(),
+ MessageCreditUnit.MESSAGE, 1,
+ Option.UNRELIABLE);
+ }
+ if (messageListener != null && !_synchronousQueue.isEmpty())
+ {
+ Iterator messages=_synchronousQueue.iterator();
+ while (messages.hasNext())
+ {
+ AbstractJMSMessage message=(AbstractJMSMessage) messages.next();
+ messages.remove();
+ _session.rejectMessage(message, true);
+ }
+ }
+ }
+
+ public void failedOverPost()
+ {
+ if (_0_10session.isStarted() && _syncReceive.get())
+ {
+ _0_10session.getQpidSession().messageFlow
+ (getConsumerTagString(), MessageCreditUnit.MESSAGE, 1,
+ Option.UNRELIABLE);
+ }
+ }
+
+ /**
+ * When messages are not prefetched we need to request a message from the
+ * broker.
+ * Note that if the timeout is too short a message may be queued in _synchronousQueue until
+ * this consumer closes or request it.
+ * @param l
+ * @return
+ * @throws InterruptedException
+ */
+ public Object getMessageFromQueue(long l) throws InterruptedException
+ {
+ if (capacity == 0)
+ {
+ _syncReceive.set(true);
+ }
+ if (_0_10session.isStarted() && capacity == 0 && _synchronousQueue.isEmpty())
+ {
+ _0_10session.getQpidSession().messageFlow(getConsumerTagString(),
+ MessageCreditUnit.MESSAGE, 1,
+ Option.UNRELIABLE);
+ }
+ Object o = super.getMessageFromQueue(l);
+ if (o == null && _0_10session.isStarted())
+ {
+
+ _0_10session.getQpidSession().messageFlush
+ (getConsumerTagString(), Option.UNRELIABLE, Option.SYNC);
+ _0_10session.getQpidSession().sync();
+ _0_10session.getQpidSession().messageFlow
+ (getConsumerTagString(), MessageCreditUnit.BYTE,
+ 0xFFFFFFFF, Option.UNRELIABLE);
+
+ if (capacity > 0)
+ {
+ _0_10session.getQpidSession().messageFlow
+ (getConsumerTagString(),
+ MessageCreditUnit.MESSAGE,
+ capacity,
+ Option.UNRELIABLE);
+ }
+ _0_10session.syncDispatchQueue();
+ o = super.getMessageFromQueue(-1);
+ }
+ if (capacity == 0)
+ {
+ _syncReceive.set(false);
+ }
+ return o;
+ }
+
+ void postDeliver(AbstractJMSMessage msg) throws JMSException
+ {
+ super.postDeliver(msg);
+ if (_acknowledgeMode == org.apache.qpid.jms.Session.NO_ACKNOWLEDGE && !_session.isInRecovery())
+ {
+ _session.acknowledgeMessage(msg.getDeliveryTag(), false);
+ }
+
+ if (_acknowledgeMode == org.apache.qpid.jms.Session.AUTO_ACKNOWLEDGE &&
+ !_session.isInRecovery() &&
+ _session.getAMQConnection().getSyncAck())
+ {
+ ((AMQSession_0_10) getSession()).flushAcknowledgments();
+ ((AMQSession_0_10) getSession()).getQpidSession().sync();
+ }
+ }
+
+ Message receiveBrowse() throws JMSException
+ {
+ return receiveNoWait();
+ }
+
+ @Override public void rollbackPendingMessages()
+ {
+ if (_synchronousQueue.size() > 0)
+ {
+ RangeSet ranges = new RangeSet();
+ Iterator iterator = _synchronousQueue.iterator();
+ while (iterator.hasNext())
+ {
+
+ Object o = iterator.next();
+ if (o instanceof AbstractJMSMessage)
+ {
+ ranges.add((int) ((AbstractJMSMessage) o).getDeliveryTag());
+ iterator.remove();
+ }
+ else
+ {
+ _logger.error("Queue contained a :" + o.getClass()
+ + " unable to reject as it is not an AbstractJMSMessage. Will be cleared");
+ iterator.remove();
+ }
+ }
+
+ _0_10session.getQpidSession().messageRelease(ranges, Option.SET_REDELIVERED);
+ clearReceiveQueue();
+ }
+ }
+
+ public boolean isExclusive()
+ {
+ AMQDestination dest = this.getDestination();
+ if (dest.getDestSyntax() == AMQDestination.DestSyntax.ADDR)
+ {
+ if (dest.getAddressType() == AMQDestination.TOPIC_TYPE)
+ {
+ return true;
+ }
+ else
+ {
+ return dest.getLink().getSubscription().isExclusive();
+ }
+ }
+ else
+ {
+ return _exclusive;
+ }
+ }
+
+ void cleanupQueue() throws AMQException, FailoverException
+ {
+ AMQDestination dest = this.getDestination();
+ if (dest != null && dest.getDestSyntax() == AMQDestination.DestSyntax.ADDR)
+ {
+ if (dest.getDelete() == AddressOption.ALWAYS ||
+ dest.getDelete() == AddressOption.RECEIVER )
+ {
+ ((AMQSession_0_10) getSession()).getQpidSession().queueDelete(
+ this.getDestination().getQueueName());
+ }
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.java
new file mode 100644
index 0000000000..00acd5e866
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageConsumer_0_8.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.client;
+
+import javax.jms.InvalidSelectorException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.message.*;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.filter.JMSSelectorFilter;
+import org.apache.qpid.framing.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BasicMessageConsumer_0_8 extends BasicMessageConsumer<UnprocessedMessage_0_8>
+{
+ protected final Logger _logger = LoggerFactory.getLogger(getClass());
+
+ protected BasicMessageConsumer_0_8(int channelId, AMQConnection connection, AMQDestination destination,
+ String messageSelector, boolean noLocal, MessageFactoryRegistry messageFactory, AMQSession session,
+ AMQProtocolHandler protocolHandler, FieldTable arguments, int prefetchHigh, int prefetchLow,
+ boolean exclusive, int acknowledgeMode, boolean noConsume, boolean autoClose) throws JMSException
+ {
+ super(channelId, connection, destination,messageSelector,noLocal,messageFactory,session,
+ protocolHandler, arguments, prefetchHigh, prefetchLow, exclusive,
+ acknowledgeMode, noConsume, autoClose);
+ try
+ {
+
+ if (messageSelector != null && messageSelector.length() > 0)
+ {
+ JMSSelectorFilter _filter = new JMSSelectorFilter(messageSelector);
+ }
+ }
+ catch (AMQInternalException e)
+ {
+ throw new InvalidSelectorException("cannot create consumer because of selector issue");
+ }
+ }
+
+ void sendCancel() throws AMQException, FailoverException
+ {
+ BasicCancelBody body = getSession().getMethodRegistry().createBasicCancelBody(new AMQShortString(String.valueOf(_consumerTag)), false);
+
+ final AMQFrame cancelFrame = body.generateFrame(_channelId);
+
+ _protocolHandler.syncWrite(cancelFrame, BasicCancelOkBody.class);
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("CancelOk'd for consumer:" + debugIdentity());
+ }
+ }
+
+ public AbstractJMSMessage createJMSMessageFromUnprocessedMessage(AMQMessageDelegateFactory delegateFactory, UnprocessedMessage_0_8 messageFrame)throws Exception
+ {
+
+ return _messageFactory.createMessage(messageFrame.getDeliveryTag(),
+ messageFrame.isRedelivered(), messageFrame.getExchange(),
+ messageFrame.getRoutingKey(), messageFrame.getContentHeader(), messageFrame.getBodies());
+
+ }
+
+ Message receiveBrowse() throws JMSException
+ {
+ return receive();
+ }
+
+ void cleanupQueue() throws AMQException, FailoverException
+ {
+
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java
new file mode 100644
index 0000000000..8756ac4d05
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer.java
@@ -0,0 +1,601 @@
+/*
+ *
+ * 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 java.io.UnsupportedEncodingException;
+import java.util.UUID;
+
+import javax.jms.BytesMessage;
+import javax.jms.DeliveryMode;
+import javax.jms.Destination;
+import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.ObjectMessage;
+import javax.jms.StreamMessage;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.MessageConverter;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.util.UUIDGen;
+import org.apache.qpid.util.UUIDs;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class BasicMessageProducer extends Closeable implements org.apache.qpid.jms.MessageProducer
+{
+ enum PublishMode { ASYNC_PUBLISH_ALL, SYNC_PUBLISH_PERSISTENT, SYNC_PUBLISH_ALL };
+
+ protected final Logger _logger = LoggerFactory.getLogger(getClass());
+
+ private AMQConnection _connection;
+
+ /**
+ * If true, messages will not get a timestamp.
+ */
+ protected boolean _disableTimestamps;
+
+ /**
+ * Priority of messages created by this producer.
+ */
+ private int _messagePriority = Message.DEFAULT_PRIORITY;
+
+ /**
+ * Time to live of messages. Specified in milliseconds but AMQ has 1 second resolution.
+ */
+ private long _timeToLive;
+
+ /**
+ * Delivery mode used for this producer.
+ */
+ private int _deliveryMode = DeliveryMode.PERSISTENT;
+
+ /**
+ * The Destination used for this consumer, if specified upon creation.
+ */
+ protected AMQDestination _destination;
+
+ /**
+ * Default encoding used for messages produced by this producer.
+ */
+ private String _encoding;
+
+ /**
+ * Default encoding used for message produced by this producer.
+ */
+ private String _mimeType;
+
+ protected AMQProtocolHandler _protocolHandler;
+
+ /**
+ * True if this producer was created from a transacted session
+ */
+ private boolean _transacted;
+
+ protected int _channelId;
+
+ /**
+ * This is an id generated by the session and is used to tie individual producers to the session. This means we
+ * can deregister a producer with the session when the producer is clsoed. We need to be able to tie producers
+ * to the session so that when an error is propagated to the session it can close the producer (meaning that
+ * a client that happens to hold onto a producer reference will get an error if he tries to use it subsequently).
+ */
+ private long _producerId;
+
+ /**
+ * The session used to create this producer
+ */
+ protected AMQSession _session;
+
+ private final boolean _immediate;
+
+ private final boolean _mandatory;
+
+ private final boolean _waitUntilSent;
+
+ private boolean _disableMessageId;
+
+ private UUIDGen _messageIdGenerator = UUIDs.newGenerator();
+
+ protected String _userID; // ref user id used in the connection.
+
+ private static final ContentBody[] NO_CONTENT_BODIES = new ContentBody[0];
+
+ protected PublishMode publishMode = PublishMode.ASYNC_PUBLISH_ALL;
+
+ protected BasicMessageProducer(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId,
+ AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory,
+ boolean waitUntilSent) throws AMQException
+ {
+ _connection = connection;
+ _destination = destination;
+ _transacted = transacted;
+ _protocolHandler = protocolHandler;
+ _channelId = channelId;
+ _session = session;
+ _producerId = producerId;
+ if (destination != null && !(destination instanceof AMQUndefinedDestination))
+ {
+ declareDestination(destination);
+ }
+
+ _immediate = immediate;
+ _mandatory = mandatory;
+ _waitUntilSent = waitUntilSent;
+ _userID = connection.getUsername();
+ setPublishMode();
+ }
+
+ void setPublishMode()
+ {
+ // Publish mode could be configured at destination level as well.
+ // Will add support for this when we provide a more robust binding URL
+
+ String syncPub = _connection.getSyncPublish();
+ // Support for deprecated option sync_persistence
+ if (syncPub.equals("persistent") || _connection.getSyncPersistence())
+ {
+ publishMode = PublishMode.SYNC_PUBLISH_PERSISTENT;
+ }
+ else if (syncPub.equals("all"))
+ {
+ publishMode = PublishMode.SYNC_PUBLISH_ALL;
+ }
+
+ _logger.info("MessageProducer " + toString() + " using publish mode : " + publishMode);
+ }
+
+ void resubscribe() throws AMQException
+ {
+ if (_destination != null && !(_destination instanceof AMQUndefinedDestination))
+ {
+ declareDestination(_destination);
+ }
+ }
+
+ abstract void declareDestination(AMQDestination destination) throws AMQException;
+
+ public void setDisableMessageID(boolean b) throws JMSException
+ {
+ checkPreConditions();
+ checkNotClosed();
+ _disableMessageId = b;
+ }
+
+ public boolean getDisableMessageID() throws JMSException
+ {
+ checkNotClosed();
+
+ return _disableMessageId;
+ }
+
+ public void setDisableMessageTimestamp(boolean b) throws JMSException
+ {
+ checkPreConditions();
+ _disableTimestamps = b;
+ }
+
+ public boolean getDisableMessageTimestamp() throws JMSException
+ {
+ checkNotClosed();
+
+ return _disableTimestamps;
+ }
+
+ public void setDeliveryMode(int i) throws JMSException
+ {
+ checkPreConditions();
+ if ((i != DeliveryMode.NON_PERSISTENT) && (i != DeliveryMode.PERSISTENT))
+ {
+ throw new JMSException("DeliveryMode must be either NON_PERSISTENT or PERSISTENT. Value of " + i
+ + " is illegal");
+ }
+
+ _deliveryMode = i;
+ }
+
+ public int getDeliveryMode() throws JMSException
+ {
+ checkNotClosed();
+
+ return _deliveryMode;
+ }
+
+ public void setPriority(int i) throws JMSException
+ {
+ checkPreConditions();
+ if ((i < 0) || (i > 9))
+ {
+ throw new IllegalArgumentException("Priority of " + i + " is illegal. Value must be in range 0 to 9");
+ }
+
+ _messagePriority = i;
+ }
+
+ public int getPriority() throws JMSException
+ {
+ checkNotClosed();
+
+ return _messagePriority;
+ }
+
+ public void setTimeToLive(long l) throws JMSException
+ {
+ checkPreConditions();
+ if (l < 0)
+ {
+ throw new IllegalArgumentException("Time to live must be non-negative - supplied value was " + l);
+ }
+
+ _timeToLive = l;
+ }
+
+ public long getTimeToLive() throws JMSException
+ {
+ checkNotClosed();
+
+ return _timeToLive;
+ }
+
+ public Destination getDestination() throws JMSException
+ {
+ checkNotClosed();
+
+ return _destination;
+ }
+
+ public void close()
+ {
+ _closed.set(true);
+ _session.deregisterProducer(_producerId);
+ }
+
+ public void send(Message message) throws JMSException
+ {
+ checkPreConditions();
+ checkInitialDestination();
+
+
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, message, _deliveryMode, _messagePriority, _timeToLive, _mandatory, _immediate);
+ }
+ }
+
+ public void send(Message message, int deliveryMode) throws JMSException
+ {
+ checkPreConditions();
+ checkInitialDestination();
+
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, message, deliveryMode, _messagePriority, _timeToLive, _mandatory, _immediate);
+ }
+ }
+
+ public void send(Message message, int deliveryMode, boolean immediate) throws JMSException
+ {
+ checkPreConditions();
+ checkInitialDestination();
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, message, deliveryMode, _messagePriority, _timeToLive, _mandatory, immediate);
+ }
+ }
+
+ public void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException
+ {
+ checkPreConditions();
+ checkInitialDestination();
+ synchronized (_connection.getFailoverMutex())
+ {
+ sendImpl(_destination, message, deliveryMode, priority, timeToLive, _mandatory, _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message) throws JMSException
+ {
+ checkPreConditions();
+ checkDestination(destination);
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, message, _deliveryMode, _messagePriority, _timeToLive, _mandatory,
+ _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive)
+ throws JMSException
+ {
+ checkPreConditions();
+ checkDestination(destination);
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, message, deliveryMode, priority, timeToLive, _mandatory, _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive,
+ boolean mandatory) throws JMSException
+ {
+ checkPreConditions();
+ checkDestination(destination);
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, message, deliveryMode, priority, timeToLive, mandatory, _immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive,
+ boolean mandatory, boolean immediate) throws JMSException
+ {
+ checkPreConditions();
+ checkDestination(destination);
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, message, deliveryMode, priority, timeToLive, mandatory, immediate);
+ }
+ }
+
+ public void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive,
+ boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException
+ {
+ checkPreConditions();
+ checkDestination(destination);
+ synchronized (_connection.getFailoverMutex())
+ {
+ validateDestination(destination);
+ sendImpl((AMQDestination) destination, message, deliveryMode, priority, timeToLive, mandatory, immediate,
+ waitUntilSent);
+ }
+ }
+
+ private AbstractJMSMessage convertToNativeMessage(Message message) throws JMSException
+ {
+ if (message instanceof AbstractJMSMessage)
+ {
+ return (AbstractJMSMessage) message;
+ }
+ else
+ {
+ AbstractJMSMessage newMessage;
+
+ if (message instanceof BytesMessage)
+ {
+ newMessage = new MessageConverter(_session, (BytesMessage) message).getConvertedMessage();
+ }
+ else if (message instanceof MapMessage)
+ {
+ newMessage = new MessageConverter(_session, (MapMessage) message).getConvertedMessage();
+ }
+ else if (message instanceof ObjectMessage)
+ {
+ newMessage = new MessageConverter(_session, (ObjectMessage) message).getConvertedMessage();
+ }
+ else if (message instanceof TextMessage)
+ {
+ newMessage = new MessageConverter(_session, (TextMessage) message).getConvertedMessage();
+ }
+ else if (message instanceof StreamMessage)
+ {
+ newMessage = new MessageConverter(_session, (StreamMessage) message).getConvertedMessage();
+ }
+ else
+ {
+ newMessage = new MessageConverter(_session, message).getConvertedMessage();
+ }
+
+ if (newMessage != null)
+ {
+ return newMessage;
+ }
+ else
+ {
+ throw new JMSException("Unable to send message, due to class conversion error: "
+ + message.getClass().getName());
+ }
+ }
+ }
+
+ private void validateDestination(Destination destination) throws JMSException
+ {
+ if (!(destination instanceof AMQDestination))
+ {
+ throw new JMSException("Unsupported destination class: "
+ + ((destination != null) ? destination.getClass() : null));
+ }
+
+ AMQDestination amqDestination = (AMQDestination) destination;
+ if(!amqDestination.isExchangeExistsChecked())
+ {
+ try
+ {
+ declareDestination(amqDestination);
+ }
+ catch(Exception e)
+ {
+ JMSException ex = new JMSException("Error validating destination");
+ ex.initCause(e);
+ ex.setLinkedException(e);
+
+ throw ex;
+ }
+ amqDestination.setExchangeExistsChecked(true);
+ }
+ }
+
+ protected void sendImpl(AMQDestination destination, Message message, int deliveryMode, int priority, long timeToLive,
+ boolean mandatory, boolean immediate) throws JMSException
+ {
+ sendImpl(destination, message, deliveryMode, priority, timeToLive, mandatory, immediate, _waitUntilSent);
+ }
+
+ /**
+ * The caller of this method must hold the failover mutex.
+ *
+ * @param destination
+ * @param origMessage
+ * @param deliveryMode
+ * @param priority
+ * @param timeToLive
+ * @param mandatory
+ * @param immediate
+ *
+ * @throws JMSException
+ */
+ protected void sendImpl(AMQDestination destination, Message origMessage, int deliveryMode, int priority, long timeToLive,
+ boolean mandatory, boolean immediate, boolean wait) throws JMSException
+ {
+ checkTemporaryDestination(destination);
+ origMessage.setJMSDestination(destination);
+
+ AbstractJMSMessage message = convertToNativeMessage(origMessage);
+
+ if (_transacted)
+ {
+ if (_session.hasFailedOver() && _session.isDirty())
+ {
+ throw new JMSAMQException("Failover has occurred and session is dirty so unable to send.",
+ new AMQSessionDirtyException("Failover has occurred and session is dirty " +
+ "so unable to send."));
+ }
+ }
+
+ UUID messageId = null;
+ if (_disableMessageId)
+ {
+ message.setJMSMessageID((UUID)null);
+ }
+ else
+ {
+ messageId = _messageIdGenerator.generate();
+ message.setJMSMessageID(messageId);
+ }
+
+ sendMessage(destination, origMessage, message, messageId, deliveryMode, priority, timeToLive, mandatory, immediate, wait);
+
+ if (message != origMessage)
+ {
+ _logger.debug("Updating original message");
+ origMessage.setJMSPriority(message.getJMSPriority());
+ origMessage.setJMSTimestamp(message.getJMSTimestamp());
+ _logger.debug("Setting JMSExpiration:" + message.getJMSExpiration());
+ origMessage.setJMSExpiration(message.getJMSExpiration());
+ origMessage.setJMSMessageID(message.getJMSMessageID());
+ }
+
+ if (_transacted)
+ {
+ _session.markDirty();
+ }
+ }
+
+ abstract void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message,
+ UUID messageId, int deliveryMode, int priority, long timeToLive, boolean mandatory,
+ boolean immediate, boolean wait) throws JMSException;
+
+ private void checkTemporaryDestination(AMQDestination destination) throws JMSException
+ {
+ if (destination instanceof TemporaryDestination)
+ {
+ _logger.debug("destination is temporary destination");
+ TemporaryDestination tempDest = (TemporaryDestination) destination;
+ if (tempDest.getSession().isClosed())
+ {
+ _logger.debug("session is closed");
+ throw new JMSException("Session for temporary destination has been closed");
+ }
+
+ if (tempDest.isDeleted())
+ {
+ _logger.debug("destination is deleted");
+ throw new JMSException("Cannot send to a deleted temporary destination");
+ }
+ }
+ }
+
+ public void setMimeType(String mimeType) throws JMSException
+ {
+ checkNotClosed();
+ _mimeType = mimeType;
+ }
+
+ public void setEncoding(String encoding) throws JMSException, UnsupportedEncodingException
+ {
+ checkNotClosed();
+ _encoding = encoding;
+ }
+
+ private void checkPreConditions() throws javax.jms.IllegalStateException, JMSException
+ {
+ checkNotClosed();
+
+ if ((_session == null) || _session.isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Invalid Session");
+ }
+ if(_session.getAMQConnection().isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Connection closed");
+ }
+ }
+
+ private void checkInitialDestination()
+ {
+ if (_destination == null)
+ {
+ throw new UnsupportedOperationException("Destination is null");
+ }
+ }
+
+ private void checkDestination(Destination suppliedDestination) throws InvalidDestinationException
+ {
+ if ((_destination != null) && (suppliedDestination != null))
+ {
+ throw new UnsupportedOperationException(
+ "This message producer was created with a Destination, therefore you cannot use an unidentified Destination");
+ }
+
+ if (suppliedDestination == null)
+ {
+ throw new InvalidDestinationException("Supplied Destination was invalid");
+ }
+
+ }
+
+ public AMQSession getSession()
+ {
+ return _session;
+ }
+
+ public boolean isBound(AMQDestination destination) throws JMSException
+ {
+ return _session.isQueueBound(destination.getExchangeName(), null, destination.getRoutingKey());
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java
new file mode 100644
index 0000000000..62d1d1698c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_10.java
@@ -0,0 +1,269 @@
+/* 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 static org.apache.qpid.transport.Option.NONE;
+import static org.apache.qpid.transport.Option.SYNC;
+import static org.apache.qpid.transport.Option.UNRELIABLE;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.jms.DeliveryMode;
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQDestination.AddressOption;
+import org.apache.qpid.client.AMQDestination.DestSyntax;
+import org.apache.qpid.client.message.AMQMessageDelegate_0_10;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.messaging.address.Link.Reliability;
+import org.apache.qpid.client.messaging.address.Node.QueueNode;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.transport.DeliveryProperties;
+import org.apache.qpid.transport.Header;
+import org.apache.qpid.transport.MessageAcceptMode;
+import org.apache.qpid.transport.MessageAcquireMode;
+import org.apache.qpid.transport.MessageDeliveryMode;
+import org.apache.qpid.transport.MessageDeliveryPriority;
+import org.apache.qpid.transport.MessageProperties;
+import org.apache.qpid.transport.Option;
+import org.apache.qpid.util.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a 0_10 message producer.
+ */
+public class BasicMessageProducer_0_10 extends BasicMessageProducer
+{
+ private static final Logger _logger = LoggerFactory.getLogger(BasicMessageProducer_0_10.class);
+ private byte[] userIDBytes;
+
+ BasicMessageProducer_0_10(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId,
+ AMQSession session, AMQProtocolHandler protocolHandler, long producerId,
+ boolean immediate, boolean mandatory, boolean waitUntilSent) throws AMQException
+ {
+ super(connection, destination, transacted, channelId, session, protocolHandler, producerId, immediate,
+ mandatory, waitUntilSent);
+
+ userIDBytes = Strings.toUTF8(_userID);
+ }
+
+ void declareDestination(AMQDestination destination) throws AMQException
+ {
+ if (destination.getDestSyntax() == DestSyntax.BURL)
+ {
+ if (getSession().isDeclareExchanges())
+ {
+ String name = destination.getExchangeName().toString();
+ ((AMQSession_0_10) getSession()).getQpidSession().exchangeDeclare
+ (name,
+ destination.getExchangeClass().toString(),
+ null, null,
+ name.startsWith("amq.") ? Option.PASSIVE : Option.NONE);
+ }
+ }
+ else
+ {
+ try
+ {
+ getSession().handleAddressBasedDestination(destination,false,false);
+ }
+ catch(Exception e)
+ {
+ AMQException ex = new AMQException("Exception occured while verifying destination",e);
+ throw ex;
+ }
+ }
+ }
+
+ //--- Overwritten methods
+
+ /**
+ * Sends a message to a given destination
+ */
+ void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message,
+ UUID messageId, int deliveryMode, int priority, long timeToLive, boolean mandatory,
+ boolean immediate, boolean wait) throws JMSException
+ {
+ message.prepareForSending();
+
+ AMQMessageDelegate_0_10 delegate = (AMQMessageDelegate_0_10) message.getDelegate();
+
+ DeliveryProperties deliveryProp = delegate.getDeliveryProperties();
+ MessageProperties messageProps = delegate.getMessageProperties();
+
+ // On the receiving side, this will be read in to the JMSXUserID as well.
+ messageProps.setUserId(userIDBytes);
+
+ if (messageId != null)
+ {
+ messageProps.setMessageId(messageId);
+ }
+ else if (messageProps.hasMessageId())
+ {
+ messageProps.clearMessageId();
+ }
+
+ long currentTime = 0;
+ if (timeToLive > 0 || !_disableTimestamps)
+ {
+ currentTime = System.currentTimeMillis();
+ }
+
+ if (timeToLive > 0)
+ {
+ deliveryProp.setTtl(timeToLive);
+ message.setJMSExpiration(currentTime + timeToLive);
+ }
+
+ if (!_disableTimestamps)
+ {
+
+ deliveryProp.setTimestamp(currentTime);
+ message.setJMSTimestamp(currentTime);
+ }
+
+ if (!deliveryProp.hasDeliveryMode() || deliveryProp.getDeliveryMode().getValue() != deliveryMode)
+ {
+ MessageDeliveryMode mode;
+ switch (deliveryMode)
+ {
+ case DeliveryMode.PERSISTENT:
+ mode = MessageDeliveryMode.PERSISTENT;
+ break;
+ case DeliveryMode.NON_PERSISTENT:
+ mode = MessageDeliveryMode.NON_PERSISTENT;
+ break;
+ default:
+ throw new IllegalArgumentException("illegal delivery mode: " + deliveryMode);
+ }
+ deliveryProp.setDeliveryMode(mode);
+ message.setJMSDeliveryMode(deliveryMode);
+ }
+ if (!deliveryProp.hasPriority() || deliveryProp.getPriority().getValue() != priority)
+ {
+ deliveryProp.setPriority(MessageDeliveryPriority.get((short) priority));
+ message.setJMSPriority(priority);
+ }
+ String exchangeName = destination.getExchangeName() == null ? "" : destination.getExchangeName().toString();
+ if ( deliveryProp.getExchange() == null || ! deliveryProp.getExchange().equals(exchangeName))
+ {
+ deliveryProp.setExchange(exchangeName);
+ }
+ String routingKey = destination.getRoutingKey().toString();
+ if (deliveryProp.getRoutingKey() == null || ! deliveryProp.getRoutingKey().equals(routingKey))
+ {
+ deliveryProp.setRoutingKey(routingKey);
+ }
+
+ if (destination.getDestSyntax() == AMQDestination.DestSyntax.ADDR &&
+ (destination.getSubject() != null ||
+ (messageProps.getApplicationHeaders() != null && messageProps.getApplicationHeaders().get("qpid.subject") != null))
+ )
+ {
+ Map<String,Object> appProps = messageProps.getApplicationHeaders();
+ if (appProps == null)
+ {
+ appProps = new HashMap<String,Object>();
+ messageProps.setApplicationHeaders(appProps);
+ }
+
+ if (appProps.get("qpid.subject") == null)
+ {
+ // use default subject in address string
+ appProps.put("qpid.subject",destination.getSubject());
+ }
+
+ if (destination.getTargetNode().getType() == AMQDestination.TOPIC_TYPE)
+ {
+ deliveryProp.setRoutingKey((String)
+ messageProps.getApplicationHeaders().get("qpid.subject"));
+ }
+ }
+
+ messageProps.setContentLength(message.getContentLength());
+
+ // send the message
+ try
+ {
+ org.apache.qpid.transport.Session ssn = (org.apache.qpid.transport.Session)
+ ((AMQSession_0_10) getSession()).getQpidSession();
+
+ // if true, we need to sync the delivery of this message
+ boolean sync = false;
+
+ sync = ( (publishMode == PublishMode.SYNC_PUBLISH_ALL) ||
+ (publishMode == PublishMode.SYNC_PUBLISH_PERSISTENT &&
+ deliveryMode == DeliveryMode.PERSISTENT)
+ );
+
+ boolean unreliable = (destination.getDestSyntax() == DestSyntax.ADDR) &&
+ (destination.getLink().getReliability() == Reliability.UNRELIABLE);
+
+ org.apache.mina.common.ByteBuffer data = message.getData();
+ ByteBuffer buffer = data == null ? ByteBuffer.allocate(0) : data.buf().slice();
+
+ ssn.messageTransfer(destination.getExchangeName() == null ? "" : destination.getExchangeName().toString(),
+ MessageAcceptMode.NONE,
+ MessageAcquireMode.PRE_ACQUIRED,
+ new Header(deliveryProp, messageProps),
+ buffer, sync ? SYNC : NONE, unreliable ? UNRELIABLE : NONE);
+ if (sync)
+ {
+ ssn.sync();
+ ((AMQSession_0_10) getSession()).getCurrentException();
+ }
+
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException("Exception when sending message");
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+
+ public boolean isBound(AMQDestination destination) throws JMSException
+ {
+ return _session.isQueueBound(destination);
+ }
+
+ @Override
+ public void close()
+ {
+ super.close();
+ AMQDestination dest = _destination;
+ if (dest != null && dest.getDestSyntax() == AMQDestination.DestSyntax.ADDR)
+ {
+ if (dest.getDelete() == AddressOption.ALWAYS ||
+ dest.getDelete() == AddressOption.SENDER )
+ {
+ ((AMQSession_0_10) getSession()).getQpidSession().queueDelete(
+ _destination.getQueueName());
+ }
+ }
+ }
+}
+
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java
new file mode 100644
index 0000000000..27f7486890
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/BasicMessageProducer_0_8.java
@@ -0,0 +1,229 @@
+/*
+ *
+ * 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 java.util.UUID;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Topic;
+import javax.jms.Queue;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.client.message.AMQMessageDelegate;
+import org.apache.qpid.client.message.AMQMessageDelegate_0_8;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.BasicConsumeBody;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.BasicPublishBody;
+import org.apache.qpid.framing.CompositeAMQDataBlock;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.ExchangeDeclareBody;
+
+public class BasicMessageProducer_0_8 extends BasicMessageProducer
+{
+
+ BasicMessageProducer_0_8(AMQConnection connection, AMQDestination destination, boolean transacted, int channelId,
+ AMQSession session, AMQProtocolHandler protocolHandler, long producerId, boolean immediate, boolean mandatory,
+ boolean waitUntilSent) throws AMQException
+ {
+ super(connection, destination,transacted,channelId,session, protocolHandler, producerId, immediate, mandatory,waitUntilSent);
+ }
+
+ void declareDestination(AMQDestination destination)
+ {
+
+ ExchangeDeclareBody body = getSession().getMethodRegistry().createExchangeDeclareBody(_session.getTicket(),
+ destination.getExchangeName(),
+ destination.getExchangeClass(),
+ false,
+ false,
+ false,
+ false,
+ true,
+ null);
+ // Declare the exchange
+ // Note that the durable and internal arguments are ignored since passive is set to false
+
+ AMQFrame declare = body.generateFrame(_channelId);
+
+ _protocolHandler.writeFrame(declare);
+ }
+
+ void sendMessage(AMQDestination destination, Message origMessage, AbstractJMSMessage message,
+ UUID messageId, int deliveryMode,int priority, long timeToLive, boolean mandatory,
+ boolean immediate, boolean wait) throws JMSException
+ {
+ BasicPublishBody body = getSession().getMethodRegistry().createBasicPublishBody(_session.getTicket(),
+ destination.getExchangeName(),
+ destination.getRoutingKey(),
+ mandatory,
+ immediate);
+
+ AMQFrame publishFrame = body.generateFrame(_channelId);
+
+ message.prepareForSending();
+ ByteBuffer payload = message.getData();
+ AMQMessageDelegate_0_8 delegate = (AMQMessageDelegate_0_8) message.getDelegate();
+ BasicContentHeaderProperties contentHeaderProperties = delegate.getContentHeaderProperties();
+
+ contentHeaderProperties.setUserId(_userID);
+
+ //Set the JMS_QPID_DESTTYPE for 0-8/9 messages
+ int type;
+ if (destination instanceof Topic)
+ {
+ type = AMQDestination.TOPIC_TYPE;
+ }
+ else if (destination instanceof Queue)
+ {
+ type = AMQDestination.QUEUE_TYPE;
+ }
+ else
+ {
+ type = AMQDestination.UNKNOWN_TYPE;
+ }
+
+ //Set JMS_QPID_DESTTYPE
+ delegate.getContentHeaderProperties().getHeaders().setInteger(CustomJMSXProperty.JMS_QPID_DESTTYPE.getShortStringName(), type);
+
+ if (!_disableTimestamps)
+ {
+ final long currentTime = System.currentTimeMillis();
+ contentHeaderProperties.setTimestamp(currentTime);
+
+ if (timeToLive > 0)
+ {
+ contentHeaderProperties.setExpiration(currentTime + timeToLive);
+ }
+ else
+ {
+ contentHeaderProperties.setExpiration(0);
+ }
+ }
+
+ contentHeaderProperties.setDeliveryMode((byte) deliveryMode);
+ contentHeaderProperties.setPriority((byte) priority);
+
+ final int size = (payload != null) ? payload.limit() : 0;
+ final int contentBodyFrameCount = calculateContentBodyFrameCount(payload);
+ final AMQFrame[] frames = new AMQFrame[2 + contentBodyFrameCount];
+
+ if (payload != null)
+ {
+ createContentBodies(payload, frames, 2, _channelId);
+ }
+
+ if ((contentBodyFrameCount != 0) && _logger.isDebugEnabled())
+ {
+ _logger.debug("Sending content body frames to " + destination);
+ }
+
+
+ // TODO: This is a hacky way of getting the AMQP class-id for the Basic class
+ int classIfForBasic = getSession().getMethodRegistry().createBasicQosOkBody().getClazz();
+
+ AMQFrame contentHeaderFrame =
+ ContentHeaderBody.createAMQFrame(_channelId,
+ classIfForBasic, 0, contentHeaderProperties, size);
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Sending content header frame to " + destination);
+ }
+
+ frames[0] = publishFrame;
+ frames[1] = contentHeaderFrame;
+ CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames);
+
+ try
+ {
+ _session.checkFlowControl();
+ }
+ catch (InterruptedException e)
+ {
+ JMSException jmse = new JMSException("Interrupted while waiting for flow control to be removed");
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+
+ _protocolHandler.writeFrame(compositeFrame, wait);
+ }
+
+ /**
+ * Create content bodies. This will split a large message into numerous bodies depending on the negotiated
+ * maximum frame size.
+ *
+ * @param payload
+ * @param frames
+ * @param offset
+ * @param channelId @return the array of content bodies
+ */
+ private void createContentBodies(ByteBuffer payload, AMQFrame[] frames, int offset, int channelId)
+ {
+
+ if (frames.length == (offset + 1))
+ {
+ frames[offset] = ContentBody.createAMQFrame(channelId, new ContentBody(payload));
+ }
+ else
+ {
+
+ final long framePayloadMax = _session.getAMQConnection().getMaximumFrameSize() - 1;
+ long remaining = payload.remaining();
+ for (int i = offset; i < frames.length; i++)
+ {
+ payload.position((int) framePayloadMax * (i - offset));
+ int length = (remaining >= framePayloadMax) ? (int) framePayloadMax : (int) remaining;
+ payload.limit(payload.position() + length);
+ frames[i] = ContentBody.createAMQFrame(channelId, new ContentBody(payload.slice()));
+
+ remaining -= length;
+ }
+ }
+
+ }
+
+ private int calculateContentBodyFrameCount(ByteBuffer payload)
+ {
+ // we substract one from the total frame maximum size to account for the end of frame marker in a body frame
+ // (0xCE byte).
+ int frameCount;
+ if ((payload == null) || (payload.remaining() == 0))
+ {
+ frameCount = 0;
+ }
+ else
+ {
+ int dataLength = payload.remaining();
+ final long framePayloadMax = _session.getAMQConnection().getMaximumFrameSize() - 1;
+ int lastFrame = ((dataLength % framePayloadMax) > 0) ? 1 : 0;
+ frameCount = (int) (dataLength / framePayloadMax) + lastFrame;
+ }
+
+ return frameCount;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/ChannelToSessionMap.java b/qpid/java/client/src/main/java/org/apache/qpid/client/ChannelToSessionMap.java
new file mode 100644
index 0000000000..2fdb35de49
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/ChannelToSessionMap.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.client;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class ChannelToSessionMap
+{
+ private final AMQSession[] _fastAccessSessions = new AMQSession[16];
+ private final LinkedHashMap<Integer, AMQSession> _slowAccessSessions = new LinkedHashMap<Integer, AMQSession>();
+ private int _size = 0;
+ private static final int FAST_CHANNEL_ACCESS_MASK = 0xFFFFFFF0;
+ private AtomicInteger _idFactory = new AtomicInteger(0);
+ private int _maxChannelID;
+ private int _minChannelID;
+
+ public AMQSession get(int channelId)
+ {
+ if ((channelId & FAST_CHANNEL_ACCESS_MASK) == 0)
+ {
+ return _fastAccessSessions[channelId];
+ }
+ else
+ {
+ return _slowAccessSessions.get(channelId);
+ }
+ }
+
+ public AMQSession put(int channelId, AMQSession session)
+ {
+ AMQSession oldVal;
+ if ((channelId & FAST_CHANNEL_ACCESS_MASK) == 0)
+ {
+ oldVal = _fastAccessSessions[channelId];
+ _fastAccessSessions[channelId] = session;
+ }
+ else
+ {
+ oldVal = _slowAccessSessions.put(channelId, session);
+ }
+ if ((oldVal != null) && (session == null))
+ {
+ _size--;
+ }
+ else if ((oldVal == null) && (session != null))
+ {
+ _size++;
+ }
+
+ return session;
+
+ }
+
+ public AMQSession remove(int channelId)
+ {
+ AMQSession session;
+ if ((channelId & FAST_CHANNEL_ACCESS_MASK) == 0)
+ {
+ session = _fastAccessSessions[channelId];
+ _fastAccessSessions[channelId] = null;
+ }
+ else
+ {
+ session = _slowAccessSessions.remove(channelId);
+ }
+
+ if (session != null)
+ {
+ _size--;
+ }
+ return session;
+
+ }
+
+ public Collection<AMQSession> values()
+ {
+ ArrayList<AMQSession> values = new ArrayList<AMQSession>(size());
+
+ for (int i = 0; i < 16; i++)
+ {
+ if (_fastAccessSessions[i] != null)
+ {
+ values.add(_fastAccessSessions[i]);
+ }
+ }
+ values.addAll(_slowAccessSessions.values());
+
+ return values;
+ }
+
+ public int size()
+ {
+ return _size;
+ }
+
+ public void clear()
+ {
+ _size = 0;
+ _slowAccessSessions.clear();
+ for (int i = 0; i < 16; i++)
+ {
+ _fastAccessSessions[i] = null;
+ }
+ }
+
+ /*
+ * Synchronized on whole method so that we don't need to consider the
+ * increment-then-reset path in too much detail
+ */
+ public synchronized int getNextChannelId()
+ {
+ int id = _minChannelID;
+
+ boolean done = false;
+ while (!done)
+ {
+ id = _idFactory.getAndIncrement();
+ if (id == _maxChannelID)
+ {
+ //go back to the start
+ _idFactory.set(_minChannelID);
+ }
+ if ((id & FAST_CHANNEL_ACCESS_MASK) == 0)
+ {
+ done = (_fastAccessSessions[id] == null);
+ }
+ else
+ {
+ done = (!_slowAccessSessions.keySet().contains(id));
+ }
+ }
+
+ return id;
+ }
+
+ public void setMaxChannelID(int maxChannelID)
+ {
+ _maxChannelID = maxChannelID;
+ }
+
+ public void setMinChannelID(int minChannelID)
+ {
+ _minChannelID = minChannelID;
+ _idFactory.set(_minChannelID);
+ }
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/Closeable.java b/qpid/java/client/src/main/java/org/apache/qpid/client/Closeable.java
new file mode 100644
index 0000000000..e6771e122c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/Closeable.java
@@ -0,0 +1,101 @@
+/*
+ *
+ * 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 javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Captures the 'closed' state of an object, that is initially open, can be tested to see if it is closed, and provides
+ * a 'close' method to close it.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Mark an object as closed.
+ * <tr><td> Check if an object is closed.
+ * <tr><td> Raise a JMS exception if an object is closed.
+ * </table>
+ *
+ * @todo Might be better to make this an interface. This whole class doesn't really encapsulate a terribly neat
+ * piece of re-usable functionality. A simple interface defining a close method would suffice.
+ *
+ * @todo The convenience method {@link #checkNotClosed} is not that helpfull, what if the caller wants to do something
+ * other than throw an exception? It doesn't really represent a very usefull re-usable piece of code. Consider
+ * inlining it and dropping the method.
+ */
+public abstract class Closeable
+{
+ /**
+ * We use an atomic boolean so that we do not have to synchronized access to this flag. Synchronizing access to this
+ * flag would mean have a synchronized block in every method.
+ */
+ protected final AtomicBoolean _closed = new AtomicBoolean(false);
+
+ /**
+ * Are we in the process of closing. We have this distinction so we can
+ * still signal we are in the process of closing so other objects can tell
+ * the difference and tidy up.
+ */
+ protected final AtomicBoolean _closing = new AtomicBoolean(false);
+
+ /**
+ * Checks if this is closed, and raises a JMSException if it is.
+ *
+ * @throws JMSException If this is closed.
+ */
+ protected void checkNotClosed() throws JMSException
+ {
+ if (isClosed())
+ {
+ throw new IllegalStateException("Object " + toString() + " has been closed");
+ }
+ }
+
+ /**
+ * Checks if this is closed.
+ *
+ * @return <tt>true</tt> if this is closed, <tt>false</tt> otherwise.
+ */
+ public boolean isClosed()
+ {
+ return _closed.get();
+ }
+
+ /**
+ * Checks if this is closis.
+ *
+ * @return <tt>true</tt> if we are closing, <tt>false</tt> otherwise.
+ */
+ public boolean isClosing()
+ {
+ return _closing.get();
+ }
+
+
+ /**
+ * Closes this object.
+ *
+ * @throws JMSException If this cannot be closed for any reason.
+ */
+ public abstract void close() throws JMSException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/ConnectionTuneParameters.java b/qpid/java/client/src/main/java/org/apache/qpid/client/ConnectionTuneParameters.java
new file mode 100644
index 0000000000..b1ec7216bc
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/ConnectionTuneParameters.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * 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;
+
+public class ConnectionTuneParameters
+{
+ private long _frameMax;
+
+ private int _channelMax;
+
+ private int _heartbeat;
+
+ private long _txnLimit;
+
+ public long getFrameMax()
+ {
+ return _frameMax;
+ }
+
+ public void setFrameMax(long frameMax)
+ {
+ _frameMax = frameMax;
+ }
+
+ public int getChannelMax()
+ {
+ return _channelMax;
+ }
+
+ public void setChannelMax(int channelMax)
+ {
+ _channelMax = channelMax;
+ }
+
+ public int getHeartbeat()
+ {
+ return _heartbeat;
+ }
+
+ public void setHeartbeat(int hearbeat)
+ {
+ _heartbeat = hearbeat;
+ }
+
+ public long getTxnLimit()
+ {
+ return _txnLimit;
+ }
+
+ public void setTxnLimit(long txnLimit)
+ {
+ _txnLimit = txnLimit;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java b/qpid/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java
new file mode 100644
index 0000000000..7cc548915c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/CustomJMSXProperty.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import org.apache.qpid.framing.AMQShortString;
+
+public enum CustomJMSXProperty
+{
+ JMS_AMQP_NULL,
+ JMS_QPID_DESTTYPE,
+ JMSXGroupID,
+ JMSXGroupSeq,
+ JMSXUserID;
+
+
+ private final AMQShortString _nameAsShortString;
+
+ CustomJMSXProperty()
+ {
+ _nameAsShortString = new AMQShortString(toString());
+ }
+
+ public AMQShortString getShortStringName()
+ {
+ return _nameAsShortString;
+ }
+
+ private static Enumeration _names;
+
+ public static synchronized Enumeration asEnumeration()
+ {
+ if(_names == null)
+ {
+ CustomJMSXProperty[] properties = values();
+ ArrayList<String> nameList = new ArrayList<String>(properties.length);
+ for(CustomJMSXProperty property : properties)
+ {
+ nameList.add(property.toString());
+ }
+ _names = Collections.enumeration(nameList);
+ }
+ return _names;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/DispatcherCallback.java b/qpid/java/client/src/main/java/org/apache/qpid/client/DispatcherCallback.java
new file mode 100644
index 0000000000..81a55006ed
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/DispatcherCallback.java
@@ -0,0 +1,36 @@
+/*
+ * 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 java.util.Queue;
+
+public abstract class DispatcherCallback
+{
+ BasicMessageConsumer _consumer;
+
+ public DispatcherCallback(BasicMessageConsumer mc)
+ {
+ _consumer = mc;
+ }
+
+ abstract public void whilePaused(Queue<MessageConsumerPair> reprocessQueue);
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/JMSAMQException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/JMSAMQException.java
new file mode 100644
index 0000000000..1151a97cf4
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/JMSAMQException.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.qpid.client;
+
+import org.apache.qpid.AMQException;
+
+import javax.jms.JMSException;
+
+/**
+ * JMSException does not accept wrapped exceptions in its constructor. Presumably this is because it is a relatively old
+ * Java exception class, before this was added as a default to Throwable. This exception class accepts wrapped exceptions
+ * as well as error messages, through its constructor, but is a JMSException.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Accept wrapped exceptions as a JMSException.
+ * </table>
+ */
+public class JMSAMQException extends JMSException
+{
+ /**
+ * Creates a JMSException, wrapping another exception class.
+ *
+ * @param message The error message.
+ * @param cause The underlying exception that caused this one. May be null if none is to be set.
+ */
+ public JMSAMQException(String message, Exception cause)
+ {
+ super(message);
+
+ if (cause != null)
+ {
+ setLinkedException(cause);
+ initCause(cause);
+ }
+ }
+
+ /**
+ * @param cause The underlying exception.
+ *
+ * @deprecated Use the other constructor and write a helpfull message. This one will be deleted.
+ */
+ public JMSAMQException(AMQException cause)
+ {
+ super(cause.getMessage(), String.valueOf(cause.getErrorCode()));
+ setLinkedException(cause);
+ initCause(cause);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/JmsNotImplementedException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/JmsNotImplementedException.java
new file mode 100644
index 0000000000..903514c35f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/JmsNotImplementedException.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * 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 javax.jms.JMSException;
+
+public class JmsNotImplementedException extends JMSException
+{
+ public JmsNotImplementedException()
+ {
+ super("Not implemented");
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/MessageConsumerPair.java b/qpid/java/client/src/main/java/org/apache/qpid/client/MessageConsumerPair.java
new file mode 100644
index 0000000000..585d6db3fd
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/MessageConsumerPair.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+public class MessageConsumerPair
+{
+ BasicMessageConsumer _consumer;
+ Object _item;
+
+ public MessageConsumerPair(BasicMessageConsumer consumer, Object item)
+ {
+ _consumer = consumer;
+ _item = item;
+ }
+
+ public BasicMessageConsumer getConsumer()
+ {
+ return _consumer;
+ }
+
+ public Object getItem()
+ {
+ return _item;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/QpidConnectionMetaData.java b/qpid/java/client/src/main/java/org/apache/qpid/client/QpidConnectionMetaData.java
new file mode 100644
index 0000000000..3bb5707417
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/QpidConnectionMetaData.java
@@ -0,0 +1,97 @@
+/*
+ *
+ * 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 java.util.Enumeration;
+
+import javax.jms.ConnectionMetaData;
+import javax.jms.JMSException;
+
+import org.apache.qpid.common.QpidProperties;
+
+public class QpidConnectionMetaData implements ConnectionMetaData
+{
+
+
+ QpidConnectionMetaData(AMQConnection conn)
+ {
+ }
+
+ public int getJMSMajorVersion() throws JMSException
+ {
+ return 1;
+ }
+
+ public int getJMSMinorVersion() throws JMSException
+ {
+ return 1;
+ }
+
+ public String getJMSProviderName() throws JMSException
+ {
+ return "Apache " + QpidProperties.getProductName();
+ }
+
+ public String getJMSVersion() throws JMSException
+ {
+ return "1.1";
+ }
+
+ public Enumeration getJMSXPropertyNames() throws JMSException
+ {
+ return CustomJMSXProperty.asEnumeration();
+ }
+
+ public int getProviderMajorVersion() throws JMSException
+ {
+ return 0;
+ }
+
+ public int getProviderMinorVersion() throws JMSException
+ {
+ return 8;
+ }
+
+ public String getProviderVersion() throws JMSException
+ {
+ return QpidProperties.getProductName() + " (Client: [" + getClientVersion() + "] ; Broker [" + getBrokerVersion() + "] ; Protocol: [ "
+ + getProtocolVersion() + "] )";
+ }
+
+ private String getProtocolVersion()
+ {
+ // TODO - Implement based on connection negotiated protocol
+ return "0.8";
+ }
+
+ public String getBrokerVersion()
+ {
+ // TODO - get broker version
+ return "<unkown>";
+ }
+
+ public String getClientVersion()
+ {
+ return QpidProperties.getBuildVersion();
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/QueueReceiverAdaptor.java b/qpid/java/client/src/main/java/org/apache/qpid/client/QueueReceiverAdaptor.java
new file mode 100644
index 0000000000..7059588367
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/QueueReceiverAdaptor.java
@@ -0,0 +1,115 @@
+/*
+ *
+ * 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 javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.Queue;
+import javax.jms.QueueReceiver;
+
+/**
+ * Class that wraps a MessageConsumer for backwards JMS compatibility
+ * Returned by methods in AMQSession etc
+ */
+public class QueueReceiverAdaptor implements QueueReceiver {
+
+ protected MessageConsumer _consumer;
+ protected Queue _queue;
+
+ protected QueueReceiverAdaptor(Queue queue, MessageConsumer consumer)
+ {
+ _consumer = consumer;
+ _queue = queue;
+ }
+
+ public String getMessageSelector() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.getMessageSelector();
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.getMessageListener();
+ }
+
+ public void setMessageListener(MessageListener messageListener) throws JMSException
+ {
+ checkPreConditions();
+ _consumer.setMessageListener(messageListener);
+ }
+
+ public Message receive() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.receive();
+ }
+
+ public Message receive(long l) throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.receive(l);
+ }
+
+ public Message receiveNoWait() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.receiveNoWait();
+ }
+
+ public void close() throws JMSException
+ {
+ _consumer.close();
+ }
+
+ /**
+ * Return the queue associated with this receiver
+ * @return
+ * @throws JMSException
+ */
+ public Queue getQueue() throws JMSException
+ {
+ checkPreConditions();
+ return _queue;
+ }
+
+ private void checkPreConditions() throws javax.jms.IllegalStateException {
+ BasicMessageConsumer msgConsumer = (BasicMessageConsumer)_consumer;
+
+ if (msgConsumer.isClosed() ){
+ throw new javax.jms.IllegalStateException("Consumer is closed");
+ }
+
+ if(_queue == null){
+ throw new UnsupportedOperationException("Queue is null");
+ }
+
+ AMQSession session = msgConsumer.getSession();
+
+ if(session == null || session.isClosed()){
+ throw new javax.jms.IllegalStateException("Invalid Session");
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java
new file mode 100644
index 0000000000..295c6a4091
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/QueueSenderAdapter.java
@@ -0,0 +1,227 @@
+/*
+ * 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 javax.jms.Destination;
+import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Queue;
+import javax.jms.QueueSender;
+
+public class QueueSenderAdapter implements QueueSender
+{
+
+ private BasicMessageProducer _delegate;
+ private Queue _queue;
+ private boolean closed = false;
+
+ public QueueSenderAdapter(BasicMessageProducer msgProducer, Queue queue)
+ {
+ _delegate = msgProducer;
+ _queue = queue;
+ }
+
+ public Queue getQueue() throws JMSException
+ {
+ checkPreConditions();
+
+ return _queue;
+ }
+
+ public void send(Message msg) throws JMSException
+ {
+ checkQueuePreConditions(_queue);
+ _delegate.send(msg);
+ }
+
+ public void send(Queue queue, Message msg) throws JMSException
+ {
+ checkQueuePreConditions(queue);
+ _delegate.send(queue, msg);
+ }
+
+ public void publish(Message msg, int deliveryMode, int priority, long timeToLive) throws JMSException
+ {
+ checkQueuePreConditions(_queue);
+ _delegate.send(msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void send(Queue queue, Message msg, int deliveryMode, int priority, long timeToLive) throws JMSException
+ {
+ checkQueuePreConditions(queue);
+ _delegate.send(queue, msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void close() throws JMSException
+ {
+ _delegate.close();
+ closed = true;
+ }
+
+ public int getDeliveryMode() throws JMSException
+ {
+ checkPreConditions();
+
+ return _delegate.getDeliveryMode();
+ }
+
+ public Destination getDestination() throws JMSException
+ {
+ checkPreConditions();
+
+ return _delegate.getDestination();
+ }
+
+ public boolean getDisableMessageID() throws JMSException
+ {
+ checkPreConditions();
+
+ return _delegate.getDisableMessageID();
+ }
+
+ public boolean getDisableMessageTimestamp() throws JMSException
+ {
+ checkPreConditions();
+
+ return _delegate.getDisableMessageTimestamp();
+ }
+
+ public int getPriority() throws JMSException
+ {
+ checkPreConditions();
+
+ return _delegate.getPriority();
+ }
+
+ public long getTimeToLive() throws JMSException
+ {
+ checkPreConditions();
+
+ return _delegate.getTimeToLive();
+ }
+
+ public void send(Destination dest, Message msg) throws JMSException
+ {
+ checkQueuePreConditions((Queue) dest);
+ _delegate.send(dest, msg);
+ }
+
+ public void send(Message msg, int deliveryMode, int priority, long timeToLive) throws JMSException
+ {
+ checkQueuePreConditions(_queue);
+ _delegate.send(msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void send(Destination dest, Message msg, int deliveryMode, int priority, long timeToLive) throws JMSException
+ {
+ checkQueuePreConditions((Queue) dest);
+ _delegate.send(dest, msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void setDeliveryMode(int deliveryMode) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setDeliveryMode(deliveryMode);
+ }
+
+ public void setDisableMessageID(boolean disableMessageID) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setDisableMessageID(disableMessageID);
+ }
+
+ public void setDisableMessageTimestamp(boolean disableMessageTimestamp) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setDisableMessageTimestamp(disableMessageTimestamp);
+ }
+
+ public void setPriority(int priority) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setPriority(priority);
+ }
+
+ public void setTimeToLive(long timeToLive) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setTimeToLive(timeToLive);
+ }
+
+ private void checkPreConditions() throws JMSException
+ {
+ if (closed)
+ {
+ throw new javax.jms.IllegalStateException("Publisher is closed");
+ }
+
+ AMQSession session = ((BasicMessageProducer) _delegate).getSession();
+
+ if ((session == null) || session.isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Invalid Session");
+ }
+ }
+
+ private void checkQueuePreConditions(Queue queue) throws JMSException
+ {
+ checkPreConditions() ;
+
+ if (queue == null)
+ {
+ throw new UnsupportedOperationException("Queue is null.");
+ }
+
+ if (!(queue instanceof AMQDestination))
+ {
+ throw new InvalidDestinationException("Queue: " + queue + " is not a valid Qpid queue");
+ }
+
+ AMQDestination destination = (AMQDestination) queue;
+ if (!destination.isCheckedForQueueBinding() && checkQueueBeforePublish())
+ {
+ if (_delegate.getSession().isStrictAMQP())
+ {
+ _delegate._logger.warn("AMQP does not support destination validation before publish, ");
+ destination.setCheckedForQueueBinding(true);
+ }
+ else
+ {
+ if (_delegate.isBound(destination))
+ {
+ destination.setCheckedForQueueBinding(true);
+ }
+ else
+ {
+ throw new InvalidDestinationException("Queue: " + queue
+ + " is not a valid destination (no bindings on server");
+ }
+ }
+ }
+ }
+
+ private boolean checkQueueBeforePublish()
+ {
+ return "true".equalsIgnoreCase(System.getProperty("org.apache.qpid.client.verifyQueueBindingBeforePublish", "true"));
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/SSLConfiguration.java b/qpid/java/client/src/main/java/org/apache/qpid/client/SSLConfiguration.java
new file mode 100644
index 0000000000..2280cc9870
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/SSLConfiguration.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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;
+
+public class SSLConfiguration {
+
+ private String _keystorePath;
+
+ private String _keystorePassword;
+
+ private String _certType = "SunX509";
+
+ public void setKeystorePath(String path)
+ {
+ _keystorePath = path;
+ }
+
+ public String getKeystorePath()
+ {
+ return _keystorePath;
+ }
+
+ public void setKeystorePassword(String password)
+ {
+ _keystorePassword = password;
+ }
+
+ public String getKeystorePassword()
+ {
+ return _keystorePassword;
+ }
+
+ public void setCertType(String type)
+ {
+ _certType = type;
+ }
+
+ public String getCertType()
+ {
+ return _certType;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/TemporaryDestination.java b/qpid/java/client/src/main/java/org/apache/qpid/client/TemporaryDestination.java
new file mode 100644
index 0000000000..7f8e80c73a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/TemporaryDestination.java
@@ -0,0 +1,38 @@
+/*
+ * 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 javax.jms.Destination;
+import javax.jms.JMSException;
+
+/**
+ * Provides support for covenience interface implemented by both AMQTemporaryTopic and AMQTemporaryQueue
+ * so that operations related to their "temporary-ness" can be abstracted out.
+ */
+interface TemporaryDestination extends Destination
+{
+
+ public void delete() throws JMSException;
+ public AMQSession getSession();
+ public boolean isDeleted();
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/TopicPublisherAdapter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/TopicPublisherAdapter.java
new file mode 100644
index 0000000000..81b9940ed5
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/TopicPublisherAdapter.java
@@ -0,0 +1,205 @@
+/*
+ * 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 javax.jms.Destination;
+import javax.jms.IllegalStateException;
+import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Topic;
+import javax.jms.TopicPublisher;
+
+public class TopicPublisherAdapter implements TopicPublisher
+{
+
+ private BasicMessageProducer _delegate;
+ private Topic _topic;
+
+ public TopicPublisherAdapter(BasicMessageProducer msgProducer, Topic topic)
+ {
+ _delegate = msgProducer;
+ _topic = topic;
+ }
+
+ public Topic getTopic() throws JMSException
+ {
+ checkPreConditions();
+ return _topic;
+ }
+
+ public void publish(Message msg) throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(_topic);
+ _delegate.send(msg);
+ }
+
+ public void publish(Topic topic, Message msg) throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(topic);
+ _delegate.send(topic, msg);
+ }
+
+ public void publish(Message msg, int deliveryMode, int priority, long timeToLive)
+ throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(_topic);
+ _delegate.send(msg, deliveryMode, priority, timeToLive);
+ }
+
+ public int getDeliveryMode() throws JMSException {
+ checkPreConditions();
+ return _delegate.getDeliveryMode();
+ }
+
+ public void publish(Topic topic, Message msg, int deliveryMode, int priority, long timeToLive)
+ throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(topic);
+ _delegate.send(topic, msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void close() throws JMSException
+ {
+ _delegate.close();
+ }
+
+ public boolean getDisableMessageID() throws JMSException {
+ checkPreConditions();
+ return _delegate.getDisableMessageID();
+ }
+
+ public boolean getDisableMessageTimestamp() throws JMSException {
+ checkPreConditions();
+ return _delegate.getDisableMessageTimestamp();
+ }
+
+ public Destination getDestination() throws JMSException
+ {
+ checkPreConditions();
+ return _delegate.getDestination();
+ }
+
+ public int getPriority() throws JMSException {
+ checkPreConditions();
+ return _delegate.getPriority();
+ }
+
+ public long getTimeToLive() throws JMSException {
+ checkPreConditions();
+ return _delegate.getTimeToLive();
+ }
+
+ public void send(Message msg) throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(_topic);
+ _delegate.send(msg);
+ }
+
+ public void send(Destination dest, Message msg) throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(_topic);
+ _delegate.send(dest, msg);
+ }
+
+ public void send(Message msg, int deliveryMode, int priority, long timeToLive)
+ throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(_topic);
+ _delegate.send(msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void send(Destination dest, Message msg, int deliveryMode, int priority, long timeToLive) throws JMSException
+ {
+ checkPreConditions();
+ checkTopic(dest);
+ _delegate.send(dest, msg, deliveryMode, priority, timeToLive);
+ }
+
+ public void setDeliveryMode(int deliveryMode) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setDeliveryMode(deliveryMode);
+ }
+
+ public void setDisableMessageID(boolean disableMessageID) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setDisableMessageID(disableMessageID);
+ }
+
+ public void setDisableMessageTimestamp(boolean disableMessageTimestamp) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setDisableMessageTimestamp(disableMessageTimestamp);
+ }
+
+ public void setPriority(int priority) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setPriority(priority);
+ }
+
+ public void setTimeToLive(long timeToLive) throws JMSException
+ {
+ checkPreConditions();
+ _delegate.setTimeToLive(timeToLive);
+ }
+
+ private void checkPreConditions() throws IllegalStateException
+ {
+ if (_delegate.isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Publisher is _closed");
+ }
+
+ AMQSession session = _delegate.getSession();
+ if (session == null || session.isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Invalid Session");
+ }
+ }
+
+ private void checkTopic(Destination topic) throws InvalidDestinationException
+ {
+ if (topic == null)
+ {
+ throw new UnsupportedOperationException("Topic is null");
+ }
+ if (!(topic instanceof Topic))
+ {
+ throw new InvalidDestinationException("Destination " + topic + " is not a topic");
+ }
+ if(!(topic instanceof AMQDestination))
+ {
+ throw new InvalidDestinationException("Destination " + topic + " is not a Qpid topic");
+ }
+
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java b/qpid/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.java
new file mode 100644
index 0000000000..9bdef22f96
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/TopicSubscriberAdaptor.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.client;
+
+import org.apache.qpid.AMQException;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+
+/**
+ * Wraps a MessageConsumer to fulfill the extended TopicSubscriber contract
+ *
+ */
+class TopicSubscriberAdaptor<C extends BasicMessageConsumer> implements TopicSubscriber
+{
+ private final Topic _topic;
+ private final C _consumer;
+ private final boolean _noLocal;
+
+ TopicSubscriberAdaptor(Topic topic, C consumer, boolean noLocal)
+ {
+ _topic = topic;
+ _consumer = consumer;
+ _noLocal = noLocal;
+ }
+
+ TopicSubscriberAdaptor(Topic topic, C consumer)
+ {
+ this(topic, consumer, consumer.isNoLocal());
+ }
+
+ public Topic getTopic() throws JMSException
+ {
+ checkPreConditions();
+ return _topic;
+ }
+
+ public boolean getNoLocal() throws JMSException
+ {
+ checkPreConditions();
+ return _noLocal;
+ }
+
+ public String getMessageSelector() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.getMessageSelector();
+ }
+
+ public MessageListener getMessageListener() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.getMessageListener();
+ }
+
+ public void setMessageListener(MessageListener messageListener) throws JMSException
+ {
+ checkPreConditions();
+ _consumer.setMessageListener(messageListener);
+ }
+
+ public Message receive() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.receive();
+ }
+
+ public Message receive(long l) throws JMSException
+ {
+ return _consumer.receive(l);
+ }
+
+ public Message receiveNoWait() throws JMSException
+ {
+ checkPreConditions();
+ return _consumer.receiveNoWait();
+ }
+
+ public void close() throws JMSException
+ {
+ _consumer.close();
+ }
+
+ private void checkPreConditions() throws javax.jms.IllegalStateException{
+ C msgConsumer = _consumer;
+
+ if (msgConsumer.isClosed() ){
+ throw new javax.jms.IllegalStateException("Consumer is closed");
+ }
+
+ if(_topic == null){
+ throw new UnsupportedOperationException("Topic is null");
+ }
+
+ AMQSession session = msgConsumer.getSession();
+
+ if(session == null || session.isClosed()){
+ throw new javax.jms.IllegalStateException("Invalid Session");
+ }
+ }
+
+ C getMessageConsumer()
+ {
+ return _consumer;
+ }
+
+ public void addBindingKey(Topic topic, String bindingKey) throws AMQException
+ {
+ _consumer.addBindingKey((AMQDestination) topic, bindingKey);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/XAConnectionImpl.java b/qpid/java/client/src/main/java/org/apache/qpid/client/XAConnectionImpl.java
new file mode 100644
index 0000000000..43025bd724
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/XAConnectionImpl.java
@@ -0,0 +1,78 @@
+/* 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.jms.ConnectionURL;
+import org.apache.qpid.AMQException;
+
+import javax.jms.*;
+
+/**
+ * This class implements the javax.njms.XAConnection interface
+ */
+public class XAConnectionImpl extends AMQConnection implements XAConnection, XAQueueConnection, XATopicConnection
+{
+ //-- constructor
+ /**
+ * Create a XAConnection from a connectionURL
+ */
+ public XAConnectionImpl(ConnectionURL connectionURL, SSLConfiguration sslConfig) throws AMQException
+ {
+ super(connectionURL, sslConfig);
+ }
+
+ //-- interface XAConnection
+ /**
+ * Creates an XASession.
+ *
+ * @return A newly created XASession.
+ * @throws JMSException If the XAConnectiono fails to create an XASession due to
+ * some internal error.
+ */
+ public synchronized XASession createXASession() throws JMSException
+ {
+ checkNotClosed();
+ return _delegate.createXASession();
+ }
+
+ //-- Interface XAQueueConnection
+ /**
+ * Creates an XAQueueSession.
+ *
+ * @return A newly created XASession.
+ * @throws JMSException If the XAQueueConnectionImpl fails to create an XASession due to
+ * some internal error.
+ */
+ public XAQueueSession createXAQueueSession() throws JMSException
+ {
+ return (XAQueueSession) createXASession();
+ }
+
+ //-- Interface XATopicConnection
+ /**
+ * Creates an XAQueueSession.
+ *
+ * @return A newly created XASession.
+ * @throws JMSException If the XAQueueConnectionImpl fails to create an XASession due to
+ * some internal error.
+ */
+ public XATopicSession createXATopicSession() throws JMSException
+ {
+ return (XATopicSession) createXASession();
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/XAResourceImpl.java b/qpid/java/client/src/main/java/org/apache/qpid/client/XAResourceImpl.java
new file mode 100644
index 0000000000..8a75082202
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/XAResourceImpl.java
@@ -0,0 +1,522 @@
+/* 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 javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.apache.qpid.AMQInvalidArgumentException;
+import org.apache.qpid.dtx.XidImpl;
+import org.apache.qpid.transport.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is an implementation of javax.njms.XAResource.
+ */
+public class XAResourceImpl implements XAResource
+{
+ /**
+ * this XAResourceImpl's logger
+ */
+ private static final Logger _logger = LoggerFactory.getLogger(XAResourceImpl.class);
+
+ /**
+ * Reference to the associated XASession
+ */
+ private XASessionImpl _xaSession = null;
+
+ /**
+ * The XID of this resource
+ */
+ private Xid _xid;
+
+ /**
+ * The time for this resource
+ */
+ private int _timeout;
+
+ //--- constructor
+
+ /**
+ * Create an XAResource associated with a XASession
+ *
+ * @param xaSession The session XAresource
+ */
+ protected XAResourceImpl(XASessionImpl xaSession)
+ {
+ _xaSession = xaSession;
+ }
+
+ //--- The XAResource
+ /**
+ * Commits the global transaction specified by xid.
+ *
+ * @param xid A global transaction identifier
+ * @param b If true, use a one-phase commit protocol to commit the work done on behalf of xid.
+ * @throws XAException An error has occurred. An error has occurred. Possible XAExceptions are XA_HEURHAZ,
+ * XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
+ */
+ public void commit(Xid xid, boolean b) throws XAException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("commit tx branch with xid: ", xid);
+ }
+ Future<XaResult> future =
+ _xaSession.getQpidSession().dtxCommit(convertXid(xid), b ? Option.ONE_PHASE : Option.NONE);
+
+ // now wait on the future for the result
+ XaResult result = null;
+ try
+ {
+ result = future.get();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr(e.getException().getErrorCode());
+ }
+ finally
+ {
+ _xid = null;
+ }
+ checkStatus(result.getStatus());
+ }
+
+ /**
+ * Ends the work performed on behalf of a transaction branch.
+ * The resource manager disassociates the XA resource from the transaction branch specified
+ * and lets the transaction complete.
+ * <ul>
+ * <li> If TMSUSPEND is specified in the flags, the transaction branch is temporarily suspended in an incomplete state.
+ * The transaction context is in a suspended state and must be resumed via the start method with TMRESUME specified.
+ * <li> If TMFAIL is specified, the portion of work has failed. The resource manager may mark the transaction as rollback-only
+ * <li> If TMSUCCESS is specified, the portion of work has completed successfully.
+ * /ul>
+ *
+ * @param xid A global transaction identifier that is the same as the identifier used previously in the start method
+ * @param flag One of TMSUCCESS, TMFAIL, or TMSUSPEND.
+ * @throws XAException An error has occurred. An error has occurred. Possible XAException values are XAER_RMERR,
+ * XAER_RMFAILED, XAER_NOTA, XAER_INVAL, XAER_PROTO, or XA_RB*.
+ */
+ public void end(Xid xid, int flag) throws XAException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("end tx branch with xid: ", xid);
+ }
+ switch (flag)
+ {
+ case(XAResource.TMSUCCESS):
+ break;
+ case(XAResource.TMFAIL):
+ break;
+ case(XAResource.TMSUSPEND):
+ break;
+ default:
+ throw new XAException(XAException.XAER_INVAL);
+ }
+ _xaSession.flushAcknowledgments();
+ Future<XaResult> future = _xaSession.getQpidSession()
+ .dtxEnd(convertXid(xid),
+ flag == XAResource.TMFAIL ? Option.FAIL : Option.NONE,
+ flag == XAResource.TMSUSPEND ? Option.SUSPEND : Option.NONE);
+ // now wait on the future for the result
+ XaResult result = null;
+ try
+ {
+ result = future.get();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr(e.getException().getErrorCode());
+ }
+ checkStatus(result.getStatus());
+ }
+
+
+ /**
+ * Tells the resource manager to forget about a heuristically completed transaction branch.
+ *
+ * @param xid String(xid.getGlobalTransactionId() A global transaction identifier
+ * @throws XAException An error has occurred. Possible exception values are XAER_RMERR, XAER_RMFAIL,
+ * XAER_NOTA, XAER_INVAL, or XAER_PROTO.
+ */
+ public void forget(Xid xid) throws XAException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("forget tx branch with xid: ", xid);
+ }
+ _xaSession.getQpidSession().dtxForget(convertXid(xid));
+ try
+ {
+ _xaSession.getQpidSession().sync();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr(e.getException().getErrorCode());
+ }
+ finally
+ {
+ _xid = null;
+ }
+ }
+
+
+ /**
+ * Obtains the current transaction timeout value set for this XAResource instance.
+ * If XAResource.setTransactionTimeout was not used prior to invoking this method,
+ * the return value is the default timeout i.e. 0;
+ *
+ * @return The transaction timeout value in seconds.
+ * @throws XAException An error has occurred. Possible exception values are XAER_RMERR, XAER_RMFAIL.
+ */
+ public int getTransactionTimeout() throws XAException
+ {
+ return _timeout;
+ }
+
+ /**
+ * This method is called to determine if the resource manager instance represented
+ * by the target object is the same as the resouce manager instance represented by
+ * the parameter xaResource.
+ *
+ * @param xaResource An XAResource object whose resource manager instance is to
+ * be compared with the resource manager instance of the target object
+ * @return <code>true</code> if it's the same RM instance; otherwise <code>false</code>.
+ * @throws XAException An error has occurred. Possible exception values are XAER_RMERR, XAER_RMFAIL.
+ */
+ public boolean isSameRM(XAResource xaResource) throws XAException
+ {
+ // TODO : get the server identity of xaResource and compare it with our own one
+ return false;
+ }
+
+ /**
+ * Prepare for a transaction commit of the transaction specified in <code>Xid</code>.
+ *
+ * @param xid A global transaction identifier.
+ * @return A value indicating the resource manager's vote on the outcome of the transaction.
+ * The possible values are: XA_RDONLY or XA_OK.
+ * @throws XAException An error has occurred. Possible exception values are: XAER_RMERR or XAER_NOTA
+ */
+ public int prepare(Xid xid) throws XAException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("prepare ", xid);
+ }
+ Future<XaResult> future = _xaSession.getQpidSession().dtxPrepare(convertXid(xid));
+ XaResult result = null;
+ try
+ {
+ result = future.get();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr(e.getException().getErrorCode());
+ }
+ DtxXaStatus status = result.getStatus();
+ int outcome = XAResource.XA_OK;
+ switch (status)
+ {
+ case XA_OK:
+ break;
+ case XA_RDONLY:
+ outcome = XAResource.XA_RDONLY;
+ break;
+ default:
+ checkStatus(status);
+ }
+ return outcome;
+ }
+
+ /**
+ * Obtains a list of prepared transaction branches.
+ * <p/>
+ * The transaction manager calls this method during recovery to obtain the list of transaction branches
+ * that are currently in prepared or heuristically completed states.
+ *
+ * @param flag One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS.
+ * TMNOFLAGS must be used when no other flags are set in the parameter.
+ * @return zero or more XIDs of the transaction branches that are currently in a prepared or heuristically
+ * completed state.
+ * @throws XAException An error has occurred. Possible value is XAER_INVAL.
+ */
+ public Xid[] recover(int flag) throws XAException
+ {
+ // the flag is ignored
+ Future<RecoverResult> future = _xaSession.getQpidSession().dtxRecover();
+ RecoverResult res = null;
+ try
+ {
+ res = future.get();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr( e.getException().getErrorCode());
+ }
+ Xid[] result = new Xid[res.getInDoubt().size()];
+ int i = 0;
+ for (Object obj : res.getInDoubt())
+ {
+ org.apache.qpid.transport.Xid xid = (org.apache.qpid.transport.Xid) obj;
+ result[i] = new XidImpl(xid.getBranchId(), (int) xid.getFormat(), xid.getGlobalId());
+ i++;
+ }
+ return result;
+ }
+
+ /**
+ * Informs the resource manager to roll back work done on behalf of a transaction branch
+ *
+ * @param xid A global transaction identifier.
+ * @throws XAException An error has occurred.
+ */
+ public void rollback(Xid xid) throws XAException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("rollback tx branch with xid: ", xid);
+ }
+
+ Future<XaResult> future = _xaSession.getQpidSession().dtxRollback(convertXid(xid));
+ // now wait on the future for the result
+ XaResult result = null;
+ try
+ {
+ result = future.get();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr( e.getException().getErrorCode());
+ }
+ finally
+ {
+ _xid = null;
+ }
+ checkStatus(result.getStatus());
+ }
+
+ /**
+ * Sets the current transaction timeout value for this XAResource instance.
+ * Once set, this timeout value is effective until setTransactionTimeout is
+ * invoked again with a different value.
+ * To reset the timeout value to the default value used by the resource manager, set the value to zero.
+ *
+ * @param timeout The transaction timeout value in seconds.
+ * @return true if transaction timeout value is set successfully; otherwise false.
+ * @throws XAException An error has occurred. Possible exception values are XAER_RMERR, XAER_RMFAIL, or XAER_INVAL.
+ */
+ public boolean setTransactionTimeout(int timeout) throws XAException
+ {
+ _timeout = timeout;
+ if (timeout != _timeout && _xid != null)
+ {
+ setDtxTimeout(_timeout);
+ }
+ return true;
+ }
+
+ private void setDtxTimeout(int timeout) throws XAException
+ {
+ _xaSession.getQpidSession()
+ .dtxSetTimeout(XidImpl.convert(_xid), timeout);
+ }
+
+ /**
+ * Starts work on behalf of a transaction branch specified in xid.
+ * <ul>
+ * <li> If TMJOIN is specified, an exception is thrown as it is not supported
+ * <li> If TMRESUME is specified, the start applies to resuming a suspended transaction specified in the parameter xid.
+ * <li> If neither TMJOIN nor TMRESUME is specified and the transaction specified by xid has previously been seen by the
+ * resource manager, the resource manager throws the XAException exception with XAER_DUPID error code.
+ * </ul>
+ *
+ * @param xid A global transaction identifier to be associated with the resource
+ * @param flag One of TMNOFLAGS, TMJOIN, or TMRESUME
+ * @throws XAException An error has occurred. Possible exceptions
+ * are XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
+ */
+ public void start(Xid xid, int flag) throws XAException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("start tx branch with xid: ", xid);
+ }
+ switch (flag)
+ {
+ case(XAResource.TMNOFLAGS):
+ break;
+ case(XAResource.TMJOIN):
+ break;
+ case(XAResource.TMRESUME):
+ break;
+ default:
+ throw new XAException(XAException.XAER_INVAL);
+ }
+ Future<XaResult> future = _xaSession.getQpidSession()
+ .dtxStart(convertXid(xid),
+ flag == XAResource.TMJOIN ? Option.JOIN : Option.NONE,
+ flag == XAResource.TMRESUME ? Option.RESUME : Option.NONE);
+ // now wait on the future for the result
+ XaResult result = null;
+ try
+ {
+ result = future.get();
+ }
+ catch (SessionException e)
+ {
+ // we need to restore the qpid session that has been closed
+ _xaSession.createSession();
+ convertExecutionErrorToXAErr(e.getException().getErrorCode());
+ // TODO: The amqp spec does not allow to make the difference
+ // between an already known XID and a wrong arguments (join and resume are set)
+ // TODO: make sure amqp addresses that
+ }
+ checkStatus(result.getStatus());
+ _xid = xid;
+ if (_timeout > 0)
+ {
+ setDtxTimeout(_timeout);
+ }
+ }
+
+ //------------------------------------------------------------------------
+ // Private methods
+ //------------------------------------------------------------------------
+
+ /**
+ * Check xa method outcome and, when required, convert the status into the corresponding xa exception
+ * @param status method status code
+ * @throws XAException corresponding XA Exception when required
+ */
+ private void checkStatus(DtxXaStatus status) throws XAException
+ {
+ switch (status)
+ {
+ case XA_OK:
+ // Do nothing this ok
+ break;
+ case XA_RBROLLBACK:
+ // The tx has been rolled back for an unspecified reason.
+ throw new XAException(XAException.XA_RBROLLBACK);
+ case XA_RBTIMEOUT:
+ // The transaction branch took too long.
+ throw new XAException(XAException.XA_RBTIMEOUT);
+ case XA_HEURHAZ:
+ // The transaction branch may have been heuristically completed.
+ throw new XAException(XAException.XA_HEURHAZ);
+ case XA_HEURCOM:
+ // The transaction branch has been heuristically committed.
+ throw new XAException(XAException.XA_HEURCOM);
+ case XA_HEURRB:
+ // The transaction branch has been heuristically rolled back.
+ throw new XAException(XAException.XA_HEURRB);
+ case XA_HEURMIX:
+ // The transaction branch has been heuristically committed and rolled back.
+ throw new XAException(XAException.XA_HEURMIX);
+ case XA_RDONLY:
+ // The transaction branch was read-only and has been committed.
+ throw new XAException(XAException.XA_RDONLY);
+ default:
+ // this should not happen
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("got unexpected status value: ", status);
+ }
+ //A resource manager error has occured in the transaction branch.
+ throw new XAException(XAException.XAER_RMERR);
+ }
+ }
+
+ /**
+ * Convert execution error to xa exception.
+ * @param error the execution error code
+ * @throws XAException
+ */
+ private void convertExecutionErrorToXAErr(ExecutionErrorCode error) throws XAException
+ {
+ switch (error)
+ {
+ case NOT_ALLOWED:
+ // The XID already exists.
+ throw new XAException(XAException.XAER_DUPID);
+ case NOT_FOUND:
+ // The XID is not valid.
+ try
+ {
+ throw new XAException(XAException.XAER_NOTA);
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ throw e;
+ }
+ case ILLEGAL_STATE:
+ // Routine was invoked in an inproper context.
+ throw new XAException(XAException.XAER_PROTO);
+ case NOT_IMPLEMENTED:
+ // the command is not implemented
+ throw new XAException(XAException.XAER_RMERR);
+ case COMMAND_INVALID:
+ // Invalid call
+ throw new XAException(XAException.XAER_INVAL);
+ default:
+ // this should not happen
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Got unexpected error: " + error);
+ }
+ //A resource manager error has occured in the transaction branch.
+ throw new XAException(XAException.XAER_RMERR);
+ }
+ }
+
+ /**
+ * convert a generic xid into qpid format
+ * @param xid xid to be converted
+ * @return the qpid formated xid
+ * @throws XAException when xid is null
+ */
+ private org.apache.qpid.transport.Xid convertXid(Xid xid) throws XAException
+ {
+ if (xid == null)
+ {
+ // Invalid arguments were given.
+ throw new XAException(XAException.XAER_INVAL);
+ }
+ return XidImpl.convert(xid);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/XASessionImpl.java b/qpid/java/client/src/main/java/org/apache/qpid/client/XASessionImpl.java
new file mode 100644
index 0000000000..354b67cd35
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/XASessionImpl.java
@@ -0,0 +1,159 @@
+/* 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.client.message.MessageFactoryRegistry;
+
+import javax.jms.*;
+import javax.transaction.xa.XAResource;
+
+/**
+ * This is an implementation of the javax.njms.XASEssion interface.
+ */
+public class XASessionImpl extends AMQSession_0_10 implements XASession, XATopicSession, XAQueueSession
+{
+ /**
+ * XAResource associated with this XASession
+ */
+ private final XAResourceImpl _xaResource;
+
+ /**
+ * This XASession Qpid DtxSession
+ */
+ private org.apache.qpid.transport.Session _qpidDtxSession;
+
+ /**
+ * The standard session
+ */
+ private Session _jmsSession;
+
+
+ //-- Constructors
+ /**
+ * Create a JMS XASession
+ */
+ public XASessionImpl(org.apache.qpid.transport.Connection qpidConnection, AMQConnection con, int channelId,
+ int defaultPrefetchHigh, int defaultPrefetchLow)
+ {
+ super(qpidConnection, con, channelId, false, // this is not a transacted session
+ Session.AUTO_ACKNOWLEDGE, // the ack mode is transacted
+ MessageFactoryRegistry.newDefaultRegistry(), defaultPrefetchHigh, defaultPrefetchLow);
+ createSession();
+ _xaResource = new XAResourceImpl(this);
+ }
+
+ //-- public methods
+
+ /**
+ * Create a qpid session.
+ */
+ public void createSession()
+ {
+ _qpidDtxSession = _qpidConnection.createSession(0);
+ _qpidDtxSession.setSessionListener(this);
+ _qpidDtxSession.dtxSelect();
+ }
+
+
+ //--- javax.njms.XASEssion API
+
+ /**
+ * Gets the session associated with this XASession.
+ *
+ * @return The session object.
+ * @throws JMSException if an internal error occurs.
+ */
+ public Session getSession() throws JMSException
+ {
+ if (_jmsSession == null)
+ {
+ _jmsSession = getAMQConnection().createSession(true, getAcknowledgeMode());
+ }
+ return _jmsSession;
+ }
+
+ /**
+ * Returns an XA resource.
+ *
+ * @return An XA resource.
+ */
+ public XAResource getXAResource()
+ {
+ return _xaResource;
+ }
+
+ //-- overwritten mehtods
+ /**
+ * Throws a {@link TransactionInProgressException}, since it should
+ * not be called for an XASession object.
+ *
+ * @throws TransactionInProgressException always.
+ */
+ public void commit() throws JMSException
+ {
+ throw new TransactionInProgressException(
+ "XASession: A direct invocation of the commit operation is probibited!");
+ }
+
+ /**
+ * Throws a {@link TransactionInProgressException}, since it should
+ * not be called for an XASession object.
+ *
+ * @throws TransactionInProgressException always.
+ */
+ public void rollback() throws JMSException
+ {
+ throw new TransactionInProgressException(
+ "XASession: A direct invocation of the rollback operation is probibited!");
+ }
+
+ /**
+ * Access to the underlying Qpid Session
+ *
+ * @return The associated Qpid Session.
+ */
+ protected org.apache.qpid.transport.Session getQpidSession()
+ {
+ return _qpidDtxSession;
+ }
+
+ //--- interface XAQueueSession
+ /**
+ * Gets the topic session associated with this <CODE>XATopicSession</CODE>.
+ *
+ * @return the topic session object
+ * @throws JMSException If an internal error occurs.
+ */
+ public QueueSession getQueueSession() throws JMSException
+ {
+ return (QueueSession) getSession();
+ }
+
+ //--- interface XATopicSession
+
+ /**
+ * Gets the topic session associated with this <CODE>XATopicSession</CODE>.
+ *
+ * @return the topic session object
+ * @throws JMSException If an internal error occurs.
+ */
+ public TopicSession getTopicSession() throws JMSException
+ {
+ return (TopicSession) getSession();
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverException.java
new file mode 100644
index 0000000000..037b0dc2d1
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverException.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.client.failover;
+
+/**
+ * FailoverException is used to indicate that a synchronous request has failed to receive the reply that it is waiting
+ * for because the fail-over process has been started whilst it was waiting for its reply. Synchronous methods generally
+ * raise this exception to indicate that they must be re-tried once the fail-over process has completed.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Used to indicate failure of a synchronous request due to fail-over.
+ * </table>
+ *
+ * @todo This exception is created and passed as an argument to a method, rather than thrown. The exception is being
+ * used to represent an event, passed out to other threads. Use of exceptions as arguments rather than as
+ * exceptions is extremly confusing. Ideally use a condition or set a flag and check it instead.
+ * This exceptions-as-events pattern seems to be in a similar style to Mina code, which is not pretty, but
+ * potentially acceptable for that reason. We have the option of extending the mina model to add more events
+ * to it, that is, anything that is interested in handling failover as an event occurs below the main
+ * amq event handler, which knows the specific interface of the qpid handlers, which can pass this down as
+ * an explicit event, without it being an exception. Add failover method to BlockingMethodFrameListener,
+ * have it set a flag or interrupt the waiting thread, which then creates and raises this exception.
+ */
+public class FailoverException extends Exception
+{
+ public FailoverException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java
new file mode 100644
index 0000000000..f74dbba939
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverHandler.java
@@ -0,0 +1,268 @@
+/*
+ *
+ * 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.failover;
+
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.AMQState;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * FailoverHandler is a continuation that performs the failover procedure on a protocol session. As described in the
+ * class level comment for {@link AMQProtocolHandler}, a protocol connection can span many physical transport
+ * connections, failing over to a new connection if the transport connection fails. The procedure to establish a new
+ * connection is expressed as a continuation, in order that it may be run in a seperate thread to the i/o thread that
+ * detected the failure and is used to handle the communication to establish a new connection.
+ *
+ * </p>The reason this needs to be a separate thread is because this work cannot be done inside the i/o processor
+ * thread. The significant task is the connection setup which involves a protocol exchange until a particular state
+ * is achieved. This procedure waits until the state is achieved which would prevent the i/o thread doing the work
+ * it needs to do to achieve the new state.
+ *
+ * <p/>The failover procedure does the following:
+ *
+ * <ol>
+ * <li>Sets the failing over condition to true.</li>
+ * <li>Creates a {@link FailoverException} and gets the protocol connection handler to propagate this event to all
+ * interested parties.</li>
+ * <li>Takes the failover mutex on the protocol connection handler.</li>
+ * <li>Abandons the fail over if any of the interested parties vetoes it. The mutex is released and the condition
+ * reset.</li>
+ * <li>Creates a new {@link AMQStateManager} and re-established the connection through it.</li>
+ * <li>Informs the AMQConnection if the connection cannot be re-established.</li>
+ * <li>Recreates all sessions from the old connection to the new.</li>
+ * <li>Resets the failing over condition and releases the mutex.</li>
+ * </ol>
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Update fail-over state <td> {@link AMQProtocolHandler}
+ * </table>
+ *
+ * @todo The failover latch and mutex are used like a lock and condition. If the retrotranlator supports lock/condition
+ * then could change over to using them. 1.4 support still needed.
+ *
+ * @todo If the condition is set to null on a vetoes fail-over and there are already other threads waiting on the
+ * condition, they will never be released. It might be an idea to reset the condition in a finally block.
+ *
+ * @todo Creates a {@link AMQDisconnectedException} and passes it to the AMQConnection. No need to use an
+ * exception-as-argument here, could just as easily call a specific method for this purpose on AMQConnection.
+ *
+ * @todo Creates a {@link FailoverException} and propagates it to the MethodHandlers. No need to use an
+ * exception-as-argument here, could just as easily call a specific method for this purpose on
+ * {@link org.apache.qpid.protocol.AMQMethodListener}.
+ */
+public class FailoverHandler implements Runnable
+{
+ /** Used for debugging. */
+ private static final Logger _logger = LoggerFactory.getLogger(FailoverHandler.class);
+
+ /** Holds the protocol handler for the failed connection, upon which the new connection is to be set up. */
+ private AMQProtocolHandler _amqProtocolHandler;
+
+ /** Used to hold the host to fail over to. This is optional and if not set a reconnect to the previous host is tried. */
+ private String _host;
+
+ /** Used to hold the port to fail over to. */
+ private int _port;
+
+ /**
+ * Creates a failover handler on a protocol session, for a particular MINA session (network connection).
+ *
+ * @param amqProtocolHandler The protocol handler that spans the failover.
+ */
+ public FailoverHandler(AMQProtocolHandler amqProtocolHandler)
+ {
+ _amqProtocolHandler = amqProtocolHandler;
+ }
+
+ /**
+ * Performs the failover procedure. See the class level comment, {@link FailoverHandler}, for a description of the
+ * failover procedure.
+ */
+ public void run()
+ {
+ if (Thread.currentThread().isDaemon())
+ {
+ throw new IllegalStateException("FailoverHandler must run on a non-daemon thread.");
+ }
+
+ // Create a latch, upon which tasks that must not run in parallel with a failover can wait for completion of
+ // the fail over.
+ _amqProtocolHandler.setFailoverLatch(new CountDownLatch(1));
+
+ // We wake up listeners. If they can handle failover, they will extend the
+ // FailoverRetrySupport class and will in turn block on the latch until failover
+ // has completed before retrying the operation.
+ _amqProtocolHandler.notifyFailoverStarting();
+
+ // Since failover impacts several structures we protect them all with a single mutex. These structures
+ // are also in child objects of the connection. This allows us to manipulate them without affecting
+ // client code which runs in a separate thread.
+ synchronized (_amqProtocolHandler.getConnection().getFailoverMutex())
+ {
+ //Clear the exception now that we have the failover mutex there can be no one else waiting for a frame so
+ // we can clear the exception.
+ _amqProtocolHandler.failoverInProgress();
+
+ // We switch in a new state manager temporarily so that the interaction to get to the "connection open"
+ // state works, without us having to terminate any existing "state waiters". We could theoretically
+ // have a state waiter waiting until the connection is closed for some reason. Or in future we may have
+ // a slightly more complex state model therefore I felt it was worthwhile doing this.
+ AMQStateManager existingStateManager = _amqProtocolHandler.getStateManager();
+
+
+ // Use a fresh new StateManager for the reconnection attempts
+ _amqProtocolHandler.setStateManager(new AMQStateManager());
+
+
+ if (!_amqProtocolHandler.getConnection().firePreFailover(_host != null))
+ {
+ _logger.info("Failover process veto-ed by client");
+
+ //Restore Existing State Manager
+ _amqProtocolHandler.setStateManager(existingStateManager);
+
+ //todo: ritchiem these exceptions are useless... Would be better to attempt to propogate exception that
+ // prompted the failover event.
+ if (_host != null)
+ {
+ _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Redirect was vetoed by client", null));
+ }
+ else
+ {
+ _amqProtocolHandler.getConnection().exceptionReceived(new AMQDisconnectedException("Failover was vetoed by client", null));
+ }
+
+ _amqProtocolHandler.getFailoverLatch().countDown();
+ _amqProtocolHandler.setFailoverLatch(null);
+
+ return;
+ }
+
+ _logger.info("Starting failover process");
+
+ boolean failoverSucceeded;
+ // when host is non null we have a specified failover host otherwise we all the client to cycle through
+ // all specified hosts
+
+ // if _host has value then we are performing a redirect.
+ if (_host != null)
+ {
+ failoverSucceeded = _amqProtocolHandler.getConnection().attemptReconnection(_host, _port);
+ }
+ else
+ {
+ failoverSucceeded = _amqProtocolHandler.getConnection().attemptReconnection();
+ }
+
+ if (!failoverSucceeded)
+ {
+ //Restore Existing State Manager
+ _amqProtocolHandler.setStateManager(existingStateManager);
+
+ _amqProtocolHandler.getConnection().exceptionReceived(
+ new AMQDisconnectedException("Server closed connection and no failover " +
+ "was successful", null));
+ }
+ else
+ {
+ // Set the new Protocol Session in the StateManager.
+ existingStateManager.setProtocolSession(_amqProtocolHandler.getProtocolSession());
+
+ // Now that the ProtocolHandler has been reconnected clean up
+ // the state of the old state manager. As if we simply reinstate
+ // it any old exception that had occured prior to failover may
+ // prohibit reconnection.
+ // e.g. During testing when the broker is shutdown gracefully.
+ // The broker
+ // Clear any exceptions we gathered
+ if (existingStateManager.getCurrentState() != AMQState.CONNECTION_OPEN)
+ {
+ // Clear the state of the previous state manager as it may
+ // have received an exception
+ existingStateManager.clearLastException();
+ existingStateManager.changeState(AMQState.CONNECTION_OPEN);
+ }
+
+
+ //Restore Existing State Manager
+ _amqProtocolHandler.setStateManager(existingStateManager);
+ try
+ {
+ if (_amqProtocolHandler.getConnection().firePreResubscribe())
+ {
+ _logger.info("Resubscribing on new connection");
+ _amqProtocolHandler.getConnection().resubscribeSessions();
+ }
+ else
+ {
+ _logger.info("Client vetoed automatic resubscription");
+ }
+
+ _amqProtocolHandler.getConnection().fireFailoverComplete();
+ _amqProtocolHandler.setFailoverState(FailoverState.NOT_STARTED);
+ _logger.info("Connection failover completed successfully");
+ }
+ catch (Exception e)
+ {
+ _logger.info("Failover process failed - exception being propagated by protocol handler");
+ _amqProtocolHandler.setFailoverState(FailoverState.FAILED);
+ /*try
+ {*/
+ _amqProtocolHandler.exception(e);
+ /*}
+ catch (Exception ex)
+ {
+ _logger.error("Error notifying protocol session of error: " + ex, ex);
+ }*/
+ }
+ }
+ }
+
+ _amqProtocolHandler.getFailoverLatch().countDown();
+ }
+
+ /**
+ * Sets the host name to fail over to. This is optional and if not set a reconnect to the previous host is tried.
+ *
+ * @param host The host name to fail over to.
+ */
+ public void setHost(String host)
+ {
+ _host = host;
+ }
+
+ /**
+ * Sets the port to fail over to.
+ *
+ * @param port The port to fail over to.
+ */
+ public void setPort(int port)
+ {
+ _port = port;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverNoopSupport.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverNoopSupport.java
new file mode 100644
index 0000000000..51cc94965a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverNoopSupport.java
@@ -0,0 +1,75 @@
+/*
+ * 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.failover;
+
+import org.apache.qpid.client.AMQConnection;
+
+/**
+ * FailoverNoopSupport is a {@link FailoverSupport} implementation that does not really provide any failover support
+ * at all. It wraps a {@link FailoverProtectedOperation} but should that operation throw {@link FailoverException} this
+ * support class simply re-raises that exception as an IllegalStateException. This support wrapper should only be
+ * used where the caller can be certain that the failover protected operation cannot acutally throw a failover exception,
+ * for example, because the caller already holds a lock preventing that condition from arising.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Perform a fail-over protected operation raising providing no handling of fail-over conditions.
+ * </table>
+ */
+public class FailoverNoopSupport<T, E extends Exception> implements FailoverSupport<T, E>
+{
+ /** The protected operation that is to be retried in the event of fail-over. */
+ FailoverProtectedOperation<T, E> operation;
+
+ /** The connection on which the fail-over protected operation is to be performed. */
+ AMQConnection connection;
+
+ /**
+ * Creates an automatic retrying fail-over handler for the specified operation.
+ *
+ * @param operation The fail-over protected operation to wrap in this handler.
+ */
+ public FailoverNoopSupport(FailoverProtectedOperation<T, E> operation, AMQConnection con)
+ {
+ this.operation = operation;
+ this.connection = con;
+ }
+
+ /**
+ * Delegates to another continuation which is to be provided with fail-over handling.
+ *
+ * @return The return value from the delegated to continuation.
+ * @throws E Any exception that the delegated to continuation may raise.
+ */
+ public T execute() throws E
+ {
+ try
+ {
+ return operation.execute();
+ }
+ catch (FailoverException e)
+ {
+ throw new IllegalStateException("Fail-over interupted no-op failover support. "
+ + "No-op support should only be used where the caller is certain fail-over cannot occur.", e);
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverProtectedOperation.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverProtectedOperation.java
new file mode 100644
index 0000000000..e9c5f24791
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverProtectedOperation.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.client.failover;
+
+/**
+ * FailoverProtectedOperation is a continuation for an operation that may throw a {@link FailoverException} because
+ * it has been interrupted by the fail-over process. The {@link FailoverRetrySupport} class defines support wrappers
+ * for failover protected operations, in order to provide different handling schemes when failovers occurr.
+ *
+ * <p/>The type of checked exception that the operation may perform has been generified, in order that fail over
+ * protected operations can be defined that raise arbitrary exceptions. The actuall exception types used should not
+ * be sub-classes of FailoverException, or else catching FailoverException in the {@link FailoverRetrySupport} classes
+ * will mask the exception.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Perform an operation that may be interrupted by fail-over.
+ * </table>
+ */
+public interface FailoverProtectedOperation<T, E extends Exception>
+{
+ /**
+ * Performs the continuations work.
+ *
+ * @return Provdes scope for the continuation to return an arbitrary value.
+ *
+ * @throws FailoverException If the operation is interrupted by a fail-over notification.
+ */
+ public abstract T execute() throws E, FailoverException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java
new file mode 100644
index 0000000000..e9e52cc97c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverRetrySupport.java
@@ -0,0 +1,104 @@
+/*
+ *
+ * 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.failover;
+
+import org.apache.qpid.client.AMQConnection;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * FailoverRetrySupport is a continuation that wraps another continuation, delaying its execution until it is notified
+ * that a blocking condition has been met, and executing the continuation within a mutex. If the continuation fails, due
+ * to the original condition being broken, whilst the continuation is waiting for a reponse to a synchronous request,
+ * FailoverRetrySupport automatcally rechecks the condition and re-acquires the mutex and re-runs the continution. This
+ * automatic retrying is continued until the continuation succeeds, or throws an exception (different to
+ * FailoverException, which is used to signal the failure of the original condition).
+ *
+ * <p/>The blocking condition used is that the connection is not currently failing over, and the mutex used is the
+ * connection failover mutex, which guards against the fail-over process being run during fail-over vulnerable methods.
+ * These are used like a lock and condition variable.
+ *
+ * <p/>The wrapped operation may throw a FailoverException, this is an exception that can be raised by a
+ * {@link org.apache.qpid.client.protocol.BlockingMethodFrameListener}, in response to it being notified that a
+ * fail-over wants to start whilst it was waiting. Methods that are vulnerable to fail-over are those that are
+ * synchronous, where a failure will prevent them from getting the reply they are waiting for and asynchronous
+ * methods that should not be attempted when a fail-over is in progress.
+ *
+ * <p/>Wrapping a synchronous method in a FailoverRetrySupport will have the effect that the operation will not be
+ * started during fail-over, but be delayed until any current fail-over has completed. Should a fail-over process want
+ * to start whilst waiting for the synchrnous reply, the FailoverRetrySupport will detect this and rety the operation
+ * until it succeeds. Synchronous methods are usually coordinated with a
+ * {@link org.apache.qpid.client.protocol.BlockingMethodFrameListener} which is notified when a fail-over process wants
+ * to start and throws a FailoverException in response to this.
+ *
+ * <p/>Wrapping an asynchronous method in a FailoverRetrySupport will have the effect that the operation will not be
+ * started during fail-over, but be delayed until any current fail-over has completed.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a continuation synchronized on a fail-over lock and condition.
+ * <tr><td> Automatically retry the continuation accross fail-overs until it succeeds, or raises an exception.
+ * </table>
+ *
+ * @todo Another continuation. Could use an interface Continuation (as described in other todos, for example, see
+ * {@link org.apache.qpid.pool.Job}). Then have a wrapping continuation (this), which blocks on an arbitrary
+ * Condition or Latch (specified in constructor call), that this blocks on before calling the wrapped Continuation.
+ * Must work on Java 1.4, so check retrotranslator works on Lock/Condition or latch first. Argument and return type
+ * to match wrapped condition as type parameters. Rename to AsyncConditionalContinuation or something like that.
+ *
+ * @todo InterruptedException not handled well.
+ */
+public class FailoverRetrySupport<T, E extends Exception> implements FailoverSupport<T, E>
+{
+ /** Used for debugging. */
+ private static final Logger _log = LoggerFactory.getLogger(FailoverRetrySupport.class);
+
+ /** The protected operation that is to be retried in the event of fail-over. */
+ FailoverProtectedOperation<T, E> operation;
+
+ /** The connection on which the fail-over protected operation is to be performed. */
+ AMQConnection connection;
+
+ /**
+ * Creates an automatic retrying fail-over handler for the specified operation.
+ *
+ * @param operation The fail-over protected operation to wrap in this handler.
+ */
+ public FailoverRetrySupport(FailoverProtectedOperation<T, E> operation, AMQConnection con)
+ {
+ this.operation = operation;
+ this.connection = con;
+ }
+
+ /**
+ * Delays a continuation until the "not failing over" condition is met on the specified connection. Repeats
+ * until the operation throws AMQException or succeeds without being interrupted by fail-over.
+ *
+ * @return The result of executing the continuation.
+ *
+ * @throws E Any underlying exception is allowed to fall through.
+ */
+ public T execute() throws E
+ {
+ return connection.executeRetrySupport(operation);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverState.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverState.java
new file mode 100644
index 0000000000..807a5f7d13
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverState.java
@@ -0,0 +1,64 @@
+/*
+ *
+ * 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.failover;
+
+/**
+ * Defines the possible states of the failover process; not started, in progress, failed.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent a one of the states of the fail-over process.
+ * </table>
+ */
+public final class FailoverState
+{
+ /** The string description on this state. */
+ private final String _state;
+
+ /** Failover has not yet been attempted on this connection. */
+ public static final FailoverState NOT_STARTED = new FailoverState("NOT STARTED");
+
+ /** Failover has been requested on this connection but has not completed. */
+ public static final FailoverState IN_PROGRESS = new FailoverState("IN PROGRESS");
+
+ /** Failover has been attempted and failed. */
+ public static final FailoverState FAILED = new FailoverState("FAILED");
+
+ /**
+ * Creates a type safe enumeration of a fail-over state.
+ *
+ * @param state The fail-over state description string.
+ */
+ private FailoverState(String state)
+ {
+ _state = state;
+ }
+
+ /**
+ * Prints this state, mainly for debugging purposes.
+ *
+ * @return The string description of this state.
+ */
+ public String toString()
+ {
+ return "FailoverState: " + _state;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverSupport.java b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverSupport.java
new file mode 100644
index 0000000000..ef2e7e1d65
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/failover/FailoverSupport.java
@@ -0,0 +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.client.failover;
+
+/**
+ * FailoverSupport defines an interface for different types of fail-over handlers, that provide different types of
+ * behaviour for handling fail-overs during operations that can be interrupted by the fail-over process. For example,
+ * the support could automatically retry once the fail-over process completes, could prevent an operation from being
+ * started whilst fail-over is running, or could quietly abandon the operation or raise an exception, and so on.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Perform a fail-over protected operation with handling for fail-over conditions.
+ * </table>
+ *
+ * @todo Continuation, extend some sort of re-usable Continuation interface, which might look very like this one.
+ */
+public interface FailoverSupport<T, E extends Exception>
+{
+ /**
+ * Delegates to another continuation which is to be provided with fail-over handling.
+ *
+ * @return The return value from the delegated to continuation.
+ *
+ * @throws E Any exception that the delegated to continuation may raise.
+ */
+ public T execute() throws E;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java
new file mode 100644
index 0000000000..af47673a43
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/AccessRequestOkMethodHandler.java
@@ -0,0 +1,50 @@
+package org.apache.qpid.client.handler;
+/*
+ *
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+
+public class AccessRequestOkMethodHandler implements StateAwareMethodListener<AccessRequestOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AccessRequestOkMethodHandler.class);
+
+ private static AccessRequestOkMethodHandler _handler = new AccessRequestOkMethodHandler();
+
+ public static AccessRequestOkMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ public void methodReceived(AMQProtocolSession session, AccessRequestOkBody method, int channelId)
+ throws AMQException
+ {
+ _logger.debug("AccessRequestOk method received");
+ session.setTicket(method.getTicket(), channelId);
+
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java
new file mode 100644
index 0000000000..5cb9412d51
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicCancelOkMethodHandler.java
@@ -0,0 +1,55 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.BasicCancelOkBody;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BasicCancelOkMethodHandler implements StateAwareMethodListener<BasicCancelOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(BasicCancelOkMethodHandler.class);
+
+ private static final BasicCancelOkMethodHandler _instance = new BasicCancelOkMethodHandler();
+
+ public static BasicCancelOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private BasicCancelOkMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, BasicCancelOkBody body, int channelId)
+ throws AMQException
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("New BasicCancelOk method received for consumer:" + body.getConsumerTag());
+ }
+
+ session.confirmConsumerCancelled(channelId, body.getConsumerTag());
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.java
new file mode 100644
index 0000000000..6237234c4d
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicDeliverMethodHandler.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.client.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.message.UnprocessedMessage_0_8;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.BasicDeliverBody;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BasicDeliverMethodHandler implements StateAwareMethodListener<BasicDeliverBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(BasicDeliverMethodHandler.class);
+
+ private static final BasicDeliverMethodHandler _instance = new BasicDeliverMethodHandler();
+
+ public static BasicDeliverMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQProtocolSession session, BasicDeliverBody body, int channelId)
+ throws AMQException
+ {
+ final UnprocessedMessage_0_8 msg = new UnprocessedMessage_0_8(
+ body.getDeliveryTag(),
+ body.getConsumerTag().toIntValue(),
+ body.getExchange(),
+ body.getRoutingKey(),
+ body.getRedelivered());
+ _logger.debug("New JmsDeliver method received:" + session);
+ session.unprocessedMessageReceived(channelId, msg);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java
new file mode 100644
index 0000000000..3bbc9209c5
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/BasicReturnMethodHandler.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.message.ReturnMessage;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.BasicReturnBody;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BasicReturnMethodHandler implements StateAwareMethodListener<BasicReturnBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(BasicReturnMethodHandler.class);
+
+ private static final BasicReturnMethodHandler _instance = new BasicReturnMethodHandler();
+
+ public static BasicReturnMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+
+ public void methodReceived(AMQProtocolSession session, BasicReturnBody body, int channelId)
+ throws AMQException
+ {
+ _logger.debug("New JmsBounce method received");
+ final ReturnMessage msg = new ReturnMessage(
+ body.getExchange(),
+ body.getRoutingKey(),
+ body.getReplyText(),
+ body.getReplyCode()
+ );
+
+ session.unprocessedMessageReceived(channelId, msg);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.java
new file mode 100644
index 0000000000..2cf19bf391
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseMethodHandler.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.client.handler;
+
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQInvalidRoutingKeyException;
+import org.apache.qpid.client.AMQNoConsumersException;
+import org.apache.qpid.client.AMQNoRouteException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ChannelCloseBody;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.protocol.AMQConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ChannelCloseMethodHandler implements StateAwareMethodListener<ChannelCloseBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseMethodHandler.class);
+
+ private static ChannelCloseMethodHandler _handler = new ChannelCloseMethodHandler();
+
+ public static ChannelCloseMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ public void methodReceived(AMQProtocolSession session, ChannelCloseBody method, int channelId)
+ throws AMQException
+ {
+ _logger.debug("ChannelClose method received");
+
+ AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode());
+ AMQShortString reason = method.getReplyText();
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Channel close reply code: " + errorCode + ", reason: " + reason);
+ }
+
+ ChannelCloseOkBody body = session.getMethodRegistry().createChannelCloseOkBody();
+ AMQFrame frame = body.generateFrame(channelId);
+ session.writeFrame(frame);
+ try
+ {
+ if (errorCode != AMQConstant.REPLY_SUCCESS)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Channel close received with errorCode " + errorCode + ", and reason " + reason);
+ }
+
+ if (errorCode == AMQConstant.NO_CONSUMERS)
+ {
+ throw new AMQNoConsumersException("Error: " + reason, null, null);
+ }
+ else if (errorCode == AMQConstant.NO_ROUTE)
+ {
+ throw new AMQNoRouteException("Error: " + reason, null, null);
+ }
+ else if (errorCode == AMQConstant.INVALID_ARGUMENT)
+ {
+ _logger.debug("Broker responded with Invalid Argument.");
+
+ throw new org.apache.qpid.AMQInvalidArgumentException(String.valueOf(reason), null);
+ }
+ else if (errorCode == AMQConstant.INVALID_ROUTING_KEY)
+ {
+ _logger.debug("Broker responded with Invalid Routing Key.");
+
+ throw new AMQInvalidRoutingKeyException(String.valueOf(reason), null);
+ }
+ else
+ {
+
+ throw new AMQChannelClosedException(errorCode, "Error: " + reason, null);
+ }
+
+ }
+ }
+ finally
+ {
+ // fixme why is this only done when the close is expected...
+ // should the above forced closes not also cause a close?
+ // ----------
+ // 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.
+ // ----
+ // 2009-09-07 - ritchiem
+ // calling channelClosed will only close this session and will not
+ // prevent failover. If we don't close the session here then we will
+ // have problems during the session close as it will attempt to
+ // close the session that the broker has closed,
+
+ session.channelClosed(channelId, errorCode, String.valueOf(reason));
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.java
new file mode 100644
index 0000000000..72936779c2
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelCloseOkMethodHandler.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.client.handler;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ChannelCloseOkBody;
+import org.apache.qpid.protocol.AMQConstant;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ChannelCloseOkMethodHandler implements StateAwareMethodListener<ChannelCloseOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseOkMethodHandler.class);
+
+ private static final ChannelCloseOkMethodHandler _instance = new ChannelCloseOkMethodHandler();
+
+ public static ChannelCloseOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQProtocolSession session, ChannelCloseOkBody method, int channelId)
+ throws AMQException
+ {
+ _logger.info("Received channel-close-ok for channel-id " + channelId);
+
+ session.channelClosed(channelId, AMQConstant.REPLY_SUCCESS, "Channel closed successfully");
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java
new file mode 100644
index 0000000000..2153b9cc8c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowMethodHandler.java
@@ -0,0 +1,51 @@
+package org.apache.qpid.client.handler;
+
+import org.apache.qpid.framing.ChannelFlowBody;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+
+public class ChannelFlowMethodHandler implements StateAwareMethodListener<ChannelFlowBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ChannelFlowMethodHandler.class);
+ private static final ChannelFlowMethodHandler _instance = new ChannelFlowMethodHandler();
+
+ public static ChannelFlowMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelFlowMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, ChannelFlowBody body, int channelId)
+ throws AMQException
+ {
+ session.setFlowControl(channelId, body.getActive());
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java
new file mode 100644
index 0000000000..6f66a972d5
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ChannelFlowOkMethodHandler.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ChannelFlowOkBody;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ChannelFlowOkMethodHandler implements StateAwareMethodListener<ChannelFlowOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ChannelFlowOkMethodHandler.class);
+ private static final ChannelFlowOkMethodHandler _instance = new ChannelFlowOkMethodHandler();
+
+ public static ChannelFlowOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ChannelFlowOkMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, ChannelFlowOkBody body, int channelId)
+ throws AMQException
+ {
+
+ _logger.debug("Received Channel.Flow-Ok message, active = " + body.getActive());
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java
new file mode 100644
index 0000000000..ec98783a8a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl.java
@@ -0,0 +1,543 @@
+/*
+ *
+ * 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;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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 ChannelCloseOkMethodHandler _channelCloseOkMethodHandler = ChannelCloseOkMethodHandler.getInstance();
+ private static final ChannelFlowOkMethodHandler _channelFlowOkMethodHandler = ChannelFlowOkMethodHandler.getInstance();
+ private static final ChannelFlowMethodHandler _channelFlowMethodHandler = ChannelFlowMethodHandler.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 final Logger _logger = LoggerFactory.getLogger(ClientMethodDispatcherImpl.class);
+
+ private static interface DispatcherFactory
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQProtocolSession session);
+ }
+
+ private static final Map<ProtocolVersion, DispatcherFactory> _dispatcherFactories =
+ new HashMap<ProtocolVersion, DispatcherFactory>();
+
+ static
+ {
+ _dispatcherFactories.put(ProtocolVersion.v8_0,
+ new DispatcherFactory()
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQProtocolSession session)
+ {
+ return new ClientMethodDispatcherImpl_8_0(session);
+ }
+ });
+
+ _dispatcherFactories.put(ProtocolVersion.v0_9,
+ new DispatcherFactory()
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQProtocolSession session)
+ {
+ return new ClientMethodDispatcherImpl_0_9(session);
+ }
+ });
+
+
+ _dispatcherFactories.put(ProtocolVersion.v0_91,
+ new DispatcherFactory()
+ {
+ public ClientMethodDispatcherImpl createMethodDispatcher(AMQProtocolSession session)
+ {
+ return new ClientMethodDispatcherImpl_0_91(session);
+ }
+ });
+
+ }
+
+ public static ClientMethodDispatcherImpl newMethodDispatcher(ProtocolVersion version, AMQProtocolSession session)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("New Method Dispatcher:" + session);
+ }
+
+ DispatcherFactory factory = _dispatcherFactories.get(version);
+ return factory.createMethodDispatcher(session);
+ }
+
+ AMQProtocolSession _session;
+
+ public ClientMethodDispatcherImpl(AMQProtocolSession session)
+ {
+ _session = session;
+ }
+
+ public AMQStateManager getStateManager()
+ {
+ return _session.getStateManager();
+ }
+
+ public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException
+ {
+ _basicCancelOkMethodHandler.methodReceived(_session, 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(_session, 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(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException
+ {
+ _channelCloseMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+ _channelCloseOkMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException
+ {
+ _channelFlowMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException
+ {
+ _channelFlowOkMethodHandler.methodReceived(_session, 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(_session, 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(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException
+ {
+ _connectionRedirectMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException
+ {
+ _connectionSecureMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException
+ {
+ _connectionStartMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException
+ {
+ _connectionTuneMethodHandler.methodReceived(_session, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException
+ {
+ _queueDeleteOkMethodHandler.methodReceived(_session, 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(_session, 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/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java
new file mode 100644
index 0000000000..d3e9fba8ed
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_9.java
@@ -0,0 +1,153 @@
+/*
+ *
+ * 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;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+
+public class ClientMethodDispatcherImpl_0_9 extends ClientMethodDispatcherImpl implements MethodDispatcher_0_9
+{
+ public ClientMethodDispatcherImpl_0_9(AMQProtocolSession session)
+ {
+ super(session);
+ }
+
+ 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/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_91.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_91.java
new file mode 100644
index 0000000000..f15340ae00
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_0_91.java
@@ -0,0 +1,158 @@
+/*
+ *
+ * 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_91.MethodDispatcher_0_91;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.AMQMethodNotImplementedException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+
+public class ClientMethodDispatcherImpl_0_91 extends ClientMethodDispatcherImpl implements MethodDispatcher_0_91
+{
+ public ClientMethodDispatcherImpl_0_91(AMQProtocolSession session)
+ {
+ super(session);
+ }
+
+ 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 dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ClientMethodDispatcherImpl_8_0.java
new file mode 100644
index 0000000000..19f758817d
--- /dev/null
+++ b/qpid/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.protocol.AMQProtocolSession;
+
+public class ClientMethodDispatcherImpl_8_0 extends ClientMethodDispatcherImpl implements MethodDispatcher_8_0
+{
+ public ClientMethodDispatcherImpl_8_0(AMQProtocolSession session)
+ {
+ super(session);
+ }
+
+ 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/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java
new file mode 100644
index 0000000000..b392604822
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionCloseMethodHandler.java
@@ -0,0 +1,113 @@
+/*
+ *
+ * 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.AMQConnectionClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQSecurityException;
+import org.apache.qpid.client.AMQAuthenticationException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+import org.apache.qpid.protocol.AMQConstant;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConnectionCloseMethodHandler implements StateAwareMethodListener<ConnectionCloseBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ConnectionCloseMethodHandler.class);
+
+ private static ConnectionCloseMethodHandler _handler = new ConnectionCloseMethodHandler();
+
+ public static ConnectionCloseMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ private ConnectionCloseMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQProtocolSession session, ConnectionCloseBody method, int channelId)
+ throws AMQException
+ {
+ _logger.info("ConnectionClose frame received");
+
+ // does it matter
+ // stateManager.changeState(AMQState.CONNECTION_CLOSING);
+
+ AMQConstant errorCode = AMQConstant.getConstant(method.getReplyCode());
+ AMQShortString reason = method.getReplyText();
+
+ AMQException error = null;
+
+ 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.
+ session.writeFrame(closeOkBody.generateFrame(0));
+
+ if (errorCode != AMQConstant.REPLY_SUCCESS)
+ {
+ if (errorCode == AMQConstant.NOT_ALLOWED)
+ {
+ _logger.info("Error :" + errorCode + ":" + Thread.currentThread().getName());
+
+ error = new AMQAuthenticationException(errorCode, reason == null ? null : reason.toString(), null);
+ }
+ else if (errorCode == AMQConstant.ACCESS_REFUSED)
+ {
+ _logger.info("Error :" + errorCode + ":" + Thread.currentThread().getName());
+
+ error = new AMQSecurityException(reason == null ? null : reason.toString(), null);
+ }
+ else
+ {
+ _logger.info("Connection close received with error code " + errorCode);
+
+ error = new AMQConnectionClosedException(errorCode, "Error: " + reason, null);
+ }
+ }
+ }
+ finally
+ {
+
+ if (error != null)
+ {
+ session.notifyError(error);
+ }
+
+ // Close the protocol Session, including any open TCP connections
+ session.closeProtocolSession();
+
+ // Closing the session should not introduce a race condition as this thread will continue to propgate any
+ // exception in to the exceptionCaught method of the SessionHandler.
+ // Any sessionClosed event should occur after this.
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.java
new file mode 100644
index 0000000000..e40cafd72f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionOpenOkMethodHandler.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.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.StateAwareMethodListener;
+
+public class ConnectionOpenOkMethodHandler implements StateAwareMethodListener<ConnectionOpenOkBody>
+{
+ private static final ConnectionOpenOkMethodHandler _instance = new ConnectionOpenOkMethodHandler();
+
+ public static ConnectionOpenOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionOpenOkMethodHandler()
+ {
+ }
+
+ public void methodReceived(AMQProtocolSession session, ConnectionOpenOkBody body, int channelId)
+ {
+ session.getStateManager().changeState(AMQState.CONNECTION_OPEN);
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java
new file mode 100644
index 0000000000..472c471fd6
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionRedirectMethodHandler.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ConnectionRedirectBody;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConnectionRedirectMethodHandler implements StateAwareMethodListener<ConnectionRedirectBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ConnectionRedirectMethodHandler.class);
+
+ private static final int DEFAULT_REDIRECT_PORT = 5672;
+
+ private static ConnectionRedirectMethodHandler _handler = new ConnectionRedirectMethodHandler();
+
+ public static ConnectionRedirectMethodHandler getInstance()
+ {
+ return _handler;
+ }
+
+ private ConnectionRedirectMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, ConnectionRedirectBody method, int channelId)
+ throws AMQException
+ {
+ _logger.info("ConnectionRedirect frame received");
+
+ String host = method.getHost().toString();
+ // the host is in the form hostname:port with the port being optional
+ int portIndex = host.indexOf(':');
+
+ int port;
+ if (portIndex == -1)
+ {
+ port = DEFAULT_REDIRECT_PORT;
+ }
+ else
+ {
+ port = Integer.parseInt(host.substring(portIndex + 1));
+ host = host.substring(0, portIndex);
+
+ }
+
+ session.failover(host, port);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.java
new file mode 100644
index 0000000000..9a9bee757b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionSecureMethodHandler.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.client.handler;
+
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ConnectionSecureBody;
+import org.apache.qpid.framing.ConnectionSecureOkBody;
+
+public class ConnectionSecureMethodHandler implements StateAwareMethodListener<ConnectionSecureBody>
+{
+ private static final ConnectionSecureMethodHandler _instance = new ConnectionSecureMethodHandler();
+
+ public static ConnectionSecureMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQProtocolSession session, ConnectionSecureBody body, int channelId)
+ throws AMQException
+ {
+ SaslClient client = session.getSaslClient();
+ if (client == null)
+ {
+ throw new AMQException(null, "No SASL client set up - cannot proceed with authentication", null);
+ }
+
+
+
+ try
+ {
+ // Evaluate server challenge
+ byte[] response = client.evaluateChallenge(body.getChallenge());
+
+ ConnectionSecureOkBody secureOkBody = session.getMethodRegistry().createConnectionSecureOkBody(response);
+
+ session.writeFrame(secureOkBody.generateFrame(channelId));
+ }
+ catch (SaslException e)
+ {
+ throw new AMQException(null, "Error processing SASL challenge: " + e, e);
+ }
+
+
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java
new file mode 100644
index 0000000000..2b49bb8f81
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionStartMethodHandler.java
@@ -0,0 +1,239 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.security.AMQCallbackHandler;
+import org.apache.qpid.client.security.CallbackHandlerRegistry;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.common.ClientProperties;
+import org.apache.qpid.common.QpidProperties;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ConnectionStartBody;
+import org.apache.qpid.framing.ConnectionStartOkBody;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+import org.apache.qpid.framing.ProtocolVersion;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import java.io.UnsupportedEncodingException;
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+public class ConnectionStartMethodHandler implements StateAwareMethodListener<ConnectionStartBody>
+{
+ private static final Logger _log = LoggerFactory.getLogger(ConnectionStartMethodHandler.class);
+
+ private static final ConnectionStartMethodHandler _instance = new ConnectionStartMethodHandler();
+
+ public static ConnectionStartMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ConnectionStartMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, ConnectionStartBody body, int channelId)
+ throws AMQException
+ {
+ _log.debug("public void methodReceived(AMQStateManager stateManager, AMQProtocolSession protocolSession, "
+ + "AMQMethodEvent evt): called");
+
+ ProtocolVersion pv = new ProtocolVersion((byte) body.getVersionMajor(), (byte) body.getVersionMinor());
+
+ // 0-9-1 is indistinguishable from 0-9 using only major and minor ... if we established the connection as 0-9-1
+ // and now get back major = 0 , minor = 9 then we can assume it means 0-9-1
+
+ if(pv.equals(ProtocolVersion.v0_9) && session.getProtocolVersion().equals(ProtocolVersion.v0_91))
+ {
+ pv = ProtocolVersion.v0_91;
+ }
+
+ // 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.
+ // It needs to do this since frame lookup is done by version.
+ if (Boolean.getBoolean("qpid.accept.broker.version") && !pv.isSupported())
+ {
+
+ pv = ProtocolVersion.getLatestSupportedVersion();
+ }
+
+ if (pv.isSupported())
+ {
+ session.setProtocolVersion(pv);
+
+ try
+ {
+ // Used to hold the SASL mechanism to authenticate with.
+ String mechanism;
+
+ if (body.getMechanisms()== null)
+ {
+ throw new AMQException(null, "mechanism not specified in ConnectionStart method frame", null);
+ }
+ else
+ {
+ mechanism = chooseMechanism(body.getMechanisms());
+ _log.debug("mechanism = " + mechanism);
+ }
+
+ if (mechanism == null)
+ {
+ throw new AMQException(null, "No supported security mechanism found, passed: " + new String(body.getMechanisms()), null);
+ }
+
+ byte[] saslResponse;
+ try
+ {
+ SaslClient sc =
+ Sasl.createSaslClient(new String[] { mechanism }, null, "AMQP", "localhost", null,
+ createCallbackHandler(mechanism, session));
+ if (sc == null)
+ {
+ throw new AMQException(null, "Client SASL configuration error: no SaslClient could be created for mechanism " + mechanism
+ + ". Please ensure all factories are registered. See DynamicSaslRegistrar for "
+ + " details of how to register non-standard SASL client providers.", null);
+ }
+
+ session.setSaslClient(sc);
+ saslResponse = (sc.hasInitialResponse() ? sc.evaluateChallenge(new byte[0]) : null);
+ }
+ catch (SaslException e)
+ {
+ session.setSaslClient(null);
+ throw new AMQException(null, "Unable to create SASL client: " + e, e);
+ }
+
+ if (body.getLocales() == null)
+ {
+ throw new AMQException(null, "Locales is not defined in Connection Start method", null);
+ }
+
+ final String locales = new String(body.getLocales(), "utf8");
+ final StringTokenizer tokenizer = new StringTokenizer(locales, " ");
+ if (tokenizer.hasMoreTokens())
+ {
+ tokenizer.nextToken();
+ }
+ else
+ {
+ throw new AMQException(null, "No locales sent from server, passed: " + locales, null);
+ }
+
+ session.getStateManager().changeState(AMQState.CONNECTION_NOT_TUNED);
+ FieldTable clientProperties = FieldTableFactory.newFieldTable();
+
+ clientProperties.setString(new AMQShortString(ClientProperties.instance.toString()),
+ 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.
+ session.writeFrame(connectionStartOkBody.generateFrame(channelId));
+
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new AMQException(null, "Unable to decode data: " + e, e);
+ }
+ }
+ else
+ {
+ _log.error("Broker requested Protocol [" + body.getVersionMajor() + "-" + body.getVersionMinor()
+ + "] which is not supported by this version of the client library");
+
+ session.closeProtocolSession();
+ }
+ }
+
+ private String getFullSystemInfo()
+ {
+ StringBuffer fullSystemInfo = new StringBuffer();
+ fullSystemInfo.append(System.getProperty("java.runtime.name"));
+ fullSystemInfo.append(", " + System.getProperty("java.runtime.version"));
+ fullSystemInfo.append(", " + System.getProperty("java.vendor"));
+ fullSystemInfo.append(", " + System.getProperty("os.arch"));
+ fullSystemInfo.append(", " + System.getProperty("os.name"));
+ fullSystemInfo.append(", " + System.getProperty("os.version"));
+ fullSystemInfo.append(", " + System.getProperty("sun.os.patch.level"));
+
+ return fullSystemInfo.toString();
+ }
+
+ private String chooseMechanism(byte[] availableMechanisms) throws UnsupportedEncodingException
+ {
+ final String mechanisms = new String(availableMechanisms, "utf8");
+ StringTokenizer tokenizer = new StringTokenizer(mechanisms, " ");
+ HashSet mechanismSet = new HashSet();
+ while (tokenizer.hasMoreTokens())
+ {
+ mechanismSet.add(tokenizer.nextToken());
+ }
+
+ String preferredMechanisms = CallbackHandlerRegistry.getInstance().getMechanisms();
+ StringTokenizer prefTokenizer = new StringTokenizer(preferredMechanisms, " ");
+ while (prefTokenizer.hasMoreTokens())
+ {
+ String mech = prefTokenizer.nextToken();
+ if (mechanismSet.contains(mech))
+ {
+ return mech;
+ }
+ }
+
+ return null;
+ }
+
+ private AMQCallbackHandler createCallbackHandler(String mechanism, AMQProtocolSession protocolSession)
+ throws AMQException
+ {
+ Class mechanismClass = CallbackHandlerRegistry.getInstance().getCallbackHandlerClass(mechanism);
+ try
+ {
+ Object instance = mechanismClass.newInstance();
+ AMQCallbackHandler cbh = (AMQCallbackHandler) instance;
+ cbh.initialise(protocolSession.getAMQConnection().getConnectionURL());
+
+ return cbh;
+ }
+ catch (Exception e)
+ {
+ throw new AMQException(null, "Unable to create callback handler: " + e, e);
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.java
new file mode 100644
index 0000000000..d1b2caf987
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ConnectionTuneMethodHandler.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.AMQException;
+import org.apache.qpid.client.ConnectionTuneParameters;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.*;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConnectionTuneMethodHandler implements StateAwareMethodListener<ConnectionTuneBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ConnectionTuneMethodHandler.class);
+
+ private static final ConnectionTuneMethodHandler _instance = new ConnectionTuneMethodHandler();
+
+ public static ConnectionTuneMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ protected ConnectionTuneMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, ConnectionTuneBody frame, int channelId)
+ {
+ _logger.debug("ConnectionTune frame received");
+ final MethodRegistry methodRegistry = session.getMethodRegistry();
+
+
+ ConnectionTuneParameters params = session.getConnectionTuneParameters();
+ if (params == null)
+ {
+ params = new ConnectionTuneParameters();
+ }
+
+ int maxChannelNumber = frame.getChannelMax();
+ //0 implies no limit, except that forced by protocol limitations (0xFFFF)
+ params.setChannelMax(maxChannelNumber == 0 ? AMQProtocolSession.MAX_CHANNEL_MAX : maxChannelNumber);
+
+ params.setFrameMax(frame.getFrameMax());
+ params.setHeartbeat(Integer.getInteger("amqj.heartbeat.delay", frame.getHeartbeat()));
+ session.setConnectionTuneParameters(params);
+
+ session.getStateManager().changeState(AMQState.CONNECTION_NOT_OPENED);
+
+ 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);
+
+ ConnectionOpenBody openBody = methodRegistry.createConnectionOpenBody(virtualHost,null,true);
+
+ // Be aware of possible changes to parameter order as versions change.
+ session.writeFrame(openBody.generateFrame(channelId));
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java
new file mode 100644
index 0000000000..690d782b40
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/ExchangeBoundOkMethodHandler.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.ExchangeBoundOkBody;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class ExchangeBoundOkMethodHandler implements StateAwareMethodListener<ExchangeBoundOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ExchangeBoundOkMethodHandler.class);
+ private static final ExchangeBoundOkMethodHandler _instance = new ExchangeBoundOkMethodHandler();
+
+ public static ExchangeBoundOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private ExchangeBoundOkMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, ExchangeBoundOkBody body, int channelId)
+ throws AMQException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Received Exchange.Bound-Ok message, response code: " + body.getReplyCode() + " text: "
+ + body.getReplyText());
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java
new file mode 100644
index 0000000000..01d82c9b55
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/handler/QueueDeleteOkMethodHandler.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.client.state.StateAwareMethodListener;
+import org.apache.qpid.framing.QueueDeleteOkBody;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class QueueDeleteOkMethodHandler implements StateAwareMethodListener<QueueDeleteOkBody>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(QueueDeleteOkMethodHandler.class);
+ private static final QueueDeleteOkMethodHandler _instance = new QueueDeleteOkMethodHandler();
+
+ public static QueueDeleteOkMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private QueueDeleteOkMethodHandler()
+ { }
+
+ public void methodReceived(AMQProtocolSession session, QueueDeleteOkBody body, int channelId)
+ throws AMQException
+ {
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Received Queue.Delete-Ok message, message count: " + body.getMessageCount());
+ }
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java
new file mode 100644
index 0000000000..c2821591d8
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate.java
@@ -0,0 +1,140 @@
+/*
+ *
+ * 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.message;
+
+import org.apache.qpid.client.AMQSession;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+
+import java.nio.ByteBuffer;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.UUID;
+
+public interface AMQMessageDelegate
+{
+ void acknowledgeThis() throws JMSException;
+
+ String getJMSMessageID() throws JMSException;
+
+ void setJMSMessageID(String string) throws JMSException;
+
+ long getJMSTimestamp() throws JMSException;
+
+ void setJMSTimestamp(long l) throws JMSException;
+
+ byte[] getJMSCorrelationIDAsBytes() throws JMSException;
+
+ void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException;
+
+ void setJMSCorrelationID(String string) throws JMSException;
+
+ String getJMSCorrelationID() throws JMSException;
+
+ Destination getJMSReplyTo() throws JMSException;
+
+ void setJMSReplyTo(Destination destination) throws JMSException;
+
+ Destination getJMSDestination() throws JMSException;
+
+ int getJMSDeliveryMode() throws JMSException;
+
+ void setJMSDeliveryMode(int i) throws JMSException;
+
+ String getJMSType() throws JMSException;
+
+ void setJMSType(String string) throws JMSException;
+
+ long getJMSExpiration() throws JMSException;
+
+ void setJMSExpiration(long l) throws JMSException;
+
+ int getJMSPriority() throws JMSException;
+
+ void setJMSPriority(int i) throws JMSException;
+
+ void clearProperties() throws JMSException;
+
+ boolean propertyExists(String string) throws JMSException;
+
+ boolean getBooleanProperty(String string) throws JMSException;
+
+ byte getByteProperty(String string) throws JMSException;
+
+ short getShortProperty(String string) throws JMSException;
+
+ int getIntProperty(String string) throws JMSException;
+
+ long getLongProperty(String string) throws JMSException;
+
+ float getFloatProperty(String string) throws JMSException;
+
+ double getDoubleProperty(String string) throws JMSException;
+
+ String getStringProperty(String string) throws JMSException;
+
+ Object getObjectProperty(String string) throws JMSException;
+
+ Enumeration getPropertyNames() throws JMSException;
+
+ void setBooleanProperty(String string, boolean b) throws JMSException;
+
+ void setByteProperty(String string, byte b) throws JMSException;
+
+ void setShortProperty(String string, short i) throws JMSException;
+
+ void setIntProperty(String string, int i) throws JMSException;
+
+ void setLongProperty(String string, long l) throws JMSException;
+
+ void setFloatProperty(String string, float v) throws JMSException;
+
+ void setDoubleProperty(String string, double v) throws JMSException;
+
+ void setStringProperty(String string, String string1) throws JMSException;
+
+ void setObjectProperty(String string, Object object) throws JMSException;
+
+ void acknowledge() throws JMSException;
+
+ public void setJMSDestination(Destination destination);
+
+ public void setContentType(String contentType);
+ public String getContentType();
+
+ public void setEncoding(String encoding);
+ public String getEncoding();
+
+
+ String getReplyToString();
+
+ void removeProperty(final String propertyName) throws JMSException;
+
+ void setAMQSession(final AMQSession s);
+
+ AMQSession getAMQSession();
+
+ long getDeliveryTag();
+
+ void setJMSMessageID(final UUID messageId) throws JMSException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegateFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegateFactory.java
new file mode 100644
index 0000000000..8c3f2fd08f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegateFactory.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.client.message;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.AMQException;
+
+public interface AMQMessageDelegateFactory<D extends AMQMessageDelegate>
+{
+ public static AMQMessageDelegateFactory DEFAULT_FACTORY = null;
+
+ public static AMQMessageDelegateFactory<AMQMessageDelegate_0_8> FACTORY_0_8 =
+ new AMQMessageDelegateFactory<AMQMessageDelegate_0_8>()
+ {
+ public AMQMessageDelegate_0_8 createDelegate()
+ {
+ return new AMQMessageDelegate_0_8();
+ }
+ };
+
+ public static AMQMessageDelegateFactory<AMQMessageDelegate_0_10> FACTORY_0_10 =
+ new AMQMessageDelegateFactory<AMQMessageDelegate_0_10>()
+ {
+ public AMQMessageDelegate_0_10 createDelegate()
+ {
+ return new AMQMessageDelegate_0_10();
+ }
+ };
+
+
+ public D createDelegate();
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java
new file mode 100644
index 0000000000..fb7b191656
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_10.java
@@ -0,0 +1,980 @@
+/*
+ *
+ * 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.message;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.jms.DeliveryMode;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.Session;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQPInvalidClassException;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQSession_0_10;
+import org.apache.qpid.client.CustomJMSXProperty;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.jms.Message;
+import org.apache.qpid.transport.DeliveryProperties;
+import org.apache.qpid.transport.ExchangeQueryResult;
+import org.apache.qpid.transport.Future;
+import org.apache.qpid.transport.Header;
+import org.apache.qpid.transport.MessageDeliveryMode;
+import org.apache.qpid.transport.MessageDeliveryPriority;
+import org.apache.qpid.transport.MessageProperties;
+import org.apache.qpid.transport.ReplyTo;
+
+/**
+ * This extends AbstractAMQMessageDelegate which contains common code between
+ * both the 0_8 and 0_10 Message types.
+ *
+ */
+public class AMQMessageDelegate_0_10 extends AbstractAMQMessageDelegate
+{
+ private static final Map<ReplyTo, SoftReference<Destination>> _destinationCache = Collections.synchronizedMap(new HashMap<ReplyTo, SoftReference<Destination>>());
+
+ public static final String JMS_TYPE = "x-jms-type";
+
+
+ private boolean _readableProperties = false;
+
+ private Destination _destination;
+
+
+ private MessageProperties _messageProps;
+ private DeliveryProperties _deliveryProps;
+ /** If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required */
+ private AMQSession _session;
+ private final long _deliveryTag;
+
+
+ protected AMQMessageDelegate_0_10()
+ {
+ this(new MessageProperties(), new DeliveryProperties(), -1);
+ _readableProperties = false;
+ }
+
+ protected AMQMessageDelegate_0_10(MessageProperties messageProps, DeliveryProperties deliveryProps, long deliveryTag)
+ {
+ _messageProps = messageProps;
+ _deliveryProps = deliveryProps;
+ _deliveryTag = deliveryTag;
+ _readableProperties = (_messageProps != null);
+
+ AMQDestination dest;
+
+ dest = generateDestination(new AMQShortString(_deliveryProps.getExchange()),
+ new AMQShortString(_deliveryProps.getRoutingKey()));
+ setJMSDestination(dest);
+ }
+
+ /**
+ * Use the 0-10 ExchangeQuery call to validate the exchange type.
+ *
+ * This is used primarily to provide the correct JMSDestination value.
+ *
+ * The query is performed synchronously iff the map exchange is not already
+ * present in the exchange Map.
+ *
+ * @param header The message headers, from which the exchange name can be extracted
+ * @param session The 0-10 session to use to call ExchangeQuery
+ */
+ public static void updateExchangeTypeMapping(Header header, org.apache.qpid.transport.Session session)
+ {
+ DeliveryProperties deliveryProps = header.get(DeliveryProperties.class);
+ if (deliveryProps != null)
+ {
+ String exchange = deliveryProps.getExchange();
+ checkAndUpdateExchange(exchange,session);
+
+ }
+
+ MessageProperties msgProps = header.get(MessageProperties.class);
+ if (msgProps != null && msgProps.getReplyTo() != null)
+ {
+ String exchange = msgProps.getReplyTo().getExchange();
+ checkAndUpdateExchange(exchange,session);
+
+ }
+ }
+
+ private static void checkAndUpdateExchange(String exchange, org.apache.qpid.transport.Session session)
+ {
+ if (exchange != null && !exchangeMapContains(exchange))
+ {
+ Future<ExchangeQueryResult> future =
+ session.exchangeQuery(exchange.toString());
+ ExchangeQueryResult res = future.get();
+
+ updateExchangeType(exchange, res.getType());
+ }
+ }
+
+
+ public String getJMSMessageID() throws JMSException
+ {
+ UUID id = _messageProps.getMessageId();
+ return id == null ? null : "ID:" + id;
+ }
+
+ public void setJMSMessageID(String messageId) throws JMSException
+ {
+ if(messageId == null)
+ {
+ _messageProps.clearMessageId();
+ }
+ else
+ {
+ if(messageId.startsWith("ID:"))
+ {
+ try
+ {
+ _messageProps.setMessageId(UUID.fromString(messageId.substring(3)));
+ }
+ catch(IllegalArgumentException ex)
+ {
+ throw new JMSException("MessageId '"+messageId+"' is not of the correct format, it must be ID: followed by a UUID");
+ }
+ }
+ else
+ {
+ throw new JMSException("MessageId '"+messageId+"' is not of the correct format, it must be ID: followed by a UUID");
+ }
+ }
+ }
+
+ public void setJMSMessageID(UUID messageId) throws JMSException
+ {
+ if(messageId == null)
+ {
+ _messageProps.clearMessageId();
+ }
+ else
+ {
+ _messageProps.setMessageId(messageId);
+ }
+ }
+
+
+ public long getJMSTimestamp() throws JMSException
+ {
+ return _deliveryProps.getTimestamp();
+ }
+
+ public void setJMSTimestamp(long timestamp) throws JMSException
+ {
+ _deliveryProps.setTimestamp(timestamp);
+ }
+
+ public byte[] getJMSCorrelationIDAsBytes() throws JMSException
+ {
+ return _messageProps.getCorrelationId();
+ }
+
+ public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException
+ {
+ _messageProps.setCorrelationId(bytes);
+ }
+
+ public void setJMSCorrelationID(String correlationId) throws JMSException
+ {
+
+ setJMSCorrelationIDAsBytes(correlationId == null ? null : correlationId.getBytes());
+ }
+
+ public String getJMSCorrelationID() throws JMSException
+ {
+
+ byte[] correlationIDAsBytes = getJMSCorrelationIDAsBytes();
+ return correlationIDAsBytes == null ? null : new String(correlationIDAsBytes);
+ }
+
+ public Destination getJMSReplyTo()
+ {
+ ReplyTo replyTo = _messageProps.getReplyTo();
+
+ if (replyTo == null)
+ {
+ return null;
+ }
+ else
+ {
+ Destination dest = null;
+ SoftReference<Destination> ref = _destinationCache.get(replyTo);
+ if (ref != null)
+ {
+ dest = ref.get();
+ }
+ if (dest == null)
+ {
+ String exchange = replyTo.getExchange();
+ String routingKey = replyTo.getRoutingKey();
+
+ dest = generateDestination(new AMQShortString(exchange), new AMQShortString(routingKey));
+ _destinationCache.put(replyTo, new SoftReference<Destination>(dest));
+ }
+
+ return dest;
+ }
+ }
+
+ public void setJMSReplyTo(Destination destination) throws JMSException
+ {
+ if (destination == null)
+ {
+ _messageProps.setReplyTo(null);
+ return;
+ }
+
+ if (!(destination instanceof AMQDestination))
+ {
+ throw new IllegalArgumentException(
+ "ReplyTo destination may only be an AMQDestination - passed argument was type " + destination.getClass());
+ }
+
+ final AMQDestination amqd = (AMQDestination) destination;
+
+ if (amqd.getDestSyntax() == AMQDestination.DestSyntax.ADDR)
+ {
+ try
+ {
+ int type = ((AMQSession_0_10)_session).resolveAddressType(amqd);
+ if (type == AMQDestination.QUEUE_TYPE)
+ {
+ ((AMQSession_0_10)_session).setLegacyFiledsForQueueType(amqd);
+ }
+ else
+ {
+ ((AMQSession_0_10)_session).setLegacyFiledsForTopicType(amqd);
+ }
+ }
+ catch(AMQException ex)
+ {
+ JMSException e = new JMSException("Error occured while figuring out the node type");
+ e.initCause(ex);
+ e.setLinkedException(ex);
+ throw e;
+ }
+ }
+
+ final ReplyTo replyTo = new ReplyTo(amqd.getExchangeName().toString(), amqd.getRoutingKey().toString());
+ _destinationCache.put(replyTo, new SoftReference<Destination>(destination));
+ _messageProps.setReplyTo(replyTo);
+
+ }
+
+ public Destination getJMSDestination() throws JMSException
+ {
+ return _destination;
+ }
+
+ public void setJMSDestination(Destination destination)
+ {
+ _destination = destination;
+ }
+
+ public void setContentType(String contentType)
+ {
+ _messageProps.setContentType(contentType);
+ }
+
+ public String getContentType()
+ {
+ return _messageProps.getContentType();
+ }
+
+ public void setEncoding(String encoding)
+ {
+ if(encoding == null || encoding.length() == 0)
+ {
+ _messageProps.clearContentEncoding();
+ }
+ else
+ {
+ _messageProps.setContentEncoding(encoding);
+ }
+ }
+
+ public String getEncoding()
+ {
+ return _messageProps.getContentEncoding();
+ }
+
+ public String getReplyToString()
+ {
+ Destination replyTo = getJMSReplyTo();
+ if(replyTo != null)
+ {
+ return ((AMQDestination)replyTo).toURL();
+ }
+ else
+ {
+ return null;
+ }
+
+ }
+
+ public int getJMSDeliveryMode() throws JMSException
+ {
+
+ MessageDeliveryMode deliveryMode = _deliveryProps.getDeliveryMode();
+ if(deliveryMode != null)
+ {
+ switch(deliveryMode)
+ {
+ case PERSISTENT :
+ return DeliveryMode.PERSISTENT;
+ case NON_PERSISTENT:
+ return DeliveryMode.NON_PERSISTENT;
+ default:
+ throw new JMSException("Unknown Message Delivery Mode: " + _deliveryProps.getDeliveryMode());
+ }
+ }
+ else
+ {
+ return Message.DEFAULT_DELIVERY_MODE;
+ }
+
+ }
+
+ public void setJMSDeliveryMode(int deliveryMode) throws JMSException
+ {
+ switch(deliveryMode)
+ {
+ case DeliveryMode.PERSISTENT:
+ _deliveryProps.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
+ break;
+ case DeliveryMode.NON_PERSISTENT:
+ _deliveryProps.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
+ break;
+ default:
+ throw new JMSException("Unknown JMS Delivery Mode: " + deliveryMode);
+ }
+
+ }
+
+
+ public String getJMSType() throws JMSException
+ {
+ if(getApplicationHeaders().containsKey(JMS_TYPE))
+ {
+ return getStringProperty(JMS_TYPE);
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private Map<String, Object> getApplicationHeaders()
+ {
+ Map<String, Object> map = _messageProps.getApplicationHeaders();
+ return map == null ? Collections.EMPTY_MAP : map;
+ }
+
+ public void setJMSType(String type) throws JMSException
+ {
+ Map<String, Object> headers = _messageProps.getApplicationHeaders();
+ if(type == null)
+ {
+ if(headers != null)
+ {
+ headers.remove(JMS_TYPE);
+ }
+ }
+ else
+ {
+ if(headers == null)
+ {
+ headers = new HashMap<String,Object>();
+ _messageProps.setApplicationHeaders(headers);
+
+ }
+ headers.put(JMS_TYPE, type);
+ }
+ }
+
+ public long getJMSExpiration() throws JMSException
+ {
+ return _deliveryProps.getExpiration();
+ }
+
+ public void setJMSExpiration(long l) throws JMSException
+ {
+ _deliveryProps.setExpiration(l);
+ }
+
+
+
+ public boolean propertyExists(String propertyName) throws JMSException
+ {
+ return getApplicationHeaders().containsKey(propertyName);
+ }
+
+ public boolean getBooleanProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+
+ Object o = getApplicationHeaders().get(propertyName);
+
+ if(o instanceof Boolean)
+ {
+ return ((Boolean)o).booleanValue();
+ }
+ else if(o instanceof String)
+ {
+ return Boolean.valueOf((String) o).booleanValue();
+ }
+ else if(getApplicationHeaders().containsKey(propertyName))
+ {
+ throw new MessageFormatException("getBooleanProperty(\""+propertyName+"\") failed as value is not boolean: " + o);
+ }
+ else
+ {
+ return Boolean.valueOf(null);
+ }
+ }
+
+ public byte getByteProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof Byte)
+ {
+ return ((Byte)o).byteValue();
+ }
+ else if(o instanceof String)
+ {
+ return Byte.valueOf((String) o).byteValue();
+ }
+ else if(getApplicationHeaders().containsKey(propertyName))
+ {
+ throw new MessageFormatException("getByteProperty(\""+propertyName+"\") failed as value is not a byte: " + o);
+ }
+ else
+ {
+ return Byte.valueOf(null);
+ }
+ }
+
+ public short getShortProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof Short)
+ {
+ return ((Short)o).shortValue();
+ }
+ else
+ {
+ try
+ {
+ return Short.valueOf(getByteProperty(propertyName));
+ }
+ catch(MessageFormatException e)
+ {
+ throw new MessageFormatException("getShortProperty(\""+propertyName+"\") failed as value is not a short: " + o);
+ }
+ }
+
+
+ }
+
+ public int getIntProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof Integer)
+ {
+ return ((Integer)o).intValue();
+ }
+ else
+ {
+ try
+ {
+ return Integer.valueOf(getShortProperty(propertyName));
+ }
+ catch(MessageFormatException e)
+ {
+ throw new MessageFormatException("getIntProperty(\""+propertyName+"\") failed as value is not an int: " + o);
+ }
+
+ }
+ }
+
+ public long getLongProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof Long)
+ {
+ return ((Long)o).longValue();
+ }
+ else
+ {
+ try
+ {
+ return Long.valueOf(getIntProperty(propertyName));
+ }
+ catch(MessageFormatException e)
+ {
+ throw new MessageFormatException("getLongProperty(\""+propertyName+"\") failed as value is not a long: " + o);
+ }
+
+ }
+ }
+
+ public float getFloatProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof Float)
+ {
+ return ((Float)o).floatValue();
+ }
+ else if(o instanceof String)
+ {
+ return Float.valueOf((String) o).floatValue();
+ }
+ else if(getApplicationHeaders().containsKey(propertyName))
+ {
+ throw new MessageFormatException("getFloatProperty(\""+propertyName+"\") failed as value is not a float: " + o);
+ }
+ else
+ {
+ throw new NullPointerException("No such property: " + propertyName);
+ }
+
+ }
+
+ public double getDoubleProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof Double)
+ {
+ return ((Double)o).doubleValue();
+ }
+ else if (o instanceof String)
+ {
+ return Double.valueOf((String)o);
+ }
+ else
+ {
+ try
+ {
+ return Double.valueOf(getFloatProperty(propertyName));
+ }
+ catch(MessageFormatException e)
+ {
+ throw new MessageFormatException("getDoubleProperty(\""+propertyName+"\") failed as value is not a double: " + o);
+ }
+
+ }
+ }
+
+ public String getStringProperty(String propertyName) throws JMSException
+ {
+ if (propertyName.equals(CustomJMSXProperty.JMSXUserID.toString()))
+ {
+ return new String(_messageProps.getUserId());
+ }
+ else
+ {
+ checkPropertyName(propertyName);
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ Object o = propertyMap.get(propertyName);
+
+ if(o instanceof String)
+ {
+ return (String) o;
+ }
+ else if(o == null)
+ {
+ return null;
+ }
+ else if(o.getClass().isArray())
+ {
+ throw new MessageFormatException("getString(\""+propertyName+"\") failed as value of type " + o.getClass()+ " is an array.");
+ }
+ else
+ {
+ return String.valueOf(o);
+ }
+
+ }
+ }
+
+ public Object getObjectProperty(String propertyName) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ Map<String, Object> propertyMap = getApplicationHeaders();
+
+ return propertyMap.get(propertyName);
+
+ }
+
+ public Enumeration getPropertyNames() throws JMSException
+ {
+ List<String> props = new ArrayList<String>();
+ Map<String, Object> propertyMap = getApplicationHeaders();
+ for (String prop: getApplicationHeaders().keySet())
+ {
+ Object value = propertyMap.get(prop);
+ if (value instanceof Boolean || value instanceof Number
+ || value instanceof String)
+ {
+ props.add(prop);
+ }
+ }
+
+ return java.util.Collections.enumeration(props);
+ }
+
+ public void setBooleanProperty(String propertyName, boolean b) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, b);
+ }
+
+ public void setByteProperty(String propertyName, byte b) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, b);
+ }
+
+ public void setShortProperty(String propertyName, short i) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, i);
+ }
+
+ public void setIntProperty(String propertyName, int i) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, i);
+ }
+
+ public void setLongProperty(String propertyName, long l) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, l);
+ }
+
+ public void setFloatProperty(String propertyName, float f) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, f);
+ }
+
+ public void setDoubleProperty(String propertyName, double v) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, v);
+ }
+
+ public void setStringProperty(String propertyName, String value) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ setApplicationHeader(propertyName, value);
+ }
+
+ private static final Set<Class> ALLOWED = new HashSet();
+ static
+ {
+ ALLOWED.add(Boolean.class);
+ ALLOWED.add(Byte.class);
+ ALLOWED.add(Short.class);
+ ALLOWED.add(Integer.class);
+ ALLOWED.add(Long.class);
+ ALLOWED.add(Float.class);
+ ALLOWED.add(Double.class);
+ ALLOWED.add(Character.class);
+ ALLOWED.add(String.class);
+ ALLOWED.add(byte[].class);
+ }
+
+ public void setObjectProperty(String propertyName, Object object) throws JMSException
+ {
+ checkPropertyName(propertyName);
+ checkWritableProperties();
+ if (object == null)
+ {
+ throw new MessageFormatException(AMQPInvalidClassException.INVALID_OBJECT_MSG + "null");
+ }
+ else if (!ALLOWED.contains(object.getClass()))
+ {
+ throw new MessageFormatException(AMQPInvalidClassException.INVALID_OBJECT_MSG + object.getClass());
+ }
+ setApplicationHeader(propertyName, object);
+ }
+
+ private void setApplicationHeader(String propertyName, Object object)
+ {
+ Map<String, Object> headers = _messageProps.getApplicationHeaders();
+ if(headers == null)
+ {
+ headers = new HashMap<String,Object>();
+ _messageProps.setApplicationHeaders(headers);
+ }
+ headers.put(propertyName, object);
+ }
+
+ public void removeProperty(String propertyName) throws JMSException
+ {
+ Map<String, Object> headers = _messageProps.getApplicationHeaders();
+ if(headers != null)
+ {
+ headers.remove(propertyName);
+ }
+ }
+
+
+
+ protected void checkWritableProperties() throws MessageNotWriteableException
+ {
+ if (_readableProperties)
+ {
+ throw new MessageNotWriteableException("You need to call clearProperties() to make the message writable");
+ }
+ }
+
+
+ public int getJMSPriority() throws JMSException
+ {
+ MessageDeliveryPriority messageDeliveryPriority = _deliveryProps.getPriority();
+ return messageDeliveryPriority == null ? Message.DEFAULT_PRIORITY : messageDeliveryPriority.getValue();
+ }
+
+ public void setJMSPriority(int i) throws JMSException
+ {
+ _deliveryProps.setPriority(MessageDeliveryPriority.get((short)i));
+ }
+
+ public void clearProperties() throws JMSException
+ {
+ if(!getApplicationHeaders().isEmpty())
+ {
+ getApplicationHeaders().clear();
+ }
+
+ _readableProperties = false;
+ }
+
+
+ public void acknowledgeThis() throws JMSException
+ {
+ // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge
+ // is not specified. In our case, we only set the session field where client acknowledge mode is specified.
+ if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+ {
+ if (_session.getAMQConnection().isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Connection is already closed");
+ }
+
+ // we set multiple to true here since acknowledgment implies acknowledge of all previous messages
+ // received on the session
+ _session.acknowledgeMessage(_deliveryTag, true);
+ }
+ }
+
+ public void acknowledge() throws JMSException
+ {
+ if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+ {
+ _session.acknowledge();
+ }
+ }
+
+
+ /**
+ * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user calls
+ * acknowledge()
+ *
+ * @param s the AMQ session that delivered this message
+ */
+ public void setAMQSession(AMQSession s)
+ {
+ _session = s;
+ }
+
+ public AMQSession getAMQSession()
+ {
+ return _session;
+ }
+
+ /**
+ * Get the AMQ message number assigned to this message
+ *
+ * @return the message number
+ */
+ public long getDeliveryTag()
+ {
+ return _deliveryTag;
+ }
+
+
+
+
+
+
+ protected void checkPropertyName(CharSequence propertyName)
+ {
+ if (propertyName == null)
+ {
+ throw new IllegalArgumentException("Property name must not be null");
+ }
+ else if (propertyName.length() == 0)
+ {
+ throw new IllegalArgumentException("Property name must not be the empty string");
+ }
+
+ checkIdentiferFormat(propertyName);
+ }
+
+ protected void checkIdentiferFormat(CharSequence propertyName)
+ {
+// JMS requirements 3.5.1 Property Names
+// Identifiers:
+// - An identifier is an unlimited-length character sequence that must begin
+// with a Java identifier start character; all following characters must be Java
+// identifier part characters. An identifier start character is any character for
+// which the method Character.isJavaIdentifierStart returns true. This includes
+// '_' and '$'. An identifier part character is any character for which the
+// method Character.isJavaIdentifierPart returns true.
+// - Identifiers cannot be the names NULL, TRUE, or FALSE.
+// Identifiers cannot be NOT, AND, OR, BETWEEN, LIKE, IN, IS, or
+// ESCAPE.
+// Identifiers are either header field references or property references. The
+// type of a property value in a message selector corresponds to the type
+// used to set the property. If a property that does not exist in a message is
+// referenced, its value is NULL. The semantics of evaluating NULL values
+// in a selector are described in Section 3.8.1.2, Null Values.
+// The conversions that apply to the get methods for properties do not
+// apply when a property is used in a message selector expression. For
+// example, suppose you set a property as a string value, as in the
+// following:
+// myMessage.setStringProperty("NumberOfOrders", "2");
+// The following expression in a message selector would evaluate to false,
+// because a string cannot be used in an arithmetic expression:
+// "NumberOfOrders > 1"
+// Identifiers are case sensitive.
+// Message header field references are restricted to JMSDeliveryMode,
+// JMSPriority, JMSMessageID, JMSTimestamp, JMSCorrelationID, and
+// JMSType. JMSMessageID, JMSCorrelationID, and JMSType values may be
+// null and if so are treated as a NULL value.
+
+ if (Boolean.getBoolean("strict-jms"))
+ {
+ // JMS start character
+ if (!(Character.isJavaIdentifierStart(propertyName.charAt(0))))
+ {
+ throw new IllegalArgumentException("Identifier '" + propertyName + "' does not start with a valid JMS identifier start character");
+ }
+
+ // JMS part character
+ int length = propertyName.length();
+ for (int c = 1; c < length; c++)
+ {
+ if (!(Character.isJavaIdentifierPart(propertyName.charAt(c))))
+ {
+ throw new IllegalArgumentException("Identifier '" + propertyName + "' contains an invalid JMS identifier character");
+ }
+ }
+
+ // JMS invalid names
+ if ((propertyName.equals("NULL")
+ || propertyName.equals("TRUE")
+ || propertyName.equals("FALSE")
+ || propertyName.equals("NOT")
+ || propertyName.equals("AND")
+ || propertyName.equals("OR")
+ || propertyName.equals("BETWEEN")
+ || propertyName.equals("LIKE")
+ || propertyName.equals("IN")
+ || propertyName.equals("IS")
+ || propertyName.equals("ESCAPE")))
+ {
+ throw new IllegalArgumentException("Identifier '" + propertyName + "' is not allowed in JMS");
+ }
+ }
+
+ }
+
+
+ public MessageProperties getMessageProperties()
+ {
+ return _messageProps;
+ }
+
+
+ public DeliveryProperties getDeliveryProperties()
+ {
+ return _deliveryProps;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java
new file mode 100644
index 0000000000..cec4268a7b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQMessageDelegate_0_8.java
@@ -0,0 +1,576 @@
+/*
+ *
+ * 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.message;
+
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.client.CustomJMSXProperty;
+import org.apache.qpid.client.JMSAMQException;
+import org.apache.qpid.collections.ReferenceMap;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderProperties;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.BindingURL;
+
+public class AMQMessageDelegate_0_8 extends AbstractAMQMessageDelegate
+{
+ private static final Map _destinationCache = Collections.synchronizedMap(new ReferenceMap());
+
+ public static final String JMS_TYPE = "x-jms-type";
+
+
+ private boolean _readableProperties = false;
+
+ private Destination _destination;
+ private JMSHeaderAdapter _headerAdapter;
+ private static final boolean STRICT_AMQP_COMPLIANCE =
+ Boolean.parseBoolean(System.getProperties().getProperty(AMQSession.STRICT_AMQP, AMQSession.STRICT_AMQP_DEFAULT));
+
+ private ContentHeaderProperties _contentHeaderProperties;
+ /** If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required */
+ private AMQSession _session;
+ private final long _deliveryTag;
+
+ // The base set of items that needs to be set.
+ private AMQMessageDelegate_0_8(BasicContentHeaderProperties properties, long deliveryTag)
+ {
+ _contentHeaderProperties = properties;
+ _deliveryTag = deliveryTag;
+ _readableProperties = (_contentHeaderProperties != null);
+ _headerAdapter = new JMSHeaderAdapter(_readableProperties ? ((BasicContentHeaderProperties) _contentHeaderProperties).getHeaders()
+ : (new BasicContentHeaderProperties()).getHeaders() );
+ }
+
+ // Used for the creation of new messages
+ protected AMQMessageDelegate_0_8()
+ {
+ this(new BasicContentHeaderProperties(), -1);
+ _readableProperties = false;
+ _headerAdapter = new JMSHeaderAdapter(((BasicContentHeaderProperties) _contentHeaderProperties).getHeaders());
+
+ }
+
+ // Used when generating a received message object
+ protected AMQMessageDelegate_0_8(long deliveryTag, BasicContentHeaderProperties contentHeader, AMQShortString exchange,
+ AMQShortString routingKey)
+ {
+ this(contentHeader, deliveryTag);
+
+ Integer type = contentHeader.getHeaders().getInteger(CustomJMSXProperty.JMS_QPID_DESTTYPE.getShortStringName());
+
+ AMQDestination dest = null;
+
+ // If we have a type set the attempt to use that.
+ if (type != null)
+ {
+ switch (type.intValue())
+ {
+ case AMQDestination.QUEUE_TYPE:
+ dest = new AMQQueue(exchange, routingKey, routingKey);
+ break;
+ case AMQDestination.TOPIC_TYPE:
+ dest = new AMQTopic(exchange, routingKey, null);
+ break;
+ default:
+ // Use the generateDestination method
+ dest = null;
+ }
+ }
+
+ if (dest == null)
+ {
+ dest = generateDestination(exchange, routingKey);
+ }
+
+ setJMSDestination(dest);
+ }
+
+
+
+ public String getJMSMessageID() throws JMSException
+ {
+ return getContentHeaderProperties().getMessageIdAsString();
+ }
+
+ public void setJMSMessageID(String messageId) throws JMSException
+ {
+ if (messageId != null)
+ {
+ getContentHeaderProperties().setMessageId(messageId);
+ }
+ }
+
+ public void setJMSMessageID(UUID messageId) throws JMSException
+ {
+ if (messageId != null)
+ {
+ getContentHeaderProperties().setMessageId("ID:" + messageId);
+ }
+ }
+
+
+ public long getJMSTimestamp() throws JMSException
+ {
+ return getContentHeaderProperties().getTimestamp();
+ }
+
+ public void setJMSTimestamp(long timestamp) throws JMSException
+ {
+ getContentHeaderProperties().setTimestamp(timestamp);
+ }
+
+ public byte[] getJMSCorrelationIDAsBytes() throws JMSException
+ {
+ return getContentHeaderProperties().getCorrelationIdAsString().getBytes();
+ }
+
+ public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException
+ {
+ getContentHeaderProperties().setCorrelationId(new String(bytes));
+ }
+
+ public void setJMSCorrelationID(String correlationId) throws JMSException
+ {
+ getContentHeaderProperties().setCorrelationId(correlationId);
+ }
+
+ public String getJMSCorrelationID() throws JMSException
+ {
+ return getContentHeaderProperties().getCorrelationIdAsString();
+ }
+
+ public Destination getJMSReplyTo() throws JMSException
+ {
+ String replyToEncoding = getContentHeaderProperties().getReplyToAsString();
+ if (replyToEncoding == null)
+ {
+ return null;
+ }
+ else
+ {
+ Destination dest = (Destination) _destinationCache.get(replyToEncoding);
+ if (dest == null)
+ {
+ try
+ {
+ BindingURL binding = new AMQBindingURL(replyToEncoding);
+ dest = AMQDestination.createDestination(binding);
+ }
+ catch (URISyntaxException e)
+ {
+ throw new JMSAMQException("Illegal value in JMS_ReplyTo property: " + replyToEncoding, e);
+ }
+
+ _destinationCache.put(replyToEncoding, dest);
+ }
+
+ return dest;
+ }
+ }
+
+ public void setJMSReplyTo(Destination destination) throws JMSException
+ {
+ if (destination == null)
+ {
+ getContentHeaderProperties().setReplyTo((String) null);
+ return; // We're done here
+ }
+
+ if (!(destination instanceof AMQDestination))
+ {
+ throw new IllegalArgumentException(
+ "ReplyTo destination may only be an AMQDestination - passed argument was type " + destination.getClass());
+ }
+
+ final AMQDestination amqd = (AMQDestination) destination;
+
+ final AMQShortString encodedDestination = amqd.getEncodedName();
+ _destinationCache.put(encodedDestination, destination);
+ getContentHeaderProperties().setReplyTo(encodedDestination);
+ }
+
+ public Destination getJMSDestination() throws JMSException
+ {
+ return _destination;
+ }
+
+ public void setJMSDestination(Destination destination)
+ {
+ _destination = destination;
+ }
+
+ public void setContentType(String contentType)
+ {
+ getContentHeaderProperties().setContentType(contentType);
+ }
+
+ public String getContentType()
+ {
+ return getContentHeaderProperties().getContentTypeAsString();
+ }
+
+ public void setEncoding(String encoding)
+ {
+ getContentHeaderProperties().setEncoding(encoding);
+ }
+
+ public String getEncoding()
+ {
+ return getContentHeaderProperties().getEncodingAsString();
+ }
+
+ public String getReplyToString()
+ {
+ return getContentHeaderProperties().getReplyToAsString();
+ }
+
+ public int getJMSDeliveryMode() throws JMSException
+ {
+ return getContentHeaderProperties().getDeliveryMode();
+ }
+
+ public void setJMSDeliveryMode(int i) throws JMSException
+ {
+ getContentHeaderProperties().setDeliveryMode((byte) i);
+ }
+
+ public BasicContentHeaderProperties getContentHeaderProperties()
+ {
+ return (BasicContentHeaderProperties) _contentHeaderProperties;
+ }
+
+
+ public String getJMSType() throws JMSException
+ {
+ return getContentHeaderProperties().getTypeAsString();
+ }
+
+ public void setJMSType(String string) throws JMSException
+ {
+ getContentHeaderProperties().setType(string);
+ }
+
+ public long getJMSExpiration() throws JMSException
+ {
+ return getContentHeaderProperties().getExpiration();
+ }
+
+ public void setJMSExpiration(long l) throws JMSException
+ {
+ getContentHeaderProperties().setExpiration(l);
+ }
+
+
+
+ public boolean propertyExists(String propertyName) throws JMSException
+ {
+ return getJmsHeaders().propertyExists(propertyName);
+ }
+
+ public boolean getBooleanProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getBoolean(propertyName);
+ }
+
+ public byte getByteProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getByte(propertyName);
+ }
+
+ public short getShortProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getShort(propertyName);
+ }
+
+ public int getIntProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getInteger(propertyName);
+ }
+
+ public long getLongProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getLong(propertyName);
+ }
+
+ public float getFloatProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getFloat(propertyName);
+ }
+
+ public double getDoubleProperty(String propertyName) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getDouble(propertyName);
+ }
+
+ public String getStringProperty(String propertyName) throws JMSException
+ {
+ //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()))
+ {
+ return ((BasicContentHeaderProperties) _contentHeaderProperties).getUserIdAsString();
+ }
+ else
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ return getJmsHeaders().getString(propertyName);
+ }
+ }
+
+ public Object getObjectProperty(String propertyName) throws JMSException
+ {
+ return getJmsHeaders().getObject(propertyName);
+ }
+
+ public Enumeration getPropertyNames() throws JMSException
+ {
+ return getJmsHeaders().getPropertyNames();
+ }
+
+ public void setBooleanProperty(String propertyName, boolean b) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ checkWritableProperties();
+ getJmsHeaders().setBoolean(propertyName, b);
+ }
+
+ public void setByteProperty(String propertyName, byte b) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ checkWritableProperties();
+ getJmsHeaders().setByte(propertyName, new Byte(b));
+ }
+
+ public void setShortProperty(String propertyName, short i) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ checkWritableProperties();
+ getJmsHeaders().setShort(propertyName, new Short(i));
+ }
+
+ public void setIntProperty(String propertyName, int i) throws JMSException
+ {
+ checkWritableProperties();
+ getJmsHeaders().setInteger(propertyName, new Integer(i));
+ }
+
+ public void setLongProperty(String propertyName, long l) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ checkWritableProperties();
+ getJmsHeaders().setLong(propertyName, new Long(l));
+ }
+
+ public void setFloatProperty(String propertyName, float f) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ checkWritableProperties();
+ getJmsHeaders().setFloat(propertyName, new Float(f));
+ }
+
+ public void setDoubleProperty(String propertyName, double v) throws JMSException
+ {
+ if (STRICT_AMQP_COMPLIANCE)
+ {
+ throw new UnsupportedOperationException("JMS Proprerties not supported in AMQP");
+ }
+
+ checkWritableProperties();
+ getJmsHeaders().setDouble(propertyName, new Double(v));
+ }
+
+ public void setStringProperty(String propertyName, String value) throws JMSException
+ {
+ checkWritableProperties();
+ getJmsHeaders().setString(propertyName, value);
+ }
+
+ public void setObjectProperty(String propertyName, Object object) throws JMSException
+ {
+ checkWritableProperties();
+ getJmsHeaders().setObject(propertyName, object);
+ }
+
+ public void removeProperty(String propertyName) throws JMSException
+ {
+ getJmsHeaders().remove(propertyName);
+ }
+
+
+ private JMSHeaderAdapter getJmsHeaders()
+ {
+ return _headerAdapter;
+ }
+
+ protected void checkWritableProperties() throws MessageNotWriteableException
+ {
+ if (_readableProperties)
+ {
+ throw new MessageNotWriteableException("You need to call clearProperties() to make the message writable");
+ }
+ _contentHeaderProperties.updated();
+ }
+
+
+ public int getJMSPriority() throws JMSException
+ {
+ return getContentHeaderProperties().getPriority();
+ }
+
+ public void setJMSPriority(int i) throws JMSException
+ {
+ getContentHeaderProperties().setPriority((byte) i);
+ }
+
+ public void clearProperties() throws JMSException
+ {
+ getJmsHeaders().clear();
+
+ _readableProperties = false;
+ }
+
+
+ public void acknowledgeThis() throws JMSException
+ {
+ // the JMS 1.1 spec says in section 3.6 that calls to acknowledge are ignored when client acknowledge
+ // is not specified. In our case, we only set the session field where client acknowledge mode is specified.
+ if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+ {
+ if (_session.getAMQConnection().isClosed())
+ {
+ throw new javax.jms.IllegalStateException("Connection is already closed");
+ }
+
+ // we set multiple to true here since acknowledgement implies acknowledge of all previous messages
+ // received on the session
+ _session.acknowledgeMessage(_deliveryTag, true);
+ }
+ }
+
+ public void acknowledge() throws JMSException
+ {
+ if (_session != null && _session.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+ {
+ _session.acknowledge();
+ }
+ }
+
+
+ /**
+ * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user calls
+ * acknowledge()
+ *
+ * @param s the AMQ session that delivered this message
+ */
+ public void setAMQSession(AMQSession s)
+ {
+ _session = s;
+ }
+
+ public AMQSession getAMQSession()
+ {
+ return _session;
+ }
+
+ /**
+ * Get the AMQ message number assigned to this message
+ *
+ * @return the message number
+ */
+ public long getDeliveryTag()
+ {
+ return _deliveryTag;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessage.java
new file mode 100644
index 0000000000..58f108f1a4
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessage.java
@@ -0,0 +1,122 @@
+package org.apache.qpid.client.message;
+/*
+ *
+ * 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 java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.transport.codec.BBDecoder;
+import org.apache.qpid.transport.codec.BBEncoder;
+
+public class AMQPEncodedMapMessage extends JMSMapMessage
+{
+ public static final String MIME_TYPE = "amqp/map";
+
+ public AMQPEncodedMapMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ this(delegateFactory, null);
+ }
+
+ AMQPEncodedMapMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data) throws JMSException
+ {
+ super(delegateFactory, data);
+ }
+
+ AMQPEncodedMapMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ super(delegate, data);
+ }
+
+ @ Override
+ protected String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ @ Override
+ public void setObject(String propName, Object value) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ if ((value instanceof Boolean) || (value instanceof Byte) || (value instanceof Short) || (value instanceof Integer)
+ || (value instanceof Long) || (value instanceof Character) || (value instanceof Float)
+ || (value instanceof Double) || (value instanceof String) || (value instanceof byte[])
+ || (value instanceof List) || (value instanceof Map) || (value instanceof UUID) || (value == null))
+ {
+ _map.put(propName, value);
+ }
+ else
+ {
+ throw new MessageFormatException("Cannot set property " + propName + " to value " + value + "of type "
+ + value.getClass().getName() + ".");
+ }
+ }
+
+ // The super clas methods resets the buffer
+ @ Override
+ public ByteBuffer getData()
+ {
+ writeMapToData();
+ return _data;
+ }
+
+ @ Override
+ protected void populateMapFromData() throws JMSException
+ {
+ if (_data != null)
+ {
+ _data.rewind();
+ BBDecoder decoder = new BBDecoder();
+ decoder.init(_data.buf());
+ _map = decoder.readMap();
+ }
+ else
+ {
+ _map.clear();
+ }
+ }
+
+ @ Override
+ protected void writeMapToData()
+ {
+ BBEncoder encoder = new BBEncoder(1024);
+ encoder.writeMap(_map);
+ _data = ByteBuffer.wrap(encoder.segment());
+ }
+
+ // for testing
+ Map<String,Object> getMap()
+ {
+ return _map;
+ }
+
+ void setMap(Map<String,Object> map)
+ {
+ _map = map;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageFactory.java
new file mode 100644
index 0000000000..4978d1ce85
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageFactory.java
@@ -0,0 +1,46 @@
+package org.apache.qpid.client.message;
+/*
+ *
+ * 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.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+
+public class AMQPEncodedMapMessageFactory extends AbstractJMSMessageFactory
+{
+
+ @Override
+ protected AbstractJMSMessage createMessage(AMQMessageDelegate delegate,
+ ByteBuffer data) throws AMQException
+ {
+ return new AMQPEncodedMapMessage(delegate,data);
+ }
+
+ @Override
+ public AbstractJMSMessage createMessage(
+ AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ return new AMQPEncodedMapMessage(delegateFactory);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java
new file mode 100644
index 0000000000..89fbc9722c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractAMQMessageDelegate.java
@@ -0,0 +1,206 @@
+/*
+ *
+ * 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.message;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.qpid.client.AMQAnyDestination;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+
+/**
+ * This abstract class provides exchange lookup functionality that is shared
+ * between all MessageDelegates. Update facilities are provided so that the 0-10
+ * code base can update the mappings. The 0-8 code base does not have the
+ * facility to update the exchange map so it can only use the default mappings.
+ *
+ * That said any updates that a 0-10 client performs will also benefit any 0-8
+ * connections in this VM.
+ *
+ */
+public abstract class AbstractAMQMessageDelegate implements AMQMessageDelegate
+{
+
+ private static Map<String, Integer> _exchangeTypeToDestinationType = new ConcurrentHashMap<String, Integer>();
+ private static Map<String,ExchangeInfo> _exchangeMap = new ConcurrentHashMap<String, ExchangeInfo>();
+
+ /**
+ * Add default Mappings for the Direct, Default, Topic and Fanout exchanges.
+ */
+ static
+ {
+ _exchangeTypeToDestinationType.put("", AMQDestination.QUEUE_TYPE);
+ _exchangeTypeToDestinationType.put(ExchangeDefaults.DIRECT_EXCHANGE_CLASS.toString(), AMQDestination.QUEUE_TYPE);
+ _exchangeTypeToDestinationType.put(ExchangeDefaults.TOPIC_EXCHANGE_CLASS.toString(), AMQDestination.TOPIC_TYPE);
+ _exchangeTypeToDestinationType.put(ExchangeDefaults.FANOUT_EXCHANGE_CLASS.toString(), AMQDestination.TOPIC_TYPE);
+ _exchangeTypeToDestinationType.put(ExchangeDefaults.HEADERS_EXCHANGE_CLASS.toString(), AMQDestination.QUEUE_TYPE);
+
+ _exchangeMap.put("", new ExchangeInfo("","",AMQDestination.QUEUE_TYPE));
+
+ _exchangeMap.put(ExchangeDefaults.DIRECT_EXCHANGE_NAME.toString(),
+ new ExchangeInfo(ExchangeDefaults.DIRECT_EXCHANGE_NAME.toString(),
+ ExchangeDefaults.DIRECT_EXCHANGE_CLASS.toString(),
+ AMQDestination.QUEUE_TYPE));
+
+ _exchangeMap.put(ExchangeDefaults.TOPIC_EXCHANGE_NAME.toString(),
+ new ExchangeInfo(ExchangeDefaults.TOPIC_EXCHANGE_NAME.toString(),
+ ExchangeDefaults.TOPIC_EXCHANGE_CLASS.toString(),
+ AMQDestination.TOPIC_TYPE));
+
+ _exchangeMap.put(ExchangeDefaults.FANOUT_EXCHANGE_NAME.toString(),
+ new ExchangeInfo(ExchangeDefaults.FANOUT_EXCHANGE_NAME.toString(),
+ ExchangeDefaults.FANOUT_EXCHANGE_CLASS.toString(),
+ AMQDestination.TOPIC_TYPE));
+
+ _exchangeMap.put(ExchangeDefaults.HEADERS_EXCHANGE_NAME.toString(),
+ new ExchangeInfo(ExchangeDefaults.HEADERS_EXCHANGE_NAME.toString(),
+ ExchangeDefaults.HEADERS_EXCHANGE_CLASS.toString(),
+ AMQDestination.QUEUE_TYPE));
+
+ }
+
+ /**
+ * Called when a Destination is requried.
+ *
+ * This will create the AMQDestination that is the correct type and value
+ * based on the incomming values.
+ * @param exchange The exchange name
+ * @param routingKey The routing key to be used for the Destination
+ * @return AMQDestination of the correct subtype
+ */
+ protected AMQDestination generateDestination(AMQShortString exchange, AMQShortString routingKey)
+ {
+ AMQDestination dest;
+ ExchangeInfo exchangeInfo = _exchangeMap.get(exchange.asString());
+
+ if (exchangeInfo == null)
+ {
+ exchangeInfo = new ExchangeInfo(exchange.asString(),"",AMQDestination.UNKNOWN_TYPE);
+ }
+
+ if ("topic".equals(exchangeInfo.exchangeType))
+ {
+ dest = new AMQTopic(exchange, routingKey, null);
+ }
+ else if ("direct".equals(exchangeInfo.exchangeType))
+ {
+ dest = new AMQQueue(exchange, routingKey, routingKey);
+ }
+ else
+ {
+ dest = new AMQAnyDestination(exchange,
+ new AMQShortString(exchangeInfo.exchangeType),
+ routingKey,
+ false,
+ false,
+ routingKey,
+ false,
+ new AMQShortString[] {routingKey});
+ }
+
+ return dest;
+ }
+
+ /**
+ * Update the exchange name to type mapping.
+ *
+ * If the newType is not known then an UNKNOWN_TYPE is created. Only if the
+ * exchange is of a known type: amq.direct, amq.topic, amq.fanout can we
+ * create a suitable AMQDestination representation
+ *
+ * @param exchange the name of the exchange
+ * @param newtype the AMQP exchange class name i.e. direct
+ */
+ protected static void updateExchangeType(String exchange, String newtype)
+ {
+ Integer type = _exchangeTypeToDestinationType.get(newtype);
+ if (type == null)
+ {
+ type = AMQDestination.UNKNOWN_TYPE;
+ }
+
+ _exchangeMap.put(exchange, new ExchangeInfo(exchange,newtype,type));
+ }
+
+ /**
+ * Accessor method to allow lookups of the given exchange name.
+ *
+ * This check allows the prevention of extra work required such as asking
+ * the broker for the exchange class name.
+ *
+ * @param exchange the exchange name to lookup
+ * @return true if there is a mapping for this exchange
+ */
+ protected static boolean exchangeMapContains(String exchange)
+ {
+ return _exchangeMap.containsKey(exchange);
+ }
+}
+
+class ExchangeInfo
+{
+ String exchangeName;
+ String exchangeType;
+ int destType = AMQDestination.QUEUE_TYPE;
+
+ public ExchangeInfo(String exchangeName, String exchangeType,
+ int destType)
+ {
+ super();
+ this.exchangeName = exchangeName;
+ this.exchangeType = exchangeType;
+ this.destType = destType;
+ }
+
+ public String getExchangeName()
+ {
+ return exchangeName;
+ }
+
+ public void setExchangeName(String exchangeName)
+ {
+ this.exchangeName = exchangeName;
+ }
+
+ public String getExchangeType()
+ {
+ return exchangeType;
+ }
+
+ public void setExchangeType(String exchangeType)
+ {
+ this.exchangeType = exchangeType;
+ }
+
+ public int getDestType()
+ {
+ return destType;
+ }
+
+ public void setDestType(int destType)
+ {
+ this.destType = destType;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesMessage.java
new file mode 100644
index 0000000000..3846ee043d
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesMessage.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * 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.message;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import javax.jms.JMSException;
+import javax.jms.MessageEOFException;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.transport.util.Functions;
+
+/**
+ * @author Apache Software Foundation
+ */
+public abstract class AbstractBytesMessage extends AbstractJMSMessage
+{
+
+ /**
+ * The default initial size of the buffer. The buffer expands automatically.
+ */
+ private static final int DEFAULT_BUFFER_INITIAL_SIZE = 1024;
+
+ AbstractBytesMessage(AMQMessageDelegateFactory delegateFactory)
+ {
+ this(delegateFactory, null);
+ }
+
+ /**
+ * Construct a bytes message with existing data.
+ *
+ * @param delegateFactory
+ * @param data the data that comprises this message. If data is null, you get a 1024 byte buffer that is
+ */
+ AbstractBytesMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data)
+ {
+ super(delegateFactory, data); // this instanties a content header
+ setContentType(getMimeType());
+
+ if (_data == null)
+ {
+ allocateInitialBuffer();
+ }
+ }
+
+ protected void allocateInitialBuffer()
+ {
+ _data = ByteBuffer.allocate(DEFAULT_BUFFER_INITIAL_SIZE);
+ _data.setAutoExpand(true);
+ }
+
+ AbstractBytesMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ super(delegate, data);
+ setContentType(getMimeType());
+ }
+
+
+ public void clearBodyImpl() throws JMSException
+ {
+ allocateInitialBuffer();
+ }
+
+ public String toBodyString() throws JMSException
+ {
+ try
+ {
+ if (_data != null)
+ {
+ return Functions.str(_data.buf(), 100,0);
+ }
+ else
+ {
+ return "";
+ }
+
+ }
+ catch (Exception e)
+ {
+ JMSException jmse = new JMSException(e.toString());
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+
+ }
+
+ /**
+ * Check that there is at least a certain number of bytes available to read
+ *
+ * @param len the number of bytes
+ * @throws javax.jms.MessageEOFException if there are less than len bytes available to read
+ */
+ protected void checkAvailable(int len) throws MessageEOFException
+ {
+ if (_data.remaining() < len)
+ {
+ throw new MessageEOFException("Unable to read " + len + " bytes");
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesTypedMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesTypedMessage.java
new file mode 100644
index 0000000000..85818dcd2b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractBytesTypedMessage.java
@@ -0,0 +1,804 @@
+/*
+ * 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.message;
+
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+
+import javax.jms.JMSException;
+import javax.jms.MessageEOFException;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageNotReadableException;
+import javax.jms.MessageNotWriteableException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+/**
+ * @author Apache Software Foundation
+ */
+public abstract class AbstractBytesTypedMessage extends AbstractBytesMessage
+{
+
+ protected static final byte BOOLEAN_TYPE = (byte) 1;
+
+ protected static final byte BYTE_TYPE = (byte) 2;
+
+ protected static final byte BYTEARRAY_TYPE = (byte) 3;
+
+ protected static final byte SHORT_TYPE = (byte) 4;
+
+ protected static final byte CHAR_TYPE = (byte) 5;
+
+ protected static final byte INT_TYPE = (byte) 6;
+
+ protected static final byte LONG_TYPE = (byte) 7;
+
+ protected static final byte FLOAT_TYPE = (byte) 8;
+
+ protected static final byte DOUBLE_TYPE = (byte) 9;
+
+ protected static final byte STRING_TYPE = (byte) 10;
+
+ protected static final byte NULL_STRING_TYPE = (byte) 11;
+
+ /**
+ * This is set when reading a byte array. The readBytes(byte[]) method supports multiple calls to read
+ * a byte array in multiple chunks, hence this is used to track how much is left to be read
+ */
+ private int _byteArrayRemaining = -1;
+
+ AbstractBytesTypedMessage(AMQMessageDelegateFactory delegateFactory)
+ {
+
+ this(delegateFactory, null);
+ }
+
+ /**
+ * Construct a stream message with existing data.
+ *
+ * @param delegateFactory
+ * @param data the data that comprises this message. If data is null, you get a 1024 byte buffer that is
+ */
+ AbstractBytesTypedMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data)
+ {
+
+ super(delegateFactory, data); // this instanties a content header
+ }
+
+ AbstractBytesTypedMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+
+ super(delegate, data);
+ }
+
+
+ protected byte readWireType() throws MessageFormatException, MessageEOFException,
+ MessageNotReadableException
+ {
+ checkReadable();
+ checkAvailable(1);
+ return _data.get();
+ }
+
+ protected void writeTypeDiscriminator(byte type) throws MessageNotWriteableException
+ {
+ checkWritable();
+ _data.put(type);
+ _changedData = true;
+ }
+
+ protected boolean readBoolean() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ boolean result;
+ try
+ {
+ switch (wireType)
+ {
+ case BOOLEAN_TYPE:
+ checkAvailable(1);
+ result = readBooleanImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Boolean.parseBoolean(readStringImpl());
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a boolean");
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ private boolean readBooleanImpl()
+ {
+ return _data.get() != 0;
+ }
+
+ protected byte readByte() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ byte result;
+ try
+ {
+ switch (wireType)
+ {
+ case BYTE_TYPE:
+ checkAvailable(1);
+ result = readByteImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Byte.parseByte(readStringImpl());
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a byte");
+ }
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ return result;
+ }
+
+ private byte readByteImpl()
+ {
+ return _data.get();
+ }
+
+ protected short readShort() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ short result;
+ try
+ {
+ switch (wireType)
+ {
+ case SHORT_TYPE:
+ checkAvailable(2);
+ result = readShortImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Short.parseShort(readStringImpl());
+ break;
+ case BYTE_TYPE:
+ checkAvailable(1);
+ result = readByteImpl();
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a short");
+ }
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ return result;
+ }
+
+ private short readShortImpl()
+ {
+ return _data.getShort();
+ }
+
+ /**
+ * Note that this method reads a unicode character as two bytes from the stream
+ *
+ * @return the character read from the stream
+ * @throws javax.jms.JMSException
+ */
+ protected char readChar() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ try
+ {
+ if(wireType == NULL_STRING_TYPE){
+ throw new NullPointerException();
+ }
+
+ if (wireType != CHAR_TYPE)
+ {
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a char");
+ }
+ else
+ {
+ checkAvailable(2);
+ return readCharImpl();
+ }
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ private char readCharImpl()
+ {
+ return _data.getChar();
+ }
+
+ protected int readInt() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ int result;
+ try
+ {
+ switch (wireType)
+ {
+ case INT_TYPE:
+ checkAvailable(4);
+ result = readIntImpl();
+ break;
+ case SHORT_TYPE:
+ checkAvailable(2);
+ result = readShortImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Integer.parseInt(readStringImpl());
+ break;
+ case BYTE_TYPE:
+ checkAvailable(1);
+ result = readByteImpl();
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to an int");
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ protected int readIntImpl()
+ {
+ return _data.getInt();
+ }
+
+ protected long readLong() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ long result;
+ try
+ {
+ switch (wireType)
+ {
+ case LONG_TYPE:
+ checkAvailable(8);
+ result = readLongImpl();
+ break;
+ case INT_TYPE:
+ checkAvailable(4);
+ result = readIntImpl();
+ break;
+ case SHORT_TYPE:
+ checkAvailable(2);
+ result = readShortImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Long.parseLong(readStringImpl());
+ break;
+ case BYTE_TYPE:
+ checkAvailable(1);
+ result = readByteImpl();
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a long");
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ private long readLongImpl()
+ {
+ return _data.getLong();
+ }
+
+ protected float readFloat() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ float result;
+ try
+ {
+ switch (wireType)
+ {
+ case FLOAT_TYPE:
+ checkAvailable(4);
+ result = readFloatImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Float.parseFloat(readStringImpl());
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a float");
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ private float readFloatImpl()
+ {
+ return _data.getFloat();
+ }
+
+ protected double readDouble() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ double result;
+ try
+ {
+ switch (wireType)
+ {
+ case DOUBLE_TYPE:
+ checkAvailable(8);
+ result = readDoubleImpl();
+ break;
+ case FLOAT_TYPE:
+ checkAvailable(4);
+ result = readFloatImpl();
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = Double.parseDouble(readStringImpl());
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a double");
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ private double readDoubleImpl()
+ {
+ return _data.getDouble();
+ }
+
+ protected String readString() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ String result;
+ try
+ {
+ switch (wireType)
+ {
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = readStringImpl();
+ break;
+ case NULL_STRING_TYPE:
+ result = null;
+ throw new NullPointerException("data is null");
+ case BOOLEAN_TYPE:
+ checkAvailable(1);
+ result = String.valueOf(readBooleanImpl());
+ break;
+ case LONG_TYPE:
+ checkAvailable(8);
+ result = String.valueOf(readLongImpl());
+ break;
+ case INT_TYPE:
+ checkAvailable(4);
+ result = String.valueOf(readIntImpl());
+ break;
+ case SHORT_TYPE:
+ checkAvailable(2);
+ result = String.valueOf(readShortImpl());
+ break;
+ case BYTE_TYPE:
+ checkAvailable(1);
+ result = String.valueOf(readByteImpl());
+ break;
+ case FLOAT_TYPE:
+ checkAvailable(4);
+ result = String.valueOf(readFloatImpl());
+ break;
+ case DOUBLE_TYPE:
+ checkAvailable(8);
+ result = String.valueOf(readDoubleImpl());
+ break;
+ case CHAR_TYPE:
+ checkAvailable(2);
+ result = String.valueOf(readCharImpl());
+ break;
+ default:
+ _data.position(position);
+ throw new MessageFormatException("Unable to convert " + wireType + " to a String");
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ protected String readStringImpl() throws JMSException
+ {
+ try
+ {
+ return _data.getString(Charset.forName("UTF-8").newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException jmse = new JMSException("Error decoding byte stream as a UTF8 string: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ protected int readBytes(byte[] bytes) throws JMSException
+ {
+ if (bytes == null)
+ {
+ throw new IllegalArgumentException("byte array must not be null");
+ }
+ checkReadable();
+ // first call
+ if (_byteArrayRemaining == -1)
+ {
+ // type discriminator checked separately so you get a MessageFormatException rather than
+ // an EOF even in the case where both would be applicable
+ checkAvailable(1);
+ byte wireType = readWireType();
+ if (wireType != BYTEARRAY_TYPE)
+ {
+ throw new MessageFormatException("Unable to convert " + wireType + " to a byte array");
+ }
+ checkAvailable(4);
+ int size = _data.getInt();
+ // length of -1 indicates null
+ if (size == -1)
+ {
+ return -1;
+ }
+ else
+ {
+ if (size > _data.remaining())
+ {
+ throw new MessageEOFException("Byte array has stated length " + size + " but message only contains " +
+ _data.remaining() + " bytes");
+ }
+ else
+ {
+ _byteArrayRemaining = size;
+ }
+ }
+ }
+ else if (_byteArrayRemaining == 0)
+ {
+ _byteArrayRemaining = -1;
+ return -1;
+ }
+
+ int returnedSize = readBytesImpl(bytes);
+ if (returnedSize < bytes.length)
+ {
+ _byteArrayRemaining = -1;
+ }
+ return returnedSize;
+ }
+
+ private int readBytesImpl(byte[] bytes)
+ {
+ int count = (_byteArrayRemaining >= bytes.length ? bytes.length : _byteArrayRemaining);
+ _byteArrayRemaining -= count;
+
+ if (count == 0)
+ {
+ return 0;
+ }
+ else
+ {
+ _data.get(bytes, 0, count);
+ return count;
+ }
+ }
+
+ protected Object readObject() throws JMSException
+ {
+ int position = _data.position();
+ byte wireType = readWireType();
+ Object result = null;
+ try
+ {
+ switch (wireType)
+ {
+ case BOOLEAN_TYPE:
+ checkAvailable(1);
+ result = readBooleanImpl();
+ break;
+ case BYTE_TYPE:
+ checkAvailable(1);
+ result = readByteImpl();
+ break;
+ case BYTEARRAY_TYPE:
+ checkAvailable(4);
+ int size = _data.getInt();
+ if (size == -1)
+ {
+ result = null;
+ }
+ else
+ {
+ _byteArrayRemaining = size;
+ byte[] bytesResult = new byte[size];
+ readBytesImpl(bytesResult);
+ result = bytesResult;
+ }
+ break;
+ case SHORT_TYPE:
+ checkAvailable(2);
+ result = readShortImpl();
+ break;
+ case CHAR_TYPE:
+ checkAvailable(2);
+ result = readCharImpl();
+ break;
+ case INT_TYPE:
+ checkAvailable(4);
+ result = readIntImpl();
+ break;
+ case LONG_TYPE:
+ checkAvailable(8);
+ result = readLongImpl();
+ break;
+ case FLOAT_TYPE:
+ checkAvailable(4);
+ result = readFloatImpl();
+ break;
+ case DOUBLE_TYPE:
+ checkAvailable(8);
+ result = readDoubleImpl();
+ break;
+ case NULL_STRING_TYPE:
+ result = null;
+ break;
+ case STRING_TYPE:
+ checkAvailable(1);
+ result = readStringImpl();
+ break;
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ _data.position(position);
+ throw e;
+ }
+ }
+
+ protected void writeBoolean(boolean b) throws JMSException
+ {
+ writeTypeDiscriminator(BOOLEAN_TYPE);
+ _data.put(b ? (byte) 1 : (byte) 0);
+ }
+
+ protected void writeByte(byte b) throws JMSException
+ {
+ writeTypeDiscriminator(BYTE_TYPE);
+ _data.put(b);
+ }
+
+ protected void writeShort(short i) throws JMSException
+ {
+ writeTypeDiscriminator(SHORT_TYPE);
+ _data.putShort(i);
+ }
+
+ protected void writeChar(char c) throws JMSException
+ {
+ writeTypeDiscriminator(CHAR_TYPE);
+ _data.putChar(c);
+ }
+
+ protected void writeInt(int i) throws JMSException
+ {
+ writeTypeDiscriminator(INT_TYPE);
+ writeIntImpl(i);
+ }
+
+ protected void writeIntImpl(int i)
+ {
+ _data.putInt(i);
+ }
+
+ protected void writeLong(long l) throws JMSException
+ {
+ writeTypeDiscriminator(LONG_TYPE);
+ _data.putLong(l);
+ }
+
+ protected void writeFloat(float v) throws JMSException
+ {
+ writeTypeDiscriminator(FLOAT_TYPE);
+ _data.putFloat(v);
+ }
+
+ protected void writeDouble(double v) throws JMSException
+ {
+ writeTypeDiscriminator(DOUBLE_TYPE);
+ _data.putDouble(v);
+ }
+
+ protected void writeString(String string) throws JMSException
+ {
+ if (string == null)
+ {
+ writeTypeDiscriminator(NULL_STRING_TYPE);
+ }
+ else
+ {
+ writeTypeDiscriminator(STRING_TYPE);
+ try
+ {
+ writeStringImpl(string);
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException jmse = new JMSException("Unable to encode string: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+ }
+
+ protected void writeStringImpl(String string)
+ throws CharacterCodingException
+ {
+ _data.putString(string, Charset.forName("UTF-8").newEncoder());
+ // we must write the null terminator ourselves
+ _data.put((byte) 0);
+ }
+
+ protected void writeBytes(byte[] bytes) throws JMSException
+ {
+ writeBytes(bytes, 0, bytes == null ? 0 : bytes.length);
+ }
+
+ protected void writeBytes(byte[] bytes, int offset, int length) throws JMSException
+ {
+ writeTypeDiscriminator(BYTEARRAY_TYPE);
+ if (bytes == null)
+ {
+ _data.putInt(-1);
+ }
+ else
+ {
+ _data.putInt(length);
+ _data.put(bytes, offset, length);
+ }
+ }
+
+ protected void writeObject(Object object) throws JMSException
+ {
+ checkWritable();
+ Class clazz;
+
+ if (object == null)
+ {
+ // string handles the output of null values
+ clazz = String.class;
+ }
+ else
+ {
+ clazz = object.getClass();
+ }
+
+ if (clazz == Byte.class)
+ {
+ writeByte((Byte) object);
+ }
+ else if (clazz == Boolean.class)
+ {
+ writeBoolean((Boolean) object);
+ }
+ else if (clazz == byte[].class)
+ {
+ writeBytes((byte[]) object);
+ }
+ else if (clazz == Short.class)
+ {
+ writeShort((Short) object);
+ }
+ else if (clazz == Character.class)
+ {
+ writeChar((Character) object);
+ }
+ else if (clazz == Integer.class)
+ {
+ writeInt((Integer) object);
+ }
+ else if (clazz == Long.class)
+ {
+ writeLong((Long) object);
+ }
+ else if (clazz == Float.class)
+ {
+ writeFloat((Float) object);
+ }
+ else if (clazz == Double.class)
+ {
+ writeDouble((Double) object);
+ }
+ else if (clazz == String.class)
+ {
+ writeString((String) object);
+ }
+ else
+ {
+ throw new MessageFormatException("Only primitives plus byte arrays and String are valid types");
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java
new file mode 100644
index 0000000000..6ba55b207a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessage.java
@@ -0,0 +1,536 @@
+/*
+ *
+ * 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.message;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.UUID;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageNotReadableException;
+import javax.jms.MessageNotWriteableException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public abstract class AbstractJMSMessage implements org.apache.qpid.jms.Message
+{
+
+
+
+ protected ByteBuffer _data;
+ protected boolean _readableMessage = false;
+ protected boolean _changedData = true;
+
+ /** If the acknowledge mode is CLIENT_ACKNOWLEDGE the session is required */
+
+
+
+
+ protected AMQMessageDelegate _delegate;
+ private boolean _redelivered;
+
+ protected AbstractJMSMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data)
+ {
+ _delegate = delegateFactory.createDelegate();
+ _data = data;
+ if (_data != null)
+ {
+ _data.acquire();
+ }
+
+
+ _readableMessage = (data != null);
+ _changedData = (data == null);
+
+ }
+
+ protected AbstractJMSMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+
+ _delegate = delegate;
+
+ _data = data;
+ if (_data != null)
+ {
+ _data.acquire();
+ }
+
+ _readableMessage = data != null;
+
+ }
+
+ public String getJMSMessageID() throws JMSException
+ {
+ return _delegate.getJMSMessageID();
+ }
+
+ public void setJMSMessageID(String messageId) throws JMSException
+ {
+ _delegate.setJMSMessageID(messageId);
+ }
+
+ public void setJMSMessageID(UUID messageId) throws JMSException
+ {
+ _delegate.setJMSMessageID(messageId);
+ }
+
+
+ public long getJMSTimestamp() throws JMSException
+ {
+ return _delegate.getJMSTimestamp();
+ }
+
+ public void setJMSTimestamp(long timestamp) throws JMSException
+ {
+ _delegate.setJMSTimestamp(timestamp);
+ }
+
+ public byte[] getJMSCorrelationIDAsBytes() throws JMSException
+ {
+ return _delegate.getJMSCorrelationIDAsBytes();
+ }
+
+ public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException
+ {
+ _delegate.setJMSCorrelationIDAsBytes(bytes);
+ }
+
+ public void setJMSCorrelationID(String correlationId) throws JMSException
+ {
+ _delegate.setJMSCorrelationID(correlationId);
+ }
+
+ public String getJMSCorrelationID() throws JMSException
+ {
+ return _delegate.getJMSCorrelationID();
+ }
+
+ public Destination getJMSReplyTo() throws JMSException
+ {
+ return _delegate.getJMSReplyTo();
+ }
+
+ public void setJMSReplyTo(Destination destination) throws JMSException
+ {
+ _delegate.setJMSReplyTo(destination);
+ }
+
+ public Destination getJMSDestination() throws JMSException
+ {
+ return _delegate.getJMSDestination();
+ }
+
+ public void setJMSDestination(Destination destination)
+ {
+ _delegate.setJMSDestination(destination);
+ }
+
+ public int getJMSDeliveryMode() throws JMSException
+ {
+ return _delegate.getJMSDeliveryMode();
+ }
+
+ public void setJMSDeliveryMode(int i) throws JMSException
+ {
+ _delegate.setJMSDeliveryMode(i);
+ }
+
+
+ public boolean getJMSRedelivered() throws JMSException
+ {
+ return _redelivered;
+ }
+
+ public void setJMSRedelivered(boolean b) throws JMSException
+ {
+ _redelivered = b;
+ }
+
+
+ public String getJMSType() throws JMSException
+ {
+ return _delegate.getJMSType();
+ }
+
+ public void setJMSType(String string) throws JMSException
+ {
+ _delegate.setJMSType(string);
+ }
+
+ public long getJMSExpiration() throws JMSException
+ {
+ return _delegate.getJMSExpiration();
+ }
+
+ public void setJMSExpiration(long l) throws JMSException
+ {
+ _delegate.setJMSExpiration(l);
+ }
+
+ public int getJMSPriority() throws JMSException
+ {
+ return _delegate.getJMSPriority();
+ }
+
+ public void setJMSPriority(int i) throws JMSException
+ {
+ _delegate.setJMSPriority(i);
+ }
+
+
+ public boolean propertyExists(String propertyName) throws JMSException
+ {
+ return _delegate.propertyExists(propertyName);
+ }
+
+ public boolean getBooleanProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getBooleanProperty(s);
+ }
+
+ public byte getByteProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getByteProperty(s);
+ }
+
+ public short getShortProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getShortProperty(s);
+ }
+
+ public int getIntProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getIntProperty(s);
+ }
+
+ public long getLongProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getLongProperty(s);
+ }
+
+ public float getFloatProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getFloatProperty(s);
+ }
+
+ public double getDoubleProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getDoubleProperty(s);
+ }
+
+ public String getStringProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getStringProperty(s);
+ }
+
+ public Object getObjectProperty(final String s)
+ throws JMSException
+ {
+ return _delegate.getObjectProperty(s);
+ }
+
+ public Enumeration getPropertyNames()
+ throws JMSException
+ {
+ return _delegate.getPropertyNames();
+ }
+
+ public void setBooleanProperty(final String s, final boolean b)
+ throws JMSException
+ {
+ _delegate.setBooleanProperty(s, b);
+ }
+
+ public void setByteProperty(final String s, final byte b)
+ throws JMSException
+ {
+ _delegate.setByteProperty(s, b);
+ }
+
+ public void setShortProperty(final String s, final short i)
+ throws JMSException
+ {
+ _delegate.setShortProperty(s, i);
+ }
+
+ public void setIntProperty(final String s, final int i)
+ throws JMSException
+ {
+ _delegate.setIntProperty(s, i);
+ }
+
+ public void setLongProperty(final String s, final long l)
+ throws JMSException
+ {
+ _delegate.setLongProperty(s, l);
+ }
+
+ public void setFloatProperty(final String s, final float v)
+ throws JMSException
+ {
+ _delegate.setFloatProperty(s, v);
+ }
+
+ public void setDoubleProperty(final String s, final double v)
+ throws JMSException
+ {
+ _delegate.setDoubleProperty(s, v);
+ }
+
+ public void setStringProperty(final String s, final String s1)
+ throws JMSException
+ {
+ _delegate.setStringProperty(s, s1);
+ }
+
+ public void setObjectProperty(final String s, final Object o)
+ throws JMSException
+ {
+ _delegate.setObjectProperty(s, o);
+ }
+
+
+
+ public void clearProperties() throws JMSException
+ {
+ _delegate.clearProperties();
+ }
+
+ public void clearBody() throws JMSException
+ {
+ clearBodyImpl();
+ _readableMessage = false;
+
+ }
+
+
+ public void acknowledgeThis() throws JMSException
+ {
+ _delegate.acknowledgeThis();
+ }
+
+ public void acknowledge() throws JMSException
+ {
+ _delegate.acknowledge();
+ }
+
+ /**
+ * This forces concrete classes to implement clearBody()
+ *
+ * @throws JMSException
+ */
+ public abstract void clearBodyImpl() throws JMSException;
+
+ /**
+ * Get a String representation of the body of the message. Used in the toString() method which outputs this before
+ * message properties.
+ */
+ public abstract String toBodyString() throws JMSException;
+
+ protected abstract String getMimeType();
+
+
+
+ public String toString()
+ {
+ try
+ {
+ StringBuffer buf = new StringBuffer("Body:\n");
+
+ buf.append(toBodyString());
+ buf.append("\nJMS Correlation ID: ").append(getJMSCorrelationID());
+ buf.append("\nJMS timestamp: ").append(getJMSTimestamp());
+ buf.append("\nJMS expiration: ").append(getJMSExpiration());
+ buf.append("\nJMS priority: ").append(getJMSPriority());
+ buf.append("\nJMS delivery mode: ").append(getJMSDeliveryMode());
+ buf.append("\nJMS reply to: ").append(getReplyToString());
+ buf.append("\nJMS Redelivered: ").append(_redelivered);
+ buf.append("\nJMS Destination: ").append(getJMSDestination());
+ buf.append("\nJMS Type: ").append(getJMSType());
+ buf.append("\nJMS MessageID: ").append(getJMSMessageID());
+ buf.append("\nJMS Content-Type: ").append(getContentType());
+ buf.append("\nAMQ message number: ").append(getDeliveryTag());
+
+ buf.append("\nProperties:");
+ final Enumeration propertyNames = getPropertyNames();
+ if (!propertyNames.hasMoreElements())
+ {
+ buf.append("<NONE>");
+ }
+ else
+ {
+ buf.append('\n');
+ while(propertyNames.hasMoreElements())
+ {
+ String propertyName = (String) propertyNames.nextElement();
+ buf.append("\t").append(propertyName).append(" = ").append(getObjectProperty(propertyName)).append("\n");
+ }
+
+ }
+
+ return buf.toString();
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ public AMQMessageDelegate getDelegate()
+ {
+ return _delegate;
+ }
+
+ public ByteBuffer getData()
+ {
+ // make sure we rewind the data just in case any method has moved the
+ // position beyond the start
+ if (_data != null)
+ {
+ reset();
+ }
+
+ return _data;
+ }
+
+ protected void checkReadable() throws MessageNotReadableException
+ {
+ if (!_readableMessage)
+ {
+ throw new MessageNotReadableException("You need to call reset() to make the message readable");
+ }
+ }
+
+ protected void checkWritable() throws MessageNotWriteableException
+ {
+ if (_readableMessage)
+ {
+ throw new MessageNotWriteableException("You need to call clearBody() to make the message writable");
+ }
+ }
+
+ public void reset()
+ {
+ if (!_changedData)
+ {
+ _data.rewind();
+ }
+ else
+ {
+ _data.flip();
+ _changedData = false;
+ }
+ }
+
+ public int getContentLength()
+ {
+ if(_data != null)
+ {
+ return _data.remaining();
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ public void receivedFromServer()
+ {
+ _changedData = false;
+ }
+
+ /**
+ * The session is set when CLIENT_ACKNOWLEDGE mode is used so that the CHANNEL ACK can be sent when the user calls
+ * acknowledge()
+ *
+ * @param s the AMQ session that delivered this message
+ */
+ public void setAMQSession(AMQSession s)
+ {
+ _delegate.setAMQSession(s);
+ }
+
+ public AMQSession getAMQSession()
+ {
+ return _delegate.getAMQSession();
+ }
+
+ /**
+ * Get the AMQ message number assigned to this message
+ *
+ * @return the message number
+ */
+ public long getDeliveryTag()
+ {
+ return _delegate.getDeliveryTag();
+ }
+
+ /** Invoked prior to sending the message. Allows the message to be modified if necessary before sending. */
+ public void prepareForSending() throws JMSException
+ {
+ }
+
+
+ public void setContentType(String contentType)
+ {
+ _delegate.setContentType(contentType);
+ }
+
+ public String getContentType()
+ {
+ return _delegate.getContentType();
+ }
+
+ public void setEncoding(String encoding)
+ {
+ _delegate.setEncoding(encoding);
+ }
+
+ public String getEncoding()
+ {
+ return _delegate.getEncoding();
+ }
+
+ public String getReplyToString()
+ {
+ return _delegate.getReplyToString();
+ }
+
+ protected void removeProperty(final String propertyName) throws JMSException
+ {
+ _delegate.removeProperty(propertyName);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java
new file mode 100644
index 0000000000..40c1df0c5d
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/AbstractJMSMessageFactory.java
@@ -0,0 +1,173 @@
+/*
+ *
+ * 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.message;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.transport.MessageProperties;
+import org.apache.qpid.transport.DeliveryProperties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+
+import java.util.Iterator;
+import java.util.List;
+
+public abstract class AbstractJMSMessageFactory implements MessageFactory
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AbstractJMSMessageFactory.class);
+
+ protected AbstractJMSMessage create08MessageWithBody(long messageNbr, ContentHeaderBody contentHeader,
+ AMQShortString exchange, AMQShortString routingKey,
+ List bodies) throws AMQException
+ {
+ ByteBuffer data;
+ final boolean debug = _logger.isDebugEnabled();
+
+ // we optimise the non-fragmented case to avoid copying
+ if ((bodies != null) && (bodies.size() == 1))
+ {
+ if (debug)
+ {
+ _logger.debug("Non-fragmented message body (bodySize=" + contentHeader.bodySize + ")");
+ }
+
+ data = ((ContentBody) bodies.get(0)).payload;
+ }
+ else if (bodies != null)
+ {
+ if (debug)
+ {
+ _logger.debug("Fragmented message body (" + bodies
+ .size() + " frames, bodySize=" + contentHeader.bodySize + ")");
+ }
+
+ data = ByteBuffer.allocate((int) contentHeader.bodySize); // XXX: Is cast a problem?
+ final Iterator it = bodies.iterator();
+ while (it.hasNext())
+ {
+ ContentBody cb = (ContentBody) it.next();
+ 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();
+ }
+ else // bodies == null
+ {
+ data = ByteBuffer.allocate(0);
+ }
+
+ if (debug)
+ {
+ _logger.debug("Creating message from buffer with position=" + data.position() + " and remaining=" + data
+ .remaining());
+ }
+
+ AMQMessageDelegate delegate = new AMQMessageDelegate_0_8(messageNbr,
+ (BasicContentHeaderProperties) contentHeader.getProperties(),
+ exchange, routingKey);
+
+ return createMessage(delegate, data);
+ }
+
+ protected abstract AbstractJMSMessage createMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException;
+
+
+ protected AbstractJMSMessage create010MessageWithBody(long messageNbr, MessageProperties msgProps,
+ DeliveryProperties deliveryProps,
+ java.nio.ByteBuffer body) throws AMQException
+ {
+ ByteBuffer data;
+ final boolean debug = _logger.isDebugEnabled();
+
+
+ if (body != null)
+ {
+ data = ByteBuffer.wrap(body);
+ }
+ else // body == null
+ {
+ data = ByteBuffer.allocate(0);
+ }
+
+ if (debug)
+ {
+ _logger.debug("Creating message from buffer with position=" + data.position() + " and remaining=" + data
+ .remaining());
+ }
+ AMQMessageDelegate delegate = new AMQMessageDelegate_0_10(msgProps, deliveryProps, messageNbr);
+
+ AbstractJMSMessage message = createMessage(delegate, data);
+ return message;
+ }
+
+ private static final String asString(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ return null;
+ }
+ else
+ {
+ return new String(bytes);
+ }
+ }
+
+
+ public AbstractJMSMessage createMessage(long messageNbr, boolean redelivered, ContentHeaderBody contentHeader,
+ AMQShortString exchange, AMQShortString routingKey, List bodies)
+ throws JMSException, AMQException
+ {
+ final AbstractJMSMessage msg = create08MessageWithBody(messageNbr, contentHeader, exchange, routingKey, bodies);
+ msg.setJMSRedelivered(redelivered);
+ msg.receivedFromServer();
+ return msg;
+ }
+
+ public AbstractJMSMessage createMessage(long messageNbr, boolean redelivered, MessageProperties msgProps,
+ DeliveryProperties deliveryProps, java.nio.ByteBuffer body)
+ throws JMSException, AMQException
+ {
+ final AbstractJMSMessage msg =
+ create010MessageWithBody(messageNbr,msgProps,deliveryProps, body);
+ msg.setJMSRedelivered(redelivered);
+ msg.receivedFromServer();
+ return msg;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/CloseConsumerMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/CloseConsumerMessage.java
new file mode 100644
index 0000000000..4af04912e5
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/CloseConsumerMessage.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * 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.message;
+
+import org.apache.qpid.client.BasicMessageConsumer;
+
+public final class CloseConsumerMessage extends UnprocessedMessage
+{
+
+ public CloseConsumerMessage(BasicMessageConsumer consumer)
+ {
+ super(consumer.getConsumerTag());
+ }
+
+
+ public long getDeliveryTag()
+ {
+ return 0;
+ }
+
+ public boolean isRedelivered()
+ {
+ return false;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/FieldTableSupport.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/FieldTableSupport.java
new file mode 100644
index 0000000000..49ae8c14b2
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/FieldTableSupport.java
@@ -0,0 +1,54 @@
+package org.apache.qpid.client.message;
+/*
+ *
+ * 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 java.util.HashMap;
+import java.util.Map;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+
+public class FieldTableSupport
+{
+ public static FieldTable convertToFieldTable(Map<String,?> props)
+ {
+ FieldTable ft = new FieldTable();
+ if (props != null)
+ {
+ for (String key : props.keySet())
+ {
+ ft.setObject(key, props.get(key));
+ }
+ }
+ return ft;
+ }
+
+ public static Map<String,Object> convertToMap(FieldTable ft)
+ {
+ Map<String,Object> map = new HashMap<String,Object>();
+ for (AMQShortString key: ft.keySet() )
+ {
+ map.put(key.asString(), ft.getObject(key));
+ }
+
+ return map;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessage.java
new file mode 100644
index 0000000000..b87275a9ce
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessage.java
@@ -0,0 +1,386 @@
+/*
+ *
+ * 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.message;
+
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+import javax.jms.BytesMessage;
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public class JMSBytesMessage extends AbstractBytesMessage implements BytesMessage
+{
+ public static final String MIME_TYPE = "application/octet-stream";
+
+
+
+ public JMSBytesMessage(AMQMessageDelegateFactory delegateFactory)
+ {
+ this(delegateFactory,null);
+
+ }
+
+ /**
+ * Construct a bytes message with existing data.
+ *
+ * @param delegateFactory
+ * @param data the data that comprises this message. If data is null, you get a 1024 byte buffer that is
+ */
+ JMSBytesMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data)
+ {
+
+ super(delegateFactory, data); // this instanties a content header
+ }
+
+ JMSBytesMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ super(delegate, data);
+ }
+
+
+ public void reset()
+ {
+ super.reset();
+ _readableMessage = true;
+ }
+
+ protected String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public long getBodyLength() throws JMSException
+ {
+ checkReadable();
+ return _data.limit();
+ }
+
+ public boolean readBoolean() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(1);
+ return _data.get() != 0;
+ }
+
+ public byte readByte() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(1);
+ return _data.get();
+ }
+
+ public int readUnsignedByte() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(1);
+ return _data.getUnsigned();
+ }
+
+ public short readShort() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(2);
+ return _data.getShort();
+ }
+
+ public int readUnsignedShort() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(2);
+ return _data.getUnsignedShort();
+ }
+
+ /**
+ * Note that this method reads a unicode character as two bytes from the stream
+ *
+ * @return the character read from the stream
+ * @throws JMSException
+ */
+ public char readChar() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(2);
+ return _data.getChar();
+ }
+
+ public int readInt() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(4);
+ return _data.getInt();
+ }
+
+ public long readLong() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(8);
+ return _data.getLong();
+ }
+
+ public float readFloat() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(4);
+ return _data.getFloat();
+ }
+
+ public double readDouble() throws JMSException
+ {
+ checkReadable();
+ checkAvailable(8);
+ return _data.getDouble();
+ }
+
+ public String readUTF() throws JMSException
+ {
+ checkReadable();
+ // we check only for one byte since theoretically the string could be only a
+ // single byte when using UTF-8 encoding
+
+ try
+ {
+ short length = readShort();
+ if(length == 0)
+ {
+ return "";
+ }
+ else
+ {
+ CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
+ ByteBuffer encodedString = _data.slice();
+ encodedString.limit(length);
+ _data.position(_data.position()+length);
+ CharBuffer string = decoder.decode(encodedString.buf());
+
+ return string.toString();
+ }
+
+
+
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException jmse = new JMSException("Error decoding byte stream as a UTF8 string: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ public int readBytes(byte[] bytes) throws JMSException
+ {
+ if (bytes == null)
+ {
+ throw new IllegalArgumentException("byte array must not be null");
+ }
+ checkReadable();
+ int count = (_data.remaining() >= bytes.length ? bytes.length : _data.remaining());
+ if (count == 0)
+ {
+ return -1;
+ }
+ else
+ {
+ _data.get(bytes, 0, count);
+ return count;
+ }
+ }
+
+ public int readBytes(byte[] bytes, int maxLength) throws JMSException
+ {
+ if (bytes == null)
+ {
+ throw new IllegalArgumentException("byte array must not be null");
+ }
+ if (maxLength > bytes.length)
+ {
+ throw new IllegalArgumentException("maxLength must be <= bytes.length");
+ }
+ checkReadable();
+ int count = (_data.remaining() >= maxLength ? maxLength : _data.remaining());
+ if (count == 0)
+ {
+ return -1;
+ }
+ else
+ {
+ _data.get(bytes, 0, count);
+ return count;
+ }
+ }
+
+ public void writeBoolean(boolean b) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.put(b ? (byte) 1 : (byte) 0);
+ }
+
+ public void writeByte(byte b) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.put(b);
+ }
+
+ public void writeShort(short i) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.putShort(i);
+ }
+
+ public void writeChar(char c) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.putChar(c);
+ }
+
+ public void writeInt(int i) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.putInt(i);
+ }
+
+ public void writeLong(long l) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.putLong(l);
+ }
+
+ public void writeFloat(float v) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.putFloat(v);
+ }
+
+ public void writeDouble(double v) throws JMSException
+ {
+ checkWritable();
+ _changedData = true;
+ _data.putDouble(v);
+ }
+
+ public void writeUTF(String string) throws JMSException
+ {
+ checkWritable();
+ try
+ {
+ CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
+ java.nio.ByteBuffer encodedString = encoder.encode(CharBuffer.wrap(string));
+
+ _data.putShort((short)encodedString.limit());
+ _data.put(encodedString);
+ _changedData = true;
+ //_data.putString(string, Charset.forName("UTF-8").newEncoder());
+ // we must add the null terminator manually
+ //_data.put((byte)0);
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException jmse = new JMSException("Unable to encode string: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ public void writeBytes(byte[] bytes) throws JMSException
+ {
+ checkWritable();
+ _data.put(bytes);
+ _changedData = true;
+ }
+
+ public void writeBytes(byte[] bytes, int offset, int length) throws JMSException
+ {
+ checkWritable();
+ _data.put(bytes, offset, length);
+ _changedData = true;
+ }
+
+ public void writeObject(Object object) throws JMSException
+ {
+ checkWritable();
+ if (object == null)
+ {
+ throw new NullPointerException("Argument must not be null");
+ }
+ Class clazz = object.getClass();
+ if (clazz == Byte.class)
+ {
+ writeByte((Byte) object);
+ }
+ else if (clazz == Boolean.class)
+ {
+ writeBoolean((Boolean) object);
+ }
+ else if (clazz == byte[].class)
+ {
+ writeBytes((byte[]) object);
+ }
+ else if (clazz == Short.class)
+ {
+ writeShort((Short) object);
+ }
+ else if (clazz == Character.class)
+ {
+ writeChar((Character) object);
+ }
+ else if (clazz == Integer.class)
+ {
+ writeInt((Integer) object);
+ }
+ else if (clazz == Long.class)
+ {
+ writeLong((Long) object);
+ }
+ else if (clazz == Float.class)
+ {
+ writeFloat((Float) object);
+ }
+ else if (clazz == Double.class)
+ {
+ writeDouble((Double) object);
+ }
+ else if (clazz == String.class)
+ {
+ writeUTF((String) object);
+ }
+ else
+ {
+ throw new MessageFormatException("Only primitives plus byte arrays and String are valid types");
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessageFactory.java
new file mode 100644
index 0000000000..cb04ebee1b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSBytesMessageFactory.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * 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.message;
+
+import javax.jms.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public class JMSBytesMessageFactory extends AbstractJMSMessageFactory
+{
+ protected AbstractJMSMessage createMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ return new JMSBytesMessage(delegate, data);
+ }
+
+ public AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ return new JMSBytesMessage(delegateFactory);
+ }
+
+ // 0_10 specific
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSHeaderAdapter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSHeaderAdapter.java
new file mode 100644
index 0000000000..e295d4a2a0
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSHeaderAdapter.java
@@ -0,0 +1,553 @@
+/*
+ *
+ * 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.message;
+
+import java.util.Enumeration;
+
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQPInvalidClassException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+
+
+public final class JMSHeaderAdapter
+{
+ private final FieldTable _headers;
+
+ public JMSHeaderAdapter(FieldTable headers)
+ {
+ _headers = headers;
+ }
+
+
+ public FieldTable getHeaders()
+ {
+ return _headers;
+ }
+
+ public boolean getBoolean(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Boolean b = getHeaders().getBoolean(string);
+
+ if (b == null)
+ {
+ if (getHeaders().containsKey(string))
+ {
+ Object str = getHeaders().getObject(string);
+
+ if (str == null || !(str instanceof String))
+ {
+ throw new MessageFormatException("getBoolean can't use " + string + " item.");
+ }
+ else
+ {
+ return Boolean.valueOf((String) str);
+ }
+ }
+ else
+ {
+ b = Boolean.valueOf(null);
+ }
+ }
+
+ return b;
+ }
+
+ public boolean getBoolean(AMQShortString string) throws JMSException
+ {
+ checkPropertyName(string);
+ Boolean b = getHeaders().getBoolean(string);
+
+ if (b == null)
+ {
+ if (getHeaders().containsKey(string))
+ {
+ Object str = getHeaders().getObject(string);
+
+ if (str == null || !(str instanceof String))
+ {
+ throw new MessageFormatException("getBoolean can't use " + string + " item.");
+ }
+ else
+ {
+ return Boolean.valueOf((String) str);
+ }
+ }
+ else
+ {
+ b = Boolean.valueOf(null);
+ }
+ }
+
+ return b;
+ }
+
+ public char getCharacter(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Character c = getHeaders().getCharacter(string);
+
+ if (c == null)
+ {
+ if (getHeaders().isNullStringValue(string))
+ {
+ throw new NullPointerException("Cannot convert null char");
+ }
+ else
+ {
+ throw new MessageFormatException("getChar can't use " + string + " item.");
+ }
+ }
+ else
+ {
+ return (char) c;
+ }
+ }
+
+ public byte[] getBytes(String string) throws JMSException
+ {
+ return getBytes(new AMQShortString(string));
+ }
+
+ public byte[] getBytes(AMQShortString string) throws JMSException
+ {
+ checkPropertyName(string);
+
+ byte[] bs = getHeaders().getBytes(string);
+
+ if (bs == null)
+ {
+ throw new MessageFormatException("getBytes can't use " + string + " item.");
+ }
+ else
+ {
+ return bs;
+ }
+ }
+
+ public byte getByte(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Byte b = getHeaders().getByte(string);
+ if (b == null)
+ {
+ if (getHeaders().containsKey(string))
+ {
+ Object str = getHeaders().getObject(string);
+
+ if (str == null || !(str instanceof String))
+ {
+ throw new MessageFormatException("getByte can't use " + string + " item.");
+ }
+ else
+ {
+ return Byte.valueOf((String) str);
+ }
+ }
+ else
+ {
+ b = Byte.valueOf(null);
+ }
+ }
+
+ return b;
+ }
+
+ public short getShort(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Short s = getHeaders().getShort(string);
+
+ if (s == null)
+ {
+ s = Short.valueOf(getByte(string));
+ }
+
+ return s;
+ }
+
+ public int getInteger(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Integer i = getHeaders().getInteger(string);
+
+ if (i == null)
+ {
+ i = Integer.valueOf(getShort(string));
+ }
+
+ return i;
+ }
+
+ public long getLong(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Long l = getHeaders().getLong(string);
+
+ if (l == null)
+ {
+ l = Long.valueOf(getInteger(string));
+ }
+
+ return l;
+ }
+
+ public float getFloat(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Float f = getHeaders().getFloat(string);
+
+ if (f == null)
+ {
+ if (getHeaders().containsKey(string))
+ {
+ Object str = getHeaders().getObject(string);
+
+ if (str == null || !(str instanceof String))
+ {
+ throw new MessageFormatException("getFloat can't use " + string + " item.");
+ }
+ else
+ {
+ return Float.valueOf((String) str);
+ }
+ }
+ else
+ {
+ throw new NullPointerException("No such property: " + string);
+ }
+
+ }
+
+ return f;
+ }
+
+ public double getDouble(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ Double d = getHeaders().getDouble(string);
+
+ if (d == null)
+ {
+ d = Double.valueOf(getFloat(string));
+ }
+
+ return d;
+ }
+
+ public String getString(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ String s = getHeaders().getString(string);
+
+ if (s == null)
+ {
+ if (getHeaders().containsKey(string))
+ {
+ Object o = getHeaders().getObject(string);
+ if (o instanceof byte[])
+ {
+ throw new MessageFormatException("getObject couldn't find " + string + " item.");
+ }
+ else
+ {
+ if (o == null)
+ {
+ return null;
+ }
+ else
+ {
+ s = String.valueOf(o);
+ }
+ }
+ }//else return s // null;
+ }
+
+ return s;
+ }
+
+ public Object getObject(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ return getHeaders().getObject(string);
+ }
+
+ public void setBoolean(AMQShortString string, boolean b) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setBoolean(string, b);
+ }
+
+ public void setBoolean(String string, boolean b) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setBoolean(string, b);
+ }
+
+ public void setChar(String string, char c) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setChar(string, c);
+ }
+
+ public Object setBytes(AMQShortString string, byte[] bytes)
+ {
+ checkPropertyName(string);
+ return getHeaders().setBytes(string, bytes);
+ }
+
+ public Object setBytes(String string, byte[] bytes)
+ {
+ checkPropertyName(string);
+ return getHeaders().setBytes(string, bytes);
+ }
+
+ public Object setBytes(String string, byte[] bytes, int start, int length)
+ {
+ checkPropertyName(string);
+ return getHeaders().setBytes(string, bytes, start, length);
+ }
+
+ public void setByte(String string, byte b) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setByte(string, b);
+ }
+
+ public void setByte(AMQShortString string, byte b) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setByte(string, b);
+ }
+
+
+ public void setShort(String string, short i) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setShort(string, i);
+ }
+
+ public void setInteger(String string, int i) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setInteger(string, i);
+ }
+
+ public void setInteger(AMQShortString string, int i) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setInteger(string, i);
+ }
+
+ public void setLong(String string, long l) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setLong(string, l);
+ }
+
+ public void setFloat(String string, float v) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setFloat(string, v);
+ }
+
+ public void setDouble(String string, double v) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setDouble(string, v);
+ }
+
+ public void setString(String string, String string1) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setString(string, string1);
+ }
+
+ public void setString(AMQShortString string, String string1) throws JMSException
+ {
+ checkPropertyName(string);
+ getHeaders().setString(string, string1);
+ }
+
+ public void setObject(String string, Object object) throws JMSException
+ {
+ checkPropertyName(string);
+ try
+ {
+ getHeaders().setObject(string, object);
+ }
+ catch (AMQPInvalidClassException aice)
+ {
+ MessageFormatException mfe = new MessageFormatException(AMQPInvalidClassException.INVALID_OBJECT_MSG + (object == null ? "null" : object.getClass()));
+ mfe.setLinkedException(aice);
+ mfe.initCause(aice);
+ throw mfe;
+ }
+ }
+
+ public boolean itemExists(String string) throws JMSException
+ {
+ checkPropertyName(string);
+ return getHeaders().containsKey(string);
+ }
+
+ public Enumeration getPropertyNames()
+ {
+ return getHeaders().getPropertyNames();
+ }
+
+ public void clear()
+ {
+ getHeaders().clear();
+ }
+
+ public boolean propertyExists(AMQShortString propertyName)
+ {
+ checkPropertyName(propertyName);
+ return getHeaders().propertyExists(propertyName);
+ }
+
+ public boolean propertyExists(String propertyName)
+ {
+ checkPropertyName(propertyName);
+ return getHeaders().propertyExists(propertyName);
+ }
+
+ public Object put(Object key, Object value)
+ {
+ checkPropertyName(key.toString());
+ return getHeaders().setObject(key.toString(), value);
+ }
+
+ public Object remove(AMQShortString propertyName)
+ {
+ checkPropertyName(propertyName);
+ return getHeaders().remove(propertyName);
+ }
+
+ public Object remove(String propertyName)
+ {
+ checkPropertyName(propertyName);
+ return getHeaders().remove(propertyName);
+ }
+
+ public boolean isEmpty()
+ {
+ return getHeaders().isEmpty();
+ }
+
+ public void writeToBuffer(ByteBuffer data)
+ {
+ getHeaders().writeToBuffer(data);
+ }
+
+ public Enumeration getMapNames()
+ {
+ return getPropertyNames();
+ }
+
+ protected void checkPropertyName(CharSequence propertyName)
+ {
+ if (propertyName == null)
+ {
+ throw new IllegalArgumentException("Property name must not be null");
+ }
+ else if (propertyName.length() == 0)
+ {
+ throw new IllegalArgumentException("Property name must not be the empty string");
+ }
+
+ checkIdentiferFormat(propertyName);
+ }
+
+ protected void checkIdentiferFormat(CharSequence propertyName)
+ {
+// JMS requirements 3.5.1 Property Names
+// Identifiers:
+// - An identifier is an unlimited-length character sequence that must begin
+// with a Java identifier start character; all following characters must be Java
+// identifier part characters. An identifier start character is any character for
+// which the method Character.isJavaIdentifierStart returns true. This includes
+// '_' and '$'. An identifier part character is any character for which the
+// method Character.isJavaIdentifierPart returns true.
+// - Identifiers cannot be the names NULL, TRUE, or FALSE.
+// Identifiers cannot be NOT, AND, OR, BETWEEN, LIKE, IN, IS, or
+// ESCAPE.
+// Identifiers are either header field references or property references. The
+// type of a property value in a message selector corresponds to the type
+// used to set the property. If a property that does not exist in a message is
+// referenced, its value is NULL. The semantics of evaluating NULL values
+// in a selector are described in Section 3.8.1.2, Null Values.
+// The conversions that apply to the get methods for properties do not
+// apply when a property is used in a message selector expression. For
+// example, suppose you set a property as a string value, as in the
+// following:
+// myMessage.setStringProperty("NumberOfOrders", "2");
+// The following expression in a message selector would evaluate to false,
+// because a string cannot be used in an arithmetic expression:
+// "NumberOfOrders > 1"
+// Identifiers are case sensitive.
+// Message header field references are restricted to JMSDeliveryMode,
+// JMSPriority, JMSMessageID, JMSTimestamp, JMSCorrelationID, and
+// JMSType. JMSMessageID, JMSCorrelationID, and JMSType values may be
+// null and if so are treated as a NULL value.
+
+ if (Boolean.getBoolean("strict-jms"))
+ {
+ // JMS start character
+ if (!(Character.isJavaIdentifierStart(propertyName.charAt(0))))
+ {
+ throw new IllegalArgumentException("Identifier '" + propertyName + "' does not start with a valid JMS identifier start character");
+ }
+
+ // JMS part character
+ int length = propertyName.length();
+ for (int c = 1; c < length; c++)
+ {
+ if (!(Character.isJavaIdentifierPart(propertyName.charAt(c))))
+ {
+ throw new IllegalArgumentException("Identifier '" + propertyName + "' contains an invalid JMS identifier character");
+ }
+ }
+
+ // JMS invalid names
+ if ((propertyName.equals("NULL")
+ || propertyName.equals("TRUE")
+ || propertyName.equals("FALSE")
+ || propertyName.equals("NOT")
+ || propertyName.equals("AND")
+ || propertyName.equals("OR")
+ || propertyName.equals("BETWEEN")
+ || propertyName.equals("LIKE")
+ || propertyName.equals("IN")
+ || propertyName.equals("IS")
+ || propertyName.equals("ESCAPE")))
+ {
+ throw new IllegalArgumentException("Identifier '" + propertyName + "' is not allowed in JMS");
+ }
+ }
+
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java
new file mode 100644
index 0000000000..306ffeeadf
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessage.java
@@ -0,0 +1,512 @@
+/*
+ * 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.message;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+
+import java.nio.charset.CharacterCodingException;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+public class JMSMapMessage extends AbstractBytesTypedMessage implements javax.jms.MapMessage
+{
+ private static final Logger _logger = LoggerFactory.getLogger(JMSMapMessage.class);
+
+ public static final String MIME_TYPE = "jms/map-message";
+
+ protected Map<String, Object> _map = new HashMap<String, Object>();
+
+ public JMSMapMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ this(delegateFactory, null);
+ }
+
+ JMSMapMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data) throws JMSException
+ {
+
+ super(delegateFactory, data); // this instantiates a content header
+ if(data != null)
+ {
+ populateMapFromData();
+ }
+
+ }
+
+ JMSMapMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+
+ super(delegate, data);
+ try
+ {
+ populateMapFromData();
+ }
+ catch (JMSException je)
+ {
+ throw new AMQException(null, "Error populating MapMessage from ByteBuffer", je);
+
+ }
+
+ }
+
+
+ public String toBodyString() throws JMSException
+ {
+ return _map == null ? "" : _map.toString();
+ }
+
+ protected String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public ByteBuffer getData()
+ {
+ // What if _data is null?
+ writeMapToData();
+
+ return super.getData();
+ }
+
+ @Override
+ public void clearBodyImpl() throws JMSException
+ {
+ super.clearBodyImpl();
+ _map.clear();
+ }
+
+ public boolean getBoolean(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Boolean)
+ {
+ return ((Boolean) value).booleanValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Boolean.valueOf((String) value);
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to boolean.");
+ }
+
+ }
+
+ public byte getByte(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Byte)
+ {
+ return ((Byte) value).byteValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Byte.valueOf((String) value).byteValue();
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to byte.");
+ }
+ }
+
+ public short getShort(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Short)
+ {
+ return ((Short) value).shortValue();
+ }
+ else if (value instanceof Byte)
+ {
+ return ((Byte) value).shortValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Short.valueOf((String) value).shortValue();
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to short.");
+ }
+
+ }
+
+ public int getInt(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Integer)
+ {
+ return ((Integer) value).intValue();
+ }
+ else if (value instanceof Short)
+ {
+ return ((Short) value).intValue();
+ }
+ else if (value instanceof Byte)
+ {
+ return ((Byte) value).intValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Integer.valueOf((String) value).intValue();
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to int.");
+ }
+
+ }
+
+ public long getLong(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Long)
+ {
+ return ((Long) value).longValue();
+ }
+ else if (value instanceof Integer)
+ {
+ return ((Integer) value).longValue();
+ }
+
+ if (value instanceof Short)
+ {
+ return ((Short) value).longValue();
+ }
+
+ if (value instanceof Byte)
+ {
+ return ((Byte) value).longValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Long.valueOf((String) value).longValue();
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to long.");
+ }
+
+ }
+
+ public char getChar(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (!_map.containsKey(propName))
+ {
+ throw new MessageFormatException("Property " + propName + " not present");
+ }
+ else if (value instanceof Character)
+ {
+ return ((Character) value).charValue();
+ }
+ else if (value == null)
+ {
+ throw new NullPointerException("Property " + propName + " has null value and therefore cannot "
+ + "be converted to char.");
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to boolan.");
+ }
+
+ }
+
+ public float getFloat(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Float)
+ {
+ return ((Float) value).floatValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Float.valueOf((String) value).floatValue();
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to float.");
+ }
+ }
+
+ public double getDouble(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (value instanceof Double)
+ {
+ return ((Double) value).doubleValue();
+ }
+ else if (value instanceof Float)
+ {
+ return ((Float) value).doubleValue();
+ }
+ else if ((value instanceof String) || (value == null))
+ {
+ return Double.valueOf((String) value).doubleValue();
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to double.");
+ }
+ }
+
+ public String getString(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if ((value instanceof String) || (value == null))
+ {
+ return (String) value;
+ }
+ else if (value instanceof byte[])
+ {
+ throw new MessageFormatException("Property " + propName + " of type byte[] " + "cannot be converted to String.");
+ }
+ else
+ {
+ return value.toString();
+ }
+
+ }
+
+ public byte[] getBytes(String propName) throws JMSException
+ {
+ Object value = _map.get(propName);
+
+ if (!_map.containsKey(propName))
+ {
+ throw new MessageFormatException("Property " + propName + " not present");
+ }
+ else if ((value instanceof byte[]) || (value == null))
+ {
+ return (byte[]) value;
+ }
+ else
+ {
+ throw new MessageFormatException("Property " + propName + " of type " + value.getClass().getName()
+ + " cannot be converted to byte[].");
+ }
+ }
+
+ public Object getObject(String propName) throws JMSException
+ {
+ return _map.get(propName);
+ }
+
+ public Enumeration getMapNames() throws JMSException
+ {
+ return Collections.enumeration(_map.keySet());
+ }
+
+ public void setBoolean(String propName, boolean b) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, b);
+ }
+
+ public void setByte(String propName, byte b) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, b);
+ }
+
+ public void setShort(String propName, short i) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, i);
+ }
+
+ public void setChar(String propName, char c) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, c);
+ }
+
+ public void setInt(String propName, int i) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, i);
+ }
+
+ public void setLong(String propName, long l) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, l);
+ }
+
+ public void setFloat(String propName, float v) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, v);
+ }
+
+ public void setDouble(String propName, double v) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, v);
+ }
+
+ public void setString(String propName, String string1) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, string1);
+ }
+
+ public void setBytes(String propName, byte[] bytes) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ _map.put(propName, bytes);
+ }
+
+ public void setBytes(String propName, byte[] bytes, int offset, int length) throws JMSException
+ {
+ if ((offset == 0) && (length == bytes.length))
+ {
+ setBytes(propName, bytes);
+ }
+ else
+ {
+ byte[] newBytes = new byte[length];
+ System.arraycopy(bytes, offset, newBytes, 0, length);
+ setBytes(propName, newBytes);
+ }
+ }
+
+ public void setObject(String propName, Object value) throws JMSException
+ {
+ checkWritable();
+ checkPropertyName(propName);
+ if ((value instanceof Boolean) || (value instanceof Byte) || (value instanceof Short) || (value instanceof Integer)
+ || (value instanceof Long) || (value instanceof Character) || (value instanceof Float)
+ || (value instanceof Double) || (value instanceof String) || (value instanceof byte[]) || (value == null))
+ {
+ _map.put(propName, value);
+ }
+ else
+ {
+ throw new MessageFormatException("Cannot set property " + propName + " to value " + value + "of type "
+ + value.getClass().getName() + ".");
+ }
+ }
+
+ protected void checkPropertyName(String propName)
+ {
+ if ((propName == null) || propName.equals(""))
+ {
+ throw new IllegalArgumentException("Property name cannot be null, or the empty String.");
+ }
+ }
+
+ public boolean itemExists(String propName) throws JMSException
+ {
+ return _map.containsKey(propName);
+ }
+
+ protected void populateMapFromData() throws JMSException
+ {
+ if (_data != null)
+ {
+ _data.rewind();
+
+ final int entries = readIntImpl();
+ for (int i = 0; i < entries; i++)
+ {
+ String propName = readStringImpl();
+ Object value = readObject();
+ _map.put(propName, value);
+ }
+ }
+ else
+ {
+ _map.clear();
+ }
+ }
+
+ protected void writeMapToData()
+ {
+ allocateInitialBuffer();
+ final int size = _map.size();
+ writeIntImpl(size);
+ for (Map.Entry<String, Object> entry : _map.entrySet())
+ {
+ try
+ {
+ writeStringImpl(entry.getKey());
+ }
+ catch (CharacterCodingException e)
+ {
+ throw new IllegalArgumentException("Cannot encode property key name " + entry.getKey(), e);
+
+ }
+
+ try
+ {
+ writeObject(entry.getValue());
+ }
+ catch (JMSException e)
+ {
+ Object value = entry.getValue();
+ throw new IllegalArgumentException("Cannot encode property key name " + entry.getKey() + " value : " + value
+ + " (type: " + value.getClass().getName() + ").", e);
+ }
+ }
+
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessageFactory.java
new file mode 100644
index 0000000000..eccb90560b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSMapMessageFactory.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.message;
+
+import javax.jms.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public class JMSMapMessageFactory extends AbstractJMSMessageFactory
+{
+ public AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ return new JMSMapMessage(delegateFactory);
+ }
+
+ protected AbstractJMSMessage createMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ return new JMSMapMessage(delegate, data);
+
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessage.java
new file mode 100644
index 0000000000..637d9dd692
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessage.java
@@ -0,0 +1,176 @@
+/*
+ *
+ * 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.message;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import javax.jms.JMSException;
+import javax.jms.MessageFormatException;
+import javax.jms.ObjectMessage;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.AMQException;
+
+public class JMSObjectMessage extends AbstractJMSMessage implements ObjectMessage
+{
+ public static final String MIME_TYPE = "application/java-object-stream";
+
+
+ private static final int DEFAULT_BUFFER_SIZE = 1024;
+
+ /**
+ * Creates empty, writable message for use by producers
+ * @param delegateFactory
+ */
+ public JMSObjectMessage(AMQMessageDelegateFactory delegateFactory)
+ {
+ this(delegateFactory, null);
+ }
+
+ private JMSObjectMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data)
+ {
+ super(delegateFactory, data);
+ if (data == null)
+ {
+ _data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
+ _data.setAutoExpand(true);
+ }
+
+ setContentType(getMimeType());
+ }
+
+ /**
+ * Creates read only message for delivery to consumers
+ */
+
+ JMSObjectMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ super(delegate, data);
+ }
+
+
+ public void clearBodyImpl() throws JMSException
+ {
+ if (_data != null)
+ {
+ _data.release();
+ _data = null;
+ }
+
+
+
+ }
+
+ public String toBodyString() throws JMSException
+ {
+ return String.valueOf(getObject());
+ }
+
+ public String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public void setObject(Serializable serializable) throws JMSException
+ {
+ checkWritable();
+
+ if (_data == null)
+ {
+ _data = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
+ _data.setAutoExpand(true);
+ }
+ else
+ {
+ _data.rewind();
+ }
+
+ try
+ {
+ ObjectOutputStream out = new ObjectOutputStream(_data.asOutputStream());
+ out.writeObject(serializable);
+ out.flush();
+ out.close();
+ }
+ catch (IOException e)
+ {
+ MessageFormatException mfe = new MessageFormatException("Message not serializable: " + e);
+ mfe.setLinkedException(e);
+ mfe.initCause(e);
+ throw mfe;
+ }
+
+ }
+
+ public Serializable getObject() throws JMSException
+ {
+ ObjectInputStream in = null;
+ if (_data == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ _data.rewind();
+ in = new ObjectInputStream(_data.asInputStream());
+
+ return (Serializable) in.readObject();
+ }
+ catch (IOException e)
+ {
+ MessageFormatException mfe = new MessageFormatException("Could not deserialize message: " + e);
+ mfe.setLinkedException(e);
+ mfe.initCause(e);
+ throw mfe;
+ }
+ catch (ClassNotFoundException e)
+ {
+ MessageFormatException mfe = new MessageFormatException("Could not deserialize message: " + e);
+ mfe.setLinkedException(e);
+ mfe.initCause(e);
+ throw mfe;
+ }
+ finally
+ {
+ // _data.rewind();
+ close(in);
+ }
+ }
+
+ private static void close(InputStream in)
+ {
+ try
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+ catch (IOException ignore)
+ { }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessageFactory.java
new file mode 100644
index 0000000000..03851dfa01
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSObjectMessageFactory.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * 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.message;
+
+import javax.jms.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public class JMSObjectMessageFactory extends AbstractJMSMessageFactory
+{
+ protected AbstractJMSMessage createMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ return new JMSObjectMessage(delegate, data);
+ }
+
+ public AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ return new JMSObjectMessage(delegateFactory);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessage.java
new file mode 100644
index 0000000000..ad2620852b
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessage.java
@@ -0,0 +1,206 @@
+/*
+ *
+ * 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.message;
+
+import javax.jms.JMSException;
+import javax.jms.StreamMessage;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class JMSStreamMessage extends AbstractBytesTypedMessage implements StreamMessage
+{
+ public static final String MIME_TYPE="jms/stream-message";
+
+
+
+ /**
+ * This is set when reading a byte array. The readBytes(byte[]) method supports multiple calls to read
+ * a byte array in multiple chunks, hence this is used to track how much is left to be read
+ */
+ private int _byteArrayRemaining = -1;
+
+ public JMSStreamMessage(AMQMessageDelegateFactory delegateFactory)
+ {
+ this(delegateFactory,null);
+
+ }
+
+ /**
+ * Construct a stream message with existing data.
+ *
+ * @param delegateFactory
+ * @param data the data that comprises this message. If data is null, you get a 1024 byte buffer that is
+ */
+ JMSStreamMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data)
+ {
+
+ super(delegateFactory, data); // this instanties a content header
+ }
+
+ JMSStreamMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+
+ super(delegate, data);
+ }
+
+
+ public void reset()
+ {
+ super.reset();
+ _readableMessage = true;
+ }
+
+ protected String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+
+
+ public boolean readBoolean() throws JMSException
+ {
+ return super.readBoolean();
+ }
+
+
+ public byte readByte() throws JMSException
+ {
+ return super.readByte();
+ }
+
+ public short readShort() throws JMSException
+ {
+ return super.readShort();
+ }
+
+ /**
+ * Note that this method reads a unicode character as two bytes from the stream
+ *
+ * @return the character read from the stream
+ * @throws JMSException
+ */
+ public char readChar() throws JMSException
+ {
+ return super.readChar();
+ }
+
+ public int readInt() throws JMSException
+ {
+ return super.readInt();
+ }
+
+ public long readLong() throws JMSException
+ {
+ return super.readLong();
+ }
+
+ public float readFloat() throws JMSException
+ {
+ return super.readFloat();
+ }
+
+ public double readDouble() throws JMSException
+ {
+ return super.readDouble();
+ }
+
+ public String readString() throws JMSException
+ {
+ return super.readString();
+ }
+
+ public int readBytes(byte[] bytes) throws JMSException
+ {
+ return super.readBytes(bytes);
+ }
+
+
+ public Object readObject() throws JMSException
+ {
+ return super.readObject();
+ }
+
+ public void writeBoolean(boolean b) throws JMSException
+ {
+ super.writeBoolean(b);
+ }
+
+ public void writeByte(byte b) throws JMSException
+ {
+ super.writeByte(b);
+ }
+
+ public void writeShort(short i) throws JMSException
+ {
+ super.writeShort(i);
+ }
+
+ public void writeChar(char c) throws JMSException
+ {
+ super.writeChar(c);
+ }
+
+ public void writeInt(int i) throws JMSException
+ {
+ super.writeInt(i);
+ }
+
+ public void writeLong(long l) throws JMSException
+ {
+ super.writeLong(l);
+ }
+
+ public void writeFloat(float v) throws JMSException
+ {
+ super.writeFloat(v);
+ }
+
+ public void writeDouble(double v) throws JMSException
+ {
+ super.writeDouble(v);
+ }
+
+ public void writeString(String string) throws JMSException
+ {
+ super.writeString(string);
+ }
+
+ public void writeBytes(byte[] bytes) throws JMSException
+ {
+ super.writeBytes(bytes);
+ }
+
+ public void writeBytes(byte[] bytes, int offset, int length) throws JMSException
+ {
+ super.writeBytes(bytes,offset,length);
+ }
+
+ public void writeObject(Object object) throws JMSException
+ {
+ super.writeObject(object);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessageFactory.java
new file mode 100644
index 0000000000..5e25db9ae0
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSStreamMessageFactory.java
@@ -0,0 +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.client.message;
+
+import javax.jms.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public class JMSStreamMessageFactory extends AbstractJMSMessageFactory
+{
+ protected AbstractJMSMessage createMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ return new JMSStreamMessage(delegate, data);
+ }
+ public AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ return new JMSStreamMessage(delegateFactory);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessage.java
new file mode 100644
index 0000000000..fc2006a119
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessage.java
@@ -0,0 +1,189 @@
+/*
+ *
+ * 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.message;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+
+import javax.jms.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.CustomJMSXProperty;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.util.Strings;
+
+public class JMSTextMessage extends AbstractJMSMessage implements javax.jms.TextMessage
+{
+ private static final String MIME_TYPE = "text/plain";
+
+ private String _decodedValue;
+
+ /**
+ * This constant represents the name of a property that is set when the message payload is null.
+ */
+ private static final String PAYLOAD_NULL_PROPERTY = CustomJMSXProperty.JMS_AMQP_NULL.toString();
+ private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
+
+ public JMSTextMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ this(delegateFactory, null, null);
+ }
+
+ JMSTextMessage(AMQMessageDelegateFactory delegateFactory, ByteBuffer data, String encoding) throws JMSException
+ {
+ super(delegateFactory, data); // this instantiates a content header
+ setContentType(getMimeType());
+ setEncoding(encoding);
+ }
+
+ JMSTextMessage(AMQMessageDelegate delegate, ByteBuffer data)
+ throws AMQException
+ {
+ super(delegate, data);
+ setContentType(getMimeType());
+ _data = data;
+ }
+
+
+ public void clearBodyImpl() throws JMSException
+ {
+ if (_data != null)
+ {
+ _data.release();
+ _data = null;
+ }
+
+ _decodedValue = null;
+ }
+
+ public String toBodyString() throws JMSException
+ {
+ return getText();
+ }
+
+ protected String getMimeType()
+ {
+ return MIME_TYPE;
+ }
+
+ public void setText(String text) throws JMSException
+ {
+ checkWritable();
+
+ clearBody();
+ try
+ {
+ if (text != null)
+ {
+ final String encoding = getEncoding();
+ if (encoding == null || encoding.equalsIgnoreCase("UTF-8"))
+ {
+ _data = ByteBuffer.wrap(Strings.toUTF8(text));
+ setEncoding("UTF-8");
+ }
+ else
+ {
+ _data = ByteBuffer.wrap(text.getBytes(encoding));
+ }
+ _data.position(_data.limit());
+ _changedData=true;
+ }
+ _decodedValue = text;
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ // should never occur
+ JMSException jmse = new JMSException("Unable to decode text data");
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+
+ public String getText() throws JMSException
+ {
+ if (_data == null && _decodedValue == null)
+ {
+ return null;
+ }
+ else if (_decodedValue != null)
+ {
+ return _decodedValue;
+ }
+ else
+ {
+ _data.rewind();
+
+ if (propertyExists(PAYLOAD_NULL_PROPERTY) && getBooleanProperty(PAYLOAD_NULL_PROPERTY))
+ {
+ return null;
+ }
+ if (getEncoding() != null)
+ {
+ try
+ {
+ _decodedValue = _data.getString(Charset.forName(getEncoding()).newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException jmse = new JMSException("Could not decode string data: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+ else
+ {
+ try
+ {
+ _decodedValue = _data.getString(DEFAULT_CHARSET.newDecoder());
+ }
+ catch (CharacterCodingException e)
+ {
+ JMSException jmse = new JMSException("Could not decode string data: " + e);
+ jmse.setLinkedException(e);
+ jmse.initCause(e);
+ throw jmse;
+ }
+ }
+ return _decodedValue;
+ }
+ }
+
+ @Override
+ public void prepareForSending() throws JMSException
+ {
+ super.prepareForSending();
+ if (_data == null)
+ {
+ setBooleanProperty(PAYLOAD_NULL_PROPERTY, true);
+ }
+ else
+ {
+ removeProperty(PAYLOAD_NULL_PROPERTY);
+ }
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessageFactory.java
new file mode 100644
index 0000000000..1f4d64c78f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/JMSTextMessageFactory.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.message;
+
+import javax.jms.JMSException;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+
+public class JMSTextMessageFactory extends AbstractJMSMessageFactory
+{
+
+ public AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException
+ {
+ return new JMSTextMessage(delegateFactory);
+ }
+
+ protected AbstractJMSMessage createMessage(AMQMessageDelegate delegate, ByteBuffer data) throws AMQException
+ {
+ return new JMSTextMessage(delegate, data);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageConverter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageConverter.java
new file mode 100644
index 0000000000..e606ef11c9
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageConverter.java
@@ -0,0 +1,195 @@
+/*
+ *
+ * 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.message;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.qpid.client.AMQSession;
+
+import javax.jms.*;
+
+import java.util.Enumeration;
+
+public class MessageConverter
+{
+
+ /**
+ * Log4J logger
+ */
+ protected final Logger _logger = LoggerFactory.getLogger(getClass());
+
+ /**
+ * AbstractJMSMessage which will hold the converted message
+ */
+ private AbstractJMSMessage _newMessage;
+
+ public MessageConverter(AbstractJMSMessage message) throws JMSException
+ {
+ _newMessage = message;
+ }
+
+ public MessageConverter(AMQSession session, BytesMessage bytesMessage) throws JMSException
+ {
+ bytesMessage.reset();
+
+ JMSBytesMessage nativeMsg = (JMSBytesMessage) session.createBytesMessage();
+
+ byte[] buf = new byte[1024];
+
+ int len;
+
+ while ((len = bytesMessage.readBytes(buf)) != -1)
+ {
+ nativeMsg.writeBytes(buf, 0, len);
+ }
+
+ _newMessage = nativeMsg;
+ setMessageProperties(bytesMessage);
+ }
+
+ public MessageConverter(AMQSession session, MapMessage message) throws JMSException
+ {
+ MapMessage nativeMessage = session.createMapMessage();
+
+ Enumeration mapNames = message.getMapNames();
+ while (mapNames.hasMoreElements())
+ {
+ String name = (String) mapNames.nextElement();
+ nativeMessage.setObject(name, message.getObject(name));
+ }
+
+ _newMessage = (AbstractJMSMessage) nativeMessage;
+ setMessageProperties(message);
+ }
+
+ public MessageConverter(AMQSession session, ObjectMessage origMessage) throws JMSException
+ {
+
+ ObjectMessage nativeMessage = session.createObjectMessage();
+
+ nativeMessage.setObject(origMessage.getObject());
+
+ _newMessage = (AbstractJMSMessage) nativeMessage;
+ setMessageProperties(origMessage);
+
+ }
+
+ public MessageConverter(AMQSession session, TextMessage message) throws JMSException
+ {
+ TextMessage nativeMessage = session.createTextMessage();
+
+ nativeMessage.setText(message.getText());
+
+ _newMessage = (AbstractJMSMessage) nativeMessage;
+ setMessageProperties(message);
+ }
+
+ public MessageConverter(AMQSession session, StreamMessage message) throws JMSException
+ {
+ StreamMessage nativeMessage = session.createStreamMessage();
+
+ try
+ {
+ message.reset();
+ while (true)
+ {
+ nativeMessage.writeObject(message.readObject());
+ }
+ }
+ catch (MessageEOFException e)
+ {
+ // we're at the end so don't mind the exception
+ }
+
+ _newMessage = (AbstractJMSMessage) nativeMessage;
+ setMessageProperties(message);
+ }
+
+ public MessageConverter(AMQSession session, Message message) throws JMSException
+ {
+ // Send a message with just properties.
+ // Throwing away content
+ Message nativeMessage = session.createMessage();
+
+ _newMessage = (AbstractJMSMessage) nativeMessage;
+ setMessageProperties(message);
+ }
+
+ public AbstractJMSMessage getConvertedMessage()
+ {
+ return _newMessage;
+ }
+
+ /**
+ * Sets all message properties
+ */
+ protected void setMessageProperties(Message message) throws JMSException
+ {
+ setNonJMSProperties(message);
+ setJMSProperties(message);
+ }
+
+ /**
+ * Sets all non-JMS defined properties on converted message
+ */
+ protected void setNonJMSProperties(Message message) throws JMSException
+ {
+ Enumeration propertyNames = message.getPropertyNames();
+ while (propertyNames.hasMoreElements())
+ {
+ String propertyName = String.valueOf(propertyNames.nextElement());
+ // TODO: Shouldn't need to check for JMS properties here as don't think getPropertyNames() should return them
+ if (!propertyName.startsWith("JMSX_"))
+ {
+ Object value = message.getObjectProperty(propertyName);
+ _newMessage.setObjectProperty(propertyName, value);
+ }
+ }
+ }
+
+ /**
+ * Exposed JMS defined properties on converted message:
+ * JMSDestination - we don't set here
+ * JMSDeliveryMode - set
+ * JMSExpiration - we don't set here
+ * JMSPriority - we don't set here
+ * JMSMessageID - we don't set here
+ * JMSTimestamp - we don't set here
+ * JMSCorrelationID - set
+ * JMSReplyTo - set
+ * JMSType - set
+ * JMSRedlivered - we don't set here
+ */
+ protected void setJMSProperties(Message message) throws JMSException
+ {
+ _newMessage.setJMSDeliveryMode(message.getJMSDeliveryMode());
+
+ if (message.getJMSReplyTo() != null)
+ {
+ _newMessage.setJMSReplyTo(message.getJMSReplyTo());
+ }
+
+ _newMessage.setJMSType(message.getJMSType());
+
+ _newMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactory.java
new file mode 100644
index 0000000000..f3d96cd855
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactory.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.client.message;
+
+import java.util.List;
+
+import javax.jms.JMSException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.transport.DeliveryProperties;
+import org.apache.qpid.transport.MessageProperties;
+
+
+public interface MessageFactory
+{
+ AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered,
+ ContentHeaderBody contentHeader,
+ AMQShortString exchange, AMQShortString routingKey,
+ List bodies)
+ throws JMSException, AMQException;
+
+ AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered,
+ MessageProperties msgProps,
+ DeliveryProperties deliveryProps,
+ java.nio.ByteBuffer body)
+ throws JMSException, AMQException;
+
+ AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory) throws JMSException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactoryRegistry.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactoryRegistry.java
new file mode 100644
index 0000000000..cdb75fc9a9
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/MessageFactoryRegistry.java
@@ -0,0 +1,173 @@
+/*
+ *
+ * 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.message;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.nio.ByteBuffer;
+
+import javax.jms.JMSException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicContentHeaderProperties;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.transport.Struct;
+import org.apache.qpid.transport.MessageProperties;
+import org.apache.qpid.transport.MessageTransfer;
+import org.apache.qpid.transport.DeliveryProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MessageFactoryRegistry
+{
+ /**
+ * This class logger
+ */
+ protected final Logger _logger = LoggerFactory.getLogger(getClass());
+
+ private final Map<String, MessageFactory> _mimeStringToFactoryMap = new HashMap<String, MessageFactory>();
+ private final Map<AMQShortString, MessageFactory> _mimeShortStringToFactoryMap =
+ new HashMap<AMQShortString, MessageFactory>();
+ private final MessageFactory _default = new JMSBytesMessageFactory();
+
+ /**
+ * Construct a new registry with the default message factories registered
+ *
+ * @return a message factory registry
+ */
+ public static MessageFactoryRegistry newDefaultRegistry()
+ {
+ MessageFactoryRegistry mf = new MessageFactoryRegistry();
+ mf.registerFactory(JMSMapMessage.MIME_TYPE, new JMSMapMessageFactory());
+ mf.registerFactory("text/plain", new JMSTextMessageFactory());
+ mf.registerFactory("text/xml", new JMSTextMessageFactory());
+ mf.registerFactory(JMSBytesMessage.MIME_TYPE, new JMSBytesMessageFactory());
+ mf.registerFactory(JMSObjectMessage.MIME_TYPE, new JMSObjectMessageFactory());
+ mf.registerFactory(JMSStreamMessage.MIME_TYPE, new JMSStreamMessageFactory());
+ mf.registerFactory(AMQPEncodedMapMessage.MIME_TYPE, new AMQPEncodedMapMessageFactory());
+ mf.registerFactory(null, mf._default);
+
+ return mf;
+ }
+
+
+ public void registerFactory(String mimeType, MessageFactory mf)
+ {
+ if (mf == null)
+ {
+ throw new IllegalArgumentException("Message factory must not be null");
+ }
+
+ _mimeStringToFactoryMap.put(mimeType, mf);
+ _mimeShortStringToFactoryMap.put(new AMQShortString(mimeType), mf);
+ }
+
+ public MessageFactory deregisterFactory(String mimeType)
+ {
+ _mimeShortStringToFactoryMap.remove(new AMQShortString(mimeType));
+
+ return _mimeStringToFactoryMap.remove(mimeType);
+ }
+
+ /**
+ * Create a message. This looks up the MIME type from the content header and instantiates the appropriate
+ * concrete message type.
+ *
+ * @param deliveryTag the AMQ message id
+ * @param redelivered true if redelivered
+ * @param contentHeader the content header that was received
+ * @param bodies a list of ContentBody instances @return the message.
+ * @throws AMQException
+ * @throws JMSException
+ */
+ public AbstractJMSMessage createMessage(long deliveryTag, boolean redelivered, AMQShortString exchange,
+ AMQShortString routingKey, ContentHeaderBody contentHeader, List bodies)
+ throws AMQException, JMSException
+ {
+ BasicContentHeaderProperties properties = (BasicContentHeaderProperties) contentHeader.getProperties();
+
+ // Get the message content type. This may be null for pure AMQP messages, but will always be set for JMS over
+ // AMQP. When the type is null, it can only be assumed that the message is a byte message.
+ AMQShortString contentTypeShortString = properties.getContentType();
+ contentTypeShortString = (contentTypeShortString == null) ? new AMQShortString(
+ JMSBytesMessage.MIME_TYPE) : contentTypeShortString;
+
+ MessageFactory mf = _mimeShortStringToFactoryMap.get(contentTypeShortString);
+ if (mf == null)
+ {
+ mf = _default;
+ }
+
+ return mf.createMessage(deliveryTag, redelivered, contentHeader, exchange, routingKey, bodies);
+ }
+
+ public AbstractJMSMessage createMessage(MessageTransfer transfer) throws AMQException, JMSException
+ {
+
+ MessageProperties mprop = transfer.getHeader().get(MessageProperties.class);
+ String messageType = "";
+ if ( mprop == null || mprop.getContentType() == null)
+ {
+ _logger.debug("no message type specified, building a byte message");
+ messageType = JMSBytesMessage.MIME_TYPE;
+ }
+ else
+ {
+ messageType = mprop.getContentType();
+ }
+ MessageFactory mf = _mimeStringToFactoryMap.get(messageType);
+ if (mf == null)
+ {
+ mf = _default;
+ }
+
+ boolean redelivered = false;
+ DeliveryProperties deliverProps;
+ if((deliverProps = transfer.getHeader().get(DeliveryProperties.class)) != null)
+ {
+ redelivered = deliverProps.getRedelivered();
+ }
+ return mf.createMessage(transfer.getId(),
+ redelivered,
+ mprop == null? new MessageProperties():mprop,
+ deliverProps == null? new DeliveryProperties():deliverProps,
+ transfer.getBody());
+ }
+
+
+ public AbstractJMSMessage createMessage(AMQMessageDelegateFactory delegateFactory, String mimeType) throws AMQException, JMSException
+ {
+ if (mimeType == null)
+ {
+ throw new IllegalArgumentException("Mime type must not be null");
+ }
+
+ MessageFactory mf = _mimeStringToFactoryMap.get(mimeType);
+ if (mf == null)
+ {
+ mf = _default;
+ }
+
+ return mf.createMessage(delegateFactory);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java
new file mode 100644
index 0000000000..6e5f33a65c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/ReturnMessage.java
@@ -0,0 +1,47 @@
+package org.apache.qpid.client.message;
+/*
+ *
+ * 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.qpid.framing.AMQShortString;
+
+public class ReturnMessage extends UnprocessedMessage_0_8
+{
+ final private AMQShortString _replyText;
+ final private int _replyCode;
+
+ public ReturnMessage(AMQShortString exchange, AMQShortString routingKey, AMQShortString replyText, int replyCode)
+ {
+ super(-1,0,exchange,routingKey,false);
+ _replyText = replyText;
+ _replyCode = replyCode;
+ }
+
+ public int getReplyCode()
+ {
+ return _replyCode;
+ }
+
+ public AMQShortString getReplyText()
+ {
+ return _replyText;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java
new file mode 100644
index 0000000000..e2cb36a030
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * 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.message;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.BasicMessageConsumer;
+
+
+/**
+ * This class contains everything needed to process a JMS message. It assembles the deliver body, the content header and
+ * the content body/ies.
+ *
+ * Note that the actual work of creating a JMS message for the client code's use is done outside of the MINA dispatcher
+ * thread in order to minimise the amount of work done in the MINA dispatcher thread.
+ */
+public abstract class UnprocessedMessage implements AMQSession.Dispatchable
+{
+ private final int _consumerTag;
+
+
+ public UnprocessedMessage(int consumerTag)
+ {
+ _consumerTag = consumerTag;
+ }
+
+
+ abstract public long getDeliveryTag();
+
+
+ public int getConsumerTag()
+ {
+ return _consumerTag;
+ }
+
+ public void dispatch(AMQSession ssn)
+ {
+ ssn.dispatch(this);
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.java
new file mode 100644
index 0000000000..f31bc88509
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_10.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.client.message;
+
+import org.apache.qpid.transport.MessageTransfer;
+
+/**
+ * This class contains everything needed to process a JMS message. It assembles the deliver body, the content header and
+ * the content body/ies.
+ *
+ * Note that the actual work of creating a JMS message for the client code's use is done outside of the MINA dispatcher
+ * thread in order to minimise the amount of work done in the MINA dispatcher thread.
+ */
+public class UnprocessedMessage_0_10 extends UnprocessedMessage
+{
+ private MessageTransfer _transfer;
+
+ public UnprocessedMessage_0_10(MessageTransfer xfr)
+ {
+ super(Integer.parseInt(xfr.getDestination()));
+ _transfer = xfr;
+ }
+
+ // additional 0_10 method
+
+ public long getDeliveryTag()
+ {
+ return _transfer.getId();
+ }
+
+ public MessageTransfer getMessageTransfer()
+ {
+ return _transfer;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java b/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java
new file mode 100644
index 0000000000..685e646d85
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/message/UnprocessedMessage_0_8.java
@@ -0,0 +1,163 @@
+/*
+ *
+ * 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.message;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.BasicDeliverBody;
+import org.apache.qpid.framing.ContentBody;
+import org.apache.qpid.framing.ContentHeaderBody;
+
+/**
+ * This class contains everything needed to process a JMS message. It assembles the deliver body, the content header and
+ * the content body/ies.
+ *
+ * Note that the actual work of creating a JMS message for the client code's use is done outside of the MINA dispatcher
+ * thread in order to minimise the amount of work done in the MINA dispatcher thread.
+ */
+public class UnprocessedMessage_0_8 extends UnprocessedMessage
+{
+ private long _bytesReceived = 0;
+
+
+ private AMQShortString _exchange;
+ private AMQShortString _routingKey;
+ private final long _deliveryId;
+ protected boolean _redelivered;
+
+ private BasicDeliverBody _deliverBody;
+ private ContentHeaderBody _contentHeader;
+
+ /** List of ContentBody instances. Due to fragmentation you don't know how big this will be in general */
+ private List<ContentBody> _bodies;
+
+ public UnprocessedMessage_0_8(long deliveryId, int consumerTag, AMQShortString exchange, AMQShortString routingKey, boolean redelivered)
+ {
+ super(consumerTag);
+ _exchange = exchange;
+ _routingKey = routingKey;
+
+ _redelivered = redelivered;
+ _deliveryId = deliveryId;
+ }
+
+
+ public AMQShortString getExchange()
+ {
+ return _exchange;
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return _routingKey;
+ }
+
+ public long getDeliveryTag()
+ {
+ return _deliveryId;
+ }
+
+ public boolean isRedelivered()
+ {
+ return _redelivered;
+ }
+
+
+ public void receiveBody(ContentBody body)
+ {
+
+ if (body.payload != null)
+ {
+ final long payloadSize = body.payload.remaining();
+
+ if (_bodies == null)
+ {
+ if (payloadSize == getContentHeader().bodySize)
+ {
+ _bodies = Collections.singletonList(body);
+ }
+ else
+ {
+ _bodies = new ArrayList<ContentBody>();
+ _bodies.add(body);
+ }
+
+ }
+ else
+ {
+ _bodies.add(body);
+ }
+ _bytesReceived += payloadSize;
+ }
+ }
+
+ public void setMethodBody(BasicDeliverBody deliverBody)
+ {
+ _deliverBody = deliverBody;
+ }
+
+ public void setContentHeader(ContentHeaderBody contentHeader)
+ {
+ this._contentHeader = contentHeader;
+ }
+
+ public boolean isAllBodyDataReceived()
+ {
+ return _bytesReceived == getContentHeader().bodySize;
+ }
+
+ public BasicDeliverBody getDeliverBody()
+ {
+ return _deliverBody;
+ }
+
+ public ContentHeaderBody getContentHeader()
+ {
+ return _contentHeader;
+ }
+
+ public List<ContentBody> getBodies()
+ {
+ return _bodies;
+ }
+
+ public String toString()
+ {
+ StringBuilder buf = new StringBuilder();
+
+ if (_contentHeader != null)
+ {
+ buf.append("ContentHeader " + _contentHeader);
+ }
+ if(_deliverBody != null)
+ {
+ buf.append("Delivery tag " + _deliverBody.getDeliveryTag());
+ buf.append("Consumer tag " + _deliverBody.getConsumerTag());
+ buf.append("Deliver Body " + _deliverBody);
+ }
+
+ return buf.toString();
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/AddressHelper.java b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/AddressHelper.java
new file mode 100644
index 0000000000..368ec60525
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/AddressHelper.java
@@ -0,0 +1,332 @@
+/*
+ *
+ * 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.messaging.address;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQDestination.Binding;
+import org.apache.qpid.client.messaging.address.Link.Reliability;
+import org.apache.qpid.client.messaging.address.Link.Subscription;
+import org.apache.qpid.client.messaging.address.Node.ExchangeNode;
+import org.apache.qpid.client.messaging.address.Node.QueueNode;
+import org.apache.qpid.client.messaging.address.Node.UnknownNodeType;
+import org.apache.qpid.configuration.Accessor;
+import org.apache.qpid.configuration.Accessor.MapAccessor;
+import org.apache.qpid.messaging.Address;
+
+/**
+ * Utility class for extracting information from the address class
+ */
+public class AddressHelper
+{
+ public static final String NODE = "node";
+ public static final String LINK = "link";
+ public static final String X_DECLARE = "x-declare";
+ public static final String X_BINDINGS = "x-bindings";
+ public static final String X_SUBSCRIBE = "x-subscribes";
+ public static final String CREATE = "create";
+ public static final String ASSERT = "assert";
+ public static final String DELETE = "delete";
+ public static final String FILTER = "filter";
+ public static final String NO_LOCAL = "no-local";
+ public static final String DURABLE = "durable";
+ public static final String EXCLUSIVE = "exclusive";
+ public static final String AUTO_DELETE = "auto-delete";
+ public static final String TYPE = "type";
+ public static final String ALT_EXCHANGE = "alternate-exchange";
+ public static final String BINDINGS = "bindings";
+ public static final String BROWSE = "browse";
+ public static final String MODE = "mode";
+ public static final String CAPACITY = "capacity";
+ public static final String CAPACITY_SOURCE = "source";
+ public static final String CAPACITY_TARGET = "target";
+ public static final String NAME = "name";
+ public static final String EXCHANGE = "exchange";
+ public static final String QUEUE = "queue";
+ public static final String KEY = "key";
+ public static final String ARGUMENTS = "arguments";
+ public static final String RELIABILITY = "reliability";
+
+ private Address address;
+ private Accessor addressProps;
+ private Accessor nodeProps;
+ private Accessor linkProps;
+
+ public AddressHelper(Address address)
+ {
+ this.address = address;
+ addressProps = new MapAccessor(address.getOptions());
+ Map node_props = address.getOptions() == null
+ || address.getOptions().get(NODE) == null ? null
+ : (Map) address.getOptions().get(NODE);
+
+ if (node_props != null)
+ {
+ nodeProps = new MapAccessor(node_props);
+ }
+
+ Map link_props = address.getOptions() == null
+ || address.getOptions().get(LINK) == null ? null
+ : (Map) address.getOptions().get(LINK);
+
+ if (link_props != null)
+ {
+ linkProps = new MapAccessor(link_props);
+ }
+ }
+
+ public String getCreate()
+ {
+ return addressProps.getString(CREATE);
+ }
+
+ public String getAssert()
+ {
+ return addressProps.getString(ASSERT);
+ }
+
+ public String getDelete()
+ {
+ return addressProps.getString(DELETE);
+ }
+
+ public boolean isNoLocal()
+ {
+ Boolean b = nodeProps.getBoolean(NO_LOCAL);
+ return b == null ? false : b;
+ }
+
+ public boolean isBrowseOnly()
+ {
+ String mode = addressProps.getString(MODE);
+ return mode != null && mode.equals(BROWSE) ? true : false;
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Binding> getBindings(Map props)
+ {
+ List<Binding> bindings = new ArrayList<Binding>();
+ List<Map> bindingList = (List<Map>) props.get(X_BINDINGS);
+ if (bindingList != null)
+ {
+ for (Map bindingMap : bindingList)
+ {
+ Binding binding = new Binding(
+ (String) bindingMap.get(EXCHANGE),
+ (String) bindingMap.get(QUEUE),
+ (String) bindingMap.get(KEY),
+ bindingMap.get(ARGUMENTS) == null ? Collections.EMPTY_MAP
+ : (Map<String, Object>) bindingMap
+ .get(ARGUMENTS));
+ bindings.add(binding);
+ }
+ }
+ return bindings;
+ }
+
+ public Map getDeclareArgs(Map props)
+ {
+ if (props != null && props.get(X_DECLARE) != null)
+ {
+ return (Map) props.get(X_DECLARE);
+
+ } else
+ {
+ return Collections.EMPTY_MAP;
+ }
+ }
+
+ public int getTargetNodeType() throws Exception
+ {
+ if (nodeProps == null || nodeProps.getString(TYPE) == null)
+ {
+ // need to query and figure out
+ return AMQDestination.UNKNOWN_TYPE;
+ } else if (nodeProps.getString(TYPE).equals("queue"))
+ {
+ return AMQDestination.QUEUE_TYPE;
+ } else if (nodeProps.getString(TYPE).equals("topic"))
+ {
+ return AMQDestination.TOPIC_TYPE;
+ } else
+ {
+ throw new Exception("unkown exchange type");
+ }
+ }
+
+ public Node getTargetNode(int addressType)
+ {
+ // target node here is the default exchange
+ if (nodeProps == null || addressType == AMQDestination.QUEUE_TYPE)
+ {
+ return new ExchangeNode();
+ } else if (addressType == AMQDestination.TOPIC_TYPE)
+ {
+ Map node = (Map) address.getOptions().get(NODE);
+ return createExchangeNode(node);
+ } else
+ {
+ // don't know yet
+ return null;
+ }
+ }
+
+ private Node createExchangeNode(Map parent)
+ {
+ Map declareArgs = getDeclareArgs(parent);
+ MapAccessor argsMap = new MapAccessor(declareArgs);
+ ExchangeNode node = new ExchangeNode();
+ node.setExchangeType(argsMap.getString(TYPE) == null ? null : argsMap
+ .getString(TYPE));
+ fillInCommonNodeArgs(node, parent, argsMap);
+ return node;
+ }
+
+ private Node createQueueNode(Map parent)
+ {
+ Map declareArgs = getDeclareArgs(parent);
+ MapAccessor argsMap = new MapAccessor(declareArgs);
+ QueueNode node = new QueueNode();
+ node.setAlternateExchange(argsMap.getString(ALT_EXCHANGE));
+ node.setExclusive(argsMap.getBoolean(EXCLUSIVE) == null ? false
+ : argsMap.getBoolean(EXCLUSIVE));
+ fillInCommonNodeArgs(node, parent, argsMap);
+
+ return node;
+ }
+
+ private void fillInCommonNodeArgs(Node node, Map parent, MapAccessor argsMap)
+ {
+ node.setDurable(getDurability(parent));
+ node.setAutoDelete(argsMap.getBoolean(AUTO_DELETE) == null ? false
+ : argsMap.getBoolean(AUTO_DELETE));
+ node.setAlternateExchange(argsMap.getString(ALT_EXCHANGE));
+ node.setBindings(getBindings(parent));
+ if (getDeclareArgs(parent).containsKey(ARGUMENTS))
+ {
+ node.setDeclareArgs((Map<String,Object>)getDeclareArgs(parent).get(ARGUMENTS));
+ }
+ }
+
+ private boolean getDurability(Map map)
+ {
+ Accessor access = new MapAccessor(map);
+ Boolean result = access.getBoolean(DURABLE);
+ return (result == null) ? false : result.booleanValue();
+ }
+
+ /**
+ * if the type == queue x-declare args from the node props is used. if the
+ * type == exchange x-declare args from the link props is used else just
+ * create a default temp queue.
+ */
+ public Node getSourceNode(int addressType)
+ {
+ if (addressType == AMQDestination.QUEUE_TYPE && nodeProps != null)
+ {
+ return createQueueNode((Map) address.getOptions().get(NODE));
+ }
+ if (addressType == AMQDestination.TOPIC_TYPE && linkProps != null)
+ {
+ return createQueueNode((Map) address.getOptions().get(LINK));
+ } else
+ {
+ // need to query the info
+ return new QueueNode();
+ }
+ }
+
+ public Link getLink() throws Exception
+ {
+ Link link = new Link();
+ link.setSubscription(new Subscription());
+ if (linkProps != null)
+ {
+ link.setDurable(linkProps.getBoolean(DURABLE) == null ? false
+ : linkProps.getBoolean(DURABLE));
+ link.setName(linkProps.getString(NAME));
+
+ String reliability = linkProps.getString(RELIABILITY);
+ if ( reliability != null)
+ {
+ if (reliability.equalsIgnoreCase("unreliable"))
+ {
+ link.setReliability(Reliability.UNRELIABLE);
+ }
+ else if (reliability.equalsIgnoreCase("at-least-once"))
+ {
+ link.setReliability(Reliability.AT_LEAST_ONCE);
+ }
+ else
+ {
+ throw new Exception("The reliability mode '" +
+ reliability + "' is not yet supported");
+ }
+
+ }
+
+ if (((Map) address.getOptions().get(LINK)).get(CAPACITY) instanceof Map)
+ {
+ MapAccessor capacityProps = new MapAccessor(
+ (Map) ((Map) address.getOptions().get(LINK))
+ .get(CAPACITY));
+ link
+ .setConsumerCapacity(capacityProps
+ .getInt(CAPACITY_SOURCE) == null ? 0
+ : capacityProps.getInt(CAPACITY_SOURCE));
+ link
+ .setProducerCapacity(capacityProps
+ .getInt(CAPACITY_TARGET) == null ? 0
+ : capacityProps.getInt(CAPACITY_TARGET));
+ }
+ else
+ {
+ int cap = linkProps.getInt(CAPACITY) == null ? 0 : linkProps
+ .getInt(CAPACITY);
+ link.setConsumerCapacity(cap);
+ link.setProducerCapacity(cap);
+ }
+ link.setFilter(linkProps.getString(FILTER));
+ // so far filter type not used
+
+ if (((Map) address.getOptions().get(LINK)).containsKey(X_SUBSCRIBE))
+ {
+ Map x_subscribe = (Map)((Map) address.getOptions().get(LINK)).get(X_SUBSCRIBE);
+
+ if (x_subscribe.containsKey(ARGUMENTS))
+ {
+ link.getSubscription().setArgs((Map<String,Object>)x_subscribe.get(ARGUMENTS));
+ }
+
+ boolean exclusive = x_subscribe.containsKey(EXCLUSIVE) ?
+ Boolean.parseBoolean((String)x_subscribe.get(EXCLUSIVE)): false;
+
+ link.getSubscription().setExclusive(exclusive);
+ }
+ }
+
+ return link;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Link.java b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Link.java
new file mode 100644
index 0000000000..5f97d625b4
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Link.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.client.messaging.address;
+
+import static org.apache.qpid.client.messaging.address.Link.Reliability.UNSPECIFIED;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.qpid.client.messaging.address.Node.QueueNode;
+
+public class Link
+{
+ public enum FilterType { SQL92, XQUERY, SUBJECT }
+
+ public enum Reliability { UNRELIABLE, AT_MOST_ONCE, AT_LEAST_ONCE, EXACTLY_ONCE, UNSPECIFIED }
+
+ protected String name;
+ protected String _filter;
+ protected FilterType _filterType = FilterType.SUBJECT;
+ protected boolean _isNoLocal;
+ protected boolean _isDurable;
+ protected int _consumerCapacity = 0;
+ protected int _producerCapacity = 0;
+ protected Node node;
+ protected Subscription subscription;
+ protected Reliability reliability = UNSPECIFIED;
+
+ public Reliability getReliability()
+ {
+ return reliability;
+ }
+
+ public void setReliability(Reliability reliability)
+ {
+ this.reliability = reliability;
+ }
+
+ public Node getNode()
+ {
+ return node;
+ }
+
+ public void setNode(Node node)
+ {
+ this.node = node;
+ }
+
+ public boolean isDurable()
+ {
+ return _isDurable;
+ }
+
+ public void setDurable(boolean durable)
+ {
+ _isDurable = durable;
+ }
+
+ public String getFilter()
+ {
+ return _filter;
+ }
+
+ public void setFilter(String filter)
+ {
+ this._filter = filter;
+ }
+
+ public FilterType getFilterType()
+ {
+ return _filterType;
+ }
+
+ public void setFilterType(FilterType type)
+ {
+ _filterType = type;
+ }
+
+ public boolean isNoLocal()
+ {
+ return _isNoLocal;
+ }
+
+ public void setNoLocal(boolean noLocal)
+ {
+ _isNoLocal = noLocal;
+ }
+
+ public int getConsumerCapacity()
+ {
+ return _consumerCapacity;
+ }
+
+ public void setConsumerCapacity(int capacity)
+ {
+ _consumerCapacity = capacity;
+ }
+
+ public int getProducerCapacity()
+ {
+ return _producerCapacity;
+ }
+
+ public void setProducerCapacity(int capacity)
+ {
+ _producerCapacity = capacity;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public Subscription getSubscription()
+ {
+ return this.subscription;
+ }
+
+ public void setSubscription(Subscription subscription)
+ {
+ this.subscription = subscription;
+ }
+
+ public static class Subscription
+ {
+ private Map<String,Object> args = new HashMap<String,Object>();
+ private boolean exclusive = false;
+
+ public Map<String, Object> getArgs()
+ {
+ return args;
+ }
+
+ public void setArgs(Map<String, Object> args)
+ {
+ this.args = args;
+ }
+
+ public boolean isExclusive()
+ {
+ return exclusive;
+ }
+
+ public void setExclusive(boolean exclusive)
+ {
+ this.exclusive = exclusive;
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Node.java b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Node.java
new file mode 100644
index 0000000000..c98b194334
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/Node.java
@@ -0,0 +1,148 @@
+/*
+ *
+ * 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.messaging.address;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.naming.OperationNotSupportedException;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQDestination.Binding;
+
+public abstract class Node
+{
+ protected int _nodeType = AMQDestination.UNKNOWN_TYPE;
+ protected boolean _isDurable;
+ protected boolean _isAutoDelete;
+ protected String _alternateExchange;
+ protected List<Binding> _bindings = new ArrayList<Binding>();
+ protected Map<String,Object> _declareArgs = Collections.emptyMap();
+
+ public int getType()
+ {
+ return _nodeType;
+ }
+
+ public boolean isDurable()
+ {
+ return _isDurable;
+ }
+
+ public void setDurable(boolean durable)
+ {
+ _isDurable = durable;
+ }
+
+ public boolean isAutoDelete()
+ {
+ return _isAutoDelete;
+ }
+
+ public void setAutoDelete(boolean autoDelete)
+ {
+ _isAutoDelete = autoDelete;
+ }
+
+ public String getAlternateExchange()
+ {
+ return _alternateExchange;
+ }
+
+ public void setAlternateExchange(String altExchange)
+ {
+ _alternateExchange = altExchange;
+ }
+
+ public List<Binding> getBindings()
+ {
+ return _bindings;
+ }
+
+ public void setBindings(List<Binding> bindings)
+ {
+ _bindings = bindings;
+ }
+
+ public void addBinding(Binding binding) {
+ this._bindings.add(binding);
+ }
+
+ public Map<String,Object> getDeclareArgs()
+ {
+ return _declareArgs;
+ }
+
+ public void setDeclareArgs(Map<String,Object> options)
+ {
+ _declareArgs = options;
+ }
+
+ public static class QueueNode extends Node
+ {
+ protected boolean _isExclusive;
+ protected QpidQueueOptions _queueOptions = new QpidQueueOptions();
+
+ public QueueNode()
+ {
+ _nodeType = AMQDestination.QUEUE_TYPE;
+ }
+
+ public boolean isExclusive()
+ {
+ return _isExclusive;
+ }
+
+ public void setExclusive(boolean exclusive)
+ {
+ _isExclusive = exclusive;
+ }
+ }
+
+ public static class ExchangeNode extends Node
+ {
+ protected QpidExchangeOptions _exchangeOptions = new QpidExchangeOptions();
+ protected String _exchangeType;
+
+ public ExchangeNode()
+ {
+ _nodeType = AMQDestination.TOPIC_TYPE;
+ }
+
+ public String getExchangeType()
+ {
+ return _exchangeType;
+ }
+
+ public void setExchangeType(String exchangeType)
+ {
+ _exchangeType = exchangeType;
+ }
+
+ }
+
+ public static class UnknownNodeType extends Node
+ {
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidExchangeOptions.java b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidExchangeOptions.java
new file mode 100644
index 0000000000..3ad9aff9ea
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidExchangeOptions.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * 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.messaging.address;
+
+import java.util.HashMap;
+
+public class QpidExchangeOptions extends HashMap<String,Object>
+{
+ public static final String QPID_MSG_SEQUENCE = "qpid.msg_sequence";
+ public static final String QPID_INITIAL_VALUE_EXCHANGE = "qpid.ive";
+ public static final String QPID_EXCLUSIVE_BINDING = "qpid.exclusive-binding";
+
+ public void setMessageSequencing()
+ {
+ this.put(QPID_MSG_SEQUENCE, 1);
+ }
+
+ public void setInitialValueExchange()
+ {
+ this.put(QPID_INITIAL_VALUE_EXCHANGE, 1);
+ }
+
+ public void setExclusiveBinding()
+ {
+ this.put(QPID_EXCLUSIVE_BINDING, 1);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidQueueOptions.java b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidQueueOptions.java
new file mode 100644
index 0000000000..04aa7d146f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/messaging/address/QpidQueueOptions.java
@@ -0,0 +1,103 @@
+/*
+ *
+ * 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.messaging.address;
+
+import java.util.HashMap;
+
+public class QpidQueueOptions extends HashMap<String,Object>
+{
+ public static final String QPID_MAX_COUNT = "qpid.max_count";
+ public static final String QPID_MAX_SIZE = "qpid.max_size";
+ public static final String QPID_POLICY_TYPE = "qpid.policy_type";
+ public static final String QPID_PERSIST_LAST_NODE = "qpid.persist_last_node";
+ public static final String QPID_LVQ_KEY = "qpid.LVQ_key";
+ public static final String QPID_LAST_VALUE_QUEUE = "qpid.last_value_queue";
+ public static final String QPID_LAST_VALUE_QUEUE_NO_BROWSE = "qpid.last_value_queue_no_browse";
+ public static final String QPID_QUEUE_EVENT_GENERATION = "qpid.queue_event_generation";
+
+ public void validatePolicyType(String type)
+ {
+ if (type == null ||
+ !("reject".equals(type) || "flow_to_disk".equals(type) ||
+ "ring".equals(type) || "ring_strict".equals(type)))
+ {
+ throw new IllegalArgumentException("Invalid Queue Policy Type" +
+ " should be one of {reject|flow_to_disk|ring|ring_strict}");
+ }
+ }
+
+ public void setPolicyType(String s)
+ {
+ validatePolicyType(s);
+ this.put(QPID_POLICY_TYPE, s);
+ }
+
+ public void setMaxCount(Integer i)
+ {
+ this.put(QPID_MAX_COUNT, i);
+ }
+
+ public void setMaxSize(Integer i)
+ {
+ this.put(QPID_MAX_SIZE, i);
+ }
+
+ public void setPersistLastNode()
+ {
+ this.put(QPID_PERSIST_LAST_NODE, 1);
+ }
+
+ public void setOrderingPolicy(String s)
+ {
+ if (QpidQueueOptions.QPID_LAST_VALUE_QUEUE.equals(s))
+ {
+ this.put(QPID_LAST_VALUE_QUEUE, 1);
+ }
+ else if (QpidQueueOptions.QPID_LAST_VALUE_QUEUE_NO_BROWSE.equals(s))
+ {
+ this.put(QPID_LAST_VALUE_QUEUE_NO_BROWSE,1);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid Ordering Policy" +
+ " should be one of {" + QpidQueueOptions.QPID_LAST_VALUE_QUEUE + "|" +
+ QPID_LAST_VALUE_QUEUE_NO_BROWSE + "}");
+ }
+ }
+
+ public void setLvqKey(String key)
+ {
+ this.put(QPID_LVQ_KEY, key);
+ }
+
+ public void setQueueEvents(String value)
+ {
+ if (value != null && (value.equals("1") || value.equals("2")))
+ {
+ this.put(QPID_QUEUE_EVENT_GENERATION, value);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Invalid value for " +
+ QPID_QUEUE_EVENT_GENERATION + " should be one of {1|2}");
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java
new file mode 100644
index 0000000000..eb5af119b2
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolHandler.java
@@ -0,0 +1,881 @@
+/*
+ *
+ * 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.protocol;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.mina.filter.codec.ProtocolCodecException;
+import org.apache.qpid.AMQConnectionClosedException;
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQTimeoutException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.failover.FailoverHandler;
+import org.apache.qpid.client.failover.FailoverState;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.StateWaiter;
+import org.apache.qpid.client.state.listener.SpecificMethodFrameListener;
+import org.apache.qpid.codec.AMQCodecFactory;
+import org.apache.qpid.framing.AMQBody;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ConnectionCloseBody;
+import org.apache.qpid.framing.ConnectionCloseOkBody;
+import org.apache.qpid.framing.HeartbeatBody;
+import org.apache.qpid.framing.MethodRegistry;
+import org.apache.qpid.framing.ProtocolInitiation;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.pool.Job;
+import org.apache.qpid.pool.ReferenceCountingExecutorService;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQMethodListener;
+import org.apache.qpid.protocol.ProtocolEngine;
+import org.apache.qpid.thread.Threading;
+import org.apache.qpid.transport.NetworkDriver;
+import org.apache.qpid.transport.network.io.IoTransport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AMQProtocolHandler is the client side protocol handler for AMQP, it handles all protocol events received from the
+ * network by MINA. The primary purpose of AMQProtocolHandler is to translate the generic event model of MINA into the
+ * specific event model of AMQP, by revealing the type of the received events (from decoded data), and passing the
+ * event on to more specific handlers for the type. In this sense, it channels the richer event model of AMQP,
+ * expressed in terms of methods and so on, through the cruder, general purpose event model of MINA, expressed in
+ * terms of "message received" and so on.
+ *
+ * <p/>There is a 1:1 mapping between an AMQProtocolHandler and an {@link AMQConnection}. The connection class is
+ * exposed to the end user of the AMQP client API, and also implements the JMS Connection API, so provides the public
+ * API calls through which an individual connection can be manipulated. This protocol handler talks to the network
+ * through MINA, in a behind the scenes role; it is not an exposed part of the client API.
+ *
+ * <p/>There is a 1:many mapping between an AMQProtocolHandler and a set of {@link AMQSession}s. At the MINA level,
+ * there is one session per connection. At the AMQP level there can be many channels which are also called sessions in
+ * JMS parlance. The {@link AMQSession}s are managed through an {@link AMQProtocolSession} instance. The protocol
+ * session is similar to the MINA per-connection session, except that it can span the lifecycle of multiple MINA sessions
+ * in the event of failover. See below for more information about this.
+ *
+ * <p/>Mina provides a session container that can be used to store/retrieve arbitrary objects as String named
+ * attributes. A more convenient, type-safe, container for session data is provided in the form of
+ * {@link AMQProtocolSession}.
+ *
+ * <p/>A common way to use MINA is to have a single instance of the event handler, and for MINA to pass in its session
+ * object with every event, and for per-connection data to be held in the MINA session (perhaps using a type-safe wrapper
+ * as described above). This event handler is different, because dealing with failover complicates things. To the
+ * end client of an AMQConnection, a failed over connection is still handled through the same connection instance, but
+ * behind the scenes a new transport connection, and MINA session will have been created. The MINA session object cannot
+ * be used to track the state of the fail-over process, because it is destroyed and a new one is created, as the old
+ * connection is shutdown and a new one created. For this reason, an AMQProtocolHandler is created per AMQConnection
+ * and the protocol session data is held outside of the MINA IOSession.
+ *
+ * <p/>This handler is responsible for setting up the filter chain to filter all events for this handler through.
+ * The filter chain is set up as a stack of event handers that perform the following functions (working upwards from
+ * the network traffic at the bottom), handing off incoming events to an asynchronous thread pool to do the work,
+ * optionally handling secure sockets encoding/decoding, encoding/decoding the AMQP format itself.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Maintain fail-over state.
+ * <tr><td>
+ * </table>
+ *
+ * @todo Use a single handler instance, by shifting everything to do with the 'protocol session' state, including
+ * failover state, into AMQProtocolSession, and tracking that from AMQConnection? The lifecycles of
+ * AMQProtocolSesssion and AMQConnection will be the same, so if there is high cohesion between them, they could
+ * be merged, although there is sense in keeping the session model separate. Will clarify things by having data
+ * held per protocol handler, per protocol session, per network connection, per channel, in separate classes, so
+ * that lifecycles of the fields match lifecycles of their containing objects.
+ */
+public class AMQProtocolHandler implements ProtocolEngine
+{
+ /** Used for debugging. */
+ private static final Logger _logger = LoggerFactory.getLogger(AMQProtocolHandler.class);
+ private static final Logger _protocolLogger = LoggerFactory.getLogger("qpid.protocol");
+ private static final boolean PROTOCOL_DEBUG = (System.getProperty("amqj.protocol.logging.level") != null);
+
+ private static final long MAXIMUM_STATE_WAIT_TIME = Long.parseLong(System.getProperty("amqj.MaximumStateWait", "30000"));
+
+ /**
+ * The connection that this protocol handler is associated with. There is a 1-1 mapping between connection
+ * instances and protocol handler instances.
+ */
+ private AMQConnection _connection;
+
+ /** Our wrapper for a protocol session that provides access to session values in a typesafe manner. */
+ private volatile AMQProtocolSession _protocolSession;
+
+ /** Holds the state of the protocol session. */
+ private AMQStateManager _stateManager;
+
+ /** Holds the method listeners, */
+ private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>();
+
+ /**
+ * We create the failover handler when the session is created since it needs a reference to the IoSession in order
+ * to be able to send errors during failover back to the client application. The session won't be available in the
+ * case where we failing over due to a Connection.Redirect message from the broker.
+ */
+ private FailoverHandler _failoverHandler;
+
+ /**
+ * This flag is used to track whether failover is being attempted. It is used to prevent the application constantly
+ * attempting failover where it is failing.
+ */
+ private FailoverState _failoverState = FailoverState.NOT_STARTED;
+
+ /** Used to provide a condition to wait upon for operations that are required to wait for failover to complete. */
+ private CountDownLatch _failoverLatch;
+
+ /** The last failover exception that occurred */
+ private FailoverException _lastFailoverException;
+
+ /** Defines the default timeout to use for synchronous protocol commands. */
+ private final long DEFAULT_SYNC_TIMEOUT = Long.getLong("amqj.default_syncwrite_timeout", 1000 * 30);
+
+ /** Object to lock on when changing the latch */
+ private Object _failoverLatchChange = new Object();
+ private AMQCodecFactory _codecFactory;
+ private Job _readJob;
+ private Job _writeJob;
+ private ReferenceCountingExecutorService _poolReference = ReferenceCountingExecutorService.getInstance();
+ private NetworkDriver _networkDriver;
+ private ProtocolVersion _suggestedProtocolVersion;
+
+ private long _writtenBytes;
+ private long _readBytes;
+
+ /**
+ * Creates a new protocol handler, associated with the specified client connection instance.
+ *
+ * @param con The client connection that this is the event handler for.
+ */
+ public AMQProtocolHandler(AMQConnection con)
+ {
+ _connection = con;
+ _protocolSession = new AMQProtocolSession(this, _connection);
+ _stateManager = new AMQStateManager(_protocolSession);
+ _codecFactory = new AMQCodecFactory(false, _protocolSession);
+ _poolReference.setThreadFactory(new ThreadFactory()
+ {
+
+ public Thread newThread(final Runnable runnable)
+ {
+ try
+ {
+ return Threading.getThreadFactory().createThread(runnable);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Failed to create thread", e);
+ }
+ }
+ });
+ _readJob = new Job(_poolReference, Job.MAX_JOB_EVENTS, true);
+ _writeJob = new Job(_poolReference, Job.MAX_JOB_EVENTS, false);
+ _poolReference.acquireExecutorService();
+ _failoverHandler = new FailoverHandler(this);
+ }
+
+ /**
+ * Called when we want to create a new IoTransport session
+ * @param brokerDetail
+ */
+ public void createIoTransportSession(BrokerDetails brokerDetail)
+ {
+ _protocolSession = new AMQProtocolSession(this, _connection);
+ _stateManager.setProtocolSession(_protocolSession);
+ IoTransport.connect_0_9(getProtocolSession(),
+ brokerDetail.getHost(),
+ brokerDetail.getPort(),
+ brokerDetail.getBooleanProperty(BrokerDetails.OPTIONS_SSL));
+ _protocolSession.init();
+ }
+
+ /**
+ * Called when the network connection is closed. This can happen, either because the client explicitly requested
+ * that the connection be closed, in which case nothing is done, or because the connection died. In the case
+ * where the connection died, an attempt to failover automatically to a new connection may be started. The failover
+ * process will be started, provided that it is the clients policy to allow failover, and provided that a failover
+ * has not already been started or failed.
+ *
+ * @todo Clarify: presumably exceptionCaught is called when the client is sending during a connection failure and
+ * not otherwise? The above comment doesn't make that clear.
+ */
+ public void closed()
+ {
+ if (_connection.isClosed())
+ {
+ _logger.debug("Session closed called by client");
+ }
+ else
+ {
+ _logger.debug("Session closed called with failover state currently " + _failoverState);
+
+ // reconnetablility was introduced here so as not to disturb the client as they have made their intentions
+ // known through the policy settings.
+
+ if ((_failoverState != FailoverState.IN_PROGRESS) && _connection.failoverAllowed())
+ {
+ _logger.debug("FAILOVER STARTING");
+ if (_failoverState == FailoverState.NOT_STARTED)
+ {
+ _failoverState = FailoverState.IN_PROGRESS;
+ startFailoverThread();
+ }
+ else
+ {
+ _logger.debug("Not starting failover as state currently " + _failoverState);
+ }
+ }
+ else
+ {
+ _logger.debug("Failover not allowed by policy."); // or already in progress?
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug(_connection.getFailoverPolicy().toString());
+ }
+
+ if (_failoverState != FailoverState.IN_PROGRESS)
+ {
+ _logger.debug("sessionClose() not allowed to failover");
+ _connection.exceptionReceived(new AMQDisconnectedException(
+ "Server closed connection and reconnection " + "not permitted.",
+ _stateManager.getLastException()));
+ }
+ else
+ {
+ _logger.debug("sessionClose() failover in progress");
+ }
+ }
+ }
+
+ _logger.debug("Protocol Session [" + this + "] closed");
+ }
+
+ /** See {@link FailoverHandler} to see rationale for separate thread. */
+ private void startFailoverThread()
+ {
+ if(!_connection.isClosed())
+ {
+ final Thread failoverThread;
+ try
+ {
+ failoverThread = Threading.getThreadFactory().createThread(_failoverHandler);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Failed to create thread", e);
+ }
+ failoverThread.setName("Failover");
+ // Do not inherit daemon-ness from current thread as this can be a daemon
+ // thread such as a AnonymousIoService thread.
+ failoverThread.setDaemon(false);
+ failoverThread.start();
+ }
+ }
+
+ public void readerIdle()
+ {
+ _logger.debug("Protocol Session [" + this + "] idle: reader");
+ // failover:
+ HeartbeatDiagnostics.timeout();
+ _logger.warn("Timed out while waiting for heartbeat from peer.");
+ _networkDriver.close();
+ }
+
+ public void writerIdle()
+ {
+ _logger.debug("Protocol Session [" + this + "] idle: reader");
+ writeFrame(HeartbeatBody.FRAME);
+ HeartbeatDiagnostics.sent();
+ }
+
+ /**
+ * Invoked when any exception is thrown by the NetworkDriver
+ */
+ public void exception(Throwable cause)
+ {
+ if (_failoverState == FailoverState.NOT_STARTED)
+ {
+ // if (!(cause instanceof AMQUndeliveredException) && (!(cause instanceof AMQAuthenticationException)))
+ if ((cause instanceof AMQConnectionClosedException) || cause instanceof IOException)
+ {
+ _logger.info("Exception caught therefore going to attempt failover: " + cause, cause);
+ // this will attempt failover
+ _networkDriver.close();
+ closed();
+ }
+ else
+ {
+
+ if (cause instanceof ProtocolCodecException)
+ {
+ _logger.info("Protocol Exception caught NOT going to attempt failover as " +
+ "cause isn't AMQConnectionClosedException: " + cause, cause);
+
+ AMQException amqe = new AMQException("Protocol handler error: " + cause, cause);
+ propagateExceptionToAllWaiters(amqe);
+ }
+ _connection.exceptionReceived(cause);
+
+ }
+
+ // FIXME Need to correctly handle other exceptions. Things like ...
+ // if (cause instanceof AMQChannelClosedException)
+ // which will cause the JMSSession to end due to a channel close and so that Session needs
+ // to be removed from the map so we can correctly still call close without an exception when trying to close
+ // the server closed session. See also CloseChannelMethodHandler as the sessionClose is never called on exception
+ }
+ // we reach this point if failover was attempted and failed therefore we need to let the calling app
+ // know since we cannot recover the situation
+ else if (_failoverState == FailoverState.FAILED)
+ {
+ _logger.error("Exception caught by protocol handler: " + cause, cause);
+
+ // we notify the state manager of the error in case we have any clients waiting on a state
+ // change. Those "waiters" will be interrupted and can handle the exception
+ AMQException amqe = new AMQException("Protocol handler error: " + cause, cause);
+ propagateExceptionToAllWaiters(amqe);
+ _connection.exceptionReceived(cause);
+ }
+ }
+
+ /**
+ * There are two cases where we have other threads potentially blocking for events to be handled by this class.
+ * These are for the state manager (waiting for a state change) or a frame listener (waiting for a particular type
+ * of frame to arrive). When an error occurs we need to notify these waiters so that they can react appropriately.
+ *
+ * This should be called only when the exception is fatal for the connection.
+ *
+ * @param e the exception to propagate
+ *
+ * @see #propagateExceptionToFrameListeners
+ */
+ public void propagateExceptionToAllWaiters(Exception e)
+ {
+ getStateManager().error(e);
+
+ propagateExceptionToFrameListeners(e);
+ }
+
+ /**
+ * This caters for the case where we only need to propagate an exception to the the frame listeners to interupt any
+ * protocol level waits.
+ *
+ * This will would normally be used to notify all Frame Listeners that Failover is about to occur and they should
+ * stop waiting and relinquish the Failover lock {@see FailoverHandler}.
+ *
+ * Once the {@link FailoverHandler} has re-established the connection then the listeners will be able to re-attempt
+ * their protocol request and so listen again for the correct frame.
+ *
+ * @param e the exception to propagate
+ */
+ public void propagateExceptionToFrameListeners(Exception e)
+ {
+ synchronized (_frameListeners)
+ {
+ if (!_frameListeners.isEmpty())
+ {
+ final Iterator it = _frameListeners.iterator();
+ while (it.hasNext())
+ {
+ final AMQMethodListener ml = (AMQMethodListener) it.next();
+ ml.error(e);
+ }
+ }
+ }
+ }
+
+ public void notifyFailoverStarting()
+ {
+ // Set the last exception in the sync block to ensure the ordering with add.
+ // either this gets done and the add does the ml.error
+ // or the add completes first and the iterator below will do ml.error
+ synchronized (_frameListeners)
+ {
+ _lastFailoverException = new FailoverException("Failing over about to start");
+ }
+
+ //Only notify the Frame listeners that failover is going to occur as the State listeners shouldn't be
+ // interrupted unless failover cannot restore the state.
+ propagateExceptionToFrameListeners(_lastFailoverException);
+ }
+
+ public void failoverInProgress()
+ {
+ _lastFailoverException = null;
+ }
+
+ private static int _messageReceivedCount;
+
+
+ public void received(ByteBuffer msg)
+ {
+ try
+ {
+ _readBytes += msg.remaining();
+ final ArrayList<AMQDataBlock> dataBlocks = _codecFactory.getDecoder().decodeBuffer(msg);
+
+ Job.fireAsynchEvent(_poolReference.getPool(), _readJob, new Runnable()
+ {
+
+ public void run()
+ {
+ // Decode buffer
+
+ for (AMQDataBlock message : dataBlocks)
+ {
+
+ try
+ {
+ if (PROTOCOL_DEBUG)
+ {
+ _protocolLogger.info(String.format("RECV: [%s] %s", this, message));
+ }
+
+ if(message instanceof AMQFrame)
+ {
+ final boolean debug = _logger.isDebugEnabled();
+ final long msgNumber = ++_messageReceivedCount;
+
+ if (debug && ((msgNumber % 1000) == 0))
+ {
+ _logger.debug("Received " + _messageReceivedCount + " protocol messages");
+ }
+
+ AMQFrame frame = (AMQFrame) message;
+
+ final AMQBody bodyFrame = frame.getBodyFrame();
+
+ HeartbeatDiagnostics.received(bodyFrame instanceof HeartbeatBody);
+
+ bodyFrame.handle(frame.getChannel(), _protocolSession);
+
+ _connection.bytesReceived(_readBytes);
+ }
+ 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;
+ _suggestedProtocolVersion = protocolInit.checkVersion();
+ _logger.info("Broker suggested using protocol version:" + _suggestedProtocolVersion);
+
+ // get round a bug in old versions of qpid whereby the connection is not closed
+ _stateManager.changeState(AMQState.CONNECTION_CLOSED);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Exception processing frame", e);
+ propagateExceptionToFrameListeners(e);
+ exception(e);
+ }
+ }
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ propagateExceptionToFrameListeners(e);
+ exception(e);
+ }
+ }
+
+ public void methodBodyReceived(final int channelId, final AMQBody bodyFrame)
+ throws AMQException
+ {
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("(" + System.identityHashCode(this) + ")Method frame received: " + bodyFrame);
+ }
+
+ final AMQMethodEvent<AMQMethodBody> evt =
+ new AMQMethodEvent<AMQMethodBody>(channelId, (AMQMethodBody) bodyFrame);
+
+ try
+ {
+
+ boolean wasAnyoneInterested = getStateManager().methodReceived(evt);
+ synchronized (_frameListeners)
+ {
+ 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())
+ {
+ 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)
+ {
+ propagateExceptionToFrameListeners(e);
+
+ exception(e);
+ }
+
+ }
+
+ private static int _messagesOut;
+
+ public StateWaiter createWaiter(Set<AMQState> states) throws AMQException
+ {
+ return getStateManager().createWaiter(states);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session. Equivalent to calling
+ * getProtocolSession().write().
+ *
+ * @param frame the frame to write
+ */
+ public void writeFrame(AMQDataBlock frame)
+ {
+ writeFrame(frame, false);
+ }
+
+ public void writeFrame(AMQDataBlock frame, boolean wait)
+ {
+ final ByteBuffer buf = frame.toNioByteBuffer();
+ _writtenBytes += buf.remaining();
+ Job.fireAsynchEvent(_poolReference.getPool(), _writeJob, new Runnable()
+ {
+ public void run()
+ {
+ _networkDriver.send(buf);
+ }
+ });
+ if (PROTOCOL_DEBUG)
+ {
+ _protocolLogger.debug(String.format("SEND: [%s] %s", this, frame));
+ }
+
+ final long sentMessages = _messagesOut++;
+
+ final boolean debug = _logger.isDebugEnabled();
+
+ if (debug && ((sentMessages % 1000) == 0))
+ {
+ _logger.debug("Sent " + _messagesOut + " protocol messages");
+ }
+
+ _connection.bytesSent(_writtenBytes);
+
+ if (wait)
+ {
+ _networkDriver.flush();
+ }
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session and waits for a particular response. Equivalent to
+ * calling getProtocolSession().write() then waiting for the response.
+ *
+ * @param frame
+ * @param listener the blocking listener. Note the calling thread will block.
+ */
+ public AMQMethodEvent writeCommandFrameAndWaitForReply(AMQFrame frame, BlockingMethodFrameListener listener)
+ throws AMQException, FailoverException
+ {
+ return writeCommandFrameAndWaitForReply(frame, listener, DEFAULT_SYNC_TIMEOUT);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session and waits for a particular response. Equivalent to
+ * calling getProtocolSession().write() then waiting for the response.
+ *
+ * @param frame
+ * @param listener the blocking listener. Note the calling thread will block.
+ */
+ public AMQMethodEvent writeCommandFrameAndWaitForReply(AMQFrame frame, BlockingMethodFrameListener listener,
+ long timeout) throws AMQException, FailoverException
+ {
+ try
+ {
+ synchronized (_frameListeners)
+ {
+ if (_lastFailoverException != null)
+ {
+ throw _lastFailoverException;
+ }
+
+ if(_stateManager.getCurrentState() == AMQState.CONNECTION_CLOSED ||
+ _stateManager.getCurrentState() == AMQState.CONNECTION_CLOSING)
+ {
+ Exception e = _stateManager.getLastException();
+ if (e != null)
+ {
+ if (e instanceof AMQException)
+ {
+ AMQException amqe = (AMQException) e;
+
+ throw amqe.cloneForCurrentThread();
+ }
+ else
+ {
+ throw new AMQException(AMQConstant.INTERNAL_ERROR, e.getMessage(), e);
+ }
+ }
+ }
+
+ _frameListeners.add(listener);
+ //FIXME: At this point here we should check or before add we should check _stateManager is in an open
+ // state so as we don't check we are likely just to time out here as I believe is being seen in QPID-1255
+ }
+ writeFrame(frame);
+
+ return listener.blockForFrame(timeout);
+ // When control resumes before this line, a reply will have been received
+ // that matches the criteria defined in the blocking listener
+ }
+ finally
+ {
+ // If we don't removeKey the listener then no-one will
+ _frameListeners.remove(listener);
+ }
+
+ }
+
+ /** More convenient method to write a frame and wait for it's response. */
+ public AMQMethodEvent syncWrite(AMQFrame frame, Class responseClass) throws AMQException, FailoverException
+ {
+ return syncWrite(frame, responseClass, DEFAULT_SYNC_TIMEOUT);
+ }
+
+ /** More convenient method to write a frame and wait for it's response. */
+ public AMQMethodEvent syncWrite(AMQFrame frame, Class responseClass, long timeout) throws AMQException, FailoverException
+ {
+ return writeCommandFrameAndWaitForReply(frame, new SpecificMethodFrameListener(frame.getChannel(), responseClass),
+ timeout);
+ }
+
+ public void closeSession(AMQSession session) throws AMQException
+ {
+ _protocolSession.closeSession(session);
+ }
+
+ /**
+ * Closes the connection.
+ *
+ * <p/>If a failover exception occurs whilst closing the connection it is ignored, as the connection is closed
+ * anyway.
+ *
+ * @param timeout The timeout to wait for an acknowledgement to the close request.
+ *
+ * @throws AMQException If the close fails for any reason.
+ */
+ public void closeConnection(long timeout) throws AMQException
+ {
+ 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);
+
+ //If the connection is already closed then don't do a syncWrite
+ if (!getStateManager().getCurrentState().equals(AMQState.CONNECTION_CLOSED))
+ {
+ try
+ {
+ syncWrite(frame, ConnectionCloseOkBody.class, timeout);
+ _networkDriver.close();
+ closed();
+ }
+ catch (AMQTimeoutException e)
+ {
+ closed();
+ }
+ catch (FailoverException e)
+ {
+ _logger.debug("FailoverException interrupted connection close, ignoring as connection close anyway.");
+ }
+ }
+ _poolReference.releaseExecutorService();
+ }
+
+ /** @return the number of bytes read from this protocol session */
+ public long getReadBytes()
+ {
+ return _readBytes;
+ }
+
+ /** @return the number of bytes written to this protocol session */
+ public long getWrittenBytes()
+ {
+ return _writtenBytes;
+ }
+
+ public void failover(String host, int port)
+ {
+ _failoverHandler.setHost(host);
+ _failoverHandler.setPort(port);
+ // see javadoc for FailoverHandler to see rationale for separate thread
+ startFailoverThread();
+ }
+
+ public void blockUntilNotFailingOver() throws InterruptedException
+ {
+ synchronized(_failoverLatchChange)
+ {
+ if (_failoverLatch != null)
+ {
+ if(!_failoverLatch.await(MAXIMUM_STATE_WAIT_TIME, TimeUnit.MILLISECONDS))
+ {
+
+ }
+ }
+ }
+ }
+
+ public AMQShortString generateQueueName()
+ {
+ return _protocolSession.generateQueueName();
+ }
+
+ public CountDownLatch getFailoverLatch()
+ {
+ return _failoverLatch;
+ }
+
+ public void setFailoverLatch(CountDownLatch failoverLatch)
+ {
+ synchronized (_failoverLatchChange)
+ {
+ _failoverLatch = failoverLatch;
+ }
+ }
+
+ public AMQConnection getConnection()
+ {
+ return _connection;
+ }
+
+ public AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+ public void setStateManager(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ _stateManager.setProtocolSession(_protocolSession);
+ }
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ FailoverState getFailoverState()
+ {
+ return _failoverState;
+ }
+
+ public void setFailoverState(FailoverState failoverState)
+ {
+ _failoverState = failoverState;
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return _protocolSession.getProtocolMajorVersion();
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return _protocolSession.getProtocolMinorVersion();
+ }
+
+ public MethodRegistry getMethodRegistry()
+ {
+ return _protocolSession.getMethodRegistry();
+ }
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return _protocolSession.getProtocolVersion();
+ }
+
+ public SocketAddress getRemoteAddress()
+ {
+ return _networkDriver.getRemoteAddress();
+ }
+
+ public SocketAddress getLocalAddress()
+ {
+ return _networkDriver.getLocalAddress();
+ }
+
+ public void setNetworkDriver(NetworkDriver driver)
+ {
+ _networkDriver = driver;
+ }
+
+ /** @param delay delay in seconds (not ms) */
+ void initHeartbeats(int delay)
+ {
+ if (delay > 0)
+ {
+ getNetworkDriver().setMaxWriteIdle(delay);
+ getNetworkDriver().setMaxReadIdle(HeartbeatConfig.CONFIG.getTimeout(delay));
+ HeartbeatDiagnostics.init(delay, HeartbeatConfig.CONFIG.getTimeout(delay));
+ }
+ }
+
+ public NetworkDriver getNetworkDriver()
+ {
+ return _networkDriver;
+ }
+
+ public ProtocolVersion getSuggestedProtocolVersion()
+ {
+ return _suggestedProtocolVersion;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java
new file mode 100644
index 0000000000..5b7d272506
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/AMQProtocolSession.java
@@ -0,0 +1,467 @@
+/*
+ *
+ * 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.protocol;
+
+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.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.UnprocessedMessage;
+import org.apache.qpid.client.message.UnprocessedMessage_0_8;
+import org.apache.qpid.client.state.AMQStateManager;
+import org.apache.qpid.client.state.AMQState;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+import org.apache.qpid.transport.Sender;
+import org.apache.qpid.client.handler.ClientMethodDispatcherImpl;
+
+/**
+ * Wrapper for protocol session that provides type-safe access to session attributes. <p/> The underlying protocol
+ * session is still available but clients should not use it to obtain session attributes.
+ */
+public class AMQProtocolSession implements AMQVersionAwareProtocolSession
+{
+ protected static final int LAST_WRITE_FUTURE_JOIN_TIMEOUT = 1000 * 60 * 2;
+
+ protected static final Logger _logger = LoggerFactory.getLogger(AMQProtocolSession.class);
+
+ public static final String PROTOCOL_INITIATION_RECEIVED = "ProtocolInitiatiionReceived";
+
+ //Usable channels are numbered 1 to <ChannelMax>
+ public static final int MAX_CHANNEL_MAX = 0xFFFF;
+ public static final int MIN_USABLE_CHANNEL_NUM = 1;
+
+ protected static final String CONNECTION_TUNE_PARAMETERS = "ConnectionTuneParameters";
+
+ protected static final String AMQ_CONNECTION = "AMQConnection";
+
+ protected static final String SASL_CLIENT = "SASLClient";
+
+ /**
+ * The handler from which this session was created and which is used to handle protocol events. We send failover
+ * events to the handler.
+ */
+ protected final AMQProtocolHandler _protocolHandler;
+
+ /** Maps from the channel id to the AMQSession that it represents. */
+ protected ConcurrentMap<Integer, AMQSession> _channelId2SessionMap = new ConcurrentHashMap<Integer, AMQSession>();
+
+ protected ConcurrentMap _closingChannels = new ConcurrentHashMap();
+
+ /**
+ * Maps from a channel id to an unprocessed message. This is used to tie together the JmsDeliverBody (which arrives
+ * first) with the subsequent content header and content bodies.
+ */
+ private final ConcurrentMap<Integer, UnprocessedMessage> _channelId2UnprocessedMsgMap = new ConcurrentHashMap<Integer, UnprocessedMessage>();
+ private final UnprocessedMessage[] _channelId2UnprocessedMsgArray = new UnprocessedMessage[16];
+
+ /** Counter to ensure unique queue names */
+ protected int _queueId = 1;
+ protected final Object _queueIdLock = new Object();
+
+ private ProtocolVersion _protocolVersion;
+// private VersionSpecificRegistry _registry =
+// MainRegistry.getVersionSpecificRegistry(ProtocolVersion.getLatestSupportedVersion());
+
+ private MethodRegistry _methodRegistry =
+ MethodRegistry.getMethodRegistry(ProtocolVersion.getLatestSupportedVersion());
+
+ private MethodDispatcher _methodDispatcher;
+
+ protected final AMQConnection _connection;
+
+ private ConnectionTuneParameters _connectionTuneParameters;
+
+ private SaslClient _saslClient;
+
+ private static final int FAST_CHANNEL_ACCESS_MASK = 0xFFFFFFF0;
+
+ public AMQProtocolSession(AMQProtocolHandler protocolHandler, AMQConnection connection)
+ {
+ _protocolHandler = protocolHandler;
+ _protocolVersion = connection.getProtocolVersion();
+ _logger.info("Using ProtocolVersion for Session:" + _protocolVersion);
+ _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(ProtocolVersion.getLatestSupportedVersion(),
+ this);
+ _connection = connection;
+ }
+
+ public void init()
+ {
+ // start the process of setting up the connection. This is the first place that
+ // data is written to the server.
+ _protocolHandler.writeFrame(new ProtocolInitiation(_connection.getProtocolVersion()));
+ }
+
+ public String getClientID()
+ {
+ try
+ {
+ return getAMQConnection().getClientID();
+ }
+ catch (JMSException e)
+ {
+ // we never throw a JMSException here
+ return null;
+ }
+ }
+
+ public void setClientID(String clientID) throws JMSException
+ {
+ getAMQConnection().setClientID(clientID);
+ }
+
+ public AMQStateManager getStateManager()
+ {
+ return _protocolHandler.getStateManager();
+ }
+
+ public String getVirtualHost()
+ {
+ return getAMQConnection().getVirtualHost();
+ }
+
+ public SaslClient getSaslClient()
+ {
+ return _saslClient;
+ }
+
+ /**
+ * Store the SASL client currently being used for the authentication handshake
+ *
+ * @param client if non-null, stores this in the session. if null clears any existing client being stored
+ */
+ public void setSaslClient(SaslClient client)
+ {
+ _saslClient = client;
+ }
+
+ public ConnectionTuneParameters getConnectionTuneParameters()
+ {
+ return _connectionTuneParameters;
+ }
+
+ public void setConnectionTuneParameters(ConnectionTuneParameters params)
+ {
+ _connectionTuneParameters = params;
+ AMQConnection con = getAMQConnection();
+
+ con.setMaximumChannelCount(params.getChannelMax());
+ con.setMaximumFrameSize(params.getFrameMax());
+ _protocolHandler.initHeartbeats((int) params.getHeartbeat());
+ }
+
+ /**
+ * Callback invoked from the BasicDeliverMethodHandler when a message has been received. This is invoked on the MINA
+ * dispatcher thread.
+ *
+ * @param message
+ *
+ * @throws AMQException if this was not expected
+ */
+ public void unprocessedMessageReceived(final int channelId, UnprocessedMessage message) throws AMQException
+ {
+ if ((channelId & FAST_CHANNEL_ACCESS_MASK) == 0)
+ {
+ _channelId2UnprocessedMsgArray[channelId] = message;
+ }
+ else
+ {
+ _channelId2UnprocessedMsgMap.put(channelId, message);
+ }
+ }
+
+ public void contentHeaderReceived(int channelId, ContentHeaderBody contentHeader) throws AMQException
+ {
+ final UnprocessedMessage_0_8 msg = (UnprocessedMessage_0_8) ((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 on session:" + this, null);
+ }
+
+ if (msg.getContentHeader() != null)
+ {
+ throw new AMQException(null, "Error: received duplicate content header or did not receive correct number of content body frames on session:" + this, null);
+ }
+
+ msg.setContentHeader(contentHeader);
+ if (contentHeader.bodySize == 0)
+ {
+ deliverMessageToAMQSession(channelId, msg);
+ }
+ }
+
+ public void contentBodyReceived(final int channelId, ContentBody contentBody) throws AMQException
+ {
+ 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);
+ }
+
+ if (msg.getContentHeader() == null)
+ {
+ 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);
+ }
+
+ msg.receiveBody(contentBody);
+
+ if (msg.isAllBodyDataReceived())
+ {
+ deliverMessageToAMQSession(channelId, msg);
+ }
+ }
+
+ public void heartbeatBodyReceived(int channelId, HeartbeatBody body) throws AMQException
+ {
+
+ }
+
+ /**
+ * Deliver a message to the appropriate session, removing the unprocessed message from our map
+ *
+ * @param channelId the channel id the message should be delivered to
+ * @param msg the message
+ */
+ private void deliverMessageToAMQSession(int channelId, UnprocessedMessage msg)
+ {
+ AMQSession session = getSession(channelId);
+ session.messageReceived(msg);
+ if ((channelId & FAST_CHANNEL_ACCESS_MASK) == 0)
+ {
+ _channelId2UnprocessedMsgArray[channelId] = null;
+ }
+ else
+ {
+ _channelId2UnprocessedMsgMap.remove(channelId);
+ }
+ }
+
+ protected AMQSession getSession(int channelId)
+ {
+ return _connection.getSession(channelId);
+ }
+
+ /**
+ * Convenience method that writes a frame to the protocol session. Equivalent to calling
+ * getProtocolSession().write().
+ *
+ * @param frame the frame to write
+ */
+ public void writeFrame(AMQDataBlock frame)
+ {
+ _protocolHandler.writeFrame(frame);
+ }
+
+ public void writeFrame(AMQDataBlock frame, boolean wait)
+ {
+ _protocolHandler.writeFrame(frame, wait);
+ }
+
+ /**
+ * Starts the process of closing a session
+ *
+ * @param session the AMQSession being closed
+ */
+ public void closeSession(AMQSession session)
+ {
+ _logger.debug("closeSession called on protocol session for session " + session.getChannelId());
+ final int channelId = session.getChannelId();
+ if (channelId <= 0)
+ {
+ throw new IllegalArgumentException("Attempt to close a channel with id < 0");
+ }
+ // we need to know when a channel is closing so that we can respond
+ // with a channel.close frame when we receive any other type of frame
+ // on that channel
+ _closingChannels.putIfAbsent(channelId, session);
+ }
+
+ /**
+ * Called from the ChannelClose handler when a channel close frame is received. This method decides whether this is
+ * a response or an initiation. The latter case causes the AMQSession to be closed and an exception to be thrown if
+ * appropriate.
+ *
+ * @param channelId the id of the channel (session)
+ *
+ * @return true if the client must respond to the server, i.e. if the server initiated the channel close, false if
+ * the channel close is just the server responding to the client's earlier request to close the channel.
+ */
+ public boolean channelClosed(int channelId, AMQConstant code, String text) throws AMQException
+ {
+
+ // if this is not a response to an earlier request to close the channel
+ if (_closingChannels.remove(channelId) == null)
+ {
+ final AMQSession session = getSession(channelId);
+ try
+ {
+ session.closed(new AMQException(code, text, null));
+ }
+ catch (JMSException e)
+ {
+ throw new AMQException(null, "JMSException received while closing session", e);
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public AMQConnection getAMQConnection()
+ {
+ return _connection;
+ }
+
+ public void closeProtocolSession() throws AMQException
+ {
+ _protocolHandler.closeConnection(0);
+ }
+
+ public void failover(String host, int port)
+ {
+ _protocolHandler.failover(host, port);
+ }
+
+ protected AMQShortString generateQueueName()
+ {
+ int id;
+ synchronized (_queueIdLock)
+ {
+ id = _queueId++;
+ }
+ // convert '.', '/', ':' and ';' to single '_', for spec compliance and readability
+ String localAddress = _protocolHandler.getLocalAddress().toString().replaceAll("[./:;]", "_");
+ String queueName = "tmp_" + localAddress + "_" + id;
+ return new AMQShortString(queueName.replaceAll("_+", "_"));
+ }
+
+ public void confirmConsumerCancelled(int channelId, AMQShortString consumerTag)
+ {
+ final AMQSession session = getSession(channelId);
+
+ session.confirmConsumerCancelled(consumerTag.toIntValue());
+ }
+
+ public void setProtocolVersion(final ProtocolVersion pv)
+ {
+ _logger.info("Setting ProtocolVersion to :" + pv);
+ _protocolVersion = pv;
+ _methodRegistry = MethodRegistry.getMethodRegistry(pv);
+ _methodDispatcher = ClientMethodDispatcherImpl.newMethodDispatcher(pv, this);
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return _protocolVersion.getMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return _protocolVersion.getMajorVersion();
+ }
+
+ public ProtocolVersion getProtocolVersion()
+ {
+ return _protocolVersion;
+ }
+
+ 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);
+ }
+
+ public void notifyError(Exception error)
+ {
+ _protocolHandler.propagateExceptionToAllWaiters(error);
+ }
+
+ public void setSender(Sender<java.nio.ByteBuffer> sender)
+ {
+ // No-op, interface munging
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return "AMQProtocolSession[" + _connection + ']';
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/BlockingMethodFrameListener.java
new file mode 100644
index 0000000000..2bc609ebf2
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/BlockingMethodFrameListener.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.client.protocol;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQTimeoutException;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.util.BlockingWaiter;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQMethodListener;
+
+/**
+ * BlockingMethodFrameListener is a 'rendezvous' which acts as a {@link AMQMethodListener} that delegates handling of
+ * incoming methods to a method listener implemented as a sub-class of this and hands off the processed method or
+ * error to a consumer. The producer of the event does not have to wait for the consumer to take the event, so this
+ * differs from a 'rendezvous' in that sense.
+ *
+ * <p/>BlockingMethodFrameListeners are used to coordinate waiting for replies to method calls that expect a response.
+ * They are always used in a 'one-shot' manner, that is, to recieve just one response. Usually the caller has to register
+ * them as method listeners with an event dispatcher and remember to de-register them (in a finally block) once they
+ * have been completed.
+ *
+ * <p/>The {@link #processMethod} must return <tt>true</tt> on any incoming method that it handles. This indicates to
+ * this listeners that the method it is waiting for has arrived. Incoming methods are also filtered by channel prior to
+ * being passed to the {@link #processMethod} method, so responses are only received for a particular channel. The
+ * channel id must be passed to the constructor.
+ *
+ * <p/>Errors from the producer are rethrown to the consumer.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Accept notification of AMQP method events. <td> {@link AMQMethodEvent}
+ * <tr><td> Delegate handling of the method to another method listener. <td> {@link AMQMethodBody}
+ * <tr><td> Block until a method is handled by the delegated to handler.
+ * <tr><td> Propagate the most recent exception to the consumer.
+ * </table>
+ *
+ * @todo Might be neater if this method listener simply wrapped another that provided the method handling using a
+ * methodRecevied method. The processMethod takes an additional channelId, however none of the implementations
+ * seem to use it. So wrapping the listeners is possible.
+ * @todo If the retrotranslator can handle it, could use a SynchronousQueue to implement this rendezvous. Need to
+ * check that SynchronousQueue has a non-blocking put method available.
+ */
+public abstract class BlockingMethodFrameListener extends BlockingWaiter<AMQMethodEvent> implements AMQMethodListener
+{
+
+ /** Holds the channel id for the channel upon which this listener is waiting for a response. */
+ protected int _channelId;
+
+ /**
+ * Creates a new method listener, that filters incoming method to just those that match the specified channel id.
+ *
+ * @param channelId The channel id to filter incoming methods with.
+ */
+ public BlockingMethodFrameListener(int channelId)
+ {
+ _channelId = channelId;
+ }
+
+ /**
+ * Delegates any additional handling of the incoming methods to another handler.
+ *
+ * @param channelId The channel id of the incoming method.
+ * @param frame The method body.
+ *
+ * @return <tt>true</tt> if the method was handled, <tt>false</tt> otherwise.
+ */
+ public abstract boolean processMethod(int channelId, AMQMethodBody frame);
+
+ public boolean process(AMQMethodEvent evt)
+ {
+ AMQMethodBody method = evt.getMethod();
+
+ return (evt.getChannelId() == _channelId) && processMethod(evt.getChannelId(), method);
+ }
+
+ /**
+ * Informs this listener that an AMQP method has been received.
+ *
+ * @param evt The AMQP method.
+ *
+ * @return <tt>true</tt> if this listener has handled the method, <tt>false</tt> otherwise.
+ */
+ public boolean methodReceived(AMQMethodEvent evt)
+ {
+ return received(evt);
+ }
+
+ /**
+ * Blocks until a method is received that is handled by the delegated to method listener, or the specified timeout
+ * has passed.
+ *
+ * @param timeout The timeout in milliseconds.
+ *
+ * @return The AMQP method that was received.
+ *
+ * @throws AMQException
+ * @throws FailoverException
+ */
+ public AMQMethodEvent blockForFrame(long timeout) throws AMQException, FailoverException
+ {
+ try
+ {
+ return (AMQMethodEvent) block(timeout);
+ }
+ finally
+ {
+ //Prevent any more errors being notified to this waiter.
+ close();
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatConfig.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatConfig.java
new file mode 100644
index 0000000000..35ea44a331
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatConfig.java
@@ -0,0 +1,61 @@
+/*
+ *
+ * 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.protocol;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class HeartbeatConfig
+{
+ private static final Logger _logger = LoggerFactory.getLogger(HeartbeatConfig.class);
+ static final HeartbeatConfig CONFIG = new HeartbeatConfig();
+
+ /**
+ * The factor used to get the timeout from the delay between heartbeats.
+ */
+ private float timeoutFactor = 2;
+
+ HeartbeatConfig()
+ {
+ String property = System.getProperty("amqj.heartbeat.timeoutFactor");
+ if (property != null)
+ {
+ try
+ {
+ timeoutFactor = Float.parseFloat(property);
+ }
+ catch (NumberFormatException e)
+ {
+ _logger.warn("Invalid timeout factor (amqj.heartbeat.timeoutFactor): " + property);
+ }
+ }
+ }
+
+ float getTimeoutFactor()
+ {
+ return timeoutFactor;
+ }
+
+ int getTimeout(int writeDelay)
+ {
+ return (int) (timeoutFactor * writeDelay);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java
new file mode 100644
index 0000000000..d44faeab04
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/HeartbeatDiagnostics.java
@@ -0,0 +1,121 @@
+/*
+ *
+ * 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.protocol;
+
+class HeartbeatDiagnostics
+{
+ private static final Diagnostics _impl = init();
+
+ private static Diagnostics init()
+ {
+ return Boolean.getBoolean("amqj.heartbeat.diagnostics") ? new On() : new Off();
+ }
+
+ static void sent()
+ {
+ _impl.sent();
+ }
+
+ static void timeout()
+ {
+ _impl.timeout();
+ }
+
+ static void received(boolean heartbeat)
+ {
+ _impl.received(heartbeat);
+ }
+
+ static void init(int delay, int timeout)
+ {
+ _impl.init(delay, timeout);
+ }
+
+ private static interface Diagnostics
+ {
+ void sent();
+ void timeout();
+ void received(boolean heartbeat);
+ void init(int delay, int timeout);
+ }
+
+ private static class On implements Diagnostics
+ {
+ private final String[] messages = new String[50];
+ private int i;
+
+ private void save(String msg)
+ {
+ messages[i++] = msg;
+ if(i >= messages.length){
+ i = 0;//i.e. a circular buffer
+ }
+ }
+
+ public void sent()
+ {
+ save(System.currentTimeMillis() + ": sent heartbeat");
+ }
+
+ public void timeout()
+ {
+ for(int i = 0; i < messages.length; i++)
+ {
+ if(messages[i] != null)
+ {
+ System.out.println(messages[i]);
+ }
+ }
+ System.out.println(System.currentTimeMillis() + ": timed out");
+ }
+
+ public void received(boolean heartbeat)
+ {
+ save(System.currentTimeMillis() + ": received " + (heartbeat ? "heartbeat" : "data"));
+ }
+
+ public void init(int delay, int timeout)
+ {
+ System.out.println(System.currentTimeMillis() + ": initialised delay=" + delay + ", timeout=" + timeout);
+ }
+ }
+
+ private static class Off implements Diagnostics
+ {
+ public void sent()
+ {
+
+ }
+ public void timeout()
+ {
+
+ }
+ public void received(boolean heartbeat)
+ {
+
+ }
+
+ public void init(int delay, int timeout)
+ {
+
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java
new file mode 100644
index 0000000000..bbd0a7b144
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/protocol/ProtocolBufferMonitorFilter.java
@@ -0,0 +1,115 @@
+/*
+ *
+ * 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.protocol;
+
+import org.apache.mina.common.IoFilterAdapter;
+import org.apache.mina.common.IoSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A MINA filter that monitors the numbers of messages pending to be sent by MINA. It outputs a message
+ * when a threshold has been exceeded, and has a frequency configuration so that messages are not output
+ * too often.
+ *
+ */
+public class ProtocolBufferMonitorFilter extends IoFilterAdapter
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ProtocolBufferMonitorFilter.class);
+
+ public static final long DEFAULT_FREQUENCY = 5000;
+
+ public static final int DEFAULT_THRESHOLD = 3000;
+
+ private int _bufferedMessages = 0;
+
+ private int _threshold;
+
+ private long _lastMessageOutputTime;
+
+ private long _outputFrequencyInMillis;
+
+ public ProtocolBufferMonitorFilter()
+ {
+ _threshold = DEFAULT_THRESHOLD;
+ _outputFrequencyInMillis = DEFAULT_FREQUENCY;
+ }
+
+ public ProtocolBufferMonitorFilter(int threshold, long frequency)
+ {
+ _threshold = threshold;
+ _outputFrequencyInMillis = frequency;
+ }
+
+ public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception
+ {
+ _bufferedMessages++;
+ if (_bufferedMessages > _threshold)
+ {
+ long now = System.currentTimeMillis();
+ if ((now - _lastMessageOutputTime) > _outputFrequencyInMillis)
+ {
+ _logger.warn("Protocol message buffer exceeded threshold of " + _threshold + ". Current backlog: "
+ + _bufferedMessages);
+ _lastMessageOutputTime = now;
+ }
+ }
+
+ nextFilter.messageReceived(session, message);
+ }
+
+ public void messageSent(NextFilter nextFilter, IoSession session, Object message) throws Exception
+ {
+ _bufferedMessages--;
+ nextFilter.messageSent(session, message);
+ }
+
+ public int getBufferedMessages()
+ {
+ return _bufferedMessages;
+ }
+
+ public int getThreshold()
+ {
+ return _threshold;
+ }
+
+ public void setThreshold(int threshold)
+ {
+ _threshold = threshold;
+ }
+
+ public long getOutputFrequencyInMillis()
+ {
+ return _outputFrequencyInMillis;
+ }
+
+ public void setOutputFrequencyInMillis(long outputFrequencyInMillis)
+ {
+ _outputFrequencyInMillis = outputFrequencyInMillis;
+ }
+
+ public long getLastMessageOutputTime()
+ {
+ return _lastMessageOutputTime;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/AMQCallbackHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/AMQCallbackHandler.java
new file mode 100644
index 0000000000..67dd1a58b6
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/AMQCallbackHandler.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * 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.security;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.apache.qpid.jms.ConnectionURL;
+
+public interface AMQCallbackHandler extends CallbackHandler
+{
+ void initialise(ConnectionURL connectionURL);
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java
new file mode 100644
index 0000000000..140cbdeb75
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.java
@@ -0,0 +1,231 @@
+/*
+ *
+ * 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.security;
+
+import org.apache.qpid.util.FileUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * CallbackHandlerRegistry is a registry for call back handlers for user authentication and interaction during user
+ * authentication. It is capable of reading its configuration from a properties file containing call back handler
+ * implementing class names for different SASL mechanism names. Instantiating this registry also has the effect of
+ * configuring and registering the SASL client factory implementations using {@link DynamicSaslRegistrar}.
+ *
+ * <p/>The callback configuration should be specified in a properties file, refered to by the System property
+ * "amp.callbackhandler.properties". The format of the properties file is:
+ *
+ * <p/><pre>
+ * CallbackHanlder.mechanism=fully.qualified.class.name
+ * </pre>
+ *
+ * <p/>Where mechanism is an IANA-registered mechanism name and the fully qualified class name refers to a
+ * class that implements org.apache.qpid.client.security.AMQCallbackHanlder and provides a call back handler for the
+ * specified mechanism.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Parse callback properties.
+ * <tr><td> Provide mapping from SASL mechanisms to callback implementations.
+ * </table>
+ */
+public class CallbackHandlerRegistry
+{
+ private static final Logger _logger = LoggerFactory.getLogger(CallbackHandlerRegistry.class);
+
+ /** The name of the system property that holds the name of the callback handler properties file. */
+ private static final String FILE_PROPERTY = "amq.callbackhandler.properties";
+
+ /** The default name of the callback handler properties resource. */
+ public static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/client/security/CallbackHandlerRegistry.properties";
+
+ /** A static reference to the singleton instance of this registry. */
+ private static CallbackHandlerRegistry _instance = new CallbackHandlerRegistry();
+
+ /** Holds a map from SASL mechanism names to call back handlers. */
+ private Map<String, Class> _mechanismToHandlerClassMap = new HashMap<String, Class>();
+
+ /** Holds a space delimited list of mechanisms that callback handlers exist for. */
+ private String _mechanisms;
+
+ /**
+ * Gets the singleton instance of this registry.
+ *
+ * @return The singleton instance of this registry.
+ */
+ public static CallbackHandlerRegistry getInstance()
+ {
+ return _instance;
+ }
+
+ /**
+ * Gets the callback handler class for a given SASL mechanism name.
+ *
+ * @param mechanism The SASL mechanism name.
+ *
+ * @return The callback handler class for the mechanism, or null if none is configured for that mechanism.
+ */
+ public Class getCallbackHandlerClass(String mechanism)
+ {
+ return (Class) _mechanismToHandlerClassMap.get(mechanism);
+ }
+
+ /**
+ * Gets a space delimited list of supported SASL mechanisms.
+ *
+ * @return A space delimited list of supported SASL mechanisms.
+ */
+ public String getMechanisms()
+ {
+ return _mechanisms;
+ }
+
+ /**
+ * Creates the call back handler registry from its configuration resource or file. This also has the side effect
+ * of configuring and registering the SASL client factory implementations using {@link DynamicSaslRegistrar}.
+ */
+ private CallbackHandlerRegistry()
+ {
+ // Register any configured SASL client factories.
+ DynamicSaslRegistrar.registerSaslProviders();
+
+ String filename = System.getProperty(FILE_PROPERTY);
+ InputStream is =
+ FileUtils.openFileOrDefaultResource(filename, DEFAULT_RESOURCE_NAME,
+ CallbackHandlerRegistry.class.getClassLoader());
+
+ try
+ {
+ Properties props = new Properties();
+ props.load(is);
+ parseProperties(props);
+ _logger.info("Callback handlers available for SASL mechanisms: " + _mechanisms);
+ }
+ catch (IOException e)
+ {
+ _logger.error("Error reading properties: " + e, e);
+ }
+ finally
+ {
+ if (is != null)
+ {
+ try
+ {
+ is.close();
+
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to close properties stream: " + e, e);
+ }
+ }
+ }
+ }
+
+ /*private InputStream openPropertiesInputStream(String filename)
+ {
+ boolean useDefault = true;
+ InputStream is = null;
+ if (filename != null)
+ {
+ try
+ {
+ is = new BufferedInputStream(new FileInputStream(new File(filename)));
+ useDefault = false;
+ }
+ catch (FileNotFoundException e)
+ {
+ _logger.error("Unable to read from file " + filename + ": " + e, e);
+ }
+ }
+
+ if (useDefault)
+ {
+ is = CallbackHandlerRegistry.class.getResourceAsStream(DEFAULT_RESOURCE_NAME);
+ }
+
+ return is;
+ }*/
+
+ /**
+ * Scans the specified properties as a mapping from IANA registered SASL mechanism to call back handler
+ * implementations, that provide the necessary call back handling for obtaining user log in credentials
+ * during authentication for the specified mechanism, and builds a map from mechanism names to handler
+ * classes.
+ *
+ * @param props
+ */
+ private void parseProperties(Properties props)
+ {
+ Enumeration e = props.propertyNames();
+ while (e.hasMoreElements())
+ {
+ String propertyName = (String) e.nextElement();
+ int period = propertyName.indexOf(".");
+ if (period < 0)
+ {
+ _logger.warn("Unable to parse property " + propertyName + " when configuring SASL providers");
+
+ continue;
+ }
+
+ String mechanism = propertyName.substring(period + 1);
+ String className = props.getProperty(propertyName);
+ Class clazz = null;
+ try
+ {
+ clazz = Class.forName(className);
+ if (!AMQCallbackHandler.class.isAssignableFrom(clazz))
+ {
+ _logger.warn("SASL provider " + clazz + " does not implement " + AMQCallbackHandler.class
+ + ". Skipping");
+
+ continue;
+ }
+
+ _mechanismToHandlerClassMap.put(mechanism, clazz);
+ if (_mechanisms == null)
+ {
+ _mechanisms = mechanism;
+ }
+ else
+ {
+ // one time cost
+ _mechanisms = _mechanisms + " " + mechanism;
+ }
+ }
+ catch (ClassNotFoundException ex)
+ {
+ _logger.warn("Unable to load class " + className + ". Skipping that SASL provider");
+
+ continue;
+ }
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties b/qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
new file mode 100644
index 0000000000..1fcfde3579
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/CallbackHandlerRegistry.properties
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+CallbackHandler.CRAM-MD5-HASHED=org.apache.qpid.client.security.UsernameHashedPasswordCallbackHandler
+CallbackHandler.CRAM-MD5=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
+CallbackHandler.AMQPLAIN=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
+CallbackHandler.PLAIN=org.apache.qpid.client.security.UsernamePasswordCallbackHandler
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java
new file mode 100644
index 0000000000..2b4261b4b7
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.java
@@ -0,0 +1,210 @@
+/*
+ *
+ * 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.security;
+
+import org.apache.qpid.util.FileUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.sasl.SaslClientFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Security;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+
+/**
+ * DynamicSaslRegistrar provides a collection of helper methods for reading a configuration file that contains a mapping
+ * from SASL mechanism names to implementing client factory class names and registering a security provider with the
+ * Java runtime system, that uses the configured client factory implementations.
+ *
+ * <p/>The sasl configuration should be specified in a properties file, refered to by the System property
+ * "amp.dynamicsaslregistrar.properties". The format of the properties file is:
+ *
+ * <p/><pre>
+ * mechanism=fully.qualified.class.name
+ * </pre>
+ *
+ * <p/>Where mechanism is an IANA-registered mechanism name and the fully qualified class name refers to a class that
+ * implements javax.security.sasl.SaslClientFactory and provides the specified mechanism.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collaborations <tr><td> Parse SASL
+ * mechanism properties. <tr><td> Create and register security provider for SASL mechanisms. </table>
+ */
+public class DynamicSaslRegistrar
+{
+ private static final Logger _logger = LoggerFactory.getLogger(DynamicSaslRegistrar.class);
+
+ /** The name of the system property that holds the name of the SASL configuration properties. */
+ private static final String FILE_PROPERTY = "amq.dynamicsaslregistrar.properties";
+
+ /** The default name of the SASL properties file resource. */
+ public static final String DEFAULT_RESOURCE_NAME = "org/apache/qpid/client/security/DynamicSaslRegistrar.properties";
+
+ /** Reads the properties file, and creates a dynamic security provider to register the SASL implementations with. */
+ public static void registerSaslProviders()
+ {
+ _logger.debug("public static void registerSaslProviders(): called");
+
+ // Open the SASL properties file, using the default name is one is not specified.
+ String filename = System.getProperty(FILE_PROPERTY);
+ InputStream is =
+ FileUtils.openFileOrDefaultResource(filename, DEFAULT_RESOURCE_NAME,
+ DynamicSaslRegistrar.class.getClassLoader());
+
+ try
+ {
+ Properties props = new Properties();
+ props.load(is);
+
+ _logger.debug("props = " + props);
+
+ Map<String, Class<? extends SaslClientFactory>> factories = parseProperties(props);
+
+ if (factories.size() > 0)
+ {
+ // Ensure we are used before the defaults
+ if (Security.insertProviderAt(new JCAProvider(factories), 1) == -1)
+ {
+ _logger.error("Unable to load custom SASL providers.");
+ }
+ else
+ {
+ _logger.info("Additional SASL providers successfully registered.");
+ }
+ }
+ else
+ {
+ _logger.warn("No additional SASL providers registered.");
+ }
+ }
+ catch (IOException e)
+ {
+ _logger.error("Error reading properties: " + e, e);
+ }
+ finally
+ {
+ if (is != null)
+ {
+ try
+ {
+ is.close();
+
+ }
+ catch (IOException e)
+ {
+ _logger.error("Unable to close properties stream: " + e, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Either attempts to open the specified filename as an input stream, or uses the default SASL configuration
+ * resource.
+ *
+ * @param filename The name of the file to get the SASL properties from, null to use the default.
+ *
+ * @return An input stream to read the dynamic SASL configuration from, or null if one could not be opened.
+ */
+ /*private static InputStream openPropertiesInputStream(String filename)
+ {
+ InputStream is = null;
+
+ // Flag to indicate whether the default resource should be used. By default this is true, so that the default
+ // is used when opening the file fails.
+ boolean useDefault = true;
+
+ // Try to open the file if one was specified.
+ if (filename != null)
+ {
+ try
+ {
+ is = new BufferedInputStream(new FileInputStream(new File(filename)));
+
+ // Clear the default flag because the file was succesfully opened.
+ useDefault = false;
+ }
+ catch (FileNotFoundException e)
+ {
+ _logger.error("Unable to read from file " + filename + ": " + e, e);
+ }
+ }
+
+ // Load the default resource if a file was not specified, or if opening the file failed.
+ if (useDefault)
+ {
+ is = CallbackHandlerRegistry.class.getResourceAsStream(DEFAULT_RESOURCE_NAME);
+ }
+
+ return is;
+ }*/
+
+ /**
+ * Parses the specified properties as a mapping from IANA registered SASL mechanism names to implementing client
+ * factories. If the client factories cannot be instantiated or do not implement SaslClientFactory then the
+ * properties refering to them are ignored.
+ *
+ * @param props The properties to scan for Sasl client factory implementations.
+ *
+ * @return A map from SASL mechanism names to implementing client factory classes.
+ *
+ * @todo Why tree map here? Do really want mechanisms in alphabetical order? Seems more likely that the declared
+ * order of the mechanisms is intended to be preserved, so that they are registered in the declared order of
+ * preference. Consider LinkedHashMap instead.
+ */
+ private static Map<String, Class<? extends SaslClientFactory>> parseProperties(Properties props)
+ {
+ Enumeration e = props.propertyNames();
+
+ TreeMap<String, Class<? extends SaslClientFactory>> factoriesToRegister =
+ new TreeMap<String, Class<? extends SaslClientFactory>>();
+
+ while (e.hasMoreElements())
+ {
+ String mechanism = (String) e.nextElement();
+ String className = props.getProperty(mechanism);
+ try
+ {
+ Class<?> clazz = Class.forName(className);
+ if (!(SaslClientFactory.class.isAssignableFrom(clazz)))
+ {
+ _logger.error("Class " + clazz + " does not implement " + SaslClientFactory.class + " - skipping");
+
+ continue;
+ }
+
+ _logger.debug("Registering class "+ clazz.getName() +" for mechanism "+mechanism);
+ factoriesToRegister.put(mechanism, (Class<? extends SaslClientFactory>) clazz);
+ }
+ catch (Exception ex)
+ {
+ _logger.error("Error instantiating SaslClientFactory calss " + className + " - skipping");
+ }
+ }
+
+ return factoriesToRegister;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties b/qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
new file mode 100644
index 0000000000..b903208927
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/DynamicSaslRegistrar.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+AMQPLAIN=org.apache.qpid.client.security.amqplain.AmqPlainSaslClientFactory
+CRAM-MD5-HASHED=org.apache.qpid.client.security.crammd5hashed.CRAMMD5HashedSaslClientFactory
+ANONYMOUS=org.apache.qpid.client.security.anonymous.AnonymousSaslClientFactory
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java
new file mode 100644
index 0000000000..828d26ed0d
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/JCAProvider.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * 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.security;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.sasl.SaslClientFactory;
+
+import java.security.Provider;
+import java.security.Security;
+import java.util.Map;
+
+/**
+ * JCAProvider is a security provider for SASL client factories that is configured from a map of SASL mechanism names
+ * to client factories implementation class names. It is intended that the map of client factories can be read from a
+ * configuration file or other application configuration mechanism.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Register SASL mechanism implementations.
+ * </table>
+ */
+public class JCAProvider extends Provider
+{
+ private static final Logger log = LoggerFactory.getLogger(JCAProvider.class);
+
+ /**
+ * Creates the security provider with a map from SASL mechanisms to implementing factories.
+ *
+ * @param providerMap The map from SASL mechanims to implementing factory classes.
+ */
+ public JCAProvider(Map<String, Class<? extends SaslClientFactory>> providerMap)
+ {
+ super("AMQSASLProvider-Client", 1.0, "A JCA provider that registers all "
+ + "AMQ SASL providers that want to be registered");
+ register(providerMap);
+// Security.addProvider(this);
+ }
+
+ /**
+ * Registers client factory classes for a map of mechanism names to client factory classes.
+ *
+ * @param providerMap The map from SASL mechanims to implementing factory classes.
+ */
+ private void register(Map<String, Class<? extends SaslClientFactory>> providerMap)
+ {
+ for (Map.Entry<String, Class<? extends SaslClientFactory>> me : providerMap.entrySet())
+ {
+ put( "SaslClientFactory."+me.getKey(), me.getValue().getName());
+ log.debug("Registered SASL Client factory for " + me.getKey() + " as " + me.getValue().getName());
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernameHashedPasswordCallbackHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernameHashedPasswordCallbackHandler.java
new file mode 100644
index 0000000000..6ec83f0a23
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernameHashedPasswordCallbackHandler.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * 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.security;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.apache.qpid.jms.ConnectionURL;
+
+public class UsernameHashedPasswordCallbackHandler implements AMQCallbackHandler
+{
+ private ConnectionURL _connectionURL;
+
+ /**
+ * @see org.apache.qpid.client.security.AMQCallbackHandler#initialise(org.apache.qpid.jms.ConnectionURL)
+ */
+ @Override
+ public void initialise(ConnectionURL connectionURL)
+ {
+ _connectionURL = connectionURL;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+ {
+ for (int i = 0; i < callbacks.length; i++)
+ {
+ Callback cb = callbacks[i];
+ if (cb instanceof NameCallback)
+ {
+ ((NameCallback) cb).setName(_connectionURL.getUsername());
+ }
+ else if (cb instanceof PasswordCallback)
+ {
+ try
+ {
+ ((PasswordCallback) cb).setPassword(getHash(_connectionURL.getPassword()));
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ UnsupportedCallbackException uce = new UnsupportedCallbackException(cb);
+ uce.initCause(e);
+ throw uce;
+ }
+ }
+ else
+ {
+ throw new UnsupportedCallbackException(cb);
+ }
+ }
+ }
+
+ private char[] getHash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException
+ {
+
+ byte[] data = text.getBytes("utf-8");
+
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ for (byte b : data)
+ {
+ md.update(b);
+ }
+
+ byte[] digest = md.digest();
+
+ char[] hash = new char[digest.length];
+
+ int index = 0;
+ for (byte b : digest)
+ {
+ hash[index++] = (char) b;
+ }
+
+ return hash;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.java
new file mode 100644
index 0000000000..ad088722c8
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/UsernamePasswordCallbackHandler.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.client.security;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import org.apache.qpid.jms.ConnectionURL;
+
+public class UsernamePasswordCallbackHandler implements AMQCallbackHandler
+{
+ private ConnectionURL _connectionURL;
+
+ /**
+ * @see org.apache.qpid.client.security.AMQCallbackHandler#initialise(org.apache.qpid.jms.ConnectionURL)
+ */
+ @Override
+ public void initialise(final ConnectionURL connectionURL)
+ {
+ _connectionURL = connectionURL;
+ }
+
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
+ {
+ for (int i = 0; i < callbacks.length; i++)
+ {
+ Callback cb = callbacks[i];
+ if (cb instanceof NameCallback)
+ {
+ ((NameCallback)cb).setName(_connectionURL.getUsername());
+ }
+ else if (cb instanceof PasswordCallback)
+ {
+ ((PasswordCallback)cb).setPassword(_connectionURL.getPassword().toCharArray());
+ }
+ else
+ {
+ throw new UnsupportedCallbackException(cb);
+ }
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java
new file mode 100644
index 0000000000..f8a25c630c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClient.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * 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.security.amqplain;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+
+/**
+ * Implements the "AMQPlain" authentication protocol that uses FieldTables to send username and pwd.
+ *
+ */
+public class AmqPlainSaslClient implements SaslClient
+{
+ /**
+ * The name of this mechanism
+ */
+ public static final String MECHANISM = "AMQPLAIN";
+
+ private CallbackHandler _cbh;
+
+ public AmqPlainSaslClient(CallbackHandler cbh)
+ {
+ _cbh = cbh;
+ }
+
+ public String getMechanismName()
+ {
+ return "AMQPLAIN";
+ }
+
+ public boolean hasInitialResponse()
+ {
+ return true;
+ }
+
+ public byte[] evaluateChallenge(byte[] challenge) throws SaslException
+ {
+ // we do not care about the prompt or the default name
+ NameCallback nameCallback = new NameCallback("prompt", "defaultName");
+ PasswordCallback pwdCallback = new PasswordCallback("prompt", false);
+ Callback[] callbacks = new Callback[]{nameCallback, pwdCallback};
+ try
+ {
+ _cbh.handle(callbacks);
+ }
+ catch (Exception e)
+ {
+ throw new SaslException("Error handling SASL callbacks: " + e, e);
+ }
+ FieldTable table = FieldTableFactory.newFieldTable();
+ table.setString("LOGIN", nameCallback.getName());
+ table.setString("PASSWORD", new String(pwdCallback.getPassword()));
+ return table.getDataAsBytes();
+ }
+
+ public boolean isComplete()
+ {
+ return true;
+ }
+
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Not supported");
+ }
+
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new SaslException("Not supported");
+ }
+
+ public Object getNegotiatedProperty(String propName)
+ {
+ return null;
+ }
+
+ public void dispose() throws SaslException
+ {
+ _cbh = null;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java
new file mode 100644
index 0000000000..30cc786890
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/amqplain/AmqPlainSaslClientFactory.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * 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.security.amqplain;
+
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslClientFactory;
+import javax.security.sasl.SaslException;
+
+public class AmqPlainSaslClientFactory implements SaslClientFactory
+{
+ public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName, Map props, CallbackHandler cbh) throws SaslException
+ {
+ for (int i = 0; i < mechanisms.length; i++)
+ {
+ if (mechanisms[i].equals(AmqPlainSaslClient.MECHANISM))
+ {
+ if (cbh == null)
+ {
+ throw new SaslException("CallbackHandler must not be null");
+ }
+ return new AmqPlainSaslClient(cbh);
+ }
+ }
+ return null;
+ }
+
+ public String[] getMechanismNames(Map props)
+ {
+ if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) ||
+ props.containsKey(Sasl.POLICY_NODICTIONARY) ||
+ props.containsKey(Sasl.POLICY_NOACTIVE))
+ {
+ // returned array must be non null according to interface documentation
+ return new String[0];
+ }
+ else
+ {
+ return new String[]{AmqPlainSaslClient.MECHANISM};
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClient.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClient.java
new file mode 100644
index 0000000000..0f56b2ef6c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClient.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * 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.security.anonymous;
+
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+public class AnonymousSaslClient implements SaslClient
+{
+ public String getMechanismName() {
+ return "ANONYMOUS";
+ }
+ public boolean hasInitialResponse() {
+ return true;
+ }
+ public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
+ return new byte[0];
+ }
+ public boolean isComplete() {
+ return true;
+ }
+ public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException
+ {
+ throw new IllegalStateException("No security layer supported");
+ }
+ public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException
+ {
+ throw new IllegalStateException("No security layer supported");
+ }
+ public Object getNegotiatedProperty(String propName) {
+ return null;
+ }
+ public void dispose() throws SaslException {}
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClientFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClientFactory.java
new file mode 100644
index 0000000000..de698f87c6
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/anonymous/AnonymousSaslClientFactory.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * 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.security.anonymous;
+
+import java.util.Arrays;
+import java.util.Map;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslClientFactory;
+import javax.security.sasl.SaslException;
+import javax.security.auth.callback.CallbackHandler;
+
+public class AnonymousSaslClientFactory implements SaslClientFactory
+{
+ public SaslClient createSaslClient(String[] mechanisms, String authId,
+ String protocol, String server,
+ Map props, CallbackHandler cbh) throws SaslException
+ {
+ if (Arrays.asList(mechanisms).contains("ANONYMOUS")) {
+ return new AnonymousSaslClient();
+ } else {
+ return null;
+ }
+ }
+ public String[] getMechanismNames(Map props)
+ {
+ if (props == null || props.isEmpty()) {
+ return new String[]{"ANONYMOUS"};
+ } else {
+ return new String[0];
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/security/crammd5hashed/CRAMMD5HashedSaslClientFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/client/security/crammd5hashed/CRAMMD5HashedSaslClientFactory.java
new file mode 100644
index 0000000000..22bb1ac156
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/security/crammd5hashed/CRAMMD5HashedSaslClientFactory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.security.crammd5hashed;
+
+import org.apache.qpid.client.security.amqplain.AmqPlainSaslClient;
+
+import javax.security.sasl.SaslClientFactory;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+import javax.security.sasl.Sasl;
+import javax.security.auth.callback.CallbackHandler;
+import java.util.Map;
+import java.security.Security;
+
+public class CRAMMD5HashedSaslClientFactory implements SaslClientFactory
+{
+ /** The name of this mechanism */
+ public static final String MECHANISM = "CRAM-MD5-HASHED";
+
+
+ public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException
+ {
+ for (int i = 0; i < mechanisms.length; i++)
+ {
+ if (mechanisms[i].equals(MECHANISM))
+ {
+ if (cbh == null)
+ {
+ throw new SaslException("CallbackHandler must not be null");
+ }
+
+ String[] mechs = {"CRAM-MD5"};
+ return Sasl.createSaslClient(mechs, authorizationId, protocol, serverName, props, cbh);
+ }
+ }
+ return null;
+ }
+
+ public String[] getMechanismNames(Map props)
+ {
+ if (props != null)
+ {
+ if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) ||
+ props.containsKey(Sasl.POLICY_NODICTIONARY) ||
+ props.containsKey(Sasl.POLICY_NOACTIVE))
+ {
+ // returned array must be non null according to interface documentation
+ return new String[0];
+ }
+ }
+
+ return new String[]{MECHANISM};
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQMethodNotImplementedException.java
new file mode 100644
index 0000000000..2c99b9a97b
--- /dev/null
+++ b/qpid/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/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java
new file mode 100644
index 0000000000..d32d10542f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQState.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * 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;
+
+/**
+ * States used in the AMQ protocol. Used by the finite state machine to determine
+ * valid responses.
+ */
+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;
+
+ private AMQState(int id, String name)
+ {
+ _id = id;
+ _name = name;
+ }
+
+ public String toString()
+ {
+ return "AMQState: id = " + _id + " name: " + _name;
+ }
+
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateChangedEvent.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateChangedEvent.java
new file mode 100644
index 0000000000..edef54ccd6
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateChangedEvent.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.client.state;
+
+/**
+ * An event that is fired when the protocol state has changed.
+ *
+ */
+public class AMQStateChangedEvent
+{
+ private final AMQState _oldState;
+
+ private final AMQState _newState;
+
+ public AMQStateChangedEvent(AMQState oldState, AMQState newState)
+ {
+ _oldState = oldState;
+ _newState = newState;
+ }
+
+ public AMQState getOldState()
+ {
+ return _oldState;
+ }
+
+ public AMQState getNewState()
+ {
+ return _newState;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateListener.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateListener.java
new file mode 100644
index 0000000000..110471aad0
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateListener.java
@@ -0,0 +1,26 @@
+/*
+ *
+ * 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;
+
+public interface AMQStateListener
+{
+ void stateChanged(AMQStateChangedEvent evt);
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java
new file mode 100644
index 0000000000..9c7d62670c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/AMQStateManager.java
@@ -0,0 +1,221 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+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.Set;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.io.IOException;
+
+/**
+ * The state manager is responsible for managing the state of the protocol session. <p/>
+ * For each {@link org.apache.qpid.client.protocol.AMQProtocolHandler} there is a separate state manager.
+ *
+ * The AMQStateManager is now attached to the {@link org.apache.qpid.client.protocol.AMQProtocolHandler} and that is the sole point of reference so that
+ * As the {@link AMQProtocolSession} changes due to failover the AMQStateManager need not be copied around.
+ *
+ * The StateManager works by any component can wait for a state change to occur by using the following sequence.
+ *
+ * <li>StateWaiter waiter = stateManager.createWaiter(Set<AMQState> states);
+ * <li> // Perform action that will cause state change
+ * <li>waiter.await();
+ *
+ * The two step process is required as there is an inherit race condition between starting a process that will cause
+ * the state to change and then attempting to wait for that change. The interest in the change must be first set up so
+ * that any asynchrous errors that occur can be delivered to the correct waiters.
+ */
+public class AMQStateManager implements AMQMethodListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AMQStateManager.class);
+
+ private AMQProtocolSession _protocolSession;
+
+ /** The current state */
+ private AMQState _currentState;
+
+ private final Object _stateLock = new Object();
+
+ private static final long MAXIMUM_STATE_WAIT_TIME = Long.parseLong(System.getProperty("amqj.MaximumStateWait", "30000"));
+
+ protected final List<StateWaiter> _waiters = new CopyOnWriteArrayList<StateWaiter>();
+ private Exception _lastException;
+
+ public AMQStateManager()
+ {
+ this(null);
+ }
+
+ public AMQStateManager(AMQProtocolSession protocolSession)
+ {
+ this(AMQState.CONNECTION_NOT_STARTED, protocolSession);
+ }
+
+ protected AMQStateManager(AMQState state, AMQProtocolSession protocolSession)
+ {
+ _protocolSession = protocolSession;
+ _currentState = state;
+ }
+
+ public AMQState getCurrentState()
+ {
+ return _currentState;
+ }
+
+ public void changeState(AMQState newState)
+ {
+ _logger.debug("State changing to " + newState + " from old state " + _currentState);
+
+ synchronized (_stateLock)
+ {
+ _currentState = newState;
+
+ _logger.debug("Notififying State change to " + _waiters.size() + " : " + _waiters);
+
+ for (StateWaiter waiter : _waiters)
+ {
+ waiter.received(newState);
+ }
+ }
+ }
+
+ public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException
+ {
+ B method = evt.getMethod();
+
+ // StateAwareMethodListener handler = findStateTransitionHandler(_currentState, evt.getMethod());
+ method.execute(_protocolSession.getMethodDispatcher(), evt.getChannelId());
+ return true;
+ }
+
+ /**
+ * Setting of the ProtocolSession will be required when Failover has been successfuly compeleted.
+ *
+ * The new {@link AMQProtocolSession} that has been re-established needs to be provided as that is now the
+ * connection to the network.
+ *
+ * @param session The new protocol session
+ */
+ public void setProtocolSession(AMQProtocolSession session)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Setting ProtocolSession:" + session);
+ }
+ _protocolSession = session;
+ }
+
+ /**
+ * Propogate error to waiters
+ *
+ * @param error The error to propogate.
+ */
+ public void error(Exception error)
+ {
+ if (error instanceof AMQException)
+ {
+ // AMQException should be being notified before closing the
+ // ProtocolSession. Which will change the State to CLOSED.
+ // if we have a hard error.
+ if (((AMQException)error).isHardError())
+ {
+ changeState(AMQState.CONNECTION_CLOSING);
+ }
+ }
+ else
+ {
+ // Be on the safe side here and mark the connection closed
+ changeState(AMQState.CONNECTION_CLOSED);
+ }
+
+ if (_waiters.size() == 0)
+ {
+ _logger.error("No Waiters for error saving as last error:" + error.getMessage());
+ _lastException = error;
+ }
+ for (StateWaiter waiter : _waiters)
+ {
+ _logger.error("Notifying Waiters(" + _waiters + ") for error:" + error.getMessage());
+ waiter.error(error);
+ }
+ }
+
+ /**
+ * This provides a single place that the maximum time for state change to occur can be accessed.
+ * It is currently set via System property amqj.MaximumStateWait
+ *
+ * @return long Milliseconds value for a timeout
+ */
+ public long getWaitTimeout()
+ {
+ return MAXIMUM_STATE_WAIT_TIME;
+ }
+
+ /**
+ * Create and add a new waiter to the notifcation list.
+ *
+ * @param states The waiter will attempt to wait for one of these desired set states to be achived.
+ *
+ * @return the created StateWaiter.
+ */
+ public StateWaiter createWaiter(Set<AMQState> states)
+ {
+ final StateWaiter waiter;
+ synchronized (_stateLock)
+ {
+ waiter = new StateWaiter(this, _currentState, states);
+
+ _waiters.add(waiter);
+ }
+
+ return waiter;
+ }
+
+ /**
+ * Remove the waiter from the notification list.
+ *
+ * @param waiter The waiter to remove.
+ */
+ public void removeWaiter(StateWaiter waiter)
+ {
+ synchronized (_stateLock)
+ {
+ _waiters.remove(waiter);
+ }
+ }
+
+ public Exception getLastException()
+ {
+ return _lastException;
+ }
+
+ public void clearLastException()
+ {
+ _lastException = null;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java
new file mode 100644
index 0000000000..17d04f4fa3
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateAwareMethodListener.java
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.protocol.AMQMethodEvent;
+
+/**
+ * A frame listener that is informed of the protocl state when invoked and has
+ * the opportunity to update state.
+ *
+ */
+public interface StateAwareMethodListener<B extends AMQMethodBody>
+{
+
+ void methodReceived(AMQProtocolSession session, B body, int channelId) throws AMQException;
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java
new file mode 100644
index 0000000000..79f438d35d
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/StateWaiter.java
@@ -0,0 +1,128 @@
+/*
+ *
+ * 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.client.util.BlockingWaiter;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.AMQException;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+/**
+ * This is an implementation of the {@link BlockingWaiter} to provide error handing and a waiting mechanism for state
+ * changes.
+ *
+ * On construction the current state and a set of States to await for is provided.
+ *
+ * When await() is called the state at constuction is compared against the awaitStates. If the state at construction is
+ * a desired state then await() returns immediately.
+ *
+ * Otherwise it will block for the set timeout for a desired state to be achieved.
+ *
+ * The state changes are notified via the {@link #process} method.
+ *
+ * Any notified error is handled by the BlockingWaiter and thrown from the {@link #block} method.
+ *
+ */
+public class StateWaiter extends BlockingWaiter<AMQState>
+{
+ private static final Logger _logger = LoggerFactory.getLogger(StateWaiter.class);
+
+ Set<AMQState> _awaitStates;
+ private AMQState _startState;
+ private AMQStateManager _stateManager;
+
+ /**
+ *
+ * @param stateManager The StateManager
+ * @param currentState
+ * @param awaitStates
+ */
+ public StateWaiter(AMQStateManager stateManager, AMQState currentState, Set<AMQState> awaitStates)
+ {
+ _logger.info("New StateWaiter :" + currentState + ":" + awaitStates);
+ _stateManager = stateManager;
+ _awaitStates = awaitStates;
+ _startState = currentState;
+ }
+
+ /**
+ * When the state is changed this StateWaiter is notified to process the change.
+ *
+ * @param state The new state that has been achieved.
+ * @return
+ */
+ public boolean process(AMQState state)
+ {
+ return _awaitStates.contains(state);
+ }
+
+ /**
+ * Await for the requried State to be achieved within the default timeout.
+ * @return The achieved state that was requested.
+ * @throws AMQException The exception that prevented the required state from being achived.
+ */
+ public AMQState await() throws AMQException
+ {
+ return await(_stateManager.getWaitTimeout());
+ }
+
+ /**
+ * Await for the requried State to be achieved.
+ *
+ * <b>It is the responsibility of this class to remove the waiter from the StateManager
+ *
+ * @param timeout The time in milliseconds to wait for any of the states to be achived.
+ * @return The achieved state that was requested.
+ * @throws AMQException The exception that prevented the required state from being achived.
+ */
+ public AMQState await(long timeout) throws AMQException
+ {
+ try
+ {
+ if (process(_startState))
+ {
+ return _startState;
+ }
+
+ try
+ {
+ return (AMQState) block(timeout);
+ }
+ catch (FailoverException e)
+ {
+ _logger.error("Failover occured whilst waiting for states:" + _awaitStates);
+
+ return null;
+ }
+ }
+ finally
+ {
+ //Prevent any more errors being notified to this waiter.
+ close();
+
+ //Remove the waiter from the notifcation list in the statee manager
+ _stateManager.removeWaiter(this);
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java b/qpid/java/client/src/main/java/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java
new file mode 100644
index 0000000000..f0d7feb059
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/state/listener/SpecificMethodFrameListener.java
@@ -0,0 +1,41 @@
+/*
+ *
+ * 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.listener;
+
+
+import org.apache.qpid.client.protocol.BlockingMethodFrameListener;
+import org.apache.qpid.framing.AMQMethodBody;
+
+public class SpecificMethodFrameListener extends BlockingMethodFrameListener
+{
+ private final Class _expectedClass;
+
+ public SpecificMethodFrameListener(int channelId, Class expectedClass)
+ {
+ super(channelId);
+ _expectedClass = expectedClass;
+ }
+
+ public boolean processMethod(int channelId, AMQMethodBody frame)
+ {
+ return _expectedClass.isInstance(frame);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQNoTransportForProtocolException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQNoTransportForProtocolException.java
new file mode 100644
index 0000000000..6e47e2ce28
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQNoTransportForProtocolException.java
@@ -0,0 +1,59 @@
+/*
+ *
+ * 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.transport;
+
+import org.apache.qpid.jms.BrokerDetails;
+
+/**
+ * AMQNoTransportForProtocolException represents a connection failure where there is no transport medium to connect
+ * to the broker available. This may be the case if their is a error in the connection url, or an unsupported transport
+ * type is specified.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent absence of a transport medium.
+ * </table>
+ *
+ * @todo Error code never used. This is not an AMQException.
+ */
+public class AMQNoTransportForProtocolException extends AMQTransportConnectionException
+{
+ BrokerDetails _details;
+
+ public AMQNoTransportForProtocolException(BrokerDetails details, String message, Throwable cause)
+ {
+ super(null, message, cause);
+
+ _details = details;
+ }
+
+ public String toString()
+ {
+ if (_details != null)
+ {
+ return super.toString() + _details.toString();
+ }
+ else
+ {
+ return super.toString();
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQTransportConnectionException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQTransportConnectionException.java
new file mode 100644
index 0000000000..6bef6216bd
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/AMQTransportConnectionException.java
@@ -0,0 +1,43 @@
+/*
+ *
+ * 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.transport;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * AMQTransportConnectionException indicates a failure to establish a connection through the transporting medium, to
+ * an AMQP broker.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to connect through the transport medium.
+ * </table>
+ *
+ * @todo Error code never used. This is not an AMQException.
+ */
+public class AMQTransportConnectionException extends AMQException
+{
+ public AMQTransportConnectionException(AMQConstant errorCode, String message, Throwable cause)
+ {
+ super(errorCode, message, cause);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/transport/ITransportConnection.java b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/ITransportConnection.java
new file mode 100644
index 0000000000..7a24d6e15a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/ITransportConnection.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.transport;
+
+import java.io.IOException;
+
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.jms.BrokerDetails;
+
+public interface ITransportConnection
+{
+ void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail)
+ throws IOException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
new file mode 100644
index 0000000000..1ac8f62e32
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/SocketTransportConnection.java
@@ -0,0 +1,90 @@
+/*
+ *
+ * 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.transport;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+
+import org.apache.mina.common.ByteBuffer;
+import org.apache.mina.common.IoConnector;
+import org.apache.mina.common.SimpleByteBufferAllocator;
+import org.apache.qpid.client.SSLConfiguration;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.ssl.SSLContextFactory;
+import org.apache.qpid.transport.network.mina.MINANetworkDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SocketTransportConnection implements ITransportConnection
+{
+ private static final Logger _logger = LoggerFactory.getLogger(SocketTransportConnection.class);
+ private static final int DEFAULT_BUFFER_SIZE = 32 * 1024;
+
+ private SocketConnectorFactory _socketConnectorFactory;
+
+ static interface SocketConnectorFactory
+ {
+ IoConnector newSocketConnector();
+ }
+
+ public SocketTransportConnection(SocketConnectorFactory socketConnectorFactory)
+ {
+ _socketConnectorFactory = socketConnectorFactory;
+ }
+
+ public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) throws IOException
+ {
+ ByteBuffer.setUseDirectBuffers(Boolean.getBoolean("amqj.enableDirectBuffers"));
+
+ // the MINA default is currently to use the pooled allocator although this may change in future
+ // once more testing of the performance of the simple allocator has been done
+ if (!Boolean.getBoolean("amqj.enablePooledAllocator"))
+ {
+ _logger.info("Using SimpleByteBufferAllocator");
+ ByteBuffer.setAllocator(new SimpleByteBufferAllocator());
+ }
+
+ final IoConnector ioConnector = _socketConnectorFactory.newSocketConnector();
+ final InetSocketAddress address;
+
+ if (brokerDetail.getTransport().equals(BrokerDetails.SOCKET))
+ {
+ address = null;
+ }
+ else
+ {
+ address = new InetSocketAddress(brokerDetail.getHost(), brokerDetail.getPort());
+ _logger.info("Attempting connection to " + address);
+ }
+
+ SSLConfiguration sslConfig = protocolHandler.getConnection().getSSLConfiguration();
+ SSLContextFactory sslFactory = null;
+ if (sslConfig != null)
+ {
+ sslFactory = new SSLContextFactory(sslConfig.getKeystorePath(), sslConfig.getKeystorePassword(), sslConfig.getCertType());
+ }
+
+ MINANetworkDriver driver = new MINANetworkDriver(ioConnector);
+ driver.open(brokerDetail.getPort(), address.getAddress(), protocolHandler, null, sslFactory);
+ protocolHandler.setNetworkDriver(driver);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
new file mode 100644
index 0000000000..aef3a563af
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/TransportConnection.java
@@ -0,0 +1,351 @@
+/*
+ *
+ * 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.transport;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+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;
+import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.protocol.ProtocolEngineFactory;
+import org.apache.qpid.thread.QpidThreadExecutor;
+import org.apache.qpid.transport.network.mina.MINANetworkDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The TransportConnection is a helper class responsible for connecting to an AMQ server. It sets up the underlying
+ * connector, which currently always uses TCP/IP sockets. It creates the "protocol handler" which deals with MINA
+ * protocol events. <p/> Could be extended in future to support different transport types by turning this into concrete
+ * class/interface combo.
+ */
+public class TransportConnection
+{
+ private static ITransportConnection _instance;
+
+ private static final Map _inVmPipeAddress = new HashMap();
+ private static VmPipeAcceptor _acceptor;
+ private static int _currentInstance = -1;
+ private static int _currentVMPort = -1;
+
+ 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.AMQProtocolEngineFactory";
+
+ private static Map<String, Socket> _openSocketRegister = new ConcurrentHashMap<String, Socket>();
+
+ public static void registerOpenSocket(String socketID, Socket openSocket)
+ {
+ _openSocketRegister.put(socketID, openSocket);
+ }
+
+ public static Socket removeOpenSocket(String socketID)
+ {
+ return _openSocketRegister.remove(socketID);
+ }
+
+ public static synchronized ITransportConnection getInstance(final BrokerDetails details) throws AMQTransportConnectionException
+ {
+ int transport = getTransport(details.getTransport());
+
+ if (transport == -1)
+ {
+ throw new AMQNoTransportForProtocolException(details, null, null);
+ }
+
+ switch (transport)
+ {
+ case SOCKET:
+ return new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory()
+ {
+ public IoConnector newSocketConnector()
+ {
+ ExistingSocketConnector connector = new ExistingSocketConnector(1,new QpidThreadExecutor());
+
+ Socket socket = TransportConnection.removeOpenSocket(details.getHost());
+
+ if (socket != null)
+ {
+ _logger.info("Using existing Socket:" + socket);
+
+ ((ExistingSocketConnector) connector).setOpenSocket(socket);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Active Socket must be provided for broker " +
+ "with 'socket://<SocketID>' transport:" + details);
+ }
+ return connector;
+ }
+ });
+ case TCP:
+ return new SocketTransportConnection(new SocketTransportConnection.SocketConnectorFactory()
+ {
+ public IoConnector newSocketConnector()
+ {
+ SocketConnector result;
+ // FIXME - this needs to be sorted to use the new Mina MultiThread SA.
+ if (Boolean.getBoolean("qpidnio"))
+ {
+ _logger.warn("Using Qpid MultiThreaded NIO - " + (System.getProperties().containsKey("qpidnio")
+ ? "Qpid NIO is new default"
+ : "Sysproperty 'qpidnio' is set"));
+ result = new MultiThreadSocketConnector(1, new QpidThreadExecutor());
+ }
+ else
+ {
+ _logger.info("Using Mina NIO");
+ result = new SocketConnector(1, new QpidThreadExecutor()); // 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;
+ }
+ });
+ case VM:
+ {
+ return getVMTransport(details, Boolean.getBoolean("amqj.AutoCreateVMBroker"));
+ }
+ default:
+ throw new AMQNoTransportForProtocolException(details, "Transport not recognised:" + transport, null);
+ }
+ }
+
+ private static int getTransport(String transport)
+ {
+ if (transport.equals(BrokerDetails.SOCKET))
+ {
+ return SOCKET;
+ }
+
+ if (transport.equals(BrokerDetails.TCP))
+ {
+ return TCP;
+ }
+
+ if (transport.equals(BrokerDetails.VM))
+ {
+ return VM;
+ }
+
+ return -1;
+ }
+
+ private static ITransportConnection getVMTransport(BrokerDetails details, boolean AutoCreate)
+ throws AMQVMBrokerCreationException
+ {
+ int port = details.getPort();
+
+ synchronized (_inVmPipeAddress)
+ {
+ if (!_inVmPipeAddress.containsKey(port))
+ {
+ if (AutoCreate)
+ {
+ _logger.warn("Auto Creating InVM Broker on port:" + port);
+ createVMBroker(port);
+ }
+ else
+ {
+ throw new AMQVMBrokerCreationException(null, port, "VM Broker on port " + port
+ + " does not exist. Auto create disabled.", null);
+ }
+ }
+ }
+
+ return new VmPipeTransportConnection(port);
+ }
+
+ public static void createVMBroker(int port) throws AMQVMBrokerCreationException
+ {
+ synchronized(TransportConnection.class)
+ {
+ if (_acceptor == null)
+ {
+ _acceptor = new VmPipeAcceptor();
+
+ IoServiceConfig config = _acceptor.getDefaultConfig();
+ }
+ }
+ synchronized (_inVmPipeAddress)
+ {
+
+ if (!_inVmPipeAddress.containsKey(port))
+ {
+ _logger.info("Creating InVM Qpid.AMQP listening on port " + port);
+ IoHandlerAdapter provider = null;
+ try
+ {
+ VmPipeAddress pipe = new VmPipeAddress(port);
+
+ provider = createBrokerInstance(port);
+
+ _acceptor.bind(pipe, provider);
+
+ _inVmPipeAddress.put(port, pipe);
+ _logger.info("Created InVM Qpid.AMQP listening on port " + port);
+ }
+ catch (IOException e)
+ {
+ _logger.error("Got IOException.", e);
+
+ // Try and unbind provider
+ try
+ {
+ VmPipeAddress pipe = new VmPipeAddress(port);
+
+ try
+ {
+ _acceptor.unbind(pipe);
+ }
+ catch (Exception ignore)
+ {
+ // ignore
+ }
+
+ if (provider == null)
+ {
+ provider = createBrokerInstance(port);
+ }
+
+ _acceptor.bind(pipe, provider);
+ _inVmPipeAddress.put(port, pipe);
+ _logger.info("Created InVM Qpid.AMQP listening on port " + port);
+ }
+ catch (IOException justUseFirstException)
+ {
+ String because;
+ if (e.getCause() == null)
+ {
+ because = e.toString();
+ }
+ else
+ {
+ because = e.getCause().toString();
+ }
+
+ throw new AMQVMBrokerCreationException(null, port, because + " Stopped binding of InVM Qpid.AMQP", e);
+ }
+ }
+
+ }
+ else
+ {
+ _logger.info("InVM Qpid.AMQP on port " + port + " already exits.");
+ }
+ }
+ }
+
+ private static IoHandlerAdapter createBrokerInstance(int port) throws AMQVMBrokerCreationException
+ {
+ String protocolProviderClass = System.getProperty("amqj.protocolprovider.class", DEFAULT_QPID_SERVER);
+ _logger.info("Creating Qpid protocol provider: " + protocolProviderClass);
+
+ // can't use introspection to get Provider as it is a server class.
+ // need to go straight to IoHandlerAdapter but that requries the queues and exchange from the ApplicationRegistry which we can't access.
+
+ // get right constructor and pass in instancec ID - "port"
+ IoHandlerAdapter provider;
+ try
+ {
+ Class[] cnstr = {Integer.class};
+ Object[] params = {port};
+
+ provider = new MINANetworkDriver();
+ ProtocolEngineFactory engineFactory = (ProtocolEngineFactory) Class.forName(protocolProviderClass).getConstructor(cnstr).newInstance(params);
+ ((MINANetworkDriver) provider).setProtocolEngineFactory(engineFactory, true);
+ // Give the broker a second to create
+ _logger.info("Created VMBroker Instance:" + port);
+ }
+ catch (Exception e)
+ {
+ _logger.info("Unable to create InVM Qpid.AMQP on port " + port + ". Because: " + e.getCause());
+ String because;
+ if (e.getCause() == null)
+ {
+ because = e.toString();
+ }
+ else
+ {
+ because = e.getCause().toString();
+ }
+
+ AMQVMBrokerCreationException amqbce =
+ new AMQVMBrokerCreationException(null, port, because + " Stopped InVM Qpid.AMQP creation", e);
+ throw amqbce;
+ }
+
+ return provider;
+ }
+
+ public static void killAllVMBrokers()
+ {
+ _logger.info("Killing all VM Brokers");
+ synchronized(TransportConnection.class)
+ {
+ if (_acceptor != null)
+ {
+ _acceptor.unbindAll();
+ }
+ synchronized (_inVmPipeAddress)
+ {
+ _inVmPipeAddress.clear();
+ }
+ _acceptor = null;
+ }
+ _currentInstance = -1;
+ _currentVMPort = -1;
+ }
+
+ public static void killVMBroker(int port)
+ {
+ synchronized (_inVmPipeAddress)
+ {
+ VmPipeAddress pipe = (VmPipeAddress) _inVmPipeAddress.get(port);
+ if (pipe != null)
+ {
+ _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/qpid/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java
new file mode 100644
index 0000000000..87cc2e7a5a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/transport/VmPipeTransportConnection.java
@@ -0,0 +1,63 @@
+/*
+ *
+ * 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.transport;
+
+import java.io.IOException;
+
+import org.apache.mina.common.ConnectFuture;
+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.transport.network.mina.MINANetworkDriver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class VmPipeTransportConnection implements ITransportConnection
+{
+ private static final Logger _logger = LoggerFactory.getLogger(VmPipeTransportConnection.class);
+
+ private int _port;
+
+ private MINANetworkDriver _networkDriver;
+
+ public VmPipeTransportConnection(int port)
+ {
+ _port = port;
+ }
+
+ public void connect(AMQProtocolHandler protocolHandler, BrokerDetails brokerDetail) throws IOException
+ {
+ final VmPipeConnector ioConnector = new QpidVmPipeConnector();
+
+ final VmPipeAddress address = new VmPipeAddress(_port);
+ _logger.info("Attempting connection to " + address);
+ _networkDriver = new MINANetworkDriver(ioConnector, protocolHandler);
+ protocolHandler.setNetworkDriver(_networkDriver);
+ ConnectFuture future = ioConnector.connect(address, _networkDriver);
+ // wait for connection to complete
+ future.join();
+ // we call getSession which throws an IOException if there has been an error connecting
+ future.getSession();
+ _networkDriver.setProtocolEngine(protocolHandler);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser.java b/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser.java
new file mode 100644
index 0000000000..f3f74dd332
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser.java
@@ -0,0 +1,253 @@
+package org.apache.qpid.client.url;
+/*
+ *
+ * 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 java.net.URI;
+import java.net.URISyntaxException;
+import java.util.StringTokenizer;
+
+import org.apache.qpid.client.AMQBrokerDetails;
+import org.apache.qpid.client.AMQConnectionFactory;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.url.URLHelper;
+import org.apache.qpid.url.URLSyntaxException;
+
+public class URLParser
+{
+ private AMQConnectionURL _url;
+
+ public URLParser(AMQConnectionURL url)throws URLSyntaxException
+ {
+ _url = url;
+ parseURL(_url.getURL());
+ }
+
+ private void parseURL(String fullURL) throws URLSyntaxException
+ {
+ // Connection URL format
+ // amqp://[user:pass@][clientid]/virtualhost?brokerlist='tcp://host:port?option=\'value\',option=\'value\';vm://:3/virtualpath?option=\'value\'',failover='method?option=\'value\',option='value''"
+ // Options are of course optional except for requiring a single broker in the broker list.
+ try
+ {
+ URI connection = new URI(fullURL);
+
+ if ((connection.getScheme() == null) || !(connection.getScheme().equalsIgnoreCase(AMQConnectionURL.AMQ_PROTOCOL)))
+ {
+ throw new URISyntaxException(fullURL, "Not an AMQP URL");
+ }
+
+ if ((connection.getHost() == null) || connection.getHost().equals(""))
+ {
+ String tmp = connection.getAuthority();
+ // hack to read a clientid such as "my_clientID"
+ if (tmp != null && tmp.indexOf('@') < tmp.length()-1)
+ {
+ _url.setClientName(tmp.substring(tmp.indexOf('@')+1,tmp.length()));
+ }
+ else
+ {
+ String uid = AMQConnectionFactory.getUniqueClientID();
+ if (uid == null)
+ {
+ throw URLHelper.parseError(-1, "Client Name not specified", fullURL);
+ }
+ else
+ {
+ _url.setClientName(uid);
+ }
+ }
+
+ }
+ else
+ {
+ _url.setClientName(connection.getHost());
+ }
+
+ String userInfo = connection.getUserInfo();
+
+ if (userInfo == null)
+ {
+ // Fix for Java 1.5 which doesn't parse UserInfo for non http URIs
+ userInfo = connection.getAuthority();
+
+ if (userInfo != null)
+ {
+ int atIndex = userInfo.indexOf('@');
+
+ if (atIndex != -1)
+ {
+ userInfo = userInfo.substring(0, atIndex);
+ }
+ else
+ {
+ userInfo = null;
+ }
+ }
+
+ }
+
+ if (userInfo == null)
+ {
+ throw URLHelper.parseError(AMQConnectionURL.AMQ_PROTOCOL.length() + 3, "User information not found on url", fullURL);
+ }
+ else
+ {
+ parseUserInfo(userInfo);
+ }
+
+ String virtualHost = connection.getPath();
+
+ if ((virtualHost != null) && (!virtualHost.equals("")))
+ {
+ _url.setVirtualHost(virtualHost);
+ }
+ else
+ {
+ int authLength = connection.getAuthority().length();
+ int start = AMQConnectionURL.AMQ_PROTOCOL.length() + 3;
+ int testIndex = start + authLength;
+ if ((testIndex < fullURL.length()) && (fullURL.charAt(testIndex) == '?'))
+ {
+ throw URLHelper.parseError(start, testIndex - start, "Virtual host found", fullURL);
+ }
+ else
+ {
+ throw URLHelper.parseError(-1, "Virtual host not specified", fullURL);
+ }
+
+ }
+
+ URLHelper.parseOptions(_url.getOptions(), connection.getQuery());
+
+ processOptions();
+ }
+ catch (URISyntaxException uris)
+ {
+ if (uris instanceof URLSyntaxException)
+ {
+ throw (URLSyntaxException) uris;
+ }
+
+ int slash = fullURL.indexOf("\\");
+
+ if (slash == -1)
+ {
+ throw URLHelper.parseError(uris.getIndex(), uris.getReason(), uris.getInput());
+ }
+ else
+ {
+ if ((slash != 0) && (fullURL.charAt(slash - 1) == ':'))
+ {
+ throw URLHelper.parseError(slash - 2, fullURL.indexOf('?') - slash + 2,
+ "Virtual host looks like a windows path, forward slash not allowed in URL", fullURL);
+ }
+ else
+ {
+ throw URLHelper.parseError(slash, "Forward slash not allowed in URL", fullURL);
+ }
+ }
+
+ }
+ }
+
+ private void parseUserInfo(String userinfo) throws URLSyntaxException
+ {
+ // user info = user:pass
+
+ int colonIndex = userinfo.indexOf(':');
+
+ if (colonIndex == -1)
+ {
+ throw URLHelper.parseError(AMQConnectionURL.AMQ_PROTOCOL.length() + 3, userinfo.length(),
+ "Null password in user information not allowed.", _url.getURL());
+ }
+ else
+ {
+ _url.setUsername(userinfo.substring(0, colonIndex));
+ _url.setPassword(userinfo.substring(colonIndex + 1));
+ }
+
+ }
+
+ private void processOptions() throws URLSyntaxException
+ {
+ if (_url.getOptions().containsKey(AMQConnectionURL.OPTIONS_BROKERLIST))
+ {
+ String brokerlist = _url.getOptions().get(AMQConnectionURL.OPTIONS_BROKERLIST);
+
+ // brokerlist tcp://host:port?option='value',option='value';vm://:3/virtualpath?option='value'
+ StringTokenizer st = new StringTokenizer(brokerlist, "" + URLHelper.BROKER_SEPARATOR);
+
+ while (st.hasMoreTokens())
+ {
+ String broker = st.nextToken();
+
+ _url.addBrokerDetails(new AMQBrokerDetails(broker));
+ }
+
+ _url.getOptions().remove(AMQConnectionURL.OPTIONS_BROKERLIST);
+ }
+
+ if (_url.getOptions().containsKey(AMQConnectionURL.OPTIONS_FAILOVER))
+ {
+ String failover = _url.getOptions().get(AMQConnectionURL.OPTIONS_FAILOVER);
+
+ // failover='method?option='value',option='value''
+
+ int methodIndex = failover.indexOf('?');
+
+ if (methodIndex > -1)
+ {
+ _url.setFailoverMethod(failover.substring(0, methodIndex));
+ URLHelper.parseOptions(_url.getFailoverOptions(), failover.substring(methodIndex + 1));
+ }
+ else
+ {
+ _url.setFailoverMethod(failover);
+ }
+
+ _url.getOptions().remove(AMQConnectionURL.OPTIONS_FAILOVER);
+ }
+
+ if (_url.getOptions().containsKey(AMQConnectionURL.OPTIONS_DEFAULT_TOPIC_EXCHANGE))
+ {
+ _url.setDefaultTopicExchangeName(new AMQShortString(_url.getOptions().get(AMQConnectionURL.OPTIONS_DEFAULT_TOPIC_EXCHANGE)));
+ }
+
+ if (_url.getOptions().containsKey(AMQConnectionURL.OPTIONS_DEFAULT_QUEUE_EXCHANGE))
+ {
+ _url.setDefaultQueueExchangeName(new AMQShortString(_url.getOptions().get(AMQConnectionURL.OPTIONS_DEFAULT_QUEUE_EXCHANGE)));
+ }
+
+ if (_url.getOptions().containsKey(AMQConnectionURL.OPTIONS_TEMPORARY_QUEUE_EXCHANGE))
+ {
+ _url.setTemporaryQueueExchangeName(new AMQShortString(_url.getOptions().get(AMQConnectionURL.OPTIONS_TEMPORARY_QUEUE_EXCHANGE)));
+ }
+
+ if (_url.getOptions().containsKey(AMQConnectionURL.OPTIONS_TEMPORARY_TOPIC_EXCHANGE))
+ {
+ _url.setTemporaryTopicExchangeName(new AMQShortString(_url.getOptions().get(AMQConnectionURL.OPTIONS_TEMPORARY_TOPIC_EXCHANGE)));
+ }
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser_0_10.java b/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser_0_10.java
new file mode 100644
index 0000000000..605e9ee154
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/url/URLParser_0_10.java
@@ -0,0 +1,423 @@
+/* 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.url;
+
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.qpid.client.AMQBrokerDetails;
+import org.apache.qpid.jms.BrokerDetails;
+
+/**
+ * The format Qpid URL is based on the AMQP one.
+ * The grammar is as follows:
+ * <p> qpid_url = "qpid:" [client_props "@"] port_addr_list ["/" future-parameters]
+ * <p> port_addr_list = [port_addr ","]* port_addr
+ * <p> port_addr = tcp_port_addr | tls_prot_addr | future_prot_addr
+ * <p> tcp_port_addr = tcp_id tcp_addr
+ * <p> tcp_id = "tcp:" | ""
+ * <p> tcp_addr = host [":" port]
+ * <p> host = <as per http://www.apps.ietf.org/>
+ * <p> port = number
+ * <p> tls_prot_addr = tls_id tls_addr
+ * <p> tls_id = "tls:" | ""
+ * <p> tls_addr = host [":" port]
+ * <p> future_prot_addr = future_prot_id future_prot_addr
+ * <p> future_prot_id = <placeholder, must end in ":". Example "sctp:">
+ * <p> future_prot_addr = <placeholder, protocl-specific address>
+ * <p> future_parameters = <placeholder, not used in failover addresses>
+ * <p> client_props = [client_prop ";"]* client_prop
+ * <p> client_prop = prop "=" val
+ * <p> prop = chars as per <as per http://www.apps.ietf.org/>
+ * <p> val = valid as per <as per http://www.apps.ietf.org/>
+ * <p/>
+ * Ex: qpid:virtualhost=tcp:host-foo,test,client_id=foo@tcp:myhost.com:5672,virtualhost=prod;
+ * keystore=/opt/keystore@client_id2@tls:mysecurehost.com:5672
+ */
+public class URLParser_0_10
+{
+ private static final char[] URL_START_SEQ = new char[]{'q', 'p', 'i', 'd', ':'};
+ private static final char PROPERTY_EQUALS_CHAR = '=';
+ private static final char PROPERTY_SEPARATOR_CHAR = ';';
+ private static final char ADDRESS_SEPERATOR_CHAR = ',';
+
+ //private static final char CLIENT_ID_TRANSPORT_SEPARATOR_CHAR = ':';
+ private static final char TRANSPORT_HOST_SEPARATOR_CHAR = ':';
+ private static final char HOST_PORT_SEPARATOR_CHAR = ':';
+ private static final char AT_CHAR = '@';
+ private static final char END_OF_URL_MARKER = '^';
+
+ enum URLParserState
+ {
+ QPID_URL_START,
+ ADDRESS_START,
+ PROPERTY_NAME,
+ PROPERTY_EQUALS,
+ PROPERTY_VALUE,
+ PROPERTY_SEPARATOR,
+ AT_CHAR,
+ TRANSPORT,
+ TRANSPORT_HOST_SEPARATOR,
+ HOST,
+ HOST_PORT_SEPARATOR,
+ PORT,
+ ADDRESS_END,
+ ADDRESS_SEPERATOR,
+ QPID_URL_END,
+ ERROR
+ }
+
+ //-- Constructors
+
+ private char[] _url;
+ private List<BrokerDetails> _brokerDetailList = new ArrayList<BrokerDetails>();
+ private String _error;
+ private int _index = 0;
+ private BrokerDetails _currentBroker;
+ private String _currentPropName;
+ private boolean _endOfURL = false;
+ private URLParserState _currentParserState;
+
+ public URLParser_0_10(String url) throws MalformedURLException
+ {
+ _url = (url + END_OF_URL_MARKER).toCharArray();
+ _endOfURL = false;
+ _currentParserState = URLParserState.QPID_URL_START;
+ URLParserState prevState = _currentParserState; // for error handling
+ try
+ {
+ while (_currentParserState != URLParserState.ERROR && _currentParserState != URLParserState.QPID_URL_END)
+ {
+ prevState = _currentParserState;
+ _currentParserState = next();
+ }
+
+ if (_currentParserState == URLParserState.ERROR)
+ {
+ _error =
+ "Invalid URL format [current_state = " + prevState + ", broker details parsed so far " + _currentBroker + " ] error at (" + _index + ") due to " + _error;
+ MalformedURLException ex;
+ ex = new MalformedURLException(_error);
+ throw ex;
+ }
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ _error = "Invalid URL format [current_state = " + prevState + ", broker details parsed so far " + _currentBroker + " ] error at (" + _index + ")";
+ MalformedURLException ex = new MalformedURLException(_error);
+ throw ex;
+ }
+ }
+
+ //-- interface QpidURL
+ public List<BrokerDetails> getAllBrokerDetails()
+ {
+ return _brokerDetailList;
+ }
+
+ public String getURL()
+ {
+ return new String(_url);
+ }
+
+ private URLParserState next()
+ {
+ switch (_currentParserState)
+ {
+ case QPID_URL_START:
+ return checkSequence(URL_START_SEQ, URLParserState.ADDRESS_START);
+ case ADDRESS_START:
+ return startAddress();
+ case PROPERTY_NAME:
+ return extractPropertyName();
+ case PROPERTY_EQUALS:
+ _index++; // skip the equal sign
+ return URLParserState.PROPERTY_VALUE;
+ case PROPERTY_VALUE:
+ return extractPropertyValue();
+ case PROPERTY_SEPARATOR:
+ _index++; // skip ","
+ return URLParserState.PROPERTY_NAME;
+ case AT_CHAR:
+ _index++; // skip the @ sign
+ return URLParserState.TRANSPORT;
+ case TRANSPORT:
+ return extractTransport();
+ case TRANSPORT_HOST_SEPARATOR:
+ _index++; // skip ":"
+ return URLParserState.HOST;
+ case HOST:
+ return extractHost();
+ case HOST_PORT_SEPARATOR:
+ _index++; // skip ":"
+ return URLParserState.PORT;
+ case PORT:
+ return extractPort();
+ case ADDRESS_END:
+ return endAddress();
+ case ADDRESS_SEPERATOR:
+ _index++; // skip ","
+ return URLParserState.ADDRESS_START;
+ default:
+ return URLParserState.ERROR;
+ }
+ }
+
+ private URLParserState checkSequence(char[] expected, URLParserState nextPart)
+ {
+ for (char expectedChar : expected)
+ {
+ if (expectedChar != _url[_index])
+ {
+ _error = "Excepted (" + expectedChar + ") at position " + _index + ", got (" + _url[_index] + ")";
+ return URLParserState.ERROR;
+ }
+ _index++;
+ }
+ return nextPart;
+ }
+
+ private URLParserState startAddress()
+ {
+ _currentBroker = new AMQBrokerDetails();
+
+ for (int j = _index; j < _url.length; j++)
+ {
+ if (_url[j] == PROPERTY_EQUALS_CHAR)
+ {
+ return URLParserState.PROPERTY_NAME;
+ }
+ else if (_url[j] == ADDRESS_SEPERATOR_CHAR)
+ {
+ return URLParserState.TRANSPORT;
+ }
+ }
+ return URLParserState.TRANSPORT;
+ }
+
+ private URLParserState endAddress()
+ {
+ _brokerDetailList.add(_currentBroker);
+ if (_endOfURL)
+ {
+ return URLParserState.QPID_URL_END;
+ }
+ else
+ {
+ return URLParserState.ADDRESS_SEPERATOR;
+ }
+ }
+
+ private URLParserState extractPropertyName()
+ {
+ StringBuilder b = new StringBuilder();
+ char next = _url[_index];
+ while (next != PROPERTY_EQUALS_CHAR && next != AT_CHAR)
+ {
+ b.append(next);
+ next = _url[++_index];
+ }
+ _currentPropName = b.toString();
+ if (_currentPropName.trim().equals(""))
+ {
+ _error = "Property name cannot be empty";
+ return URLParserState.ERROR;
+ }
+ else if (next == PROPERTY_EQUALS_CHAR)
+ {
+ return URLParserState.PROPERTY_EQUALS;
+ }
+ else
+ {
+ return URLParserState.AT_CHAR;
+ }
+ }
+
+ private URLParserState extractPropertyValue()
+ {
+ StringBuilder b = new StringBuilder();
+ char next = _url[_index];
+ while (next != PROPERTY_SEPARATOR_CHAR && next != AT_CHAR)
+ {
+ b.append(next);
+ next = _url[++_index];
+ }
+ String propValue = b.toString();
+ if (propValue.trim().equals(""))
+ {
+ _error = "Property values cannot be empty";
+ return URLParserState.ERROR;
+ }
+ else
+ {
+ _currentBroker.setProperty(_currentPropName, propValue);
+ if (next == PROPERTY_SEPARATOR_CHAR)
+ {
+ return URLParserState.PROPERTY_SEPARATOR;
+ }
+ else
+ {
+ return URLParserState.AT_CHAR;
+ }
+ }
+ }
+
+ private URLParserState extractTransport()
+ {
+ String transport = buildUntil(TRANSPORT_HOST_SEPARATOR_CHAR);
+ if (transport.trim().equals(""))
+ {
+ _error = "Transport cannot be empty";
+ return URLParserState.ERROR;
+ }
+ else if (!(transport.trim().equals(BrokerDetails.PROTOCOL_TCP) || transport.trim()
+ .equals(BrokerDetails.PROTOCOL_TLS)))
+ {
+ _error = "Transport cannot be " + transport + " value must be tcp or tls";
+ return URLParserState.ERROR;
+ }
+ else
+ {
+ _currentBroker.setTransport(transport);
+ return URLParserState.TRANSPORT_HOST_SEPARATOR;
+ }
+ }
+
+ private URLParserState extractHost()
+ {
+ char nextSep = 'c';
+ String host;
+ URLParserState nextState;
+
+ for (int i = _index; i < _url.length; i++)
+ {
+ if (_url[i] == HOST_PORT_SEPARATOR_CHAR)
+ {
+ nextSep = HOST_PORT_SEPARATOR_CHAR;
+ break;
+ }
+ else if (_url[i] == ADDRESS_SEPERATOR_CHAR)
+ {
+ nextSep = ADDRESS_SEPERATOR_CHAR;
+ break;
+ }
+ }
+
+ if (nextSep == HOST_PORT_SEPARATOR_CHAR)
+ {
+ host = buildUntil(HOST_PORT_SEPARATOR_CHAR);
+ nextState = URLParserState.HOST_PORT_SEPARATOR;
+ }
+ else if (nextSep == ADDRESS_SEPERATOR_CHAR)
+ {
+ host = buildUntil(ADDRESS_SEPERATOR_CHAR);
+ nextState = URLParserState.ADDRESS_END;
+ }
+ else
+ {
+ host = buildUntil(END_OF_URL_MARKER);
+ nextState = URLParserState.ADDRESS_END;
+ _endOfURL = true;
+ }
+
+ if (host.trim().equals(""))
+ {
+ _error = "Host cannot be empty";
+ return URLParserState.ERROR;
+ }
+ else
+ {
+ _currentBroker.setHost(host);
+ return nextState;
+ }
+ }
+
+
+ private URLParserState extractPort()
+ {
+
+ StringBuilder b = new StringBuilder();
+ try
+ {
+ char next = _url[_index];
+ while (next != ADDRESS_SEPERATOR_CHAR && next != END_OF_URL_MARKER )
+ {
+ b.append(next);
+ next = _url[++_index];
+ }
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ _endOfURL = true;
+ }
+ String portStr = b.toString();
+ if (portStr.trim().equals(""))
+ {
+ _error = "Host cannot be empty";
+ return URLParserState.ERROR;
+ }
+ else
+ {
+ try
+ {
+ int port = Integer.parseInt(portStr);
+ _currentBroker.setPort(port);
+ if( _url[_index] == END_OF_URL_MARKER )
+ {
+ _endOfURL = true;
+ }
+ return URLParserState.ADDRESS_END;
+ }
+ catch (NumberFormatException e)
+ {
+ _error = "Illegal number for port";
+ return URLParserState.ERROR;
+ }
+ }
+ }
+
+ private String buildUntil(char c)
+ {
+ StringBuilder b = new StringBuilder();
+ char next = _url[_index];
+ while (next != c)
+ {
+ b.append(next);
+ next = _url[++_index];
+ }
+ return b.toString();
+ }
+
+ public static void main(String[] args)
+ {
+ String testurl = "qpid:password=pass;username=name@tcp:test1";
+ try
+ {
+ URLParser_0_10 impl = new URLParser_0_10(testurl);
+ for (BrokerDetails d : impl.getAllBrokerDetails())
+ {
+ System.out.println(d);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java b/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java
new file mode 100644
index 0000000000..208658a5ff
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/util/BlockingWaiter.java
@@ -0,0 +1,348 @@
+/*
+ *
+ * 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.util;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQTimeoutException;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+import org.apache.qpid.protocol.AMQMethodListener;
+
+/**
+ * BlockingWaiter is a 'rendezvous' which delegates handling of
+ * incoming Objects to a listener implemented as a sub-class of this and hands off the process or
+ * error to a consumer. The producer of the event does not have to wait for the consumer to take the event, so this
+ * differs from a 'rendezvous' in that sense.
+ *
+ * <p/>BlockingWaiters are used to coordinate when waiting for an an event that expect a response.
+ * They are always used in a 'one-shot' manner, that is, to recieve just one response. Usually the caller has to register
+ * them as method listeners with an event dispatcher and remember to de-register them (in a finally block) once they
+ * have been completed.
+ *
+ * <p/>The {@link #process} must return <tt>true</tt> on any incoming method that it handles. This indicates to
+ * this listeners that the object just processed ends the waiting process.
+ *
+ * <p/>Errors from the producer are rethrown to the consumer.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations </td>
+ * <tr><td> Accept generic objects as events for processing via {@link #process}. <td>
+ * <tr><td> Delegate handling and undserstanding of the object to a concrete implementation. <td>
+ * <tr><td> Block until {@link #process} determines that waiting is no longer required <td>
+ * <tr><td> Propagate the most recent exception to the consumer.<td>
+ * </table>
+ *
+ * @todo Interuption is caught but not handled. This could be allowed to fall through. This might actually be usefull
+ * for fail-over where a thread is blocking when failure happens, it could be interrupted to abandon or retry
+ * when this happens. At the very least, restore the interrupted status flag.
+ * @todo If the retrotranslator can handle it, could use a SynchronousQueue to implement this rendezvous. Need to
+ * check that SynchronousQueue has a non-blocking put method available.
+ */
+public abstract class BlockingWaiter<T>
+{
+ /** This flag is used to indicate that the blocked for method has been received. */
+ private volatile boolean _ready = false;
+
+ /** This flag is used to indicate that the received error has been processed. */
+ private volatile boolean _errorAck = false;
+
+ /** Used to protect the shared event and ready flag between the producer and consumer. */
+ private final ReentrantLock _lock = new ReentrantLock();
+
+ /** Used to signal that a method has been received */
+ private final Condition _receivedCondition = _lock.newCondition();
+
+ /** Used to signal that a error has been processed */
+ private final Condition _errorConditionAck = _lock.newCondition();
+
+ /** Used to hold the most recent exception that is passed to the {@link #error(Exception)} method. */
+ private volatile Exception _error;
+
+ /** Holds the incomming Object. */
+ protected Object _doneObject = null;
+ private AtomicBoolean _waiting = new AtomicBoolean(false);
+ private boolean _closed = false;
+
+ /**
+ * Delegates processing of the incomming object to the handler.
+ *
+ * @param object The object to process.
+ *
+ * @return <tt>true</tt> if the waiting is complete, <tt>false</tt> if waiting should continue.
+ */
+ public abstract boolean process(T object);
+
+ /**
+ * An Object has been received and should be processed to see if our wait condition has been reached.
+ *
+ * @param object The object received.
+ *
+ * @return <tt>true</tt> if the waiting is complete, <tt>false</tt> if waiting should continue.
+ */
+ public boolean received(T object)
+ {
+
+ boolean ready = process(object);
+
+ if (ready)
+ {
+ // we only update the flag from inside the synchronized block
+ // so that the blockForFrame method cannot "miss" an update - it
+ // will only ever read the flag from within the synchronized block
+ _lock.lock();
+ try
+ {
+ _doneObject = object;
+ _ready = ready;
+ _receivedCondition.signal();
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ return ready;
+ }
+
+ /**
+ * Blocks until an object is received that is handled by process, or the specified timeout
+ * has passed.
+ *
+ * Once closed any attempt to wait will throw an exception.
+ *
+ * @param timeout The timeout in milliseconds.
+ *
+ * @return The object that resolved the blocking.
+ *
+ * @throws AMQException
+ * @throws FailoverException
+ */
+ public Object block(long timeout) throws AMQException, FailoverException
+ {
+ long nanoTimeout = TimeUnit.MILLISECONDS.toNanos(timeout);
+
+ _lock.lock();
+
+ try
+ {
+ if (_closed)
+ {
+ throw throwClosedException();
+ }
+
+ if (_error == null)
+ {
+ _waiting.set(true);
+
+ while (!_ready)
+ {
+ try
+ {
+ if (timeout == -1)
+ {
+ _receivedCondition.await();
+ }
+ else
+ {
+ nanoTimeout = _receivedCondition.awaitNanos(nanoTimeout);
+
+ if (nanoTimeout <= 0 && !_ready && _error == null)
+ {
+ _error = new AMQTimeoutException("Server did not respond in a timely fashion", null);
+ _ready = true;
+ }
+ }
+ }
+ catch (InterruptedException e)
+ {
+ System.err.println(e.getMessage());
+ // IGNORE -- //fixme this isn't ideal as being interrupted isn't equivellant to sucess
+ // if (!_ready && timeout != -1)
+ // {
+ // _error = new AMQException("Server did not respond timely");
+ // _ready = true;
+ // }
+ }
+ }
+ }
+
+ if (_error != null)
+ {
+ if (_error instanceof AMQException)
+ {
+ throw (AMQException) _error;
+ }
+ else if (_error instanceof FailoverException)
+ {
+ // This should ensure that FailoverException is not wrapped and can be caught.
+ throw (FailoverException) _error; // needed to expose FailoverException.
+ }
+ else
+ {
+ throw new AMQException("Woken up due to " + _error.getClass(), _error);
+ }
+ }
+
+ }
+ finally
+ {
+ _waiting.set(false);
+
+ //Release Error handling thread
+ if (_error != null)
+ {
+ _errorAck = true;
+ _errorConditionAck.signal();
+
+ _error = null;
+ }
+ _lock.unlock();
+ }
+
+ return _doneObject;
+ }
+
+ /**
+ * This is a callback, called when an error has occured that should interupt any waiter.
+ * It is also called from within this class to avoid code repetition but it should only be called by the MINA threads.
+ *
+ * Once closed any notification of an exception will be ignored.
+ *
+ * @param e The exception being propogated.
+ */
+ public void error(Exception e)
+ {
+ // set the error so that the thread that is blocking (against blockForFrame())
+ // can pick up the exception and rethrow to the caller
+
+ _lock.lock();
+
+ try
+ {
+ if (_closed)
+ {
+ return;
+ }
+
+ if (_error == null)
+ {
+ _error = e;
+ }
+ else
+ {
+ System.err.println("WARNING: new error '" + e == null ? "null" : e.getMessage() + "' arrived while old one not yet processed:" + _error.getMessage());
+ }
+
+ if (_waiting.get())
+ {
+
+ _ready = true;
+ _receivedCondition.signal();
+
+ while (!_errorAck)
+ {
+ try
+ {
+ _errorConditionAck.await();
+ }
+ catch (InterruptedException e1)
+ {
+ System.err.println(e.getMessage());
+ }
+ }
+ _errorAck = false;
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ /**
+ * Close this Waiter so that no more errors are processed.
+ * This is a preventative method to ensure that a second error thread does not get stuck in the error method after
+ * the await has returned. This has not happend but in practise but if two errors occur on the Connection at
+ * the same time then it is conceiveably possible for the second to get stuck if the first one is processed by a
+ * waiter.
+ *
+ * Once closed any attempt to wait will throw an exception.
+ * Any notification of an exception will be ignored.
+ */
+ public void close()
+ {
+ _lock.lock();
+ try
+ {
+ //if we have already closed then our job is done.
+ if (_closed)
+ {
+ return;
+ }
+
+ //Close Waiter so no more exceptions are processed
+ _closed = true;
+
+ //Wake up any await() threads
+
+ //If we are waiting then use the error() to wake them up.
+ if (_waiting.get())
+ {
+ error(throwClosedException());
+ }
+ //If they are not waiting then there is nothing to do.
+
+ // Wake up any error handling threads
+
+ if (!_errorAck)
+ {
+ _errorAck = true;
+ _errorConditionAck.signal();
+
+ _error = null;
+ }
+ }
+ finally
+ {
+ _lock.unlock();
+ }
+ }
+
+ /**
+ * Helper method to generate the a closed Exception.
+ *
+ * todo: This should be converted to something more friendly.
+ *
+ * @return AMQException to throw to waiters when the Waiter is closed.
+ */
+ private AMQException throwClosedException()
+ {
+ return new AMQException(null, "Waiter was closed.", null);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java b/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.java
new file mode 100644
index 0000000000..ee7fc533a3
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/util/FlowControllingBlockingQueue.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.client.util;
+
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A blocking queue that emits events above a user specified threshold allowing the caller to take action (e.g. flow
+ * control) to try to prevent the queue growing (much) further. The underlying queue itself is not bounded therefore the
+ * caller is not obliged to react to the events. <p/> This implementation is <b>only</b> safe where we have a single
+ * thread adding items and a single (different) thread removing items.
+ *
+ * @todo Make this implement java.util.Queue and hide the implementation. Then different queue types can be substituted.
+ */
+public class FlowControllingBlockingQueue
+{
+ private static final Logger _logger = LoggerFactory.getLogger(FlowControllingBlockingQueue.class);
+
+ /** This queue is bounded and is used to store messages before being dispatched to the consumer */
+ private final Queue _queue = new ConcurrentLinkedQueue();
+
+ private final int _flowControlHighThreshold;
+ private final int _flowControlLowThreshold;
+
+ private final ThresholdListener _listener;
+
+ /** We require a separate count so we can track whether we have reached the threshold */
+ private int _count;
+
+ private boolean disableFlowControl;
+
+ public boolean isEmpty()
+ {
+ return _queue.isEmpty();
+ }
+
+ public interface ThresholdListener
+ {
+ void aboveThreshold(int currentValue);
+
+ void underThreshold(int currentValue);
+ }
+
+ public FlowControllingBlockingQueue(int threshold, ThresholdListener listener)
+ {
+ this(threshold, threshold, listener);
+ }
+
+ public FlowControllingBlockingQueue(int highThreshold, int lowThreshold, ThresholdListener listener)
+ {
+ _flowControlHighThreshold = highThreshold;
+ _flowControlLowThreshold = lowThreshold;
+ _listener = listener;
+ if (highThreshold == 0)
+ {
+ disableFlowControl = true;
+ }
+ }
+
+ public Object take() throws InterruptedException
+ {
+ Object o = _queue.poll();
+ if(o == null)
+ {
+ synchronized(this)
+ {
+ while((o = _queue.poll())==null)
+ {
+ wait();
+ }
+ }
+ }
+ if (!disableFlowControl && _listener != null)
+ {
+ synchronized (_listener)
+ {
+ if (_count-- == _flowControlLowThreshold)
+ {
+ _listener.underThreshold(_count);
+ }
+ }
+
+ }
+
+ return o;
+ }
+
+ public void add(Object o)
+ {
+ synchronized(this)
+ {
+ _queue.add(o);
+
+ notifyAll();
+ }
+ if (!disableFlowControl && _listener != null)
+ {
+ synchronized (_listener)
+ {
+ if (++_count == _flowControlHighThreshold)
+ {
+ _listener.aboveThreshold(_count);
+ }
+ }
+ }
+ }
+
+ public Iterator iterator()
+ {
+ return _queue.iterator();
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/client/vmbroker/AMQVMBrokerCreationException.java b/qpid/java/client/src/main/java/org/apache/qpid/client/vmbroker/AMQVMBrokerCreationException.java
new file mode 100644
index 0000000000..dc0d9b8c78
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/client/vmbroker/AMQVMBrokerCreationException.java
@@ -0,0 +1,60 @@
+/*
+ *
+ * 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.vmbroker;
+
+import org.apache.qpid.client.transport.AMQTransportConnectionException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * AMQVMBrokerCreationException represents failure to create an in VM broker on the vm transport medium.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to create an in VM broker.
+ * </table>
+ *
+ * @todo Error code never used. This is not an AMQException.
+ */
+public class AMQVMBrokerCreationException extends AMQTransportConnectionException
+{
+ private int _port;
+
+ /**
+ * @param port
+ *
+ * @deprecated
+ */
+ public AMQVMBrokerCreationException(int port)
+ {
+ this(null, port, "Unable to create vm broker", null);
+ }
+
+ public AMQVMBrokerCreationException(AMQConstant errorCode, int port, String message, Throwable cause)
+ {
+ super(errorCode, message, cause);
+ _port = port;
+ }
+
+ public String toString()
+ {
+ return super.toString() + " on port " + _port;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/collections/KeyValue.java b/qpid/java/client/src/main/java/org/apache/qpid/collections/KeyValue.java
new file mode 100644
index 0000000000..e890aba968
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/collections/KeyValue.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2003-2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.collections;
+
+/**
+ * Defines a simple key value pair.
+ * <p>
+ * A Map Entry has considerable additional semantics over and above a simple
+ * key-value pair. This interface defines the minimum key value, with just the
+ * two get methods.
+ *
+ * @since Commons Collections 3.0
+ * @version $Revision$ $Date$
+ *
+ * @author Stephen Colebourne
+ */
+public interface KeyValue {
+
+ /**
+ * Gets the key from the pair.
+ *
+ * @return the key
+ */
+ Object getKey();
+
+ /**
+ * Gets the value from the pair.
+ *
+ * @return the value
+ */
+ Object getValue();
+
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/collections/ReferenceMap.java b/qpid/java/client/src/main/java/org/apache/qpid/collections/ReferenceMap.java
new file mode 100644
index 0000000000..1516c56e42
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/collections/ReferenceMap.java
@@ -0,0 +1,957 @@
+/*
+ * Copyright 2001-2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.collections;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.AbstractCollection;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.qpid.collections.keyvalue.DefaultMapEntry;
+
+/**
+ * Hash-based {@link Map} implementation that allows
+ * mappings to be removed by the garbage collector.<p>
+ *
+ * When you construct a <code>ReferenceMap</code>, you can
+ * specify what kind of references are used to store the
+ * map's keys and values. If non-hard references are
+ * used, then the garbage collector can remove mappings
+ * if a key or value becomes unreachable, or if the
+ * JVM's memory is running low. For information on how
+ * the different reference types behave, see
+ * {@link Reference}.<p>
+ *
+ * Different types of references can be specified for keys
+ * and values. The keys can be configured to be weak but
+ * the values hard, in which case this class will behave
+ * like a <a href="http://java.sun.com/j2se/1.4/docs/api/java/util/WeakHashMap.html">
+ * <code>WeakHashMap</code></a>. However, you
+ * can also specify hard keys and weak values, or any other
+ * combination. The default constructor uses hard keys
+ * and soft values, providing a memory-sensitive cache.<p>
+ *
+ * The algorithms used are basically the same as those
+ * in {@link java.util.HashMap}. In particular, you
+ * can specify a load factor and capacity to suit your
+ * needs. All optional {@link Map} operations are
+ * supported.<p>
+ *
+ * However, this {@link Map} implementation does <I>not</I>
+ * allow null elements. Attempting to add a null key or
+ * or a null value to the map will raise a
+ * <code>NullPointerException</code>.<p>
+ *
+ * As usual, this implementation is not synchronized. You
+ * can use {@link java.util.Collections#synchronizedMap} to
+ * provide synchronized access to a <code>ReferenceMap</code>.
+ *
+ * @see java.lang.ref.Reference
+ *
+ * @deprecated Moved to map subpackage. Due to be removed in v4.0.
+ * @since Commons Collections 2.1
+ * @version $Revision$ $Date$
+ *
+ * @author Paul Jack
+ */
+public class ReferenceMap extends AbstractMap {
+
+ /**
+ * For serialization.
+ */
+ private static final long serialVersionUID = -3370601314380922368L;
+
+
+ /**
+ * Constant indicating that hard references should be used.
+ */
+ final public static int HARD = 0;
+
+
+ /**
+ * Constant indicating that soft references should be used.
+ */
+ final public static int SOFT = 1;
+
+
+ /**
+ * Constant indicating that weak references should be used.
+ */
+ final public static int WEAK = 2;
+
+
+ // --- serialized instance variables:
+
+
+ /**
+ * The reference type for keys. Must be HARD, SOFT, WEAK.
+ * Note: I originally marked this field as final, but then this class
+ * didn't compile under JDK1.2.2.
+ * @serial
+ */
+ private int keyType;
+
+
+ /**
+ * The reference type for values. Must be HARD, SOFT, WEAK.
+ * Note: I originally marked this field as final, but then this class
+ * didn't compile under JDK1.2.2.
+ * @serial
+ */
+ private int valueType;
+
+
+ /**
+ * The threshold variable is calculated by multiplying
+ * table.length and loadFactor.
+ * Note: I originally marked this field as final, but then this class
+ * didn't compile under JDK1.2.2.
+ * @serial
+ */
+ private float loadFactor;
+
+ /**
+ * Should the value be automatically purged when the associated key has been collected?
+ */
+ private boolean purgeValues = false;
+
+
+ // -- Non-serialized instance variables
+
+ /**
+ * ReferenceQueue used to eliminate stale mappings.
+ * See purge.
+ */
+ private transient ReferenceQueue queue = new ReferenceQueue();
+
+
+ /**
+ * The hash table. Its length is always a power of two.
+ */
+ private transient Entry[] table;
+
+
+ /**
+ * Number of mappings in this map.
+ */
+ private transient int size;
+
+
+ /**
+ * When size reaches threshold, the map is resized.
+ * See resize().
+ */
+ private transient int threshold;
+
+
+ /**
+ * Number of times this map has been modified.
+ */
+ private transient volatile int modCount;
+
+
+ /**
+ * Cached key set. May be null if key set is never accessed.
+ */
+ private transient Set keySet;
+
+
+ /**
+ * Cached entry set. May be null if entry set is never accessed.
+ */
+ private transient Set entrySet;
+
+
+ /**
+ * Cached values. May be null if values() is never accessed.
+ */
+ private transient Collection values;
+
+
+ /**
+ * Constructs a new <code>ReferenceMap</code> that will
+ * use hard references to keys and soft references to values.
+ */
+ public ReferenceMap() {
+ this(HARD, SOFT);
+ }
+
+ /**
+ * Constructs a new <code>ReferenceMap</code> that will
+ * use the specified types of references.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param purgeValues should the value be automatically purged when the
+ * key is garbage collected
+ */
+ public ReferenceMap(int keyType, int valueType, boolean purgeValues) {
+ this(keyType, valueType);
+ this.purgeValues = purgeValues;
+ }
+
+ /**
+ * Constructs a new <code>ReferenceMap</code> that will
+ * use the specified types of references.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ */
+ public ReferenceMap(int keyType, int valueType) {
+ this(keyType, valueType, 16, 0.75f);
+ }
+
+ /**
+ * Constructs a new <code>ReferenceMap</code> with the
+ * specified reference types, load factor and initial
+ * capacity.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param capacity the initial capacity for the map
+ * @param loadFactor the load factor for the map
+ * @param purgeValues should the value be automatically purged when the
+ * key is garbage collected
+ */
+ public ReferenceMap(
+ int keyType,
+ int valueType,
+ int capacity,
+ float loadFactor,
+ boolean purgeValues) {
+ this(keyType, valueType, capacity, loadFactor);
+ this.purgeValues = purgeValues;
+ }
+
+ /**
+ * Constructs a new <code>ReferenceMap</code> with the
+ * specified reference types, load factor and initial
+ * capacity.
+ *
+ * @param keyType the type of reference to use for keys;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param valueType the type of reference to use for values;
+ * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK}
+ * @param capacity the initial capacity for the map
+ * @param loadFactor the load factor for the map
+ */
+ public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor) {
+ super();
+
+ verify("keyType", keyType);
+ verify("valueType", valueType);
+
+ if (capacity <= 0) {
+ throw new IllegalArgumentException("capacity must be positive");
+ }
+ if ((loadFactor <= 0.0f) || (loadFactor >= 1.0f)) {
+ throw new IllegalArgumentException("Load factor must be greater than 0 and less than 1.");
+ }
+
+ this.keyType = keyType;
+ this.valueType = valueType;
+
+ int v = 1;
+ while (v < capacity) v *= 2;
+
+ this.table = new Entry[v];
+ this.loadFactor = loadFactor;
+ this.threshold = (int)(v * loadFactor);
+ }
+
+
+ // used by constructor
+ private static void verify(String name, int type) {
+ if ((type < HARD) || (type > WEAK)) {
+ throw new IllegalArgumentException(name +
+ " must be HARD, SOFT, WEAK.");
+ }
+ }
+
+
+ /**
+ * Writes this object to the given output stream.
+ *
+ * @param out the output stream to write to
+ * @throws IOException if the stream raises it
+ */
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ out.writeInt(table.length);
+
+ // Have to use null-terminated list because size might shrink
+ // during iteration
+
+ for (Iterator iter = entrySet().iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry)iter.next();
+ out.writeObject(entry.getKey());
+ out.writeObject(entry.getValue());
+ }
+ out.writeObject(null);
+ }
+
+
+ /**
+ * Reads the contents of this object from the given input stream.
+ *
+ * @param inp the input stream to read from
+ * @throws IOException if the stream raises it
+ * @throws ClassNotFoundException if the stream raises it
+ */
+ private void readObject(ObjectInputStream inp) throws IOException, ClassNotFoundException {
+ inp.defaultReadObject();
+ table = new Entry[inp.readInt()];
+ threshold = (int)(table.length * loadFactor);
+ queue = new ReferenceQueue();
+ Object key = inp.readObject();
+ while (key != null) {
+ Object value = inp.readObject();
+ put(key, value);
+ key = inp.readObject();
+ }
+ }
+
+
+ /**
+ * Constructs a reference of the given type to the given
+ * referent. The reference is registered with the queue
+ * for later purging.
+ *
+ * @param type HARD, SOFT or WEAK
+ * @param referent the object to refer to
+ * @param hash the hash code of the <I>key</I> of the mapping;
+ * this number might be different from referent.hashCode() if
+ * the referent represents a value and not a key
+ */
+ private Object toReference(int type, Object referent, int hash) {
+ switch (type) {
+ case HARD: return referent;
+ case SOFT: return new SoftRef(hash, referent, queue);
+ case WEAK: return new WeakRef(hash, referent, queue);
+ default: throw new Error();
+ }
+ }
+
+
+ /**
+ * Returns the entry associated with the given key.
+ *
+ * @param key the key of the entry to look up
+ * @return the entry associated with that key, or null
+ * if the key is not in this map
+ */
+ private Entry getEntry(Object key) {
+ if (key == null) return null;
+ int hash = key.hashCode();
+ int index = indexFor(hash);
+ for (Entry entry = table[index]; entry != null; entry = entry.next) {
+ if ((entry.hash == hash) && key.equals(entry.getKey())) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * Converts the given hash code into an index into the
+ * hash table.
+ */
+ private int indexFor(int hash) {
+ // mix the bits to avoid bucket collisions...
+ hash += ~(hash << 15);
+ hash ^= (hash >>> 10);
+ hash += (hash << 3);
+ hash ^= (hash >>> 6);
+ hash += ~(hash << 11);
+ hash ^= (hash >>> 16);
+ return hash & (table.length - 1);
+ }
+
+
+
+ /**
+ * Resizes this hash table by doubling its capacity.
+ * This is an expensive operation, as entries must
+ * be copied from the old smaller table to the new
+ * bigger table.
+ */
+ private void resize() {
+ Entry[] old = table;
+ table = new Entry[old.length * 2];
+
+ for (int i = 0; i < old.length; i++) {
+ Entry next = old[i];
+ while (next != null) {
+ Entry entry = next;
+ next = next.next;
+ int index = indexFor(entry.hash);
+ entry.next = table[index];
+ table[index] = entry;
+ }
+ old[i] = null;
+ }
+ threshold = (int)(table.length * loadFactor);
+ }
+
+
+
+ /**
+ * Purges stale mappings from this map.
+ * <p>
+ * Ordinarily, stale mappings are only removed during
+ * a write operation, although this method is called for both
+ * read and write operations to maintain a consistent state.
+ * <p>
+ * Note that this method is not synchronized! Special
+ * care must be taken if, for instance, you want stale
+ * mappings to be removed on a periodic basis by some
+ * background thread.
+ */
+ private void purge() {
+ Reference ref = queue.poll();
+ while (ref != null) {
+ purge(ref);
+ ref = queue.poll();
+ }
+ }
+
+
+ private void purge(Reference ref) {
+ // The hashCode of the reference is the hashCode of the
+ // mapping key, even if the reference refers to the
+ // mapping value...
+ int hash = ref.hashCode();
+ int index = indexFor(hash);
+ Entry previous = null;
+ Entry entry = table[index];
+ while (entry != null) {
+ if (entry.purge(ref)) {
+ if (previous == null) table[index] = entry.next;
+ else previous.next = entry.next;
+ this.size--;
+ return;
+ }
+ previous = entry;
+ entry = entry.next;
+ }
+
+ }
+
+
+ /**
+ * Returns the size of this map.
+ *
+ * @return the size of this map
+ */
+ public int size() {
+ purge();
+ return size;
+ }
+
+
+ /**
+ * Returns <code>true</code> if this map is empty.
+ *
+ * @return <code>true</code> if this map is empty
+ */
+ public boolean isEmpty() {
+ purge();
+ return size == 0;
+ }
+
+
+ /**
+ * Returns <code>true</code> if this map contains the given key.
+ *
+ * @return true if the given key is in this map
+ */
+ public boolean containsKey(Object key) {
+ purge();
+ Entry entry = getEntry(key);
+ if (entry == null) return false;
+ return entry.getValue() != null;
+ }
+
+
+ /**
+ * Returns the value associated with the given key, if any.
+ *
+ * @return the value associated with the given key, or <code>null</code>
+ * if the key maps to no value
+ */
+ public Object get(Object key) {
+ purge();
+ Entry entry = getEntry(key);
+ if (entry == null) return null;
+ return entry.getValue();
+ }
+
+
+ /**
+ * Associates the given key with the given value.<p>
+ * Neither the key nor the value may be null.
+ *
+ * @param key the key of the mapping
+ * @param value the value of the mapping
+ * @return the last value associated with that key, or
+ * null if no value was associated with the key
+ * @throws NullPointerException if either the key or value
+ * is null
+ */
+ public Object put(Object key, Object value) {
+ if (key == null) throw new NullPointerException("null keys not allowed");
+ if (value == null) throw new NullPointerException("null values not allowed");
+
+ purge();
+ if (size + 1 > threshold) resize();
+
+ int hash = key.hashCode();
+ int index = indexFor(hash);
+ Entry entry = table[index];
+ while (entry != null) {
+ if ((hash == entry.hash) && key.equals(entry.getKey())) {
+ Object result = entry.getValue();
+ entry.setValue(value);
+ return result;
+ }
+ entry = entry.next;
+ }
+ this.size++;
+ modCount++;
+ key = toReference(keyType, key, hash);
+ value = toReference(valueType, value, hash);
+ table[index] = new Entry(key, hash, value, table[index]);
+ return null;
+ }
+
+
+ /**
+ * Removes the key and its associated value from this map.
+ *
+ * @param key the key to remove
+ * @return the value associated with that key, or null if
+ * the key was not in the map
+ */
+ public Object remove(Object key) {
+ if (key == null) return null;
+ purge();
+ int hash = key.hashCode();
+ int index = indexFor(hash);
+ Entry previous = null;
+ Entry entry = table[index];
+ while (entry != null) {
+ if ((hash == entry.hash) && key.equals(entry.getKey())) {
+ if (previous == null) table[index] = entry.next;
+ else previous.next = entry.next;
+ this.size--;
+ modCount++;
+ return entry.getValue();
+ }
+ previous = entry;
+ entry = entry.next;
+ }
+ return null;
+ }
+
+
+ /**
+ * Clears this map.
+ */
+ public void clear() {
+ Arrays.fill(table, null);
+ size = 0;
+ while (queue.poll() != null); // drain the queue
+ }
+
+
+ /**
+ * Returns a set view of this map's entries.
+ *
+ * @return a set view of this map's entries
+ */
+ public Set entrySet() {
+ if (entrySet != null) {
+ return entrySet;
+ }
+ entrySet = new AbstractSet() {
+ public int size() {
+ return ReferenceMap.this.size();
+ }
+
+ public void clear() {
+ ReferenceMap.this.clear();
+ }
+
+ public boolean contains(Object o) {
+ if (o == null) return false;
+ if (!(o instanceof Map.Entry)) return false;
+ Map.Entry e = (Map.Entry)o;
+ Entry e2 = getEntry(e.getKey());
+ return (e2 != null) && e.equals(e2);
+ }
+
+ public boolean remove(Object o) {
+ boolean r = contains(o);
+ if (r) {
+ Map.Entry e = (Map.Entry)o;
+ ReferenceMap.this.remove(e.getKey());
+ }
+ return r;
+ }
+
+ public Iterator iterator() {
+ return new EntryIterator();
+ }
+
+ public Object[] toArray() {
+ return toArray(new Object[0]);
+ }
+
+ public Object[] toArray(Object[] arr) {
+ ArrayList list = new ArrayList();
+ Iterator iterator = iterator();
+ while (iterator.hasNext()) {
+ Entry e = (Entry)iterator.next();
+ list.add(new DefaultMapEntry(e.getKey(), e.getValue()));
+ }
+ return list.toArray(arr);
+ }
+ };
+ return entrySet;
+ }
+
+
+ /**
+ * Returns a set view of this map's keys.
+ *
+ * @return a set view of this map's keys
+ */
+ public Set keySet() {
+ if (keySet != null) return keySet;
+ keySet = new AbstractSet() {
+ public int size() {
+ return ReferenceMap.this.size();
+ }
+
+ public Iterator iterator() {
+ return new KeyIterator();
+ }
+
+ public boolean contains(Object o) {
+ return containsKey(o);
+ }
+
+
+ public boolean remove(Object o) {
+ Object r = ReferenceMap.this.remove(o);
+ return r != null;
+ }
+
+ public void clear() {
+ ReferenceMap.this.clear();
+ }
+
+ public Object[] toArray() {
+ return toArray(new Object[0]);
+ }
+
+ public Object[] toArray(Object[] array) {
+ Collection c = new ArrayList(size());
+ for (Iterator it = iterator(); it.hasNext(); ) {
+ c.add(it.next());
+ }
+ return c.toArray(array);
+ }
+ };
+ return keySet;
+ }
+
+
+ /**
+ * Returns a collection view of this map's values.
+ *
+ * @return a collection view of this map's values.
+ */
+ public Collection values() {
+ if (values != null) return values;
+ values = new AbstractCollection() {
+ public int size() {
+ return ReferenceMap.this.size();
+ }
+
+ public void clear() {
+ ReferenceMap.this.clear();
+ }
+
+ public Iterator iterator() {
+ return new ValueIterator();
+ }
+
+ public Object[] toArray() {
+ return toArray(new Object[0]);
+ }
+
+ public Object[] toArray(Object[] array) {
+ Collection c = new ArrayList(size());
+ for (Iterator it = iterator(); it.hasNext(); ) {
+ c.add(it.next());
+ }
+ return c.toArray(array);
+ }
+ };
+ return values;
+ }
+
+
+ // If getKey() or getValue() returns null, it means
+ // the mapping is stale and should be removed.
+ private class Entry implements Map.Entry, KeyValue {
+
+ Object key;
+ Object value;
+ int hash;
+ Entry next;
+
+
+ public Entry(Object key, int hash, Object value, Entry next) {
+ this.key = key;
+ this.hash = hash;
+ this.value = value;
+ this.next = next;
+ }
+
+
+ public Object getKey() {
+ return (keyType > HARD) ? ((Reference)key).get() : key;
+ }
+
+
+ public Object getValue() {
+ return (valueType > HARD) ? ((Reference)value).get() : value;
+ }
+
+
+ public Object setValue(Object object) {
+ Object old = getValue();
+ if (valueType > HARD) ((Reference)value).clear();
+ value = toReference(valueType, object, hash);
+ return old;
+ }
+
+
+ public boolean equals(Object o) {
+ if (o == null) return false;
+ if (o == this) return true;
+ if (!(o instanceof Map.Entry)) return false;
+
+ Map.Entry entry = (Map.Entry)o;
+ Object key = entry.getKey();
+ Object value = entry.getValue();
+ if ((key == null) || (value == null)) return false;
+ return key.equals(getKey()) && value.equals(getValue());
+ }
+
+
+ public int hashCode() {
+ Object v = getValue();
+ return hash ^ ((v == null) ? 0 : v.hashCode());
+ }
+
+
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+
+
+ boolean purge(Reference ref) {
+ boolean r = (keyType > HARD) && (key == ref);
+ r = r || ((valueType > HARD) && (value == ref));
+ if (r) {
+ if (keyType > HARD) ((Reference)key).clear();
+ if (valueType > HARD) {
+ ((Reference)value).clear();
+ } else if (purgeValues) {
+ value = null;
+ }
+ }
+ return r;
+ }
+ }
+
+
+ private class EntryIterator implements Iterator {
+ // These fields keep track of where we are in the table.
+ int index;
+ Entry entry;
+ Entry previous;
+
+ // These Object fields provide hard references to the
+ // current and next entry; this assures that if hasNext()
+ // returns true, next() will actually return a valid element.
+ Object nextKey, nextValue;
+ Object currentKey, currentValue;
+
+ int expectedModCount;
+
+
+ public EntryIterator() {
+ index = (size() != 0 ? table.length : 0);
+ // have to do this here! size() invocation above
+ // may have altered the modCount.
+ expectedModCount = modCount;
+ }
+
+
+ public boolean hasNext() {
+ checkMod();
+ while (nextNull()) {
+ Entry e = entry;
+ int i = index;
+ while ((e == null) && (i > 0)) {
+ i--;
+ e = table[i];
+ }
+ entry = e;
+ index = i;
+ if (e == null) {
+ currentKey = null;
+ currentValue = null;
+ return false;
+ }
+ nextKey = e.getKey();
+ nextValue = e.getValue();
+ if (nextNull()) entry = entry.next;
+ }
+ return true;
+ }
+
+
+ private void checkMod() {
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ }
+
+
+ private boolean nextNull() {
+ return (nextKey == null) || (nextValue == null);
+ }
+
+ protected Entry nextEntry() {
+ checkMod();
+ if (nextNull() && !hasNext()) throw new NoSuchElementException();
+ previous = entry;
+ entry = entry.next;
+ currentKey = nextKey;
+ currentValue = nextValue;
+ nextKey = null;
+ nextValue = null;
+ return previous;
+ }
+
+
+ public Object next() {
+ return nextEntry();
+ }
+
+
+ public void remove() {
+ checkMod();
+ if (previous == null) throw new IllegalStateException();
+ ReferenceMap.this.remove(currentKey);
+ previous = null;
+ currentKey = null;
+ currentValue = null;
+ expectedModCount = modCount;
+ }
+
+ }
+
+
+ private class ValueIterator extends EntryIterator {
+ public Object next() {
+ return nextEntry().getValue();
+ }
+ }
+
+
+ private class KeyIterator extends EntryIterator {
+ public Object next() {
+ return nextEntry().getKey();
+ }
+ }
+
+
+
+ // These two classes store the hashCode of the key of
+ // of the mapping, so that after they're dequeued a quick
+ // lookup of the bucket in the table can occur.
+
+
+ private static class SoftRef extends SoftReference {
+ private int hash;
+
+
+ public SoftRef(int hash, Object r, ReferenceQueue q) {
+ super(r, q);
+ this.hash = hash;
+ }
+
+
+ public int hashCode() {
+ return hash;
+ }
+ }
+
+
+ private static class WeakRef extends WeakReference {
+ private int hash;
+
+
+ public WeakRef(int hash, Object r, ReferenceQueue q) {
+ super(r, q);
+ this.hash = hash;
+ }
+
+
+ public int hashCode() {
+ return hash;
+ }
+ }
+
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractKeyValue.java b/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractKeyValue.java
new file mode 100644
index 0000000000..a7ca67ad15
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractKeyValue.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2003-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.collections.keyvalue;
+
+import org.apache.qpid.collections.KeyValue;
+
+
+/**
+ * Abstract pair class to assist with creating <code>KeyValue</code>
+ * and {@link java.util.Map.Entry Map.Entry} implementations.
+ *
+ * @since Commons Collections 3.0
+ * @version $Revision$ $Date$
+ *
+ * @author James Strachan
+ * @author Michael A. Smith
+ * @author Neil O'Toole
+ * @author Stephen Colebourne
+ */
+public abstract class AbstractKeyValue implements KeyValue {
+
+ /** The key */
+ protected Object key;
+ /** The value */
+ protected Object value;
+
+ /**
+ * Constructs a new pair with the specified key and given value.
+ *
+ * @param key the key for the entry, may be null
+ * @param value the value for the entry, may be null
+ */
+ protected AbstractKeyValue(Object key, Object value) {
+ super();
+ this.key = key;
+ this.value = value;
+ }
+
+ /**
+ * Gets the key from the pair.
+ *
+ * @return the key
+ */
+ public Object getKey() {
+ return key;
+ }
+
+ /**
+ * Gets the value from the pair.
+ *
+ * @return the value
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Gets a debugging String view of the pair.
+ *
+ * @return a String view of the entry
+ */
+ public String toString() {
+ return new StringBuffer()
+ .append(getKey())
+ .append('=')
+ .append(getValue())
+ .toString();
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractMapEntry.java b/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractMapEntry.java
new file mode 100644
index 0000000000..f4717a1c20
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/AbstractMapEntry.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2003-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.collections.keyvalue;
+
+import java.util.Map;
+
+import org.apache.qpid.collections.keyvalue.AbstractKeyValue;
+
+/**
+ * Abstract Pair class to assist with creating correct
+ * {@link java.util.Map.Entry Map.Entry} implementations.
+ *
+ * @since Commons Collections 3.0
+ * @version $Revision$ $Date$
+ *
+ * @author James Strachan
+ * @author Michael A. Smith
+ * @author Neil O'Toole
+ * @author Stephen Colebourne
+ */
+public abstract class AbstractMapEntry extends AbstractKeyValue implements Map.Entry {
+
+ /**
+ * Constructs a new entry with the given key and given value.
+ *
+ * @param key the key for the entry, may be null
+ * @param value the value for the entry, may be null
+ */
+ protected AbstractMapEntry(Object key, Object value) {
+ super(key, value);
+ }
+
+ // Map.Entry interface
+ //-------------------------------------------------------------------------
+ /**
+ * Sets the value stored in this <code>Map.Entry</code>.
+ * <p>
+ * This <code>Map.Entry</code> is not connected to a Map, so only the
+ * local data is changed.
+ *
+ * @param value the new value
+ * @return the previous value
+ */
+ public Object setValue(Object value) {
+ Object answer = this.value;
+ this.value = value;
+ return answer;
+ }
+
+ /**
+ * Compares this <code>Map.Entry</code> with another <code>Map.Entry</code>.
+ * <p>
+ * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)}
+ *
+ * @param obj the object to compare to
+ * @return true if equal key and value
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj instanceof Map.Entry == false) {
+ return false;
+ }
+ Map.Entry other = (Map.Entry) obj;
+ return
+ (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) &&
+ (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
+ }
+
+ /**
+ * Gets a hashCode compatible with the equals method.
+ * <p>
+ * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
+ *
+ * @return a suitable hash code
+ */
+ public int hashCode() {
+ return (getKey() == null ? 0 : getKey().hashCode()) ^
+ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/DefaultMapEntry.java b/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/DefaultMapEntry.java
new file mode 100644
index 0000000000..f0f04a366a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/collections/keyvalue/DefaultMapEntry.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.qpid.collections.keyvalue;
+
+import java.util.Map;
+
+import org.apache.qpid.collections.KeyValue;
+import org.apache.qpid.collections.keyvalue.AbstractMapEntry;
+
+/**
+ * A restricted implementation of {@link java.util.Map.Entry} that prevents
+ * the <code>Map.Entry</code> contract from being broken.
+ *
+ * @since Commons Collections 3.0
+ * @version $Revision$ $Date$
+ *
+ * @author James Strachan
+ * @author Michael A. Smith
+ * @author Neil O'Toole
+ * @author Stephen Colebourne
+ */
+public final class DefaultMapEntry extends AbstractMapEntry {
+
+ /**
+ * Constructs a new entry with the specified key and given value.
+ *
+ * @param key the key for the entry, may be null
+ * @param value the value for the entry, may be null
+ */
+ public DefaultMapEntry(final Object key, final Object value) {
+ super(key, value);
+ }
+
+ /**
+ * Constructs a new entry from the specified <code>KeyValue</code>.
+ *
+ * @param pair the pair to copy, must not be null
+ * @throws NullPointerException if the entry is null
+ */
+ public DefaultMapEntry(final KeyValue pair) {
+ super(pair.getKey(), pair.getValue());
+ }
+
+ /**
+ * Constructs a new entry from the specified <code>Map.Entry</code>.
+ *
+ * @param entry the entry to copy, must not be null
+ * @throws NullPointerException if the entry is null
+ */
+ public DefaultMapEntry(final Map.Entry entry) {
+ super(entry.getKey(), entry.getValue());
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/ArithmeticExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/ArithmeticExpression.java
new file mode 100644
index 0000000000..a86613f10c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/ArithmeticExpression.java
@@ -0,0 +1,268 @@
+/* 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.filter;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+
+/**
+ * An expression which performs an operation on two expression values
+ */
+public abstract class ArithmeticExpression extends BinaryExpression
+{
+
+ protected static final int INTEGER = 1;
+ protected static final int LONG = 2;
+ protected static final int DOUBLE = 3;
+
+
+ public ArithmeticExpression(Expression left, Expression right)
+ {
+ super(left, right);
+ }
+
+ public static Expression createPlus(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof String)
+ {
+ String text = (String) lvalue;
+ return text + rvalue;
+ }
+ else if (lvalue instanceof Number)
+ {
+ return plus((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call plus operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "+";
+ }
+ };
+ }
+
+ public static Expression createMinus(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return minus((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call minus operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "-";
+ }
+ };
+ }
+
+ public static Expression createMultiply(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return multiply((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call multiply operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "*";
+ }
+ };
+ }
+
+ public static Expression createDivide(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return divide((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call divide operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "/";
+ }
+ };
+ }
+
+ public static Expression createMod(Expression left, Expression right)
+ {
+ return new ArithmeticExpression(left, right)
+ {
+
+ protected Object evaluate(Object lvalue, Object rvalue)
+ {
+ if (lvalue instanceof Number)
+ {
+ return mod((Number) lvalue, asNumber(rvalue));
+ }
+
+ throw new RuntimeException("Cannot call mod operation on: " + lvalue + " and: " + rvalue);
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "%";
+ }
+ };
+ }
+
+ protected Number plus(Number left, Number right)
+ {
+ switch (numberType(left, right))
+ {
+
+ case ArithmeticExpression.INTEGER:
+ return new Integer(left.intValue() + right.intValue());
+
+ case ArithmeticExpression.LONG:
+ return new Long(left.longValue() + right.longValue());
+
+ default:
+ return new Double(left.doubleValue() + right.doubleValue());
+ }
+ }
+
+ protected Number minus(Number left, Number right)
+ {
+ switch (numberType(left, right))
+ {
+
+ case ArithmeticExpression.INTEGER:
+ return new Integer(left.intValue() - right.intValue());
+
+ case ArithmeticExpression.LONG:
+ return new Long(left.longValue() - right.longValue());
+
+ default:
+ return new Double(left.doubleValue() - right.doubleValue());
+ }
+ }
+
+ protected Number multiply(Number left, Number right)
+ {
+ switch (numberType(left, right))
+ {
+
+ case ArithmeticExpression.INTEGER:
+ return new Integer(left.intValue() * right.intValue());
+
+ case ArithmeticExpression.LONG:
+ return new Long(left.longValue() * right.longValue());
+
+ default:
+ return new Double(left.doubleValue() * right.doubleValue());
+ }
+ }
+
+ protected Number divide(Number left, Number right)
+ {
+ return new Double(left.doubleValue() / right.doubleValue());
+ }
+
+ protected Number mod(Number left, Number right)
+ {
+ return new Double(left.doubleValue() % right.doubleValue());
+ }
+
+ private int numberType(Number left, Number right)
+ {
+ if (isDouble(left) || isDouble(right))
+ {
+ return ArithmeticExpression.DOUBLE;
+ }
+ else if ((left instanceof Long) || (right instanceof Long))
+ {
+ return ArithmeticExpression.LONG;
+ }
+ else
+ {
+ return ArithmeticExpression.INTEGER;
+ }
+ }
+
+ private boolean isDouble(Number n)
+ {
+ return (n instanceof Float) || (n instanceof Double);
+ }
+
+ protected Number asNumber(Object value)
+ {
+ if (value instanceof Number)
+ {
+ return (Number) value;
+ }
+ else
+ {
+ throw new RuntimeException("Cannot convert value: " + value + " into a number");
+ }
+ }
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object lvalue = left.evaluate(message);
+ if (lvalue == null)
+ {
+ return null;
+ }
+
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ return evaluate(lvalue, rvalue);
+ }
+
+ /**
+ * @param lvalue
+ * @param rvalue
+ * @return
+ */
+ protected abstract Object evaluate(Object lvalue, Object rvalue);
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/BinaryExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/BinaryExpression.java
new file mode 100644
index 0000000000..f97f858fad
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/BinaryExpression.java
@@ -0,0 +1,103 @@
+/* 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.filter;
+
+/**
+ * An expression which performs an operation on two expression values.
+ */
+public abstract class BinaryExpression implements Expression
+{
+ protected Expression left;
+ protected Expression right;
+
+ public BinaryExpression(Expression left, Expression right)
+ {
+ this.left = left;
+ this.right = right;
+ }
+
+ public Expression getLeft()
+ {
+ return left;
+ }
+
+ public Expression getRight()
+ {
+ return right;
+ }
+
+ /**
+ * @see Object#toString()
+ */
+ public String toString()
+ {
+ return "(" + left.toString() + " " + getExpressionSymbol() + " " + right.toString() + ")";
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see Object#equals(Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Returns the symbol that represents this binary expression. For example, addition is
+ * represented by "+"
+ *
+ * @return
+ */
+ public abstract String getExpressionSymbol();
+
+ /**
+ * @param expression
+ */
+ public void setRight(Expression expression)
+ {
+ right = expression;
+ }
+
+ /**
+ * @param expression
+ */
+ public void setLeft(Expression expression)
+ {
+ left = expression;
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/BooleanExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/BooleanExpression.java
new file mode 100644
index 0000000000..14a5c7ea87
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/BooleanExpression.java
@@ -0,0 +1,33 @@
+/* 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.filter;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+
+/**
+ * A BooleanExpression is an expression that always
+ * produces a Boolean result.
+ */
+public interface BooleanExpression extends Expression
+{
+
+ public boolean matches(AbstractJMSMessage message) throws AMQInternalException;
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/ComparisonExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/ComparisonExpression.java
new file mode 100644
index 0000000000..55fca853ef
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/ComparisonExpression.java
@@ -0,0 +1,589 @@
+/* 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.filter;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+/**
+ * A filter performing a comparison of two objects
+ */
+public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression
+{
+
+ public static BooleanExpression createBetween(Expression value, Expression left, Expression right)
+ {
+ return LogicExpression.createAND(ComparisonExpression.createGreaterThanEqual(value, left), ComparisonExpression.createLessThanEqual(value, right));
+ }
+
+ public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right)
+ {
+ return LogicExpression.createOR(ComparisonExpression.createLessThan(value, left), ComparisonExpression.createGreaterThan(value, right));
+ }
+
+ private static final HashSet REGEXP_CONTROL_CHARS = new HashSet();
+
+ static
+ {
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('.'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('\\'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('['));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character(']'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('^'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('$'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('?'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('*'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('+'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('{'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('}'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('|'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('('));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character(')'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character(':'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('&'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('<'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('>'));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('='));
+ ComparisonExpression.REGEXP_CONTROL_CHARS.add(new Character('!'));
+ }
+
+ static class LikeExpression extends UnaryExpression implements BooleanExpression
+ {
+
+ Pattern likePattern;
+
+ /**
+ * @param right
+ */
+ public LikeExpression(Expression right, String like, int escape)
+ {
+ super(right);
+
+ StringBuffer regexp = new StringBuffer(like.length() * 2);
+ regexp.append("\\A"); // The beginning of the input
+ for (int i = 0; i < like.length(); i++)
+ {
+ char c = like.charAt(i);
+ if (escape == (0xFFFF & c))
+ {
+ i++;
+ if (i >= like.length())
+ {
+ // nothing left to escape...
+ break;
+ }
+
+ char t = like.charAt(i);
+ regexp.append("\\x");
+ regexp.append(Integer.toHexString(0xFFFF & t));
+ }
+ else if (c == '%')
+ {
+ regexp.append(".*?"); // Do a non-greedy match
+ }
+ else if (c == '_')
+ {
+ regexp.append("."); // match one
+ }
+ else if (ComparisonExpression.REGEXP_CONTROL_CHARS.contains(new Character(c)))
+ {
+ regexp.append("\\x");
+ regexp.append(Integer.toHexString(0xFFFF & c));
+ }
+ else
+ {
+ regexp.append(c);
+ }
+ }
+
+ regexp.append("\\z"); // The end of the input
+
+ likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL);
+ }
+
+ /**
+ * org.apache.activemq.filter.UnaryExpression#getExpressionSymbol()
+ */
+ public String getExpressionSymbol()
+ {
+ return "LIKE";
+ }
+
+ /**
+ * org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext)
+ */
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+
+ Object rv = this.getRight().evaluate(message);
+
+ if (rv == null)
+ {
+ return null;
+ }
+
+ if (!(rv instanceof String))
+ {
+ return
+ Boolean.FALSE;
+ // throw new RuntimeException("LIKE can only operate on String identifiers. LIKE attemped on: '" + rv.getClass());
+ }
+
+ return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public boolean matches(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+
+ public static BooleanExpression createLike(Expression left, String right, String escape)
+ {
+ if ((escape != null) && (escape.length() != 1))
+ {
+ throw new RuntimeException(
+ "The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape);
+ }
+
+ int c = -1;
+ if (escape != null)
+ {
+ c = 0xFFFF & escape.charAt(0);
+ }
+
+ return new LikeExpression(left, right, c);
+ }
+
+ public static BooleanExpression createNotLike(Expression left, String right, String escape)
+ {
+ return UnaryExpression.createNOT(ComparisonExpression.createLike(left, right, escape));
+ }
+
+ public static BooleanExpression createInFilter(Expression left, List elements)
+ {
+
+ if (!(left instanceof PropertyExpression))
+ {
+ throw new RuntimeException("Expected a property for In expression, got: " + left);
+ }
+
+ return UnaryExpression.createInExpression((PropertyExpression) left, elements, false);
+
+ }
+
+ public static BooleanExpression createNotInFilter(Expression left, List elements)
+ {
+
+ if (!(left instanceof PropertyExpression))
+ {
+ throw new RuntimeException("Expected a property for In expression, got: " + left);
+ }
+
+ return UnaryExpression.createInExpression((PropertyExpression) left, elements, true);
+
+ }
+
+ public static BooleanExpression createIsNull(Expression left)
+ {
+ return ComparisonExpression.doCreateEqual(left, ConstantExpression.NULL);
+ }
+
+ public static BooleanExpression createIsNotNull(Expression left)
+ {
+ return UnaryExpression.createNOT(ComparisonExpression.doCreateEqual(left, ConstantExpression.NULL));
+ }
+
+ public static BooleanExpression createNotEqual(Expression left, Expression right)
+ {
+ return UnaryExpression.createNOT(ComparisonExpression.createEqual(left, right));
+ }
+
+ public static BooleanExpression createEqual(Expression left, Expression right)
+ {
+ ComparisonExpression.checkEqualOperand(left);
+ ComparisonExpression.checkEqualOperand(right);
+ ComparisonExpression.checkEqualOperandCompatability(left, right);
+
+ return ComparisonExpression.doCreateEqual(left, right);
+ }
+
+ private static BooleanExpression doCreateEqual(Expression left, Expression right)
+ {
+ return new ComparisonExpression(left, right)
+ {
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object lv = left.evaluate(message);
+ Object rv = right.evaluate(message);
+
+ // Iff one of the values is null
+ if ((lv == null) ^ (rv == null))
+ {
+ return Boolean.FALSE;
+ }
+
+ if ((lv == rv) || lv.equals(rv))
+ {
+ return Boolean.TRUE;
+ }
+
+ if ((lv instanceof Comparable) && (rv instanceof Comparable))
+ {
+ return compare((Comparable) lv, (Comparable) rv);
+ }
+
+ return Boolean.FALSE;
+ }
+
+ protected boolean asBoolean(int answer)
+ {
+ return answer == 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "=";
+ }
+ };
+ }
+
+ public static BooleanExpression createGreaterThan(final Expression left, final Expression right)
+ {
+ ComparisonExpression.checkLessThanOperand(left);
+ ComparisonExpression.checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+ protected boolean asBoolean(int answer)
+ {
+ return answer > 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return ">";
+ }
+ };
+ }
+
+ public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right)
+ {
+ ComparisonExpression.checkLessThanOperand(left);
+ ComparisonExpression.checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+ protected boolean asBoolean(int answer)
+ {
+ return answer >= 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return ">=";
+ }
+ };
+ }
+
+ public static BooleanExpression createLessThan(final Expression left, final Expression right)
+ {
+ ComparisonExpression.checkLessThanOperand(left);
+ ComparisonExpression.checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+
+ protected boolean asBoolean(int answer)
+ {
+ return answer < 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "<";
+ }
+
+ };
+ }
+
+ public static BooleanExpression createLessThanEqual(final Expression left, final Expression right)
+ {
+ ComparisonExpression.checkLessThanOperand(left);
+ ComparisonExpression.checkLessThanOperand(right);
+
+ return new ComparisonExpression(left, right)
+ {
+
+ protected boolean asBoolean(int answer)
+ {
+ return answer <= 0;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "<=";
+ }
+ };
+ }
+
+ /**
+ * Only Numeric expressions can be used in >, >=, < or <= expressions.s
+ *
+ * @param expr
+ */
+ public static void checkLessThanOperand(Expression expr)
+ {
+ if (expr instanceof ConstantExpression)
+ {
+ Object value = ((ConstantExpression) expr).getValue();
+ if (value instanceof Number)
+ {
+ return;
+ }
+
+ // Else it's boolean or a String..
+ throw new RuntimeException("Value '" + expr + "' cannot be compared.");
+ }
+
+ if (expr instanceof BooleanExpression)
+ {
+ throw new RuntimeException("Value '" + expr + "' cannot be compared.");
+ }
+ }
+
+ /**
+ * Validates that the expression can be used in == or <> expression.
+ * Cannot not be NULL TRUE or FALSE litterals.
+ *
+ * @param expr
+ */
+ public static void checkEqualOperand(Expression expr)
+ {
+ if (expr instanceof ConstantExpression)
+ {
+ Object value = ((ConstantExpression) expr).getValue();
+ if (value == null)
+ {
+ throw new RuntimeException("'" + expr + "' cannot be compared.");
+ }
+ }
+ }
+
+ /**
+ *
+ * @param left
+ * @param right
+ */
+ private static void checkEqualOperandCompatability(Expression left, Expression right)
+ {
+ if ((left instanceof ConstantExpression) && (right instanceof ConstantExpression))
+ {
+ if ((left instanceof BooleanExpression) && !(right instanceof BooleanExpression))
+ {
+ throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'");
+ }
+ }
+ }
+
+ /**
+ * @param left
+ * @param right
+ */
+ public ComparisonExpression(Expression left, Expression right)
+ {
+ super(left, right);
+ }
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Comparable lv = (Comparable) left.evaluate(message);
+ if (lv == null)
+ {
+ return null;
+ }
+
+ Comparable rv = (Comparable) right.evaluate(message);
+ if (rv == null)
+ {
+ return null;
+ }
+
+ return compare(lv, rv);
+ }
+
+ protected Boolean compare(Comparable lv, Comparable rv)
+ {
+ Class lc = lv.getClass();
+ Class rc = rv.getClass();
+ // If the the objects are not of the same type,
+ // try to convert up to allow the comparison.
+ if (lc != rc)
+ {
+ if (lc == Byte.class)
+ {
+ if (rc == Short.class)
+ {
+ lv = new Short(((Number) lv).shortValue());
+ }
+ else if (rc == Integer.class)
+ {
+ lv = new Integer(((Number) lv).intValue());
+ }
+ else if (rc == Long.class)
+ {
+ lv = new Long(((Number) lv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Short.class)
+ {
+ if (rc == Integer.class)
+ {
+ lv = new Integer(((Number) lv).intValue());
+ }
+ else if (rc == Long.class)
+ {
+ lv = new Long(((Number) lv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Integer.class)
+ {
+ if (rc == Long.class)
+ {
+ lv = new Long(((Number) lv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Long.class)
+ {
+ if (rc == Integer.class)
+ {
+ rv = new Long(((Number) rv).longValue());
+ }
+ else if (rc == Float.class)
+ {
+ lv = new Float(((Number) lv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Float.class)
+ {
+ if (rc == Integer.class)
+ {
+ rv = new Float(((Number) rv).floatValue());
+ }
+ else if (rc == Long.class)
+ {
+ rv = new Float(((Number) rv).floatValue());
+ }
+ else if (rc == Double.class)
+ {
+ lv = new Double(((Number) lv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else if (lc == Double.class)
+ {
+ if (rc == Integer.class)
+ {
+ rv = new Double(((Number) rv).doubleValue());
+ }
+ else if (rc == Long.class)
+ {
+ rv = new Double(((Number) rv).doubleValue());
+ }
+ else if (rc == Float.class)
+ {
+ rv = new Float(((Number) rv).doubleValue());
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+ }
+
+ return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ protected abstract boolean asBoolean(int answer);
+
+ public boolean matches(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/ConstantExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/ConstantExpression.java
new file mode 100644
index 0000000000..3874d13431
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/ConstantExpression.java
@@ -0,0 +1,204 @@
+/* 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.filter;
+
+import java.math.BigDecimal;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+/**
+ * Represents a constant expression
+ */
+public class ConstantExpression implements Expression
+{
+
+ static class BooleanConstantExpression extends ConstantExpression implements BooleanExpression
+ {
+ public BooleanConstantExpression(Object value)
+ {
+ super(value);
+ }
+
+ public boolean matches(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+
+ public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null);
+ public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE);
+ public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE);
+
+ private Object value;
+
+ public static ConstantExpression createFromDecimal(String text)
+ {
+
+ // Strip off the 'l' or 'L' if needed.
+ if (text.endsWith("l") || text.endsWith("L"))
+ {
+ text = text.substring(0, text.length() - 1);
+ }
+
+ Number value;
+ try
+ {
+ value = new Long(text);
+ }
+ catch (NumberFormatException e)
+ {
+ // The number may be too big to fit in a long.
+ value = new BigDecimal(text);
+ }
+
+ long l = value.longValue();
+ if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE))
+ {
+ value = new Integer(value.intValue());
+ }
+
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFromHex(String text)
+ {
+ Number value = new Long(Long.parseLong(text.substring(2), 16));
+ long l = value.longValue();
+ if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE))
+ {
+ value = new Integer(value.intValue());
+ }
+
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFromOctal(String text)
+ {
+ Number value = new Long(Long.parseLong(text, 8));
+ long l = value.longValue();
+ if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE))
+ {
+ value = new Integer(value.intValue());
+ }
+
+ return new ConstantExpression(value);
+ }
+
+ public static ConstantExpression createFloat(String text)
+ {
+ Number value = new Double(text);
+
+ return new ConstantExpression(value);
+ }
+
+ public ConstantExpression(Object value)
+ {
+ this.value = value;
+ }
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ return value;
+ }
+
+ public Object getValue()
+ {
+ return value;
+ }
+
+ /**
+ * @see Object#toString()
+ */
+ public String toString()
+ {
+ if (value == null)
+ {
+ return "NULL";
+ }
+
+ if (value instanceof Boolean)
+ {
+ return ((Boolean) value).booleanValue() ? "TRUE" : "FALSE";
+ }
+
+ if (value instanceof String)
+ {
+ return ConstantExpression.encodeString((String) value);
+ }
+
+ return value.toString();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see Object#equals(Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Encodes the value of string so that it looks like it would look like
+ * when it was provided in a selector.
+ *
+ * @param s
+ * @return
+ */
+ public static String encodeString(String s)
+ {
+ StringBuffer b = new StringBuffer();
+ b.append('\'');
+ for (int i = 0; i < s.length(); i++)
+ {
+ char c = s.charAt(i);
+ if (c == '\'')
+ {
+ b.append(c);
+ }
+
+ b.append(c);
+ }
+
+ b.append('\'');
+
+ return b.toString();
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/Expression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/Expression.java
new file mode 100644
index 0000000000..8208f49688
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/Expression.java
@@ -0,0 +1,35 @@
+/* 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.filter;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+
+/**
+ * Represents an expression
+ */
+public interface Expression
+{
+ /**
+ * @param message The message to evaluate
+ * @return the value of this expression
+ * @throws AMQInternalException
+ */
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/JMSSelectorFilter.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/JMSSelectorFilter.java
new file mode 100644
index 0000000000..4159986090
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/JMSSelectorFilter.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.filter;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.filter.selector.SelectorParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class JMSSelectorFilter implements MessageFilter
+{
+ /**
+ * this JMSSelectorFilter's logger
+ */
+ private static final Logger _logger = LoggerFactory.getLogger(JMSSelectorFilter.class);
+
+ private String _selector;
+ private BooleanExpression _matcher;
+
+ public JMSSelectorFilter(String selector) throws AMQInternalException
+ {
+ _selector = selector;
+ if (JMSSelectorFilter._logger.isDebugEnabled())
+ {
+ JMSSelectorFilter._logger.debug("Created JMSSelectorFilter with selector:" + _selector);
+ }
+ _matcher = new SelectorParser().parse(selector);
+ }
+
+ public boolean matches(AbstractJMSMessage message)
+ {
+ try
+ {
+ boolean match = _matcher.matches(message);
+ if (JMSSelectorFilter._logger.isDebugEnabled())
+ {
+ JMSSelectorFilter._logger.debug(message + " match(" + match + ") selector(" + System
+ .identityHashCode(_selector) + "):" + _selector);
+ }
+ return match;
+ }
+ catch (AMQInternalException e)
+ {
+ JMSSelectorFilter._logger.warn("Caght exception when evaluating message selector for message " + message, e);
+ }
+ return false;
+ }
+
+ public String getSelector()
+ {
+ return _selector;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/LogicExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/LogicExpression.java
new file mode 100644
index 0000000000..7ef85cbacb
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/LogicExpression.java
@@ -0,0 +1,108 @@
+/* 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.filter;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+
+/**
+ * A filter performing a comparison of two objects
+ */
+public abstract class LogicExpression extends BinaryExpression implements BooleanExpression
+{
+
+ public static BooleanExpression createOR(BooleanExpression lvalue, BooleanExpression rvalue)
+ {
+ return new LogicExpression(lvalue, rvalue)
+ {
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+
+ Boolean lv = (Boolean) left.evaluate(message);
+ // Can we do an OR shortcut??
+ if ((lv != null) && lv.booleanValue())
+ {
+ return Boolean.TRUE;
+ }
+
+ Boolean rv = (Boolean) right.evaluate(message);
+
+ return (rv == null) ? null : rv;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "OR";
+ }
+ };
+ }
+
+ public static BooleanExpression createAND(BooleanExpression lvalue, BooleanExpression rvalue)
+ {
+ return new LogicExpression(lvalue, rvalue)
+ {
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+
+ Boolean lv = (Boolean) left.evaluate(message);
+
+ // Can we do an AND shortcut??
+ if (lv == null)
+ {
+ return null;
+ }
+
+ if (!lv.booleanValue())
+ {
+ return Boolean.FALSE;
+ }
+
+ Boolean rv = (Boolean) right.evaluate(message);
+
+ return (rv == null) ? null : rv;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "AND";
+ }
+ };
+ }
+
+ /**
+ * @param left
+ * @param right
+ */
+ public LogicExpression(BooleanExpression left, BooleanExpression right)
+ {
+ super(left, right);
+ }
+
+ public abstract Object evaluate(AbstractJMSMessage message) throws AMQInternalException;
+
+ public boolean matches(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/MessageFilter.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/MessageFilter.java
new file mode 100644
index 0000000000..62e4a28c1e
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/MessageFilter.java
@@ -0,0 +1,27 @@
+/* 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.filter;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+
+public interface MessageFilter
+{
+ boolean matches(AbstractJMSMessage message) throws AMQInternalException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/PropertyExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/PropertyExpression.java
new file mode 100644
index 0000000000..b7b6bd57bc
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/PropertyExpression.java
@@ -0,0 +1,297 @@
+/* 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.filter;
+
+import java.util.HashMap;
+
+import javax.jms.JMSException;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents a property expression
+ */
+public class PropertyExpression implements Expression
+{
+ // Constants - defined the same as JMS
+ private static final int NON_PERSISTENT = 1;
+ private static final int DEFAULT_PRIORITY = 4;
+
+ private static final Logger _logger = LoggerFactory.getLogger(PropertyExpression.class);
+
+ private static final HashMap<String, Expression> JMS_PROPERTY_EXPRESSIONS = new HashMap<String, Expression>();
+
+ static
+ {
+ JMS_PROPERTY_EXPRESSIONS.put("JMSDestination", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ //TODO
+ return null;
+ }
+ });
+ JMS_PROPERTY_EXPRESSIONS.put("JMSReplyTo", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ return message.getReplyToString();
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSType", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ try
+ {
+ return message.getJMSType();
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Error evaluating property", e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSDeliveryMode", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ try
+ {
+ int mode = message.getJMSDeliveryMode();
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("JMSDeliveryMode is :" + mode);
+ }
+
+ return mode;
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Error evaluating property",e);
+ }
+
+ return NON_PERSISTENT;
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSPriority", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ try
+ {
+ return message.getJMSPriority();
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Error evaluating property",e);
+ }
+
+ return DEFAULT_PRIORITY;
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("AMQMessageID", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+
+ try
+ {
+ return message.getJMSMessageID();
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Error evaluating property",e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSTimestamp", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ try
+ {
+ return message.getJMSTimestamp();
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Error evaluating property",e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSCorrelationID", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+
+ try
+ {
+ return message.getJMSCorrelationID();
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Error evaluating property",e);
+
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSExpiration", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+
+ try
+ {
+ return message.getJMSExpiration();
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Error evaluating property",e);
+ return null;
+ }
+
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSRedelivered", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ try
+ {
+ return message.getJMSRedelivered();
+ }
+ catch (JMSException e)
+ {
+ _logger.warn("Error evaluating property",e);
+ return null;
+ }
+ }
+ });
+
+ JMS_PROPERTY_EXPRESSIONS.put("JMSMessageID", new Expression()
+ {
+ public Object evaluate(AbstractJMSMessage message)
+ {
+ try
+ {
+ return message.getJMSMessageID();
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Error evaluating property",e);
+
+ return null;
+ }
+
+ }
+ });
+
+ }
+
+ private final String name;
+ private final Expression jmsPropertyExpression;
+
+ public PropertyExpression(String name)
+ {
+ this.name = name;
+ jmsPropertyExpression = JMS_PROPERTY_EXPRESSIONS.get(name);
+ }
+
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+
+ if (jmsPropertyExpression != null)
+ {
+ return jmsPropertyExpression.evaluate(message);
+ }
+ else
+ {
+
+ try
+ {
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Looking up property:" + name);
+ _logger.debug("Properties are:" + message.getPropertyNames());
+ }
+ return message.getObjectProperty(name);
+ }
+ catch(JMSException e)
+ {
+ throw new AMQInternalException("Exception evaluating properties for filter", e);
+ }
+ }
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString()
+ {
+ return name;
+ }
+
+ /**
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return name.hashCode();
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object o)
+ {
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+ return name.equals(((PropertyExpression) o).name);
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/filter/UnaryExpression.java b/qpid/java/client/src/main/java/org/apache/qpid/filter/UnaryExpression.java
new file mode 100644
index 0000000000..0fc3382b7e
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/filter/UnaryExpression.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.filter;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.qpid.AMQInternalException;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+
+/**
+ * An expression which performs an operation on two expression values
+ */
+public abstract class UnaryExpression implements Expression
+{
+
+ private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE);
+ protected Expression right;
+
+ public static Expression createNegate(Expression left)
+ {
+ return new UnaryExpression(left)
+ {
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (rvalue instanceof Number)
+ {
+ return UnaryExpression.negate((Number) rvalue);
+ }
+
+ return null;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "-";
+ }
+ };
+ }
+
+ public static BooleanExpression createInExpression(PropertyExpression right, List elements, final boolean not)
+ {
+
+ // Use a HashSet if there are many elements.
+ Collection t;
+ if (elements.size() == 0)
+ {
+ t = null;
+ }
+ else if (elements.size() < 5)
+ {
+ t = elements;
+ }
+ else
+ {
+ t = new HashSet(elements);
+ }
+
+ final Collection inList = t;
+
+ return new BooleanUnaryExpression(right)
+ {
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (rvalue.getClass() != String.class)
+ {
+ return null;
+ }
+
+ if (((inList != null) && inList.contains(rvalue)) ^ not)
+ {
+ return Boolean.TRUE;
+ }
+ else
+ {
+ return Boolean.FALSE;
+ }
+
+ }
+
+ public String toString()
+ {
+ StringBuffer answer = new StringBuffer();
+ answer.append(right);
+ answer.append(" ");
+ answer.append(getExpressionSymbol());
+ answer.append(" ( ");
+
+ int count = 0;
+ for (Iterator i = inList.iterator(); i.hasNext();)
+ {
+ Object o = (Object) i.next();
+ if (count != 0)
+ {
+ answer.append(", ");
+ }
+
+ answer.append(o);
+ count++;
+ }
+
+ answer.append(" )");
+
+ return answer.toString();
+ }
+
+ public String getExpressionSymbol()
+ {
+ if (not)
+ {
+ return "NOT IN";
+ }
+ else
+ {
+ return "IN";
+ }
+ }
+ };
+ }
+
+ abstract static class BooleanUnaryExpression extends UnaryExpression implements BooleanExpression
+ {
+ public BooleanUnaryExpression(Expression left)
+ {
+ super(left);
+ }
+
+ public boolean matches(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object object = evaluate(message);
+
+ return (object != null) && (object == Boolean.TRUE);
+ }
+ }
+
+ ;
+
+ public static BooleanExpression createNOT(BooleanExpression left)
+ {
+ return new BooleanUnaryExpression(left)
+ {
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Boolean lvalue = (Boolean) right.evaluate(message);
+ if (lvalue == null)
+ {
+ return null;
+ }
+
+ return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE;
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "NOT";
+ }
+ };
+ }
+ public static BooleanExpression createBooleanCast(Expression left)
+ {
+ return new BooleanUnaryExpression(left)
+ {
+ public Object evaluate(AbstractJMSMessage message) throws AMQInternalException
+ {
+ Object rvalue = right.evaluate(message);
+ if (rvalue == null)
+ {
+ return null;
+ }
+
+ if (!rvalue.getClass().equals(Boolean.class))
+ {
+ return Boolean.FALSE;
+ }
+
+ return ((Boolean) rvalue).booleanValue() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public String toString()
+ {
+ return right.toString();
+ }
+
+ public String getExpressionSymbol()
+ {
+ return "";
+ }
+ };
+ }
+
+ private static Number negate(Number left)
+ {
+ Class clazz = left.getClass();
+ if (clazz == Integer.class)
+ {
+ return new Integer(-left.intValue());
+ }
+ else if (clazz == Long.class)
+ {
+ return new Long(-left.longValue());
+ }
+ else if (clazz == Float.class)
+ {
+ return new Float(-left.floatValue());
+ }
+ else if (clazz == Double.class)
+ {
+ return new Double(-left.doubleValue());
+ }
+ else if (clazz == BigDecimal.class)
+ {
+ // We ussually get a big deciamal when we have Long.MIN_VALUE constant in the
+ // Selector. Long.MIN_VALUE is too big to store in a Long as a positive so we store it
+ // as a Big decimal. But it gets Negated right away.. to here we try to covert it back
+ // to a Long.
+ BigDecimal bd = (BigDecimal) left;
+ bd = bd.negate();
+
+ if (UnaryExpression.BD_LONG_MIN_VALUE.compareTo(bd) == 0)
+ {
+ return new Long(Long.MIN_VALUE);
+ }
+
+ return bd;
+ }
+ else
+ {
+ throw new RuntimeException("Don't know how to negate: " + left);
+ }
+ }
+
+ public UnaryExpression(Expression left)
+ {
+ this.right = left;
+ }
+
+ public Expression getRight()
+ {
+ return right;
+ }
+
+ public void setRight(Expression expression)
+ {
+ right = expression;
+ }
+
+ /**
+ * @see Object#toString()
+ */
+ public String toString()
+ {
+ return "(" + getExpressionSymbol() + " " + right.toString() + ")";
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see Object#hashCode()
+ */
+ public int hashCode()
+ {
+ return toString().hashCode();
+ }
+
+ /**
+ * TODO: more efficient hashCode()
+ *
+ * @see Object#equals(Object)
+ */
+ public boolean equals(Object o)
+ {
+
+ if ((o == null) || !this.getClass().equals(o.getClass()))
+ {
+ return false;
+ }
+
+ return toString().equals(o.toString());
+
+ }
+
+ /**
+ * Returns the symbol that represents this binary expression. For example, addition is
+ * represented by "+"
+ *
+ * @return
+ */
+ public abstract String getExpressionSymbol();
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.java
new file mode 100644
index 0000000000..6d81f728c9
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/BrokerDetails.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.jms;
+
+import java.util.Map;
+
+import org.apache.qpid.client.SSLConfiguration;
+
+public interface BrokerDetails
+{
+
+ /*
+ * Known URL Options
+ * @see ConnectionURL
+ */
+ 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 String OPTIONS_IDLE_TIMEOUT = "idle_timeout"; // deprecated
+ public static final String OPTIONS_HEARTBEAT = "heartbeat";
+ public static final String OPTIONS_SASL_MECHS = "sasl_mechs";
+ public static final String OPTIONS_SASL_ENCRYPTION = "sasl_encryption";
+ public static final String OPTIONS_SSL = "ssl";
+ public static final String OPTIONS_TCP_NO_DELAY = "tcp_nodelay";
+ public static final String OPTIONS_SASL_PROTOCOL_NAME = "sasl_protocol";
+ public static final String OPTIONS_SASL_SERVER_NAME = "sasl_server";
+
+ public static final String OPTIONS_TRUST_STORE = "trust_store";
+ public static final String OPTIONS_TRUST_STORE_PASSWORD = "trust_store_password";
+ public static final String OPTIONS_KEY_STORE = "key_store";
+ public static final String OPTIONS_KEY_STORE_PASSWORD = "key_store_password";
+ public static final String OPTIONS_SSL_VERIFY_HOSTNAME = "ssl_verify_hostname";
+ public static final String OPTIONS_SSL_CERT_ALIAS = "ssl_cert_alias";
+
+ 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";
+
+ public static final String DEFAULT_TRANSPORT = TCP;
+
+ public static final String URL_FORMAT_EXAMPLE =
+ "<transport>://<hostname>[:<port Default=\"" + DEFAULT_PORT + "\">][?<option>='<value>'[,<option>='<value>']]";
+
+ public static final long DEFAULT_CONNECT_TIMEOUT = 30000L;
+ public static final boolean USE_SSL_DEFAULT = false;
+
+ // pulled these properties from the new BrokerDetails class in the qpid package
+ public static final String PROTOCOL_TCP = "tcp";
+ public static final String PROTOCOL_TLS = "tls";
+
+ public static final String VIRTUAL_HOST = "virtualhost";
+ public static final String CLIENT_ID = "client_id";
+ public static final String USERNAME = "username";
+ public static final String PASSWORD = "password";
+
+ String getHost();
+
+ void setHost(String host);
+
+ int getPort();
+
+ void setPort(int port);
+
+ String getTransport();
+
+ void setTransport(String transport);
+
+ String getProperty(String key);
+
+ void setProperty(String key, String value);
+
+ /**
+ * Ex: keystore path
+ *
+ * @return the Properties associated with this connection.
+ */
+ public Map<String,String> getProperties();
+
+ /**
+ * Sets the properties associated with this connection
+ *
+ * @param props the new p[roperties.
+ */
+ public void setProperties(Map<String,String> props);
+
+ long getTimeout();
+
+ void setTimeout(long timeout);
+
+ SSLConfiguration getSSLConfiguration();
+
+ void setSSLConfiguration(SSLConfiguration sslConfiguration);
+
+ boolean getBooleanProperty(String propName);
+
+ String toString();
+
+ boolean equals(Object o);
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/ChannelLimitReachedException.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/ChannelLimitReachedException.java
new file mode 100644
index 0000000000..e8c2b9d682
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/ChannelLimitReachedException.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * 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.jms;
+
+import javax.jms.ResourceAllocationException;
+
+/**
+ * Indicates that the maximum number of sessions per connection limit has been reached.
+ */
+public class ChannelLimitReachedException extends ResourceAllocationException
+{
+ private static final String ERROR_CODE = "1";
+
+ private long _limit;
+
+ public ChannelLimitReachedException(long limit)
+ {
+ super("Unable to create session, the maximum number of sessions per connection is " +
+ limit + ". You must either close one or more sessions or increase the " +
+ "maximum number of sessions available per connection.", ERROR_CODE);
+ _limit = limit;
+ }
+
+ public long getLimit()
+ {
+ return _limit;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/Connection.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/Connection.java
new file mode 100644
index 0000000000..616c6dbbec
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/Connection.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * 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.jms;
+
+import javax.jms.JMSException;
+
+public interface Connection extends javax.jms.Connection
+{
+ /**
+ * @return the maximum number of sessions supported by this Connection
+ */
+ long getMaximumChannelCount() throws JMSException;
+
+ void setConnectionListener(ConnectionListener listener);
+
+ /**
+ * Get the connection listener that has been registered with this connection, if any
+ *
+ * @return the listener or null if none has been set
+ */
+ ConnectionListener getConnectionListener();
+
+ /**
+ * Create a session specifying the prefetch limit of messages.
+ *
+ * @param transacted
+ * @param acknowledgeMode
+ * @param prefetch the maximum number of messages to buffer in the client. This
+ * applies as a total across all consumers
+ * @return
+ * @throws JMSException
+ */
+ org.apache.qpid.jms.Session createSession(boolean transacted, int acknowledgeMode,
+ int prefetch) throws JMSException;
+
+
+ /**
+ * Create a session specifying the prefetch limit of messages.
+ *
+ * @param transacted
+ * @param acknowledgeMode
+ * @param prefetchHigh the maximum number of messages to buffer in the client.
+ * This applies as a total across all consumers
+ * @param prefetchLow the number of messages that must be in the buffer in the client to renable message flow.
+ * This applies as a total across all consumers
+ * @return
+ * @throws JMSException
+ */
+ org.apache.qpid.jms.Session createSession(boolean transacted, int acknowledgeMode,
+ int prefetchHigh, int prefetchLow) throws JMSException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionListener.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionListener.java
new file mode 100644
index 0000000000..11c235901c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionListener.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * 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.jms;
+
+public interface ConnectionListener
+{
+ /**
+ * Called when bytes have been transmitted to the server
+ * @param count the number of bytes sent in total since the connection was opened
+ */
+ void bytesSent(long count);
+
+ /**
+ * Called when some bytes have been received on a connection
+ * @param count the number of bytes received in total since the connection was opened
+ */
+ void bytesReceived(long count);
+
+ /**
+ * Called after the infrastructure has detected that failover is required but before attempting failover.
+ * @param redirect true if the broker requested redirect. false if failover is occurring due to a connection error.
+ * @return true to continue failing over, false to veto failover and raise a connection exception
+ */
+ boolean preFailover(boolean redirect);
+
+ /**
+ * Called after connection has been made to another broker after failover has been started but before
+ * any resubscription has been done.
+ * @return true to continue with resubscription, false to prevent automatic resubscription. This is useful in
+ * cases where the application wants to handle resubscription. Note that in the latter case all sessions, producers
+ * and consumers are invalidated.
+ */
+ boolean preResubscribe();
+
+ /**
+ * Called once failover has completed successfully. This is called irrespective of whether the client has
+ * vetoed automatic resubscription.
+ */
+ void failoverComplete();
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.java
new file mode 100644
index 0000000000..0e8ca60686
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/ConnectionURL.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.jms;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ProtocolVersion;
+
+import java.util.List;
+
+/**
+ Connection URL format
+ amqp://[user:pass@][clientid]/virtualhost?brokerlist='tcp://host:port?option=\'value\'&option=\'value\';vm://:3/virtualpath?option=\'value\''&failover='method?option=\'value\'&option='value''"
+ Options are of course optional except for requiring a single broker in the broker list.
+ The option seperator is defined to be either '&' or ','
+ */
+public interface ConnectionURL
+{
+ public static final String AMQ_PROTOCOL = "amqp";
+ public static final String OPTIONS_SYNC_PERSISTENCE = "sync_persistence";
+ public static final String OPTIONS_MAXPREFETCH = "maxprefetch";
+ public static final String OPTIONS_SYNC_ACK = "sync_ack";
+ public static final String OPTIONS_SYNC_PUBLISH = "sync_publish";
+ public static final String OPTIONS_USE_LEGACY_MAP_MESSAGE_FORMAT = "use_legacy_map_msg_format";
+ public static final String OPTIONS_BROKERLIST = "brokerlist";
+ public static final String OPTIONS_FAILOVER = "failover";
+ public static final String OPTIONS_FAILOVER_CYCLE = "cyclecount";
+ public static final String OPTIONS_DEFAULT_TOPIC_EXCHANGE = "defaultTopicExchange";
+ public static final String OPTIONS_DEFAULT_QUEUE_EXCHANGE = "defaultQueueExchange";
+ public static final String OPTIONS_TEMPORARY_TOPIC_EXCHANGE = "temporaryTopicExchange";
+ 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();
+
+ String getFailoverOption(String key);
+
+ int getBrokerCount();
+
+ BrokerDetails getBrokerDetails(int index);
+
+ void addBrokerDetails(BrokerDetails broker);
+
+ void setBrokerDetails(List<BrokerDetails> brokers);
+
+ List<BrokerDetails> getAllBrokerDetails();
+
+ String getClientName();
+
+ void setClientName(String clientName);
+
+ String getUsername();
+
+ void setUsername(String username);
+
+ String getPassword();
+
+ void setPassword(String password);
+
+ String getVirtualHost();
+
+ void setVirtualHost(String virtualHost);
+
+ String getOption(String key);
+
+ void setOption(String key, String value);
+
+ AMQShortString getDefaultQueueExchangeName();
+
+ AMQShortString getDefaultTopicExchangeName();
+
+ AMQShortString getTemporaryQueueExchangeName();
+
+ AMQShortString getTemporaryTopicExchangeName();
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java
new file mode 100644
index 0000000000..56abf03c81
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/FailoverPolicy.java
@@ -0,0 +1,324 @@
+/*
+ *
+ * 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.jms;
+
+import org.apache.qpid.jms.failover.FailoverExchangeMethod;
+import org.apache.qpid.jms.failover.FailoverMethod;
+import org.apache.qpid.jms.failover.FailoverRoundRobinServers;
+import org.apache.qpid.jms.failover.FailoverSingleServer;
+import org.apache.qpid.jms.failover.NoFailover;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FailoverPolicy
+{
+ private static final Logger _logger = LoggerFactory.getLogger(FailoverPolicy.class);
+
+ private static final long MINUTE = 60000L;
+
+ private static final long DEFAULT_METHOD_TIMEOUT = 1 * MINUTE;
+
+ private FailoverMethod[] _methods = new FailoverMethod[1];
+
+ private int _currentMethod;
+
+ private int _methodsRetries;
+
+ private int _currentRetry;
+
+ private boolean _timing;
+
+ private long _lastMethodTime;
+ private long _lastFailTime;
+
+ public FailoverPolicy(ConnectionURL connectionDetails, Connection conn)
+ {
+ FailoverMethod method;
+
+ // todo This should be integrated in to the connection url when it supports
+ // multiple strategies.
+
+ _methodsRetries = 0;
+
+ if (connectionDetails.getFailoverMethod() == null)
+ {
+ if (connectionDetails.getBrokerCount() > 1)
+ {
+ method = new FailoverRoundRobinServers(connectionDetails);
+ }
+ else
+ {
+ method = new FailoverSingleServer(connectionDetails);
+ }
+ }
+ else
+ {
+ String failoverMethod = connectionDetails.getFailoverMethod();
+
+ /*
+ if (failoverMethod.equals(FailoverMethod.RANDOM))
+ {
+ //todo write a random connection Failover
+ }
+ */
+ if (failoverMethod.equals(FailoverMethod.SINGLE_BROKER))
+ {
+ method = new FailoverSingleServer(connectionDetails);
+ }
+ else
+ {
+ if (failoverMethod.equals(FailoverMethod.ROUND_ROBIN))
+ {
+ method = new FailoverRoundRobinServers(connectionDetails);
+ }
+ else if (failoverMethod.equals(FailoverMethod.FAILOVER_EXCHANGE))
+ {
+ method = new FailoverExchangeMethod(connectionDetails, conn);
+ }
+ else if (failoverMethod.equals(FailoverMethod.NO_FAILOVER))
+ {
+ method = new NoFailover(connectionDetails);
+ }
+ else
+ {
+ try
+ {
+ Class[] constructorSpec = { ConnectionURL.class };
+ Object[] params = { connectionDetails };
+
+ method =
+ (FailoverMethod) ClassLoader.getSystemClassLoader().loadClass(failoverMethod)
+ .getConstructor(constructorSpec).newInstance(params);
+ }
+ catch (Exception cnfe)
+ {
+ throw new IllegalArgumentException("Unknown failover method:" + failoverMethod, cnfe);
+ }
+ }
+ }
+ }
+
+ if (method == null)
+ {
+ throw new IllegalArgumentException("Unknown failover method specified.");
+ }
+
+ reset();
+
+ _methods[_currentMethod] = method;
+ }
+
+ public FailoverPolicy(FailoverMethod method)
+ {
+ this(method, 0);
+ }
+
+ public FailoverPolicy(FailoverMethod method, int retries)
+ {
+ _methodsRetries = retries;
+
+ reset();
+
+ _methods[_currentMethod] = method;
+ }
+
+ private void reset()
+ {
+ _currentMethod = 0;
+ _currentRetry = 0;
+ _timing = false;
+
+ }
+
+ public boolean failoverAllowed()
+ {
+ boolean failoverAllowed;
+
+ if (_timing)
+ {
+ long now = System.currentTimeMillis();
+
+ if ((now - _lastMethodTime) >= DEFAULT_METHOD_TIMEOUT)
+ {
+ _logger.info("Failover method timeout");
+ _lastMethodTime = now;
+
+ if (!nextMethod())
+ {
+ return false;
+ }
+
+ }
+ else
+ {
+ _lastMethodTime = now;
+ }
+ }
+ else
+ {
+ _timing = true;
+ _lastMethodTime = System.currentTimeMillis();
+ _lastFailTime = _lastMethodTime;
+ }
+
+ if (_methods[_currentMethod].failoverAllowed())
+ {
+ failoverAllowed = true;
+ }
+ else
+ {
+ if (_currentMethod < (_methods.length - 1))
+ {
+ nextMethod();
+ _logger.info("Changing method to " + _methods[_currentMethod].methodName());
+
+ return failoverAllowed();
+ }
+ else
+ {
+ return cycleMethods();
+ }
+ }
+
+ return failoverAllowed;
+ }
+
+ private boolean nextMethod()
+ {
+ if (_currentMethod < (_methods.length - 1))
+ {
+ _currentMethod++;
+ _methods[_currentMethod].reset();
+
+ return true;
+ }
+ else
+ {
+ return cycleMethods();
+ }
+ }
+
+ private boolean cycleMethods()
+ {
+ if (_currentRetry < _methodsRetries)
+ {
+ _currentRetry++;
+
+ _currentMethod = 0;
+
+ _logger.info("Retrying methods starting with " + _methods[_currentMethod].methodName());
+ _methods[_currentMethod].reset();
+
+ return failoverAllowed();
+ }
+ else
+ {
+ _logger.debug("All failover methods exhausted");
+
+ return false;
+ }
+ }
+
+ /**
+ * Notification that connection was successful.
+ */
+ public void attainedConnection()
+ {
+ _currentRetry = 0;
+
+ _methods[_currentMethod].attainedConnection();
+
+ _timing = false;
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ return _methods[_currentMethod].getCurrentBrokerDetails();
+ }
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ return _methods[_currentMethod].getNextBrokerDetails();
+ }
+
+ public void setBroker(BrokerDetails broker)
+ {
+ _methods[_currentMethod].setBroker(broker);
+ }
+
+ public void addMethod(FailoverMethod method)
+ {
+ int len = _methods.length + 1;
+ FailoverMethod[] newMethods = new FailoverMethod[len];
+ System.arraycopy(_methods, 0, newMethods, 0, _methods.length);
+ int index = len - 1;
+ newMethods[index] = method;
+ _methods = newMethods;
+ }
+
+ public void setMethodRetries(int retries)
+ {
+ _methodsRetries = retries;
+ }
+
+ public FailoverMethod getCurrentMethod()
+ {
+ if ((_currentMethod >= 0) && (_currentMethod < (_methods.length)))
+ {
+ return _methods[_currentMethod];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Failover Policy:\n");
+
+ if (failoverAllowed())
+ {
+ sb.append("Failover allowed\n");
+ }
+ else
+ {
+ sb.append("Failover not allowed\n");
+ }
+
+ sb.append("Failover policy methods\n");
+ for (int i = 0; i < _methods.length; i++)
+ {
+
+ if (i == _currentMethod)
+ {
+ sb.append(">");
+ }
+
+ sb.append(_methods[i].toString());
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/Message.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/Message.java
new file mode 100644
index 0000000000..53c615a1fd
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/Message.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * 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.jms;
+
+import javax.jms.JMSException;
+
+public interface Message extends javax.jms.Message
+{
+ public static final String JMS_TYPE = "x-jms-type";
+
+ public void acknowledgeThis() throws JMSException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageConsumer.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageConsumer.java
new file mode 100644
index 0000000000..caac2b5c1f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageConsumer.java
@@ -0,0 +1,27 @@
+/*
+ *
+ * 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.jms;
+
+/**
+ */
+public interface MessageConsumer extends javax.jms.MessageConsumer
+{
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java
new file mode 100644
index 0000000000..b830c377b8
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/MessageProducer.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * 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.jms;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+/**
+ */
+public interface MessageProducer extends javax.jms.MessageProducer
+{
+ /**
+ * Set the default MIME type for messages produced by this producer. This reduces the overhead of each message.
+ * @param mimeType
+ */
+ void setMimeType(String mimeType) throws JMSException;
+
+ /**
+ * Set the default encoding for messages produced by this producer. This reduces the overhead of each message.
+ * @param encoding the encoding as understood by XXXX how do I specify this?? RG
+ * @throws UnsupportedEncodingException if the encoding is not understood
+ */
+ void setEncoding(String encoding) throws UnsupportedEncodingException, JMSException;
+
+ void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean immediate)
+ throws JMSException;
+
+ void send(Destination destination, Message message, int deliveryMode,
+ int priority, long timeToLive, boolean mandatory, boolean immediate)
+ throws JMSException;
+
+ void send(Destination destination, Message message, int deliveryMode, int priority, long timeToLive,
+ boolean mandatory, boolean immediate, boolean waitUntilSent) throws JMSException;
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/Session.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/Session.java
new file mode 100644
index 0000000000..5287381fae
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/Session.java
@@ -0,0 +1,101 @@
+/*
+ *
+ * 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.jms;
+
+import org.apache.qpid.framing.AMQShortString;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+
+
+public interface Session extends javax.jms.Session
+{
+ /**
+ * Indicates that no client acknowledgements are required. Broker assumes that once it has delivered
+ * a message packet successfully it is acknowledged.
+ */
+ static final int NO_ACKNOWLEDGE = 257;
+
+ /**
+ * Pre acknowledge means that an ack is sent per message but sent before user code has processed
+ * the message (i.e. before the onMessage() call or the receive() method has returned).
+ */
+ static final int PRE_ACKNOWLEDGE = 258;
+
+ MessageConsumer createConsumer(Destination destination,
+ int prefetch,
+ boolean noLocal,
+ boolean exclusive,
+ String selector) throws JMSException;
+
+ MessageConsumer createConsumer(Destination destination,
+ int prefetchHigh,
+ int prefetchLow,
+ boolean noLocal,
+ boolean exclusive,
+ String selector) throws JMSException;
+
+ /**
+ * @return the prefetch value used by default for consumers created on this session.
+ */
+ int getDefaultPrefetch();
+
+ /**
+ * @return the High water prefetch value used by default for consumers created on this session.
+ */
+ int getDefaultPrefetchHigh();
+
+ /**
+ * @return the Low water prefetch value used by default for consumers created on this session.
+ */
+ int getDefaultPrefetchLow();
+
+ /**
+ * Create a producer
+ * @param destination
+ * @param mandatory the value of the mandatory flag used by default on the producer
+ * @param immediate the value of the immediate flag used by default on the producer
+ * @return
+ * @throws JMSException
+ */
+ MessageProducer createProducer(Destination destination, boolean mandatory, boolean immediate)
+ throws JMSException;
+
+ /**
+ * Create a producer
+ * @param destination
+ * @param immediate the value of the immediate flag used by default on the producer
+ * @return
+ * @throws JMSException
+ */
+ MessageProducer createProducer(Destination destination, boolean immediate)
+ throws JMSException;
+
+ AMQShortString getTemporaryTopicExchangeName();
+
+ AMQShortString getDefaultQueueExchangeName();
+
+ AMQShortString getDefaultTopicExchangeName();
+
+ AMQShortString getTemporaryQueueExchangeName();
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/TopicSubscriber.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/TopicSubscriber.java
new file mode 100644
index 0000000000..1dbe464230
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/TopicSubscriber.java
@@ -0,0 +1,32 @@
+package org.apache.qpid.jms;
+/*
+ *
+ * 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.qpid.AMQException;
+
+import javax.jms.Topic;
+
+public interface TopicSubscriber extends javax.jms.TopicSubscriber
+{
+
+ void addBindingKey(Topic topic, String bindingKey) throws AMQException;
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverExchangeMethod.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverExchangeMethod.java
new file mode 100644
index 0000000000..ef30f2adbc
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverExchangeMethod.java
@@ -0,0 +1,320 @@
+/*
+ *
+ * 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.jms.failover;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQAnyDestination;
+import org.apache.qpid.client.AMQBrokerDetails;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.Connection;
+import org.apache.qpid.jms.ConnectionURL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * When using the Failover exchange a single broker is supplied in the URL.
+ * The connection will then connect to the cluster using the above broker details.
+ * Once connected, the membership details of the cluster will be obtained via
+ * subscribing to a queue bound to the failover exchange.
+ *
+ * The failover exchange will provide a list of broker URLs in the format "transport:ip:port"
+ * Out of this list we only select brokers that match the transport of the original
+ * broker supplied in the connection URL.
+ *
+ * Also properties defined for the original broker will be applied to all the brokers selected
+ * from the list.
+ */
+
+public class FailoverExchangeMethod implements FailoverMethod, MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(FailoverExchangeMethod.class);
+
+ /** This is not safe to use until attainConnection is called */
+ private Connection _conn;
+
+ /** Protects the broker list when modifications happens */
+ private Object _brokerListLock = new Object();
+
+ /** The session used to subscribe to failover exchange */
+ private Session _ssn;
+
+ private BrokerDetails _originalBrokerDetail;
+
+ /** The index into the hostDetails array of the broker to which we are connected */
+ private int _currentBrokerIndex = 0;
+
+ /** The broker currently selected **/
+ private BrokerDetails _currentBrokerDetail;
+
+ /** Array of BrokerDetail used to make connections. */
+ private ConnectionURL _connectionDetails;
+
+ /** Denotes the number of failed attempts **/
+ private int _failedAttemps = 0;
+
+ public FailoverExchangeMethod(ConnectionURL connectionDetails, Connection conn)
+ {
+ _connectionDetails = connectionDetails;
+ _originalBrokerDetail = _connectionDetails.getBrokerDetails(0);
+
+ // This is not safe to use until attainConnection is called, as this ref will not initialized fully.
+ // The reason being this constructor is called inside the AMWConnection constructor.
+ // It would be best if we find a way to pass this ref after AMQConnection is fully initialized.
+ _conn = conn;
+ }
+
+ private void subscribeForUpdates() throws JMSException
+ {
+ if (_ssn == null)
+ {
+ _ssn = _conn.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ MessageConsumer cons = _ssn.createConsumer(
+ new AMQAnyDestination(new AMQShortString("amq.failover"),
+ new AMQShortString("amq.failover"),
+ new AMQShortString(""),
+ true,true,null,false,
+ new AMQShortString[0]));
+ cons.setMessageListener(this);
+ }
+ }
+
+ public void onMessage(Message m)
+ {
+ _logger.info("Failover exchange notified cluster membership change");
+
+ String currentBrokerIP = "";
+ try
+ {
+ currentBrokerIP = InetAddress.getByName(_currentBrokerDetail.getHost()).getHostAddress();
+ }
+ catch(Exception e)
+ {
+ _logger.warn("Unable to resolve current broker host name",e);
+ }
+
+ List<BrokerDetails> brokerList = new ArrayList<BrokerDetails>();
+ try
+ {
+ List<String> list = (List<String>)m.getObjectProperty("amq.failover");
+ for (String brokerEntry:list)
+ {
+ String[] urls = brokerEntry.substring(5) .split(",");
+ // Iterate until you find the correct transport
+ // Need to reconsider the logic when the C++ broker supports
+ // SSL URLs.
+ for (String url:urls)
+ {
+ String[] tokens = url.split(":");
+ if (tokens[0].equalsIgnoreCase(_originalBrokerDetail.getTransport()))
+ {
+ BrokerDetails broker = new AMQBrokerDetails();
+ broker.setTransport(tokens[0]);
+ broker.setHost(tokens[1]);
+ broker.setPort(Integer.parseInt(tokens[2]));
+ broker.setProperties(_originalBrokerDetail.getProperties());
+ broker.setSSLConfiguration(_originalBrokerDetail.getSSLConfiguration());
+ brokerList.add(broker);
+
+ if (currentBrokerIP.equals(broker.getHost()) &&
+ _currentBrokerDetail.getPort() == broker.getPort())
+ {
+ _currentBrokerIndex = brokerList.indexOf(broker);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ catch(JMSException e)
+ {
+ _logger.error("Error parsing the message sent by failover exchange",e);
+ }
+
+ synchronized (_brokerListLock)
+ {
+ _connectionDetails.setBrokerDetails(brokerList);
+ }
+
+ _logger.info("============================================================");
+ _logger.info("Updated cluster membership details " + _connectionDetails);
+ _logger.info("============================================================");
+ }
+
+ public void attainedConnection()
+ {
+ try
+ {
+ _failedAttemps = 0;
+ _logger.info("============================================================");
+ _logger.info("Attained connection ");
+ _logger.info("============================================================");
+ subscribeForUpdates();
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Unable to subscribe for cluster membership updates",e);
+ }
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ synchronized (_brokerListLock)
+ {
+ _currentBrokerDetail = _connectionDetails.getBrokerDetails(_currentBrokerIndex);
+ return _currentBrokerDetail;
+ }
+ }
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ BrokerDetails broker = null;
+
+ synchronized(_brokerListLock)
+ {
+ if (_currentBrokerIndex == (_connectionDetails.getBrokerCount() - 1))
+ {
+ _currentBrokerIndex = 0;
+ }
+ else
+ {
+ _currentBrokerIndex++;
+ }
+
+ broker = _connectionDetails.getBrokerDetails(_currentBrokerIndex);
+
+ // When the broker list is updated it will include the current broker as well
+ // There is no point trying it again, so trying the next one.
+ if (_currentBrokerDetail != null &&
+ broker.getHost().equals(_currentBrokerDetail.getHost()) &&
+ broker.getPort() == _currentBrokerDetail.getPort())
+ {
+ if (_connectionDetails.getBrokerCount() > 1)
+ {
+ return getNextBrokerDetails();
+ }
+ else
+ {
+ _failedAttemps ++;
+ return null;
+ }
+ }
+ }
+
+ String delayStr = broker.getProperty(BrokerDetails.OPTIONS_CONNECT_DELAY);
+ if (delayStr != null)
+ {
+ 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.");
+ }
+
+ _failedAttemps ++;
+ _currentBrokerDetail = broker;
+
+ return broker;
+ }
+
+ public boolean failoverAllowed()
+ {
+ // We allow to Failover provided
+ // our broker list is not empty and
+ // we haven't gone through all of them
+
+ boolean b = _connectionDetails.getBrokerCount() > 0 &&
+ _failedAttemps <= _connectionDetails.getBrokerCount();
+
+
+ _logger.info("============================================================");
+ _logger.info(toString());
+ _logger.info("FailoverAllowed " + b);
+ _logger.info("============================================================");
+
+ return b;
+ }
+
+ public void reset()
+ {
+ _failedAttemps = 0;
+ }
+
+ public void setBroker(BrokerDetails broker)
+ {
+ // not sure if this method is needed
+ }
+
+ public void setRetries(int maxRetries)
+ {
+ // no max retries we keep trying as long
+ // as we get updates
+ }
+
+ public String methodName()
+ {
+ return "Failover Exchange";
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("FailoverExchange:\n");
+ sb.append("\n Current Broker Index:");
+ sb.append(_currentBrokerIndex);
+ sb.append("\n Failed Attempts:");
+ sb.append(_failedAttemps);
+ sb.append("\n Orignal broker details:");
+ sb.append(_originalBrokerDetail).append("\n");
+ sb.append("\n -------- Broker List -----------\n");
+ for (int i = 0; i < _connectionDetails.getBrokerCount(); i++)
+ {
+ if (i == _currentBrokerIndex)
+ {
+ sb.append(">");
+ }
+
+ sb.append(_connectionDetails.getBrokerDetails(i));
+ sb.append("\n");
+ }
+ sb.append("--------------------------------\n");
+ return sb.toString();
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverMethod.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverMethod.java
new file mode 100644
index 0000000000..1cef067e5f
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverMethod.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * 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.jms.failover;
+
+import org.apache.qpid.jms.BrokerDetails;
+
+public interface FailoverMethod
+{
+ public static final String SINGLE_BROKER = "singlebroker";
+ public static final String ROUND_ROBIN = "roundrobin";
+ public static final String FAILOVER_EXCHANGE= "failover_exchange";
+ public static final String RANDOM = "random";
+ public static final String NO_FAILOVER = "nofailover";
+
+ /**
+ * Reset the Failover to initial conditions
+ */
+ void reset();
+
+ /**
+ * Check if failover is possible for this method
+ *
+ * @return true if failover is allowed
+ */
+ boolean failoverAllowed();
+
+ /**
+ * Notification to the Failover method that a connection has been attained.
+ */
+ void attainedConnection();
+
+ /**
+ * If there is no current BrokerDetails the null will be returned.
+ * @return The current BrokerDetail value to use
+ */
+ BrokerDetails getCurrentBrokerDetails();
+
+ /**
+ * Move to the next BrokerDetails if one is available.
+ * @return the next BrokerDetail or null if there is none.
+ */
+ BrokerDetails getNextBrokerDetails();
+
+ /**
+ * Set the currently active broker to be the new value.
+ * @param broker The new BrokerDetail value
+ */
+ void setBroker(BrokerDetails broker);
+
+ /**
+ * Set the retries for this method
+ * @param maxRetries the maximum number of time to retry this Method
+ */
+ void setRetries(int maxRetries);
+
+ /**
+ * @return The name of this method for display purposes.
+ */
+ String methodName();
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java
new file mode 100644
index 0000000000..41ba4974ec
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverRoundRobinServers.java
@@ -0,0 +1,253 @@
+/*
+ *
+ * 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.jms.failover;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FailoverRoundRobinServers implements FailoverMethod
+{
+ private static final Logger _logger = LoggerFactory.getLogger(FailoverRoundRobinServers.class);
+
+ /** The default number of times to cycle through all servers */
+ public static final int DEFAULT_CYCLE_RETRIES = 1;
+ /** The default number of times to retry each server */
+ public static final int DEFAULT_SERVER_RETRIES = 0;
+
+ /** The index into the hostDetails array of the broker to which we are connected */
+ private int _currentBrokerIndex = 0;
+
+ /** The number of times to retry connecting for each server */
+ private int _serverRetries;
+
+ /** The current number of retry attempts made */
+ private int _currentServerRetry = 0;
+
+ /** The number of times to cycle through the servers */
+ private int _cycleRetries;
+
+ /** The current number of cycles performed. */
+ private int _currentCycleRetries = 0;
+
+ /** Array of BrokerDetail used to make connections. */
+ protected ConnectionURL _connectionDetails;
+
+ public FailoverRoundRobinServers(ConnectionURL connectionDetails)
+ {
+ if (!(connectionDetails.getBrokerCount() > 0))
+ {
+ throw new IllegalArgumentException("At least one broker details must be specified.");
+ }
+
+ _connectionDetails = connectionDetails;
+
+ // There is no current broker at startup so set it to -1.
+ _currentBrokerIndex = 0;
+
+ String cycleRetries = _connectionDetails.getFailoverOption(ConnectionURL.OPTIONS_FAILOVER_CYCLE);
+
+ _cycleRetries = DEFAULT_CYCLE_RETRIES;
+
+ if (cycleRetries != null)
+ {
+ try
+ {
+ _cycleRetries = Integer.parseInt(cycleRetries);
+ }
+ catch (NumberFormatException nfe)
+ {
+ _logger.warn("Cannot set cycle Retries, " + cycleRetries + " is not a number. Using default: " + DEFAULT_CYCLE_RETRIES);
+ }
+ }
+
+ _currentCycleRetries = 0;
+
+ _serverRetries = 0;
+ _currentServerRetry = 0;
+ }
+
+ public void reset()
+ {
+ _currentBrokerIndex = 0;
+ _currentCycleRetries = 0;
+ _currentServerRetry = 0;
+ }
+
+ public boolean failoverAllowed()
+ {
+ _logger.info("==== Checking failoverAllowed() ====");
+ _logger.info(toString());
+ _logger.info("====================================");
+ return ((_currentCycleRetries < _cycleRetries) || (_currentServerRetry < _serverRetries));
+ }
+
+ public void attainedConnection()
+ {
+ _currentCycleRetries = 0;
+ _currentServerRetry = 0;
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ return _connectionDetails.getBrokerDetails(_currentBrokerIndex);
+ }
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ boolean doDelay = false;
+
+ if (_currentBrokerIndex == (_connectionDetails.getBrokerCount() - 1))
+ {
+ if (_currentServerRetry < _serverRetries)
+ {
+ _logger.info("Trying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ doDelay= _currentBrokerIndex != 0;
+ _currentServerRetry++;
+ }
+ else
+ {
+ _currentCycleRetries++;
+ // failed to connect to first broker
+ _currentBrokerIndex = 0;
+
+ setBroker(_connectionDetails.getBrokerDetails(_currentBrokerIndex));
+
+ // This is zero rather than -1 as we are already retrieving the details.
+ _currentServerRetry = 0;
+ }
+ // else - should force client to stop as max retries has been reached.
+ }
+ else
+ {
+ if (_currentServerRetry < _serverRetries)
+ {
+ _logger.info("Trying " + _connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ doDelay= _currentBrokerIndex != 0;
+
+ _currentServerRetry++;
+ }
+ else
+ {
+ _currentBrokerIndex++;
+
+ setBroker(_connectionDetails.getBrokerDetails(_currentBrokerIndex));
+ // This is zero rather than -1 as we are already retrieving the details.
+ _currentServerRetry = 0;
+ }
+ }
+
+ 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
+ {
+ // Only display if option not set. Not if deDelay==false.
+ if (delayStr == null)
+ {
+ _logger.info("No delay between connect retries, use tcp://host:port?connectdelay='value' to enable.");
+ }
+ }
+
+ return broker;
+ }
+
+ public void setBroker(BrokerDetails broker)
+ {
+
+ _connectionDetails.addBrokerDetails(broker);
+
+ int index = _connectionDetails.getAllBrokerDetails().indexOf(broker);
+
+ String serverRetries = broker.getProperty(BrokerDetails.OPTIONS_RETRY);
+
+ if (serverRetries != null)
+ {
+ try
+ {
+ _serverRetries = Integer.parseInt(serverRetries);
+ }
+ catch (NumberFormatException nfe)
+ {
+ _serverRetries = DEFAULT_SERVER_RETRIES;
+ }
+ }
+
+ _currentServerRetry = 0;
+ _currentBrokerIndex = index;
+ }
+
+ public void setRetries(int maxRetries)
+ {
+ _cycleRetries = maxRetries;
+ }
+
+ public String methodName()
+ {
+ return "Cycle Servers";
+ }
+
+ public String toString()
+ {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("Cycle Servers:\n");
+
+ sb.append("Cycle Retries:");
+ sb.append(_cycleRetries);
+ sb.append("\nCurrent Cycle:");
+ sb.append(_currentCycleRetries);
+ sb.append("\nServer Retries:");
+ sb.append(_serverRetries);
+ sb.append("\nCurrent Retry:");
+ sb.append(_currentServerRetry);
+ sb.append("\nCurrent Broker:");
+ sb.append(_currentBrokerIndex);
+ sb.append("\n");
+
+ for (int i = 0; i < _connectionDetails.getBrokerCount(); i++)
+ {
+ if (i == _currentBrokerIndex)
+ {
+ sb.append(">");
+ }
+
+ sb.append(_connectionDetails.getBrokerDetails(i));
+ sb.append("\n");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java
new file mode 100644
index 0000000000..d033a49f5c
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/FailoverSingleServer.java
@@ -0,0 +1,166 @@
+/*
+ *
+ * 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.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 = 0;
+
+ /** The details of the Single Server */
+ private BrokerDetails _brokerDetail;
+
+ /** The number of times to retry connecting to the sever */
+ protected int _retries;
+
+ /** The current number of attempts made to the server */
+ protected int _currentRetries = 0;
+
+
+ public FailoverSingleServer(ConnectionURL connectionDetails)
+ {
+ if (connectionDetails.getBrokerCount() > 0)
+ {
+ setBroker(connectionDetails.getBrokerDetails(0));
+ }
+ else
+ {
+ throw new IllegalArgumentException("BrokerDetails details required for connection.");
+ }
+ }
+
+ public FailoverSingleServer(BrokerDetails brokerDetail)
+ {
+ setBroker(brokerDetail);
+ }
+
+ public void reset()
+ {
+ _currentRetries = 0;
+ }
+
+ public boolean failoverAllowed()
+ {
+ return _currentRetries < _retries;
+ }
+
+ public void attainedConnection()
+ {
+ reset();
+ }
+
+ public BrokerDetails getCurrentBrokerDetails()
+ {
+ return _brokerDetail;
+ }
+
+ public BrokerDetails getNextBrokerDetails()
+ {
+ if (_currentRetries == _retries)
+ {
+ return null;
+ }
+ else
+ {
+ if (_currentRetries < _retries)
+ {
+ _currentRetries++;
+ }
+ }
+
+
+ String delayStr = _brokerDetail.getProperty(BrokerDetails.OPTIONS_CONNECT_DELAY);
+ if (delayStr != null && _currentRetries > 0)
+ {
+ 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 _brokerDetail;
+ }
+
+ public void setBroker(BrokerDetails broker)
+ {
+ if (broker == null)
+ {
+ throw new IllegalArgumentException("BrokerDetails details cannot be null");
+ }
+ _brokerDetail = broker;
+
+ String retries = broker.getProperty(BrokerDetails.OPTIONS_RETRY);
+ if (retries != null)
+ {
+ try
+ {
+ _retries = Integer.parseInt(retries);
+ }
+ catch (NumberFormatException nfe)
+ {
+ _retries = DEFAULT_SERVER_RETRIES;
+ }
+ }
+ else
+ {
+ _retries = DEFAULT_SERVER_RETRIES;
+ }
+
+ reset();
+ }
+
+ public void setRetries(int retries)
+ {
+ _retries = retries;
+ }
+
+ public String methodName()
+ {
+ return "Single Server";
+ }
+
+ public String toString()
+ {
+ return methodName()+":\n" +
+ "Max Retries:" + _retries +
+ "\nCurrent Retry:" + _currentRetries +
+ "\n" + _brokerDetail + "\n";
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/NoFailover.java b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/NoFailover.java
new file mode 100644
index 0000000000..1231324397
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jms/failover/NoFailover.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * 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.jms.failover;
+
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+
+/**
+ * Extend the Single Server Model to gain retry functionality but once connected do not attempt to failover.
+ */
+public class NoFailover extends FailoverSingleServer
+{
+ private boolean _connected = false;
+
+ public NoFailover(BrokerDetails brokerDetail)
+ {
+ super(brokerDetail);
+ }
+
+ public NoFailover(ConnectionURL connectionDetails)
+ {
+ super(connectionDetails);
+ }
+
+ @Override
+ public void attainedConnection()
+ {
+ _connected=true;
+ _currentRetries = _retries;
+ }
+
+ @Override
+ public String methodName()
+ {
+ return "NoFailover";
+ }
+
+ @Override
+ public String toString()
+ {
+ return super.toString() + (_connected ? "Connection attained." : "Never connected.") + "\n";
+ }
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jndi/Example.properties b/qpid/java/client/src/main/java/org/apache/qpid/jndi/Example.properties
new file mode 100644
index 0000000000..def53d8494
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jndi/Example.properties
@@ -0,0 +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.
+#
+java.naming.factory.initial = org.apache.qpid.jndi.PropertiesFileInitialConextFactory
+
+# use the following property to configure the default connector
+#java.naming.provider.url - ignored.
+
+# register some connection factories
+# connectionfactory.[jndiname] = [ConnectionURL]
+connectionfactory.local = amqp://guest:guest@clientid/testpath?brokerlist='vm://:1'
+
+# register some queues in JNDI using the form
+# queue.[jndiName] = [physicalName]
+queue.MyQueue = example.MyQueue
+
+# register some topics in JNDI using the form
+# topic.[jndiName] = [physicalName]
+topic.ibmStocks = stocks.nyse.ibm
+
+# Register an AMQP destination in JNDI
+# NOTE: Qpid currently only supports direct,topics and headers
+# destination.[jniName] = [BindingURL]
+destination.direct = direct://amq.direct//directQueue
+destination.directQueue = direct://amq.direct//message_queue?routingkey="routing_key"
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jndi/NameParserImpl.java b/qpid/java/client/src/main/java/org/apache/qpid/jndi/NameParserImpl.java
new file mode 100644
index 0000000000..a3174aec7a
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jndi/NameParserImpl.java
@@ -0,0 +1,37 @@
+/**
+ *
+ * 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.jndi;
+
+import javax.naming.CompositeName;
+import javax.naming.Name;
+import javax.naming.NameParser;
+import javax.naming.NamingException;
+
+/**
+ * A default implementation of {@link NameParser}
+ * <p/>
+ * Based on class from ActiveMQ.
+ */
+public class NameParserImpl implements NameParser
+{
+ public Name parse(String name) throws NamingException
+ {
+ return new CompositeName(name);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java b/qpid/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java
new file mode 100644
index 0000000000..fec5af55c1
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jndi/PropertiesFileInitialContextFactory.java
@@ -0,0 +1,369 @@
+/*
+ *
+ * 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.jndi;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.jms.ConnectionFactory;
+import javax.jms.Destination;
+import javax.jms.Queue;
+import javax.jms.Topic;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.spi.InitialContextFactory;
+
+import org.apache.qpid.client.AMQConnectionFactory;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQHeadersExchange;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.util.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PropertiesFileInitialContextFactory implements InitialContextFactory
+{
+ protected final Logger _logger = LoggerFactory.getLogger(PropertiesFileInitialContextFactory.class);
+
+ private String CONNECTION_FACTORY_PREFIX = "connectionfactory.";
+ private String DESTINATION_PREFIX = "destination.";
+ private String QUEUE_PREFIX = "queue.";
+ private String TOPIC_PREFIX = "topic.";
+
+ public Context getInitialContext(Hashtable environment) throws NamingException
+ {
+ Map data = new ConcurrentHashMap();
+
+ try
+ {
+
+ String file = null;
+ if (environment.containsKey(Context.PROVIDER_URL))
+ {
+ file = (String) environment.get(Context.PROVIDER_URL);
+ }
+ else
+ {
+ file = System.getProperty(Context.PROVIDER_URL);
+ }
+
+ if (file != null)
+ {
+ _logger.info("Loading Properties from:" + file);
+
+ // Load the properties specified
+ BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
+ Properties p = new Properties();
+ try
+ {
+ p.load(inputStream);
+ }
+ finally
+ {
+ inputStream.close();
+ }
+
+ Strings.Resolver resolver = new Strings.ChainedResolver
+ (Strings.SYSTEM_RESOLVER, new Strings.PropertiesResolver(p));
+
+ for (Map.Entry me : p.entrySet())
+ {
+ String key = (String) me.getKey();
+ String value = (String) me.getValue();
+ String expanded = Strings.expand(value, resolver);
+ environment.put(key, expanded);
+ if (System.getProperty(key) == null)
+ {
+ System.setProperty(key, expanded);
+ }
+ }
+ _logger.info("Loaded Context Properties:" + environment.toString());
+ }
+ else
+ {
+ _logger.info("No Provider URL specified.");
+ }
+ }
+ catch (IOException ioe)
+ {
+ _logger.warn("Unable to load property file specified in Provider_URL:" + environment.get(Context.PROVIDER_URL) +"\n" +
+ "Due to:"+ioe.getMessage());
+ }
+
+ createConnectionFactories(data, environment);
+
+ createDestinations(data, environment);
+
+ createQueues(data, environment);
+
+ createTopics(data, environment);
+
+ return createContext(data, environment);
+ }
+
+ // Implementation methods
+ // -------------------------------------------------------------------------
+ protected ReadOnlyContext createContext(Map data, Hashtable environment)
+ {
+ return new ReadOnlyContext(environment, data);
+ }
+
+ protected void createConnectionFactories(Map data, Hashtable environment)
+ {
+ for (Iterator iter = environment.entrySet().iterator(); iter.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ String key = entry.getKey().toString();
+ if (key.startsWith(CONNECTION_FACTORY_PREFIX))
+ {
+ String jndiName = key.substring(CONNECTION_FACTORY_PREFIX.length());
+ ConnectionFactory cf = createFactory(entry.getValue().toString().trim());
+ if (cf != null)
+ {
+ data.put(jndiName, cf);
+ }
+ }
+ }
+ }
+
+ protected void createDestinations(Map data, Hashtable environment)
+ {
+ for (Iterator iter = environment.entrySet().iterator(); iter.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ String key = entry.getKey().toString();
+ if (key.startsWith(DESTINATION_PREFIX))
+ {
+ String jndiName = key.substring(DESTINATION_PREFIX.length());
+ Destination dest = createDestination(entry.getValue().toString().trim());
+ if (dest != null)
+ {
+ data.put(jndiName, dest);
+ }
+ }
+ }
+ }
+
+ protected void createQueues(Map data, Hashtable environment)
+ {
+ for (Iterator iter = environment.entrySet().iterator(); iter.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ String key = entry.getKey().toString();
+ if (key.startsWith(QUEUE_PREFIX))
+ {
+ String jndiName = key.substring(QUEUE_PREFIX.length());
+ Queue q = createQueue(entry.getValue().toString().trim());
+ if (q != null)
+ {
+ data.put(jndiName, q);
+ }
+ }
+ }
+ }
+
+ protected void createTopics(Map data, Hashtable environment)
+ {
+ for (Iterator iter = environment.entrySet().iterator(); iter.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry) iter.next();
+ String key = entry.getKey().toString();
+ if (key.startsWith(TOPIC_PREFIX))
+ {
+ String jndiName = key.substring(TOPIC_PREFIX.length());
+ Topic t = createTopic(entry.getValue().toString().trim());
+ if (t != null)
+ {
+ if (_logger.isDebugEnabled())
+ {
+ StringBuffer b = new StringBuffer();
+ b.append("Creating the topic: " + jndiName + " with the following binding keys ");
+ for (AMQShortString binding:((AMQTopic)t).getBindingKeys())
+ {
+ b.append(binding.toString()).append(",");
+ }
+
+ _logger.debug(b.toString());
+ }
+ data.put(jndiName, t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Factory method to create new Connection Factory instances
+ */
+ protected ConnectionFactory createFactory(String url)
+ {
+ try
+ {
+ return new AMQConnectionFactory(url);
+ }
+ catch (URLSyntaxException urlse)
+ {
+ _logger.warn("Unable to createFactories:" + urlse);
+ }
+
+ return null;
+ }
+
+ /**
+ * Factory method to create new Destination instances from an AMQP BindingURL
+ */
+ protected Destination createDestination(String str)
+ {
+ try
+ {
+ return AMQDestination.createDestination(str);
+ }
+ catch (Exception e)
+ {
+ _logger.warn("Unable to create destination:" + e, e);
+
+ return null;
+ }
+ }
+
+ /**
+ * Factory method to create new Queue instances
+ */
+ protected Queue createQueue(Object value)
+ {
+ if (value instanceof AMQShortString)
+ {
+ return new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_NAME, (AMQShortString) value);
+ }
+ else if (value instanceof String)
+ {
+ return new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_NAME, new AMQShortString((String) value));
+ }
+ else if (value instanceof BindingURL)
+ {
+ return new AMQQueue((BindingURL) value);
+ }
+
+ return null;
+ }
+
+ /**
+ * Factory method to create new Topic instances
+ */
+ protected Topic createTopic(Object value)
+ {
+ if (value instanceof AMQShortString)
+ {
+ return new AMQTopic(ExchangeDefaults.TOPIC_EXCHANGE_NAME, (AMQShortString) value);
+ }
+ else if (value instanceof String)
+ {
+ String[] keys = ((String)value).split(",");
+ AMQShortString[] bindings = new AMQShortString[keys.length];
+ int i = 0;
+ for (String key:keys)
+ {
+ bindings[i] = new AMQShortString(key.trim());
+ i++;
+ }
+ // The Destination has a dual nature. If this was used for a producer the key is used
+ // for the routing key. If it was used for the consumer it becomes the bindingKey
+ return new AMQTopic(ExchangeDefaults.TOPIC_EXCHANGE_NAME,bindings[0],null,bindings);
+ }
+ else if (value instanceof BindingURL)
+ {
+ return new AMQTopic((BindingURL) value);
+ }
+
+ return null;
+ }
+
+ /**
+ * Factory method to create new HeaderExcahnge instances
+ */
+ protected Destination createHeaderExchange(Object value)
+ {
+ if (value instanceof String)
+ {
+ return new AMQHeadersExchange((String) value);
+ }
+ else if (value instanceof BindingURL)
+ {
+ return new AMQHeadersExchange((BindingURL) value);
+ }
+
+ return null;
+ }
+
+ // Properties
+ // -------------------------------------------------------------------------
+ public String getConnectionPrefix()
+ {
+ return CONNECTION_FACTORY_PREFIX;
+ }
+
+ public void setConnectionPrefix(String connectionPrefix)
+ {
+ this.CONNECTION_FACTORY_PREFIX = connectionPrefix;
+ }
+
+ public String getDestinationPrefix()
+ {
+ return DESTINATION_PREFIX;
+ }
+
+ public void setDestinationPrefix(String destinationPrefix)
+ {
+ this.DESTINATION_PREFIX = destinationPrefix;
+ }
+
+ public String getQueuePrefix()
+ {
+ return QUEUE_PREFIX;
+ }
+
+ public void setQueuePrefix(String queuePrefix)
+ {
+ this.QUEUE_PREFIX = queuePrefix;
+ }
+
+ public String getTopicPrefix()
+ {
+ return TOPIC_PREFIX;
+ }
+
+ public void setTopicPrefix(String topicPrefix)
+ {
+ this.TOPIC_PREFIX = topicPrefix;
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/jndi/ReadOnlyContext.java b/qpid/java/client/src/main/java/org/apache/qpid/jndi/ReadOnlyContext.java
new file mode 100644
index 0000000000..1719ea1219
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/jndi/ReadOnlyContext.java
@@ -0,0 +1,527 @@
+/*
+ *
+ * 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.jndi;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.naming.Binding;
+import javax.naming.CompositeName;
+import javax.naming.Context;
+import javax.naming.LinkRef;
+import javax.naming.Name;
+import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
+import javax.naming.NameParser;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.NotContextException;
+import javax.naming.OperationNotSupportedException;
+import javax.naming.Reference;
+import javax.naming.spi.NamingManager;
+
+/**
+ * Based on class from ActiveMQ.
+ * A read-only Context
+ * <p/>
+ * This version assumes it and all its subcontext are read-only and any attempt
+ * to modify (e.g. through bind) will result in an OperationNotSupportedException.
+ * Each Context in the tree builds a cache of the entries in all sub-contexts
+ * to optimise the performance of lookup.
+ * </p>
+ * <p>This implementation is intended to optimise the performance of lookup(String)
+ * to about the level of a HashMap get. It has been observed that the scheme
+ * resolution phase performed by the JVM takes considerably longer, so for
+ * optimum performance lookups should be coded like:</p>
+ * <code>
+ * Context componentContext = (Context)new InitialContext().lookup("java:comp");
+ * String envEntry = (String) componentContext.lookup("env/myEntry");
+ * String envEntry2 = (String) componentContext.lookup("env/myEntry2");
+ * </code>
+ */
+public class ReadOnlyContext implements Context, Serializable
+{
+ private static final long serialVersionUID = -5754338187296859149L;
+ protected static final NameParser nameParser = new NameParserImpl();
+
+ protected final Hashtable environment; // environment for this context
+ protected final Map bindings; // bindings at my level
+ protected final Map treeBindings; // all bindings under me
+
+ private boolean frozen = false;
+ private String nameInNamespace = "";
+ public static final String SEPARATOR = "/";
+
+ public ReadOnlyContext()
+ {
+ environment = new Hashtable();
+ bindings = new HashMap();
+ treeBindings = new HashMap();
+ }
+
+ public ReadOnlyContext(Hashtable env)
+ {
+ if (env == null)
+ {
+ this.environment = new Hashtable();
+ }
+ else
+ {
+ this.environment = new Hashtable(env);
+ }
+
+ this.bindings = Collections.EMPTY_MAP;
+ this.treeBindings = Collections.EMPTY_MAP;
+ }
+
+ public ReadOnlyContext(Hashtable environment, Map bindings)
+ {
+ if (environment == null)
+ {
+ this.environment = new Hashtable();
+ }
+ else
+ {
+ this.environment = new Hashtable(environment);
+ }
+
+ this.bindings = bindings;
+ treeBindings = new HashMap();
+ frozen = true;
+ }
+
+ public ReadOnlyContext(Hashtable environment, Map bindings, String nameInNamespace)
+ {
+ this(environment, bindings);
+ this.nameInNamespace = nameInNamespace;
+ }
+
+ protected ReadOnlyContext(ReadOnlyContext clone, Hashtable env)
+ {
+ this.bindings = clone.bindings;
+ this.treeBindings = clone.treeBindings;
+ this.environment = new Hashtable(env);
+ }
+
+ protected ReadOnlyContext(ReadOnlyContext clone, Hashtable env, String nameInNamespace)
+ {
+ this(clone, env);
+ this.nameInNamespace = nameInNamespace;
+ }
+
+ public void freeze()
+ {
+ frozen = true;
+ }
+
+ boolean isFrozen()
+ {
+ return frozen;
+ }
+
+ /**
+ * internalBind is intended for use only during setup or possibly by suitably synchronized superclasses.
+ * It binds every possible lookup into a map in each context. To do this, each context
+ * strips off one name segment and if necessary creates a new context for it. Then it asks that context
+ * to bind the remaining name. It returns a map containing all the bindings from the next context, plus
+ * the context it just created (if it in fact created it). (the names are suitably extended by the segment
+ * originally lopped off).
+ *
+ * @param name
+ * @param value
+ * @return
+ * @throws javax.naming.NamingException
+ */
+ protected Map internalBind(String name, Object value) throws NamingException
+ {
+ assert (name != null) && (name.length() > 0);
+ assert !frozen;
+
+ Map newBindings = new HashMap();
+ int pos = name.indexOf('/');
+ if (pos == -1)
+ {
+ if (treeBindings.put(name, value) != null)
+ {
+ throw new NamingException("Something already bound at " + name);
+ }
+
+ bindings.put(name, value);
+ newBindings.put(name, value);
+ }
+ else
+ {
+ String segment = name.substring(0, pos);
+ assert segment != null;
+ assert !segment.equals("");
+ Object o = treeBindings.get(segment);
+ if (o == null)
+ {
+ o = newContext();
+ treeBindings.put(segment, o);
+ bindings.put(segment, o);
+ newBindings.put(segment, o);
+ }
+ else if (!(o instanceof ReadOnlyContext))
+ {
+ throw new NamingException("Something already bound where a subcontext should go");
+ }
+
+ ReadOnlyContext readOnlyContext = (ReadOnlyContext) o;
+ String remainder = name.substring(pos + 1);
+ Map subBindings = readOnlyContext.internalBind(remainder, value);
+ for (Iterator iterator = subBindings.entrySet().iterator(); iterator.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ String subName = segment + "/" + (String) entry.getKey();
+ Object bound = entry.getValue();
+ treeBindings.put(subName, bound);
+ newBindings.put(subName, bound);
+ }
+ }
+
+ return newBindings;
+ }
+
+ protected ReadOnlyContext newContext()
+ {
+ return new ReadOnlyContext();
+ }
+
+ public Object addToEnvironment(String propName, Object propVal) throws NamingException
+ {
+ return environment.put(propName, propVal);
+ }
+
+ public Hashtable getEnvironment() throws NamingException
+ {
+ return (Hashtable) environment.clone();
+ }
+
+ public Object removeFromEnvironment(String propName) throws NamingException
+ {
+ return environment.remove(propName);
+ }
+
+ public Object lookup(String name) throws NamingException
+ {
+ if (name.length() == 0)
+ {
+ return this;
+ }
+
+ Object result = treeBindings.get(name);
+ if (result == null)
+ {
+ result = bindings.get(name);
+ }
+
+ if (result == null)
+ {
+ int pos = name.indexOf(':');
+ if (pos > 0)
+ {
+ String scheme = name.substring(0, pos);
+ Context ctx = NamingManager.getURLContext(scheme, environment);
+ if (ctx == null)
+ {
+ throw new NamingException("scheme " + scheme + " not recognized");
+ }
+
+ return ctx.lookup(name);
+ }
+ else
+ {
+ // Split out the first name of the path
+ // and look for it in the bindings map.
+ CompositeName path = new CompositeName(name);
+
+ if (path.size() == 0)
+ {
+ return this;
+ }
+ else
+ {
+ String first = path.get(0);
+ Object obj = bindings.get(first);
+ if (obj == null)
+ {
+ throw new NameNotFoundException(name);
+ }
+ else if ((obj instanceof Context) && (path.size() > 1))
+ {
+ Context subContext = (Context) obj;
+ obj = subContext.lookup(path.getSuffix(1));
+ }
+
+ return obj;
+ }
+ }
+ }
+
+ if (result instanceof LinkRef)
+ {
+ LinkRef ref = (LinkRef) result;
+ result = lookup(ref.getLinkName());
+ }
+
+ if (result instanceof Reference)
+ {
+ try
+ {
+ result = NamingManager.getObjectInstance(result, null, null, this.environment);
+ }
+ catch (NamingException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw (NamingException) new NamingException("could not look up : " + name).initCause(e);
+ }
+ }
+
+ if (result instanceof ReadOnlyContext)
+ {
+ String prefix = getNameInNamespace();
+ if (prefix.length() > 0)
+ {
+ prefix = prefix + SEPARATOR;
+ }
+
+ result = new ReadOnlyContext((ReadOnlyContext) result, environment, prefix + name);
+ }
+
+ return result;
+ }
+
+ public Object lookup(Name name) throws NamingException
+ {
+ return lookup(name.toString());
+ }
+
+ public Object lookupLink(String name) throws NamingException
+ {
+ return lookup(name);
+ }
+
+ public Name composeName(Name name, Name prefix) throws NamingException
+ {
+ Name result = (Name) prefix.clone();
+ result.addAll(name);
+
+ return result;
+ }
+
+ public String composeName(String name, String prefix) throws NamingException
+ {
+ CompositeName result = new CompositeName(prefix);
+ result.addAll(new CompositeName(name));
+
+ return result.toString();
+ }
+
+ public NamingEnumeration list(String name) throws NamingException
+ {
+ Object o = lookup(name);
+ if (o == this)
+ {
+ return new ListEnumeration();
+ }
+ else if (o instanceof Context)
+ {
+ return ((Context) o).list("");
+ }
+ else
+ {
+ throw new NotContextException();
+ }
+ }
+
+ public NamingEnumeration listBindings(String name) throws NamingException
+ {
+ Object o = lookup(name);
+ if (o == this)
+ {
+ return new ListBindingEnumeration();
+ }
+ else if (o instanceof Context)
+ {
+ return ((Context) o).listBindings("");
+ }
+ else
+ {
+ throw new NotContextException();
+ }
+ }
+
+ public Object lookupLink(Name name) throws NamingException
+ {
+ return lookupLink(name.toString());
+ }
+
+ public NamingEnumeration list(Name name) throws NamingException
+ {
+ return list(name.toString());
+ }
+
+ public NamingEnumeration listBindings(Name name) throws NamingException
+ {
+ return listBindings(name.toString());
+ }
+
+ public void bind(Name name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void bind(String name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void close() throws NamingException
+ {
+ // ignore
+ }
+
+ public Context createSubcontext(Name name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public Context createSubcontext(String name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void destroySubcontext(Name name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void destroySubcontext(String name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public String getNameInNamespace() throws NamingException
+ {
+ return nameInNamespace;
+ }
+
+ public NameParser getNameParser(Name name) throws NamingException
+ {
+ return nameParser;
+ }
+
+ public NameParser getNameParser(String name) throws NamingException
+ {
+ return nameParser;
+ }
+
+ public void rebind(Name name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void rebind(String name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void rename(Name oldName, Name newName) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void rename(String oldName, String newName) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void unbind(Name name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void unbind(String name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ private abstract class LocalNamingEnumeration implements NamingEnumeration
+ {
+ private Iterator i = bindings.entrySet().iterator();
+
+ public boolean hasMore() throws NamingException
+ {
+ return i.hasNext();
+ }
+
+ public boolean hasMoreElements()
+ {
+ return i.hasNext();
+ }
+
+ protected Map.Entry getNext()
+ {
+ return (Map.Entry) i.next();
+ }
+
+ public void close() throws NamingException
+ { }
+ }
+
+ private class ListEnumeration extends LocalNamingEnumeration
+ {
+ public Object next() throws NamingException
+ {
+ return nextElement();
+ }
+
+ public Object nextElement()
+ {
+ Map.Entry entry = getNext();
+
+ return new NameClassPair((String) entry.getKey(), entry.getValue().getClass().getName());
+ }
+ }
+
+ private class ListBindingEnumeration extends LocalNamingEnumeration
+ {
+ public Object next() throws NamingException
+ {
+ return nextElement();
+ }
+
+ public Object nextElement()
+ {
+ Map.Entry entry = getNext();
+
+ return new Binding((String) entry.getKey(), entry.getValue());
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/naming/ReadOnlyContext.java b/qpid/java/client/src/main/java/org/apache/qpid/naming/ReadOnlyContext.java
new file mode 100644
index 0000000000..59ec4cfba7
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/naming/ReadOnlyContext.java
@@ -0,0 +1,509 @@
+/* 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.naming;
+
+import org.apache.qpid.jndi.NameParserImpl;
+
+import javax.naming.*;
+import javax.naming.spi.NamingManager;
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * Based on class from ActiveMQ.
+ * A read-only Context
+ * <p/>
+ * This version assumes it and all its subcontext are read-only and any attempt
+ * to modify (e.g. through bind) will result in an OperationNotSupportedException.
+ * Each Context in the tree builds a cache of the entries in all sub-contexts
+ * to optimise the performance of lookup.
+ * </p>
+ * <p>This implementation is intended to optimise the performance of lookup(String)
+ * to about the level of a HashMap get. It has been observed that the scheme
+ * resolution phase performed by the JVM takes considerably longer, so for
+ * optimum performance lookups should be coded like:</p>
+ * <code>
+ * Context componentContext = (Context)new InitialContext().lookup("java:comp");
+ * String envEntry = (String) componentContext.lookup("env/myEntry");
+ * String envEntry2 = (String) componentContext.lookup("env/myEntry2");
+ * </code>
+ */
+public class ReadOnlyContext implements Context, Serializable
+{
+ private static final long serialVersionUID = -5754338187296859149L;
+ protected static final NameParser nameParser = new NameParserImpl();
+
+ protected final Hashtable environment; // environment for this context
+ protected final Map bindings; // bindings at my level
+ protected final Map treeBindings; // all bindings under me
+
+ private boolean frozen = false;
+ private String nameInNamespace = "";
+ public static final String SEPARATOR = "/";
+
+ public ReadOnlyContext()
+ {
+ environment = new Hashtable();
+ bindings = new HashMap();
+ treeBindings = new HashMap();
+ }
+
+ public ReadOnlyContext(Hashtable env)
+ {
+ if (env == null)
+ {
+ this.environment = new Hashtable();
+ }
+ else
+ {
+ this.environment = new Hashtable(env);
+ }
+
+ this.bindings = Collections.EMPTY_MAP;
+ this.treeBindings = Collections.EMPTY_MAP;
+ }
+
+ public ReadOnlyContext(Hashtable environment, Map bindings)
+ {
+ if (environment == null)
+ {
+ this.environment = new Hashtable();
+ }
+ else
+ {
+ this.environment = new Hashtable(environment);
+ }
+
+ this.bindings = bindings;
+ treeBindings = new HashMap();
+ frozen = true;
+ }
+
+ public ReadOnlyContext(Hashtable environment, Map bindings, String nameInNamespace)
+ {
+ this(environment, bindings);
+ this.nameInNamespace = nameInNamespace;
+ }
+
+ protected ReadOnlyContext(ReadOnlyContext clone, Hashtable env)
+ {
+ this.bindings = clone.bindings;
+ this.treeBindings = clone.treeBindings;
+ this.environment = new Hashtable(env);
+ }
+
+ protected ReadOnlyContext(ReadOnlyContext clone, Hashtable env, String nameInNamespace)
+ {
+ this(clone, env);
+ this.nameInNamespace = nameInNamespace;
+ }
+
+ public void freeze()
+ {
+ frozen = true;
+ }
+
+ boolean isFrozen()
+ {
+ return frozen;
+ }
+
+ /**
+ * internalBind is intended for use only during setup or possibly by suitably synchronized superclasses.
+ * It binds every possible lookup into a map in each context. To do this, each context
+ * strips off one name segment and if necessary creates a new context for it. Then it asks that context
+ * to bind the remaining name. It returns a map containing all the bindings from the next context, plus
+ * the context it just created (if it in fact created it). (the names are suitably extended by the segment
+ * originally lopped off).
+ *
+ * @param name
+ * @param value
+ * @return
+ * @throws javax.naming.NamingException
+ */
+ protected Map internalBind(String name, Object value) throws NamingException
+ {
+ assert (name != null) && (name.length() > 0);
+ assert !frozen;
+
+ Map newBindings = new HashMap();
+ int pos = name.indexOf('/');
+ if (pos == -1)
+ {
+ if (treeBindings.put(name, value) != null)
+ {
+ throw new NamingException("Something already bound at " + name);
+ }
+
+ bindings.put(name, value);
+ newBindings.put(name, value);
+ }
+ else
+ {
+ String segment = name.substring(0, pos);
+ assert segment != null;
+ assert !segment.equals("");
+ Object o = treeBindings.get(segment);
+ if (o == null)
+ {
+ o = newContext();
+ treeBindings.put(segment, o);
+ bindings.put(segment, o);
+ newBindings.put(segment, o);
+ }
+ else if (!(o instanceof ReadOnlyContext))
+ {
+ throw new NamingException("Something already bound where a subcontext should go");
+ }
+
+ ReadOnlyContext readOnlyContext = (ReadOnlyContext) o;
+ String remainder = name.substring(pos + 1);
+ Map subBindings = readOnlyContext.internalBind(remainder, value);
+ for (Iterator iterator = subBindings.entrySet().iterator(); iterator.hasNext();)
+ {
+ Map.Entry entry = (Map.Entry) iterator.next();
+ String subName = segment + "/" + (String) entry.getKey();
+ Object bound = entry.getValue();
+ treeBindings.put(subName, bound);
+ newBindings.put(subName, bound);
+ }
+ }
+
+ return newBindings;
+ }
+
+ protected ReadOnlyContext newContext()
+ {
+ return new ReadOnlyContext();
+ }
+
+ public Object addToEnvironment(String propName, Object propVal) throws NamingException
+ {
+ return environment.put(propName, propVal);
+ }
+
+ public Hashtable getEnvironment() throws NamingException
+ {
+ return (Hashtable) environment.clone();
+ }
+
+ public Object removeFromEnvironment(String propName) throws NamingException
+ {
+ return environment.remove(propName);
+ }
+
+ public Object lookup(String name) throws NamingException
+ {
+ if (name.length() == 0)
+ {
+ return this;
+ }
+
+ Object result = treeBindings.get(name);
+ if (result == null)
+ {
+ result = bindings.get(name);
+ }
+
+ if (result == null)
+ {
+ int pos = name.indexOf(':');
+ if (pos > 0)
+ {
+ String scheme = name.substring(0, pos);
+ Context ctx = NamingManager.getURLContext(scheme, environment);
+ if (ctx == null)
+ {
+ throw new NamingException("scheme " + scheme + " not recognized");
+ }
+
+ return ctx.lookup(name);
+ }
+ else
+ {
+ // Split out the first name of the path
+ // and look for it in the bindings map.
+ CompositeName path = new CompositeName(name);
+
+ if (path.size() == 0)
+ {
+ return this;
+ }
+ else
+ {
+ String first = path.get(0);
+ Object obj = bindings.get(first);
+ if (obj == null)
+ {
+ throw new NameNotFoundException(name);
+ }
+ else if ((obj instanceof Context) && (path.size() > 1))
+ {
+ Context subContext = (Context) obj;
+ obj = subContext.lookup(path.getSuffix(1));
+ }
+
+ return obj;
+ }
+ }
+ }
+
+ if (result instanceof LinkRef)
+ {
+ LinkRef ref = (LinkRef) result;
+ result = lookup(ref.getLinkName());
+ }
+
+ if (result instanceof Reference)
+ {
+ try
+ {
+ result = NamingManager.getObjectInstance(result, null, null, this.environment);
+ }
+ catch (NamingException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw (NamingException) new NamingException("could not look up : " + name).initCause(e);
+ }
+ }
+
+ if (result instanceof ReadOnlyContext)
+ {
+ String prefix = getNameInNamespace();
+ if (prefix.length() > 0)
+ {
+ prefix = prefix + SEPARATOR;
+ }
+
+ result = new ReadOnlyContext((ReadOnlyContext) result, environment, prefix + name);
+ }
+
+ return result;
+ }
+
+ public Object lookup(Name name) throws NamingException
+ {
+ return lookup(name.toString());
+ }
+
+ public Object lookupLink(String name) throws NamingException
+ {
+ return lookup(name);
+ }
+
+ public Name composeName(Name name, Name prefix) throws NamingException
+ {
+ Name result = (Name) prefix.clone();
+ result.addAll(name);
+
+ return result;
+ }
+
+ public String composeName(String name, String prefix) throws NamingException
+ {
+ CompositeName result = new CompositeName(prefix);
+ result.addAll(new CompositeName(name));
+
+ return result.toString();
+ }
+
+ public NamingEnumeration list(String name) throws NamingException
+ {
+ Object o = lookup(name);
+ if (o == this)
+ {
+ return new ReadOnlyContext.ListEnumeration();
+ }
+ else if (o instanceof Context)
+ {
+ return ((Context) o).list("");
+ }
+ else
+ {
+ throw new NotContextException();
+ }
+ }
+
+ public NamingEnumeration listBindings(String name) throws NamingException
+ {
+ Object o = lookup(name);
+ if (o == this)
+ {
+ return new ReadOnlyContext.ListBindingEnumeration();
+ }
+ else if (o instanceof Context)
+ {
+ return ((Context) o).listBindings("");
+ }
+ else
+ {
+ throw new NotContextException();
+ }
+ }
+
+ public Object lookupLink(Name name) throws NamingException
+ {
+ return lookupLink(name.toString());
+ }
+
+ public NamingEnumeration list(Name name) throws NamingException
+ {
+ return list(name.toString());
+ }
+
+ public NamingEnumeration listBindings(Name name) throws NamingException
+ {
+ return listBindings(name.toString());
+ }
+
+ public void bind(Name name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void bind(String name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void close() throws NamingException
+ {
+ // ignore
+ }
+
+ public Context createSubcontext(Name name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public Context createSubcontext(String name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void destroySubcontext(Name name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void destroySubcontext(String name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public String getNameInNamespace() throws NamingException
+ {
+ return nameInNamespace;
+ }
+
+ public NameParser getNameParser(Name name) throws NamingException
+ {
+ return nameParser;
+ }
+
+ public NameParser getNameParser(String name) throws NamingException
+ {
+ return nameParser;
+ }
+
+ public void rebind(Name name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void rebind(String name, Object obj) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void rename(Name oldName, Name newName) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void rename(String oldName, String newName) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void unbind(Name name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ public void unbind(String name) throws NamingException
+ {
+ throw new OperationNotSupportedException();
+ }
+
+ private abstract class LocalNamingEnumeration implements NamingEnumeration
+ {
+ private Iterator i = bindings.entrySet().iterator();
+
+ public boolean hasMore() throws NamingException
+ {
+ return i.hasNext();
+ }
+
+ public boolean hasMoreElements()
+ {
+ return i.hasNext();
+ }
+
+ protected Map.Entry getNext()
+ {
+ return (Map.Entry) i.next();
+ }
+
+ public void close() throws NamingException
+ { }
+ }
+
+ private class ListEnumeration extends ReadOnlyContext.LocalNamingEnumeration
+ {
+ public Object next() throws NamingException
+ {
+ return nextElement();
+ }
+
+ public Object nextElement()
+ {
+ Map.Entry entry = getNext();
+
+ return new NameClassPair((String) entry.getKey(), entry.getValue().getClass().getName());
+ }
+ }
+
+ private class ListBindingEnumeration extends ReadOnlyContext.LocalNamingEnumeration
+ {
+ public Object next() throws NamingException
+ {
+ return nextElement();
+ }
+
+ public Object nextElement()
+ {
+ Map.Entry entry = getNext();
+
+ return new Binding((String) entry.getKey(), entry.getValue());
+ }
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/naming/jndi.properties b/qpid/java/client/src/main/java/org/apache/qpid/naming/jndi.properties
new file mode 100644
index 0000000000..830de5f619
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/naming/jndi.properties
@@ -0,0 +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.
+#
+java.naming.factory.initial = org.apache.qpid.naming.PropertiesFileInitialConextFactory
+
+# use the following property to configure the default connector
+#java.naming.provider.url - ignored.
+
+# register some connection factories
+# connectionfactory.[jndiname] = [ConnectionURL]
+# qpid:username=foo;password=password;client_id=id;virtualhost=path@tpc:localhost:1556
+connectionfactory.local = qpid:tcp:localhost'
+
+# register some queues in JNDI using the form
+# queue.[jndiName] = [physicalName]
+queue.MyQueue = example.MyQueue
+
+# register some topics in JNDI using the form
+# topic.[jndiName] = [physicalName]
+topic.ibmStocks = stocks.nyse.ibm
+
+# Register an AMQP destination in JNDI
+# NOTE: Qpid currently only supports direct,topics and headers
+# destination.[jniName] = [BindingURL]
+destination.direct = direct://amq.direct//directQueue
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/nclient/MessagePartListener.java b/qpid/java/client/src/main/java/org/apache/qpid/nclient/MessagePartListener.java
new file mode 100644
index 0000000000..6f07dcb469
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/nclient/MessagePartListener.java
@@ -0,0 +1,46 @@
+/* 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.nclient;
+
+import java.nio.ByteBuffer;
+
+import org.apache.qpid.transport.Header;
+import org.apache.qpid.transport.MessageTransfer;
+
+/**
+ * Assembles message parts.
+ * <p> The sequence of event for transferring a message is as follows:
+ * <ul>
+ * <li> messageHeaders
+ * <li> n calls to addData
+ * <li> messageReceived
+ * </ul>
+ * It is up to the implementation to assemble the message once the different parts
+ * are transferred.
+ */
+public interface MessagePartListener
+{
+
+ /**
+ * Inform the listener of the message transfer
+ *
+ * @param xfr the message transfer object
+ */
+ public void messageTransfer(MessageTransfer xfr);
+
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/ByteBufferMessage.java b/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/ByteBufferMessage.java
new file mode 100644
index 0000000000..14bfb4f95e
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/ByteBufferMessage.java
@@ -0,0 +1,190 @@
+package org.apache.qpid.nclient.util;
+/*
+ *
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import org.apache.qpid.transport.DeliveryProperties;
+import org.apache.qpid.transport.MessageProperties;
+import org.apache.qpid.transport.Header;
+import org.apache.qpid.api.Message;
+
+/**
+ * <p>A Simple implementation of the message interface
+ * for small messages. When the readData methods are called
+ * we assume the message is complete. i.e there want be any
+ * appendData operations after that.</p>
+ *
+ * <p>If you need large message support please see
+ * <code>FileMessage</code> and <code>StreamingMessage</code>
+ * </p>
+ */
+public class ByteBufferMessage implements Message
+{
+ private List<ByteBuffer> _data;// = new ArrayList<ByteBuffer>();
+ private ByteBuffer _readBuffer;
+ private int _dataSize;
+ private DeliveryProperties _currentDeliveryProps;
+ private MessageProperties _currentMessageProps;
+ private int _transferId;
+ private Header _header;
+
+ public ByteBufferMessage(MessageProperties messageProperties, DeliveryProperties deliveryProperties)
+ {
+ _currentMessageProps = messageProperties;
+ _currentDeliveryProps = deliveryProperties;
+ }
+
+ public void setHeader(Header header) {
+ _header = header;
+ }
+
+ public Header getHeader() {
+ return _header;
+ }
+
+ public ByteBufferMessage()
+ {
+ _currentDeliveryProps = new DeliveryProperties();
+ _currentMessageProps = new MessageProperties();
+ }
+
+ public ByteBufferMessage(int transferId)
+ {
+ _transferId = transferId;
+ }
+
+ public int getMessageTransferId()
+ {
+ return _transferId;
+ }
+
+ public void clearData()
+ {
+ _data = new LinkedList<ByteBuffer>();
+ _readBuffer = null;
+ }
+
+ public void appendData(byte[] src) throws IOException
+ {
+ appendData(ByteBuffer.wrap(src));
+ }
+
+ /**
+ * write the data from the current position up to the buffer limit
+ */
+ public void appendData(ByteBuffer src) throws IOException
+ {
+ if(_data == null)
+ {
+ _data = Collections.singletonList(src);
+ }
+ else
+ {
+ if(_data.size() == 1)
+ {
+ _data = new ArrayList<ByteBuffer>(_data);
+ }
+ _data.add(src);
+ }
+ _dataSize += src.remaining();
+ }
+
+ public DeliveryProperties getDeliveryProperties()
+ {
+ return _currentDeliveryProps;
+ }
+
+ public MessageProperties getMessageProperties()
+ {
+ return _currentMessageProps;
+ }
+
+ public void setDeliveryProperties(DeliveryProperties props)
+ {
+ _currentDeliveryProps = props;
+ }
+
+ public void setMessageProperties(MessageProperties props)
+ {
+ _currentMessageProps = props;
+ }
+
+ public void readData(byte[] target)
+ {
+ getReadBuffer().get(target);
+ }
+
+ public ByteBuffer readData()
+ {
+ return getReadBuffer();
+ }
+
+ private void buildReadBuffer()
+ {
+ //optimize for the simple cases
+ if(_data.size() == 1)
+ {
+ _readBuffer = _data.get(0).duplicate();
+ }
+ else
+ {
+ _readBuffer = ByteBuffer.allocate(_dataSize);
+ for(ByteBuffer buf:_data)
+ {
+ _readBuffer.put(buf);
+ }
+ _readBuffer.flip();
+ }
+ }
+
+ private ByteBuffer getReadBuffer()
+ {
+ if (_readBuffer != null )
+ {
+ return _readBuffer.slice();
+ }
+ else
+ {
+ if (_data.size() >0)
+ {
+ buildReadBuffer();
+ return _readBuffer.slice();
+ }
+ else
+ {
+ return ByteBuffer.allocate(0);
+ }
+ }
+ }
+
+ //hack for testing
+ @Override public String toString()
+ {
+ ByteBuffer temp = getReadBuffer();
+ byte[] b = new byte[temp.remaining()];
+ temp.get(b);
+ return new String(b);
+ }
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessageListener.java b/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessageListener.java
new file mode 100644
index 0000000000..c5edd62143
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessageListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.nclient.util;
+
+import org.apache.qpid.api.Message;
+
+/**
+ *A message listener
+ */
+public interface MessageListener
+{
+ /**
+ * Process an incoming message.
+ *
+ * @param message The incoming message.
+ */
+ public void onMessage(Message message);
+}
diff --git a/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessagePartListenerAdapter.java b/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessagePartListenerAdapter.java
new file mode 100644
index 0000000000..10fd8d2a80
--- /dev/null
+++ b/qpid/java/client/src/main/java/org/apache/qpid/nclient/util/MessagePartListenerAdapter.java
@@ -0,0 +1,88 @@
+package org.apache.qpid.nclient.util;
+/*
+ *
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.qpid.transport.*;
+import org.apache.qpid.nclient.MessagePartListener;
+
+/**
+ * This is a simple message assembler.
+ * Will call onMessage method of the adaptee
+ * when all message data is read.
+ *
+ * This is a good convinience utility for handling
+ * small messages
+ */
+public class MessagePartListenerAdapter implements MessagePartListener
+{
+ MessageListener _adaptee;
+ ByteBufferMessage _currentMsg;
+
+ public MessagePartListenerAdapter(MessageListener listener)
+ {
+ _adaptee = listener;
+ }
+
+ public void messageTransfer(MessageTransfer xfr)
+ {
+ _currentMsg = new ByteBufferMessage(xfr.getId());
+
+ for (Struct st : xfr.getHeader().getStructs())
+ {
+ if(st instanceof DeliveryProperties)
+ {
+ _currentMsg.setDeliveryProperties((DeliveryProperties)st);
+
+ }
+ else if(st instanceof MessageProperties)
+ {
+ _currentMsg.setMessageProperties((MessageProperties)st);
+ }
+
+ }
+
+
+ ByteBuffer body = xfr.getBody();
+ if (body == null)
+ {
+ body = ByteBuffer.allocate(0);
+ }
+
+
+ try
+ {
+ _currentMsg.appendData(body);
+ }
+ catch(IOException e)
+ {
+ // A chance for IO exception
+ // doesn't occur as we are using
+ // a ByteBuffer
+ }
+
+ _adaptee.onMessage(_currentMsg);
+ }
+
+}