From 202bfa576abd99ba11f0da97019a76de048479e9 Mon Sep 17 00:00:00 2001 From: Aidan Skinner Date: Mon, 16 Jun 2008 14:02:16 +0000 Subject: QPID-1104: Add an IMessage.Acknowledge(bool) so that only specific messages can be acknowledged, not all messages recieved on the Channel up to that point. Qpid.Client/Client/Message/AbstractQmsMessage.cs, dotnet/Qpid.Messaging/IMessage.cs: add paramatarised ack so that only certain messages can be ack'd Qpid.Integration.Tests/Qpid.Integration.Tests.csproj: add new test case Qpid.Integration.Tests/testcases/ClientAckTests.cs: new tests Qpid.Client/Client/BasicMessageProducer.cs, Qpid.Messaging/IMessagePublisher.cs:Add Channel property that the producer belongs too Qpid.Integration.Tests/testcases/BaseMessagingTestFixture.cs: add SendMessages method git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/branches/M2.1.x@668164 13f79535-47bb-0310-9956-ffa450edef68 --- dotnet/Qpid.Client/Client/BasicMessageProducer.cs | 812 ++++++------ .../Client/Message/AbstractQmsMessage.cs | 1393 ++++++++++---------- .../Qpid.Integration.Tests.csproj | 7 + .../testcases/BaseMessagingTestFixture.cs | 9 + .../testcases/ClientAckTests.cs | 179 +++ dotnet/Qpid.Messaging/IMessage.cs | 195 +-- dotnet/Qpid.Messaging/IMessagePublisher.cs | 189 +-- 7 files changed, 1496 insertions(+), 1288 deletions(-) create mode 100755 dotnet/Qpid.Integration.Tests/testcases/ClientAckTests.cs diff --git a/dotnet/Qpid.Client/Client/BasicMessageProducer.cs b/dotnet/Qpid.Client/Client/BasicMessageProducer.cs index f33afc452e..84605571eb 100644 --- a/dotnet/Qpid.Client/Client/BasicMessageProducer.cs +++ b/dotnet/Qpid.Client/Client/BasicMessageProducer.cs @@ -1,405 +1,407 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -using System; -using System.Threading; -using log4net; -using Apache.Qpid.Buffer; -using Apache.Qpid.Client.Message; -using Apache.Qpid.Messaging; -using Apache.Qpid.Framing; - -namespace Apache.Qpid.Client -{ - public class BasicMessageProducer : Closeable, IMessagePublisher - { - protected readonly ILog _logger = LogManager.GetLogger(typeof(BasicMessageProducer)); - - /// - /// If true, messages will not get a timestamp. - /// - private bool _disableTimestamps; - - /// - /// Priority of messages created by this producer. - /// - private int _messagePriority; - - /// - /// Time to live of messages. Specified in milliseconds but AMQ has 1 second resolution. - /// - private long _timeToLive; - - /// - /// Delivery mode used for this producer. - /// - private DeliveryMode _deliveryMode; - - private bool _immediate; - private bool _mandatory; - - string _exchangeName; - string _routingKey; - - /// - /// Default encoding used for messages produced by this producer. - /// - private string _encoding; - - /// - /// Default encoding used for message produced by this producer. - /// - private string _mimeType; - - /// - /// True if this producer was created from a transacted session - /// - private bool _transacted; - - private ushort _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 closed. We need to be able to tie producers - /// to the session so that when an error is propagated to the session it can close the producer (meaning that - /// a client that happens to hold onto a producer reference will get an error if he tries to use it subsequently). - /// - private long _producerId; - - /// - /// The session used to create this producer - /// - private AmqChannel _channel; - - public BasicMessageProducer(string exchangeName, string routingKey, - bool transacted, - ushort channelId, - AmqChannel channel, - long producerId, - DeliveryMode deliveryMode, - long timeToLive, - bool immediate, - bool mandatory, - int priority) - { - _exchangeName = exchangeName; - _routingKey = routingKey; - _transacted = transacted; - _channelId = channelId; - _channel = channel; - _producerId = producerId; - _deliveryMode = deliveryMode; - _timeToLive = timeToLive; - _immediate = immediate; - _mandatory = mandatory; - _messagePriority = priority; - - _channel.RegisterProducer(producerId, this); - } - - - #region IMessagePublisher Members - - public DeliveryMode DeliveryMode - { - get - { - CheckNotClosed(); - return _deliveryMode; - } - set - { - CheckNotClosed(); - _deliveryMode = value; - } - } - - public string ExchangeName - { - get { return _exchangeName; } - } - - public string RoutingKey - { - get { return _routingKey; } - } - - public bool DisableMessageID - { - get - { - throw new Exception("The method or operation is not implemented."); - } - set - { - throw new Exception("The method or operation is not implemented."); - } - } - - public bool DisableMessageTimestamp - { - get - { - CheckNotClosed(); - return _disableTimestamps; - } - set - { - CheckNotClosed(); - _disableTimestamps = value; - } - } - - public int Priority - { - get - { - CheckNotClosed(); - return _messagePriority; - } - set - { - CheckNotClosed(); - if ( value < 0 || value > 9 ) - { - throw new ArgumentOutOfRangeException("Priority of " + value + " is illegal. Value must be in range 0 to 9"); - } - _messagePriority = value; - } - } - - public override void Close() - { - _logger.Debug("Closing producer " + this); - Interlocked.Exchange(ref _closed, CLOSED); - _channel.DeregisterProducer(_producerId); - } - - public void Send(IMessage msg, DeliveryMode deliveryMode, int priority, long timeToLive) - { - CheckNotClosed(); - SendImpl( - _exchangeName, - _routingKey, - (AbstractQmsMessage)msg, - deliveryMode, - priority, - (uint)timeToLive, - _mandatory, - _immediate - ); - } - - public void Send(IMessage msg) - { - CheckNotClosed(); - SendImpl( - _exchangeName, - _routingKey, - (AbstractQmsMessage)msg, - _deliveryMode, - _messagePriority, - (uint)_timeToLive, - _mandatory, - _immediate - ); - } - - // This is a short-term hack (knowing that this code will be re-vamped sometime soon) - // to facilitate publishing messages to potentially non-existent recipients. - public void Send(IMessage msg, bool mandatory) - { - CheckNotClosed(); - SendImpl( - _exchangeName, - _routingKey, - (AbstractQmsMessage)msg, - _deliveryMode, - _messagePriority, - (uint)_timeToLive, - mandatory, - _immediate - ); - } - - public long TimeToLive - { - get - { - CheckNotClosed(); - return _timeToLive; - } - set - { - CheckNotClosed(); - if ( value < 0 ) - { - throw new ArgumentOutOfRangeException("Time to live must be non-negative - supplied value was " + value); - } - _timeToLive = value; - } - } - - #endregion - - public string MimeType - { - get - { - CheckNotClosed(); - return _mimeType; - } - set - { - CheckNotClosed(); - _mimeType = value; - } - } - - public string Encoding - { - get - { - CheckNotClosed(); - return _encoding; - } - set - { - CheckNotClosed(); - _encoding = value; - } - } - - public void Dispose() - { - Close(); - } - - #region Message Publishing - - private void SendImpl(string exchangeName, string routingKey, AbstractQmsMessage message, DeliveryMode deliveryMode, int priority, uint timeToLive, bool mandatory, bool immediate) - { - // todo: handle session access ticket - AMQFrame publishFrame = BasicPublishBody.CreateAMQFrame( - _channel.ChannelId, 0, exchangeName, - routingKey, mandatory, immediate - ); - - // fix message properties - if ( !_disableTimestamps ) - { - message.Timestamp = DateTime.UtcNow.Ticks; - if (timeToLive != 0) - { - message.Expiration = message.Timestamp + timeToLive; - } - } else - { - message.Expiration = 0; - } - message.DeliveryMode = deliveryMode; - message.Priority = (byte)priority; - - ByteBuffer payload = message.Data; - int payloadLength = payload.Limit; - - ContentBody[] contentBodies = CreateContentBodies(payload); - AMQFrame[] frames = new AMQFrame[2 + contentBodies.Length]; - for ( int i = 0; i < contentBodies.Length; i++ ) - { - frames[2 + i] = ContentBody.CreateAMQFrame(_channelId, contentBodies[i]); - } - if ( contentBodies.Length > 0 && _logger.IsDebugEnabled ) - { - _logger.Debug(string.Format("Sending content body frames to {{exchangeName={0} routingKey={1}}}", exchangeName, routingKey)); - } - - // weight argument of zero indicates no child content headers, just bodies - AMQFrame contentHeaderFrame = ContentHeaderBody.CreateAMQFrame( - _channelId, AmqChannel.BASIC_CONTENT_TYPE, 0, - message.ContentHeaderProperties, (uint)payloadLength - ); - if ( _logger.IsDebugEnabled ) - { - _logger.Debug(string.Format("Sending content header frame to {{exchangeName={0} routingKey={1}}}", exchangeName, routingKey)); - } - - frames[0] = publishFrame; - frames[1] = contentHeaderFrame; - CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames); - - lock ( _channel.Connection.FailoverMutex ) - { - _channel.Connection.ProtocolWriter.Write(compositeFrame); - } - } - - - /// - /// Create content bodies. This will split a large message into numerous bodies depending on the negotiated - /// maximum frame size. - /// - /// - /// return the array of content bodies - private ContentBody[] CreateContentBodies(ByteBuffer payload) - { - if ( payload == null ) - { - return null; - } else if ( payload.Remaining == 0 ) - { - return new ContentBody[0]; - } - // we substract one from the total frame maximum size to account for the end of frame marker in a body frame - // (0xCE byte). - int framePayloadMax = (int)(_channel.Connection.MaximumFrameSize - 1); - int frameCount = CalculateContentBodyFrames(payload); - ContentBody[] bodies = new ContentBody[frameCount]; - for ( int i = 0; i < frameCount; i++ ) - { - int length = (payload.Remaining >= framePayloadMax) - ? framePayloadMax : payload.Remaining; - bodies[i] = new ContentBody(payload, (uint)length); - } - return bodies; - } - - private int CalculateContentBodyFrames(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; - int framePayloadMax = (int)_channel.Connection.MaximumFrameSize - 1; - int lastFrame = ((dataLength % framePayloadMax) > 0) ? 1 : 0; - frameCount = (int)(dataLength / framePayloadMax) + lastFrame; - } - - return frameCount; - } - #endregion // Message Publishing - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +using System; +using System.Threading; +using log4net; +using Apache.Qpid.Buffer; +using Apache.Qpid.Client.Message; +using Apache.Qpid.Messaging; +using Apache.Qpid.Framing; + +namespace Apache.Qpid.Client +{ + public class BasicMessageProducer : Closeable, IMessagePublisher + { + protected readonly ILog _logger = LogManager.GetLogger(typeof(BasicMessageProducer)); + + /// + /// If true, messages will not get a timestamp. + /// + private bool _disableTimestamps; + + /// + /// Priority of messages created by this producer. + /// + private int _messagePriority; + + /// + /// Time to live of messages. Specified in milliseconds but AMQ has 1 second resolution. + /// + private long _timeToLive; + + /// + /// Delivery mode used for this producer. + /// + private DeliveryMode _deliveryMode; + + private bool _immediate; + private bool _mandatory; + + string _exchangeName; + string _routingKey; + + /// + /// Default encoding used for messages produced by this producer. + /// + private string _encoding; + + /// + /// Default encoding used for message produced by this producer. + /// + private string _mimeType; + + /// + /// True if this producer was created from a transacted session + /// + private bool _transacted; + + private ushort _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 closed. We need to be able to tie producers + /// to the session so that when an error is propagated to the session it can close the producer (meaning that + /// a client that happens to hold onto a producer reference will get an error if he tries to use it subsequently). + /// + private long _producerId; + + /// + /// The session used to create this producer + /// + private AmqChannel _channel; + + public IChannel Channel { get {return _channel;}} + + public BasicMessageProducer(string exchangeName, string routingKey, + bool transacted, + ushort channelId, + AmqChannel channel, + long producerId, + DeliveryMode deliveryMode, + long timeToLive, + bool immediate, + bool mandatory, + int priority) + { + _exchangeName = exchangeName; + _routingKey = routingKey; + _transacted = transacted; + _channelId = channelId; + _channel = channel; + _producerId = producerId; + _deliveryMode = deliveryMode; + _timeToLive = timeToLive; + _immediate = immediate; + _mandatory = mandatory; + _messagePriority = priority; + + _channel.RegisterProducer(producerId, this); + } + + + #region IMessagePublisher Members + + public DeliveryMode DeliveryMode + { + get + { + CheckNotClosed(); + return _deliveryMode; + } + set + { + CheckNotClosed(); + _deliveryMode = value; + } + } + + public string ExchangeName + { + get { return _exchangeName; } + } + + public string RoutingKey + { + get { return _routingKey; } + } + + public bool DisableMessageID + { + get + { + throw new Exception("The method or operation is not implemented."); + } + set + { + throw new Exception("The method or operation is not implemented."); + } + } + + public bool DisableMessageTimestamp + { + get + { + CheckNotClosed(); + return _disableTimestamps; + } + set + { + CheckNotClosed(); + _disableTimestamps = value; + } + } + + public int Priority + { + get + { + CheckNotClosed(); + return _messagePriority; + } + set + { + CheckNotClosed(); + if ( value < 0 || value > 9 ) + { + throw new ArgumentOutOfRangeException("Priority of " + value + " is illegal. Value must be in range 0 to 9"); + } + _messagePriority = value; + } + } + + public override void Close() + { + _logger.Debug("Closing producer " + this); + Interlocked.Exchange(ref _closed, CLOSED); + _channel.DeregisterProducer(_producerId); + } + + public void Send(IMessage msg, DeliveryMode deliveryMode, int priority, long timeToLive) + { + CheckNotClosed(); + SendImpl( + _exchangeName, + _routingKey, + (AbstractQmsMessage)msg, + deliveryMode, + priority, + (uint)timeToLive, + _mandatory, + _immediate + ); + } + + public void Send(IMessage msg) + { + CheckNotClosed(); + SendImpl( + _exchangeName, + _routingKey, + (AbstractQmsMessage)msg, + _deliveryMode, + _messagePriority, + (uint)_timeToLive, + _mandatory, + _immediate + ); + } + + // This is a short-term hack (knowing that this code will be re-vamped sometime soon) + // to facilitate publishing messages to potentially non-existent recipients. + public void Send(IMessage msg, bool mandatory) + { + CheckNotClosed(); + SendImpl( + _exchangeName, + _routingKey, + (AbstractQmsMessage)msg, + _deliveryMode, + _messagePriority, + (uint)_timeToLive, + mandatory, + _immediate + ); + } + + public long TimeToLive + { + get + { + CheckNotClosed(); + return _timeToLive; + } + set + { + CheckNotClosed(); + if ( value < 0 ) + { + throw new ArgumentOutOfRangeException("Time to live must be non-negative - supplied value was " + value); + } + _timeToLive = value; + } + } + + #endregion + + public string MimeType + { + get + { + CheckNotClosed(); + return _mimeType; + } + set + { + CheckNotClosed(); + _mimeType = value; + } + } + + public string Encoding + { + get + { + CheckNotClosed(); + return _encoding; + } + set + { + CheckNotClosed(); + _encoding = value; + } + } + + public void Dispose() + { + Close(); + } + + #region Message Publishing + + private void SendImpl(string exchangeName, string routingKey, AbstractQmsMessage message, DeliveryMode deliveryMode, int priority, uint timeToLive, bool mandatory, bool immediate) + { + // todo: handle session access ticket + AMQFrame publishFrame = BasicPublishBody.CreateAMQFrame( + _channel.ChannelId, 0, exchangeName, + routingKey, mandatory, immediate + ); + + // fix message properties + if ( !_disableTimestamps ) + { + message.Timestamp = DateTime.UtcNow.Ticks; + if (timeToLive != 0) + { + message.Expiration = message.Timestamp + timeToLive; + } + } else + { + message.Expiration = 0; + } + message.DeliveryMode = deliveryMode; + message.Priority = (byte)priority; + + ByteBuffer payload = message.Data; + int payloadLength = payload.Limit; + + ContentBody[] contentBodies = CreateContentBodies(payload); + AMQFrame[] frames = new AMQFrame[2 + contentBodies.Length]; + for ( int i = 0; i < contentBodies.Length; i++ ) + { + frames[2 + i] = ContentBody.CreateAMQFrame(_channelId, contentBodies[i]); + } + if ( contentBodies.Length > 0 && _logger.IsDebugEnabled ) + { + _logger.Debug(string.Format("Sending content body frames to {{exchangeName={0} routingKey={1}}}", exchangeName, routingKey)); + } + + // weight argument of zero indicates no child content headers, just bodies + AMQFrame contentHeaderFrame = ContentHeaderBody.CreateAMQFrame( + _channelId, AmqChannel.BASIC_CONTENT_TYPE, 0, + message.ContentHeaderProperties, (uint)payloadLength + ); + if ( _logger.IsDebugEnabled ) + { + _logger.Debug(string.Format("Sending content header frame to {{exchangeName={0} routingKey={1}}}", exchangeName, routingKey)); + } + + frames[0] = publishFrame; + frames[1] = contentHeaderFrame; + CompositeAMQDataBlock compositeFrame = new CompositeAMQDataBlock(frames); + + lock ( _channel.Connection.FailoverMutex ) + { + _channel.Connection.ProtocolWriter.Write(compositeFrame); + } + } + + + /// + /// Create content bodies. This will split a large message into numerous bodies depending on the negotiated + /// maximum frame size. + /// + /// + /// return the array of content bodies + private ContentBody[] CreateContentBodies(ByteBuffer payload) + { + if ( payload == null ) + { + return null; + } else if ( payload.Remaining == 0 ) + { + return new ContentBody[0]; + } + // we substract one from the total frame maximum size to account for the end of frame marker in a body frame + // (0xCE byte). + int framePayloadMax = (int)(_channel.Connection.MaximumFrameSize - 1); + int frameCount = CalculateContentBodyFrames(payload); + ContentBody[] bodies = new ContentBody[frameCount]; + for ( int i = 0; i < frameCount; i++ ) + { + int length = (payload.Remaining >= framePayloadMax) + ? framePayloadMax : payload.Remaining; + bodies[i] = new ContentBody(payload, (uint)length); + } + return bodies; + } + + private int CalculateContentBodyFrames(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; + int framePayloadMax = (int)_channel.Connection.MaximumFrameSize - 1; + int lastFrame = ((dataLength % framePayloadMax) > 0) ? 1 : 0; + frameCount = (int)(dataLength / framePayloadMax) + lastFrame; + } + + return frameCount; + } + #endregion // Message Publishing + } +} diff --git a/dotnet/Qpid.Client/Client/Message/AbstractQmsMessage.cs b/dotnet/Qpid.Client/Client/Message/AbstractQmsMessage.cs index 34b47137e5..4b38b1a293 100644 --- a/dotnet/Qpid.Client/Client/Message/AbstractQmsMessage.cs +++ b/dotnet/Qpid.Client/Client/Message/AbstractQmsMessage.cs @@ -1,694 +1,699 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -using System; -using System.Collections; -using System.Text; -using log4net; -using Apache.Qpid.Framing; -using Apache.Qpid.Messaging; -using Apache.Qpid.Buffer; - -namespace Apache.Qpid.Client.Message -{ - public abstract class AbstractQmsMessage : AMQMessage, IMessage - { - private static ILog log = LogManager.GetLogger(typeof(AbstractQmsMessage)); - - protected bool _redelivered; - - protected ByteBuffer _data; - protected bool _readableMessage = false; - private QpidHeaders _headers; - - protected AbstractQmsMessage(ByteBuffer data) - : base(new BasicContentHeaderProperties()) - { - Init(data); - } - - protected AbstractQmsMessage(long deliveryTag, BasicContentHeaderProperties contentHeader, ByteBuffer data) - : this(contentHeader, deliveryTag) - { - Init(data); - } - - protected AbstractQmsMessage(BasicContentHeaderProperties contentHeader, long deliveryTag) : base(contentHeader, deliveryTag) - { - Init(null); - } - - private void Init(ByteBuffer data) - { - _data = data; - if ( _data != null ) - { - _data.Acquire(); - } - _readableMessage = (data != null); - if ( ContentHeaderProperties.Headers == null ) - ContentHeaderProperties.Headers = new FieldTable(); - _headers = new QpidHeaders(ContentHeaderProperties.Headers); - } - - // - // Properties - // - - /// - /// The application message identifier - /// - public string MessageId - { - get - { - if (ContentHeaderProperties.MessageId == null) - { - ContentHeaderProperties.MessageId = "ID:" + DeliveryTag; - } - return ContentHeaderProperties.MessageId; - } - set { ContentHeaderProperties.MessageId = value; } - } - - /// - /// The message timestamp - /// - public long Timestamp - { - get - { - // TODO: look at ulong/long choice - return (long) ContentHeaderProperties.Timestamp; - } - set - { - ContentHeaderProperties.Timestamp = (ulong) value; - } - } - - /// - /// The as a byte array. - /// - public byte[] CorrelationIdAsBytes - { - get { return Encoding.Default.GetBytes(ContentHeaderProperties.CorrelationId); } - set { ContentHeaderProperties.CorrelationId = Encoding.Default.GetString(value); } - } - - /// - /// The application correlation identifier - /// - public string CorrelationId - { - get { return ContentHeaderProperties.CorrelationId; } - set { ContentHeaderProperties.CorrelationId = value; } - } - - struct Dest - { - public string ExchangeName; - public string RoutingKey; - - public Dest(string exchangeName, string routingKey) - { - ExchangeName = exchangeName; - RoutingKey = routingKey; - } - } - - /// - /// Exchange name of the reply-to address - /// - public string ReplyToExchangeName - { - get - { - return ReadReplyToHeader().ExchangeName; - } - set - { - BindingURL dest = ReadReplyToHeader(); - dest.ExchangeName = value; - WriteReplyToHeader(dest); - } - } - - /// - /// Routing key of the reply-to address - /// - public string ReplyToRoutingKey - { - get - { - return ReadReplyToHeader().RoutingKey; - } - set - { - BindingURL dest = ReadReplyToHeader(); - dest.RoutingKey = value; - WriteReplyToHeader(dest); - } - } - - /// - /// Non-persistent (1) or persistent (2) - /// - public DeliveryMode DeliveryMode - { - get - { - byte b = ContentHeaderProperties.DeliveryMode; - switch (b) - { - case 1: - return DeliveryMode.NonPersistent; - case 2: - return DeliveryMode.Persistent; - default: - throw new QpidException("Illegal value for delivery mode in content header properties"); - } - } - set - { - ContentHeaderProperties.DeliveryMode = (byte)(value==DeliveryMode.NonPersistent?1:2); - } - } - - /// - /// True, if this is a redelivered message - /// - public bool Redelivered - { - get { return _redelivered; } - set { _redelivered = value; } - } - - /// - /// The message type name - /// - public string Type - { - get { return ContentHeaderProperties.Type; } - set { ContentHeaderProperties.Type = value; } - } - - /// - /// Message expiration specification - /// - public long Expiration - { - get { return ContentHeaderProperties.Expiration; } - set { ContentHeaderProperties.Expiration = value; } - } - - /// - /// The message priority, 0 to 9 - /// - public byte Priority - { - get { return ContentHeaderProperties.Priority; } - set { ContentHeaderProperties.Priority = (byte) value; } - } - - /// - /// The MIME Content Type - /// - public string ContentType - { - get { return ContentHeaderProperties.ContentType; } - set { ContentHeaderProperties.ContentType = value; } - } - - /// - /// The MIME Content Encoding - /// - public string ContentEncoding - { - get { return ContentHeaderProperties.Encoding; } - set { ContentHeaderProperties.Encoding = value; } - } - - /// - /// Headers of this message - /// - public IHeaders Headers - { - get { return _headers; } - } - - /// - /// The creating user id - /// - public string UserId - { - get { return ContentHeaderProperties.UserId; } - set { ContentHeaderProperties.UserId = value; } - } - - /// - /// The creating application id - /// - public string AppId - { - get { return ContentHeaderProperties.AppId; } - set { ContentHeaderProperties.AppId = value; } - } - - /// - /// Intra-cluster routing identifier - /// - public string ClusterId - { - get { return ContentHeaderProperties.ClusterId; } - set { ContentHeaderProperties.ClusterId = value; } - } - - /// - /// Return the raw byte array that is used to populate the frame when sending - /// the message. - /// - /// a byte array of message data - public ByteBuffer Data - { - get - { - if (_data != null) - { - if (!_readableMessage) - { - _data.Flip(); - } - else - { - // Make sure we rewind the data just in case any method has moved the - // position beyond the start. - _data.Rewind(); - } - } - return _data; - } - - set - { - _data = value; - } - } - - public void Acknowledge() - { - // 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 (_channel != null) - { - // we set multiple to true here since acknowledgement implies acknowledge of all count messages - // received on the session - _channel.AcknowledgeMessage((ulong)DeliveryTag, true); - } - - } - - public abstract void ClearBodyImpl(); - - public void ClearBody() - { - ClearBodyImpl(); - _readableMessage = false; - } - - /// - /// 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(); - - public override string ToString() - { - try - { - StringBuilder buf = new StringBuilder("Body:\n"); - buf.Append(ToBodyString()); - buf.Append("\nQmsTimestamp: ").Append(Timestamp); - buf.Append("\nQmsExpiration: ").Append(Expiration); - buf.Append("\nQmsPriority: ").Append(Priority); - buf.Append("\nQmsDeliveryMode: ").Append(DeliveryMode); - buf.Append("\nReplyToExchangeName: ").Append(ReplyToExchangeName); - buf.Append("\nReplyToRoutingKey: ").Append(ReplyToRoutingKey); - buf.Append("\nAMQ message number: ").Append(DeliveryTag); - buf.Append("\nProperties:"); - if (ContentHeaderProperties.Headers == null) - { - buf.Append(""); - } - else - { - buf.Append(Headers.ToString()); - } - return buf.ToString(); - } - catch (Exception e) - { - return e.ToString(); - } - } - - public FieldTable PopulateHeadersFromMessageProperties() - { - if (ContentHeaderProperties.Headers == null) - { - return null; - } - else - { - // - // We need to convert every property into a String representation - // Note that type information is preserved in the property name - // - FieldTable table = new FieldTable(); - foreach (DictionaryEntry entry in ContentHeaderProperties.Headers) - { - string propertyName = (string) entry.Key; - if (propertyName == null) - { - continue; - } - else - { - table[propertyName] = entry.Value.ToString(); - } - } - return table; - } - } - - public BasicContentHeaderProperties ContentHeaderProperties - { - get - { - return (BasicContentHeaderProperties) _contentHeaderProperties; - } - } - - protected virtual void Reset() - { - _readableMessage = true; - } - - public bool IsReadable - { - get { return _readableMessage; } - } - - public bool isWritable - { - get { return !_readableMessage; } - } - - protected void CheckReadable() - { - if ( !_readableMessage ) - { - throw new MessageNotReadableException("You need to call reset() to make the message readable"); - } - } - - /// - /// Decodes the replyto field if one is set. - /// - /// Splits a replyto field containing an exchange name followed by a ':', followed by a routing key into the exchange name and - /// routing key seperately. The exchange name may be empty in which case the empty string is returned. If the exchange name is - /// empty the replyto field is expected to being with ':'. - /// - /// Anyhting other than a two part replyto field sperated with a ':' will result in an exception. - /// - /// - /// A destination initialized to the replyto location if a replyto field was set, or an empty destination otherwise. - private BindingURL ReadReplyToHeader() - { - string replyToEncoding = ContentHeaderProperties.ReplyTo; - //log.Debug("replyToEncoding = " + replyToEncoding); - - BindingURL bindingUrl = new BindingURL(replyToEncoding); - //log.Debug("bindingUrl = " + bindingUrl.ToString()); - - return bindingUrl; - - //log.Info("replyToEncoding = " + replyToEncoding); - -// if ( replyToEncoding == null ) -// { -// return new Dest(); -// } else -// { -// // Split the replyto field on a ':' -// string[] split = replyToEncoding.Split(':'); - -// // Ensure that the replyto field argument only consisted of two parts. -// if ( split.Length != 2 ) -// { -// throw new QpidException("Illegal value in ReplyTo property: " + replyToEncoding); -// } - -// // Extract the exchange name and routing key from the split replyto field. -// string exchangeName = split[0]; - -// string[] split2 = split[1].Split('/'); -// string routingKey = split2[3]; - -// return new Dest(exchangeName, routingKey); -// } - } - - private void WriteReplyToHeader(BindingURL dest) - { - string encodedDestination = string.Format("{0}:{1}", dest.ExchangeName, dest.RoutingKey); - ContentHeaderProperties.ReplyTo = encodedDestination; - } - } - - public class BindingURL - { - public readonly static string OPTION_EXCLUSIVE = "exclusive"; - public readonly static string OPTION_AUTODELETE = "autodelete"; - public readonly static string OPTION_DURABLE = "durable"; - public readonly static string OPTION_CLIENTID = "clientid"; - public readonly static string OPTION_SUBSCRIPTION = "subscription"; - public readonly static string OPTION_ROUTING_KEY = "routingkey"; - - /// Holds the undecoded URL - string url; - - /// Holds the decoded options. - IDictionary options = new Hashtable(); - - /// Holds the decoded exchange class. - string exchangeClass; - - /// Holds the decoded exchange name. - string exchangeName; - - /// Holds the destination name. - string destination; - - /// Holds the decoded queue name. - string queueName; - - /// - /// The binding URL has the format: - /// :///[]/[]? - public BindingURL(string url) - { - this.url = url; - Parse(); - } - - public string Url { get { return url; } } - - public string ExchangeClass - { - get { return exchangeClass; } - set { exchangeClass = value; } - } - - public string ExchangeName - { - get { return exchangeName; } - set { exchangeName = value; } - } - - public string QueueName - { - get { return queueName; } - set { queueName = value; } - } - - public string DestinationName - { - get { return destination; } - set { destination = value; } - } - - public string RoutingKey { - get { return (string)options[OPTION_ROUTING_KEY]; } - set { options[OPTION_ROUTING_KEY] = value; } - } - - public bool ContainsOption(string key) { return options.Contains(key); } - - public string ToString() - { - return "BindingURL: [ ExchangeClass = " + ExchangeClass + ", ExchangeName = " + ExchangeName + ", QueueName = " + QueueName + - ", DestinationName = " + DestinationName + ", RoutingKey = " + RoutingKey + " ] "; - } - - private void Parse() - { - Uri binding = new Uri(url); - - // Extract the URI scheme, this contains the exchange class. It is defaulted to the direct exchange if not specified. - string exchangeClass = binding.Scheme; - - if (exchangeClass == null) - { - url = ExchangeNameDefaults.DIRECT_EXCHANGE_CLASS + "://" + ExchangeNameDefaults.DIRECT + "//" + url; - Parse(); - - return; - } - else - { - this.exchangeClass = exchangeClass; - } - - // Extract the host name, this contains the exchange name. It is defaulted to the default direct exchange if not specified. - string exchangeName = binding.Host; - - if (exchangeName == null) - { - if (exchangeClass.Equals(ExchangeNameDefaults.DIRECT_EXCHANGE_CLASS)) - { - this.exchangeName = ""; - } - } - else - { - this.exchangeName = exchangeName; - } - - // Extract the destination and queue name. - if ((binding.AbsolutePath == null) || binding.AbsolutePath.Equals("")) - { - throw new UriFormatException("Destination or Queue required"); - } - else - { - int slashOffset = binding.AbsolutePath.IndexOf("/", 1); - if (slashOffset == -1) - { - throw new UriFormatException("Destination required"); - } - else - { - String path = binding.AbsolutePath; - - this.destination = path.Substring(1, slashOffset - 1); - this.queueName = path.Substring(slashOffset + 1); - } - } - - ParseOptions(options, binding.Query); - - // If the routing key is not set as an option, set it to the destination name. - if (!ContainsOption(OPTION_ROUTING_KEY)) - { - options[OPTION_ROUTING_KEY] = destination; - } - } - - /// - /// options looks like this - /// brokerlist='tcp://host:port?option='value',option='value';vm://:3/virtualpath?option='value'',failover='method?option='value',option='value' - /// - public static void ParseOptions(IDictionary optionMap, string options) - { - // Check that there really are some options to parse. - if ((options == null) || (options.IndexOf('=') == -1)) - { - return; - } - - int optionIndex = options.IndexOf('='); - string option = options.Substring(0, optionIndex); - int length = options.Length; - int nestedQuotes = 0; - - // Holds the index of the final "'". - int valueIndex = optionIndex; - - // Loop over all the options.Dest - while ((nestedQuotes > 0) || (valueIndex < length)) - { - valueIndex++; - - if (valueIndex >= length) - { - break; - } - - if (options[valueIndex] == '\'') - { - if ((valueIndex + 1) < options.Length) - { - if ((options[valueIndex + 1] == '&') || - (options[valueIndex + 1] == ',') || - (options[valueIndex + 1] == ';') || - (options[valueIndex + 1] == '\'')) - { - nestedQuotes--; - - if (nestedQuotes == 0) - { - // We've found the value of an option - break; - } - } - else - { - nestedQuotes++; - } - } - else - { - // We are at the end of the string - // Check to see if we are corectly closing quotes - if (options[valueIndex] == '\'') - { - nestedQuotes--; - } - - break; - } - } - } - } - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +using System; +using System.Collections; +using System.Text; +using log4net; +using Apache.Qpid.Framing; +using Apache.Qpid.Messaging; +using Apache.Qpid.Buffer; + +namespace Apache.Qpid.Client.Message +{ + public abstract class AbstractQmsMessage : AMQMessage, IMessage + { + private static ILog log = LogManager.GetLogger(typeof(AbstractQmsMessage)); + + protected bool _redelivered; + + protected ByteBuffer _data; + protected bool _readableMessage = false; + private QpidHeaders _headers; + + protected AbstractQmsMessage(ByteBuffer data) + : base(new BasicContentHeaderProperties()) + { + Init(data); + } + + protected AbstractQmsMessage(long deliveryTag, BasicContentHeaderProperties contentHeader, ByteBuffer data) + : this(contentHeader, deliveryTag) + { + Init(data); + } + + protected AbstractQmsMessage(BasicContentHeaderProperties contentHeader, long deliveryTag) : base(contentHeader, deliveryTag) + { + Init(null); + } + + private void Init(ByteBuffer data) + { + _data = data; + if ( _data != null ) + { + _data.Acquire(); + } + _readableMessage = (data != null); + if ( ContentHeaderProperties.Headers == null ) + ContentHeaderProperties.Headers = new FieldTable(); + _headers = new QpidHeaders(ContentHeaderProperties.Headers); + } + + // + // Properties + // + + /// + /// The application message identifier + /// + public string MessageId + { + get + { + if (ContentHeaderProperties.MessageId == null) + { + ContentHeaderProperties.MessageId = "ID:" + DeliveryTag; + } + return ContentHeaderProperties.MessageId; + } + set { ContentHeaderProperties.MessageId = value; } + } + + /// + /// The message timestamp + /// + public long Timestamp + { + get + { + // TODO: look at ulong/long choice + return (long) ContentHeaderProperties.Timestamp; + } + set + { + ContentHeaderProperties.Timestamp = (ulong) value; + } + } + + /// + /// The as a byte array. + /// + public byte[] CorrelationIdAsBytes + { + get { return Encoding.Default.GetBytes(ContentHeaderProperties.CorrelationId); } + set { ContentHeaderProperties.CorrelationId = Encoding.Default.GetString(value); } + } + + /// + /// The application correlation identifier + /// + public string CorrelationId + { + get { return ContentHeaderProperties.CorrelationId; } + set { ContentHeaderProperties.CorrelationId = value; } + } + + struct Dest + { + public string ExchangeName; + public string RoutingKey; + + public Dest(string exchangeName, string routingKey) + { + ExchangeName = exchangeName; + RoutingKey = routingKey; + } + } + + /// + /// Exchange name of the reply-to address + /// + public string ReplyToExchangeName + { + get + { + return ReadReplyToHeader().ExchangeName; + } + set + { + BindingURL dest = ReadReplyToHeader(); + dest.ExchangeName = value; + WriteReplyToHeader(dest); + } + } + + /// + /// Routing key of the reply-to address + /// + public string ReplyToRoutingKey + { + get + { + return ReadReplyToHeader().RoutingKey; + } + set + { + BindingURL dest = ReadReplyToHeader(); + dest.RoutingKey = value; + WriteReplyToHeader(dest); + } + } + + /// + /// Non-persistent (1) or persistent (2) + /// + public DeliveryMode DeliveryMode + { + get + { + byte b = ContentHeaderProperties.DeliveryMode; + switch (b) + { + case 1: + return DeliveryMode.NonPersistent; + case 2: + return DeliveryMode.Persistent; + default: + throw new QpidException("Illegal value for delivery mode in content header properties"); + } + } + set + { + ContentHeaderProperties.DeliveryMode = (byte)(value==DeliveryMode.NonPersistent?1:2); + } + } + + /// + /// True, if this is a redelivered message + /// + public bool Redelivered + { + get { return _redelivered; } + set { _redelivered = value; } + } + + /// + /// The message type name + /// + public string Type + { + get { return ContentHeaderProperties.Type; } + set { ContentHeaderProperties.Type = value; } + } + + /// + /// Message expiration specification + /// + public long Expiration + { + get { return ContentHeaderProperties.Expiration; } + set { ContentHeaderProperties.Expiration = value; } + } + + /// + /// The message priority, 0 to 9 + /// + public byte Priority + { + get { return ContentHeaderProperties.Priority; } + set { ContentHeaderProperties.Priority = (byte) value; } + } + + /// + /// The MIME Content Type + /// + public string ContentType + { + get { return ContentHeaderProperties.ContentType; } + set { ContentHeaderProperties.ContentType = value; } + } + + /// + /// The MIME Content Encoding + /// + public string ContentEncoding + { + get { return ContentHeaderProperties.Encoding; } + set { ContentHeaderProperties.Encoding = value; } + } + + /// + /// Headers of this message + /// + public IHeaders Headers + { + get { return _headers; } + } + + /// + /// The creating user id + /// + public string UserId + { + get { return ContentHeaderProperties.UserId; } + set { ContentHeaderProperties.UserId = value; } + } + + /// + /// The creating application id + /// + public string AppId + { + get { return ContentHeaderProperties.AppId; } + set { ContentHeaderProperties.AppId = value; } + } + + /// + /// Intra-cluster routing identifier + /// + public string ClusterId + { + get { return ContentHeaderProperties.ClusterId; } + set { ContentHeaderProperties.ClusterId = value; } + } + + /// + /// Return the raw byte array that is used to populate the frame when sending + /// the message. + /// + /// a byte array of message data + public ByteBuffer Data + { + get + { + if (_data != null) + { + if (!_readableMessage) + { + _data.Flip(); + } + else + { + // Make sure we rewind the data just in case any method has moved the + // position beyond the start. + _data.Rewind(); + } + } + return _data; + } + + set + { + _data = value; + } + } + + public void Acknowledge() + { + // we set multiple to true here since acknowledgement implies acknowledge of all messages + // received on the session. That's a bit JMSy though. + Acknowledge(true); + } + + public void Acknowledge(bool ackprevious) + { + // 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 (_channel != null) + { + _channel.AcknowledgeMessage((ulong)DeliveryTag, ackprevious); + } + + } + + public abstract void ClearBodyImpl(); + + public void ClearBody() + { + ClearBodyImpl(); + _readableMessage = false; + } + + /// + /// 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(); + + public override string ToString() + { + try + { + StringBuilder buf = new StringBuilder("Body:\n"); + buf.Append(ToBodyString()); + buf.Append("\nQmsTimestamp: ").Append(Timestamp); + buf.Append("\nQmsExpiration: ").Append(Expiration); + buf.Append("\nQmsPriority: ").Append(Priority); + buf.Append("\nQmsDeliveryMode: ").Append(DeliveryMode); + buf.Append("\nReplyToExchangeName: ").Append(ReplyToExchangeName); + buf.Append("\nReplyToRoutingKey: ").Append(ReplyToRoutingKey); + buf.Append("\nAMQ message number: ").Append(DeliveryTag); + buf.Append("\nProperties:"); + if (ContentHeaderProperties.Headers == null) + { + buf.Append(""); + } + else + { + buf.Append(Headers.ToString()); + } + return buf.ToString(); + } + catch (Exception e) + { + return e.ToString(); + } + } + + public FieldTable PopulateHeadersFromMessageProperties() + { + if (ContentHeaderProperties.Headers == null) + { + return null; + } + else + { + // + // We need to convert every property into a String representation + // Note that type information is preserved in the property name + // + FieldTable table = new FieldTable(); + foreach (DictionaryEntry entry in ContentHeaderProperties.Headers) + { + string propertyName = (string) entry.Key; + if (propertyName == null) + { + continue; + } + else + { + table[propertyName] = entry.Value.ToString(); + } + } + return table; + } + } + + public BasicContentHeaderProperties ContentHeaderProperties + { + get + { + return (BasicContentHeaderProperties) _contentHeaderProperties; + } + } + + protected virtual void Reset() + { + _readableMessage = true; + } + + public bool IsReadable + { + get { return _readableMessage; } + } + + public bool isWritable + { + get { return !_readableMessage; } + } + + protected void CheckReadable() + { + if ( !_readableMessage ) + { + throw new MessageNotReadableException("You need to call reset() to make the message readable"); + } + } + + /// + /// Decodes the replyto field if one is set. + /// + /// Splits a replyto field containing an exchange name followed by a ':', followed by a routing key into the exchange name and + /// routing key seperately. The exchange name may be empty in which case the empty string is returned. If the exchange name is + /// empty the replyto field is expected to being with ':'. + /// + /// Anyhting other than a two part replyto field sperated with a ':' will result in an exception. + /// + /// + /// A destination initialized to the replyto location if a replyto field was set, or an empty destination otherwise. + private BindingURL ReadReplyToHeader() + { + string replyToEncoding = ContentHeaderProperties.ReplyTo; + //log.Debug("replyToEncoding = " + replyToEncoding); + + BindingURL bindingUrl = new BindingURL(replyToEncoding); + //log.Debug("bindingUrl = " + bindingUrl.ToString()); + + return bindingUrl; + + //log.Info("replyToEncoding = " + replyToEncoding); + +// if ( replyToEncoding == null ) +// { +// return new Dest(); +// } else +// { +// // Split the replyto field on a ':' +// string[] split = replyToEncoding.Split(':'); + +// // Ensure that the replyto field argument only consisted of two parts. +// if ( split.Length != 2 ) +// { +// throw new QpidException("Illegal value in ReplyTo property: " + replyToEncoding); +// } + +// // Extract the exchange name and routing key from the split replyto field. +// string exchangeName = split[0]; + +// string[] split2 = split[1].Split('/'); +// string routingKey = split2[3]; + +// return new Dest(exchangeName, routingKey); +// } + } + + private void WriteReplyToHeader(BindingURL dest) + { + string encodedDestination = string.Format("{0}:{1}", dest.ExchangeName, dest.RoutingKey); + ContentHeaderProperties.ReplyTo = encodedDestination; + } + } + + public class BindingURL + { + public readonly static string OPTION_EXCLUSIVE = "exclusive"; + public readonly static string OPTION_AUTODELETE = "autodelete"; + public readonly static string OPTION_DURABLE = "durable"; + public readonly static string OPTION_CLIENTID = "clientid"; + public readonly static string OPTION_SUBSCRIPTION = "subscription"; + public readonly static string OPTION_ROUTING_KEY = "routingkey"; + + /// Holds the undecoded URL + string url; + + /// Holds the decoded options. + IDictionary options = new Hashtable(); + + /// Holds the decoded exchange class. + string exchangeClass; + + /// Holds the decoded exchange name. + string exchangeName; + + /// Holds the destination name. + string destination; + + /// Holds the decoded queue name. + string queueName; + + /// + /// The binding URL has the format: + /// :///[]/[]? + public BindingURL(string url) + { + this.url = url; + Parse(); + } + + public string Url { get { return url; } } + + public string ExchangeClass + { + get { return exchangeClass; } + set { exchangeClass = value; } + } + + public string ExchangeName + { + get { return exchangeName; } + set { exchangeName = value; } + } + + public string QueueName + { + get { return queueName; } + set { queueName = value; } + } + + public string DestinationName + { + get { return destination; } + set { destination = value; } + } + + public string RoutingKey { + get { return (string)options[OPTION_ROUTING_KEY]; } + set { options[OPTION_ROUTING_KEY] = value; } + } + + public bool ContainsOption(string key) { return options.Contains(key); } + + public string ToString() + { + return "BindingURL: [ ExchangeClass = " + ExchangeClass + ", ExchangeName = " + ExchangeName + ", QueueName = " + QueueName + + ", DestinationName = " + DestinationName + ", RoutingKey = " + RoutingKey + " ] "; + } + + private void Parse() + { + Uri binding = new Uri(url); + + // Extract the URI scheme, this contains the exchange class. It is defaulted to the direct exchange if not specified. + string exchangeClass = binding.Scheme; + + if (exchangeClass == null) + { + url = ExchangeNameDefaults.DIRECT_EXCHANGE_CLASS + "://" + ExchangeNameDefaults.DIRECT + "//" + url; + Parse(); + + return; + } + else + { + this.exchangeClass = exchangeClass; + } + + // Extract the host name, this contains the exchange name. It is defaulted to the default direct exchange if not specified. + string exchangeName = binding.Host; + + if (exchangeName == null) + { + if (exchangeClass.Equals(ExchangeNameDefaults.DIRECT_EXCHANGE_CLASS)) + { + this.exchangeName = ""; + } + } + else + { + this.exchangeName = exchangeName; + } + + // Extract the destination and queue name. + if ((binding.AbsolutePath == null) || binding.AbsolutePath.Equals("")) + { + throw new UriFormatException("Destination or Queue required"); + } + else + { + int slashOffset = binding.AbsolutePath.IndexOf("/", 1); + if (slashOffset == -1) + { + throw new UriFormatException("Destination required"); + } + else + { + String path = binding.AbsolutePath; + + this.destination = path.Substring(1, slashOffset - 1); + this.queueName = path.Substring(slashOffset + 1); + } + } + + ParseOptions(options, binding.Query); + + // If the routing key is not set as an option, set it to the destination name. + if (!ContainsOption(OPTION_ROUTING_KEY)) + { + options[OPTION_ROUTING_KEY] = destination; + } + } + + /// + /// options looks like this + /// brokerlist='tcp://host:port?option='value',option='value';vm://:3/virtualpath?option='value'',failover='method?option='value',option='value' + /// + public static void ParseOptions(IDictionary optionMap, string options) + { + // Check that there really are some options to parse. + if ((options == null) || (options.IndexOf('=') == -1)) + { + return; + } + + int optionIndex = options.IndexOf('='); + string option = options.Substring(0, optionIndex); + int length = options.Length; + int nestedQuotes = 0; + + // Holds the index of the final "'". + int valueIndex = optionIndex; + + // Loop over all the options.Dest + while ((nestedQuotes > 0) || (valueIndex < length)) + { + valueIndex++; + + if (valueIndex >= length) + { + break; + } + + if (options[valueIndex] == '\'') + { + if ((valueIndex + 1) < options.Length) + { + if ((options[valueIndex + 1] == '&') || + (options[valueIndex + 1] == ',') || + (options[valueIndex + 1] == ';') || + (options[valueIndex + 1] == '\'')) + { + nestedQuotes--; + + if (nestedQuotes == 0) + { + // We've found the value of an option + break; + } + } + else + { + nestedQuotes++; + } + } + else + { + // We are at the end of the string + // Check to see if we are corectly closing quotes + if (options[valueIndex] == '\'') + { + nestedQuotes--; + } + + break; + } + } + } + } + } +} diff --git a/dotnet/Qpid.Integration.Tests/Qpid.Integration.Tests.csproj b/dotnet/Qpid.Integration.Tests/Qpid.Integration.Tests.csproj index d271b05526..a4d2cf0e00 100644 --- a/dotnet/Qpid.Integration.Tests/Qpid.Integration.Tests.csproj +++ b/dotnet/Qpid.Integration.Tests/Qpid.Integration.Tests.csproj @@ -47,6 +47,7 @@ + @@ -63,4 +64,10 @@ + + + {68987C05-3768-452C-A6FC-6BA1D372852F} + Qpid.Client + + \ No newline at end of file diff --git a/dotnet/Qpid.Integration.Tests/testcases/BaseMessagingTestFixture.cs b/dotnet/Qpid.Integration.Tests/testcases/BaseMessagingTestFixture.cs index 7859848233..c44c5e53d1 100644 --- a/dotnet/Qpid.Integration.Tests/testcases/BaseMessagingTestFixture.cs +++ b/dotnet/Qpid.Integration.Tests/testcases/BaseMessagingTestFixture.cs @@ -297,5 +297,14 @@ namespace Apache.Qpid.Integration.Tests.testcases return buf.ToString(); } + + protected void SendMessages(int count, IMessagePublisher pub) + { + for (int i = 0; i < count; i++) + { + pub.Send(pub.Channel.CreateTextMessage("Test message "+i)); + } + + } } } diff --git a/dotnet/Qpid.Integration.Tests/testcases/ClientAckTests.cs b/dotnet/Qpid.Integration.Tests/testcases/ClientAckTests.cs new file mode 100755 index 0000000000..64b5e6c31a --- /dev/null +++ b/dotnet/Qpid.Integration.Tests/testcases/ClientAckTests.cs @@ -0,0 +1,179 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +using System; +using System.Threading; +using log4net; +using NUnit.Framework; +using Apache.Qpid.Messaging; +using Apache.Qpid.Client.Qms; +using Apache.Qpid.Client; + +namespace Apache.Qpid.Integration.Tests.testcases +{ + /// + /// Checks that byte messages can be produced and received properly. + /// + [TestFixture, Category("Integration")] + public class ClientAckTests : BaseMessagingTestFixture + { + private static ILog log = LogManager.GetLogger(typeof(ClientAckTests)); + private static string TEST_ROUTING_KEY = "MESSAGE_ACK_TEST_QUEUE"; + private IMessage msgA; + private IMessage msgB; + private IMessage msgC; + + [SetUp] + public override void Init() + { + base.Init(); + + // Create one producer and one consumer, p2p, tx, consumer with queue bound to producers routing key. + SetUpEndPoint(0, true, false, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, false, null); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + // Send 3 messages and get them back + SendMessages(3, testProducer[0]); + msgA = testConsumer[1].Receive(); + msgB = testConsumer[1].Receive(); + msgC = testConsumer[1].Receive(); + } + + [TearDown] + public override void Shutdown() + { + try + { + // Clean up after the test. + CloseEndPoint(0); + CloseEndPoint(1); + } + finally + { + base.Shutdown(); + } + } + + [Test] + /// Send 3 messages, get them back and each one rolling acks up. + public void TestAckingABCAll() + { + msgA.Acknowledge(); + msgB.Acknowledge(); + msgC.Acknowledge(); + + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + ConsumeNMessagesOnly(0, "wibble", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and ack each one individually + public void TestAckingABCIndividual() + { + msgA.Acknowledge(false); + msgB.Acknowledge(false); + msgC.Acknowledge(false); + + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + ConsumeNMessagesOnly(0, "wibble", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and the middle one only rolling acks up. + public void TestAckingBOnlyAll() + { + msgB.Acknowledge(); + + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + log.Debug("Checking we get the last message back"); + ConsumeNMessagesOnly(1, "Test message 2", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and ack the middle one only individually. + public void TestAckingBOnlyIndividual() + { + msgB.Acknowledge(false); + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + ConsumeNMessages(1, "Test message 0", testConsumer[1]); + ConsumeNMessagesOnly(1, "Test message 2", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and ack the last one, rolling acks up. + public void TestAckingCOnlyAll() + { + msgC.Acknowledge(); + + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + ConsumeNMessagesOnly(0, "wibble", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and ack the last oneindivdually. + public void TestAckingCOnlyIndividual() + { + msgC.Acknowledge(false); + + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + ConsumeNMessages(1, "Test message 0", testConsumer[1]); + ConsumeNMessagesOnly(1, "Test message 1", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and the first two indivdually. + public void TestAckingAtoBIndivdual() + { + msgA.Acknowledge(false); + msgB.Acknowledge(false); + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + ConsumeNMessagesOnly(1, "Test message 2", testConsumer[1]); + } + + [Test] + /// Send 3 messages, get them back and ack the first and last one indivdually. + public void TestAckingAandCIndivdual() + { + msgA.Acknowledge(false); + msgC.Acknowledge(false); + CloseEndPoint(1); + SetUpEndPoint(1, false, true, TEST_ROUTING_KEY + testId, AcknowledgeMode.ClientAcknowledge, false, ExchangeNameDefaults.DIRECT, + true, true, testId.ToString()); + //((AmqChannel)testChannel[2]).Suspend(false); + ConsumeNMessagesOnly(1, "Test message 1", testConsumer[1]); + } + } +} diff --git a/dotnet/Qpid.Messaging/IMessage.cs b/dotnet/Qpid.Messaging/IMessage.cs index 20ae5ee130..1a40243fd6 100644 --- a/dotnet/Qpid.Messaging/IMessage.cs +++ b/dotnet/Qpid.Messaging/IMessage.cs @@ -1,97 +1,98 @@ -/* - * - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ -namespace Apache.Qpid.Messaging -{ - public interface IMessage - { - /// - /// The MIME Content Type - /// - string ContentType { get; set;} - /// - /// The MIME Content Encoding - /// - string ContentEncoding { get; set; } - /// - /// The application correlation identifier - /// - string CorrelationId { get; set; } - /// - /// The application correlation identifier, as an array of bytes - /// - byte[] CorrelationIdAsBytes { get; set; } - /// - /// Non-persistent (1) or persistent (2) - /// - DeliveryMode DeliveryMode { get; set; } - /// - /// Message expiration specification - /// - long Expiration { get; set; } - /// - /// The application message identifier - /// - string MessageId { get; set; } - /// - /// The message priority, 0 to 9 - /// - byte Priority { get; set; } - /// - /// True if the message has been redelivered - /// - bool Redelivered { get; set; } - /// - /// Exchange name of the reply-to address - /// - string ReplyToExchangeName { get; set; } - /// - /// Routing key of the reply-to address - /// - string ReplyToRoutingKey { get; set; } - /// - /// The message timestamp - /// - long Timestamp { get; set; } - /// - /// The message type name - /// - string Type { get; set; } - /// - /// Message headers - /// - IHeaders Headers { get; } - /// - /// The creating user id - /// - string UserId { get; set; } - /// - /// The creating application id - /// - string AppId { get; set; } - /// - /// Intra-cluster routing identifier - /// - string ClusterId { get; set; } - - void Acknowledge(); - void ClearBody(); - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +namespace Apache.Qpid.Messaging +{ + public interface IMessage + { + /// + /// The MIME Content Type + /// + string ContentType { get; set;} + /// + /// The MIME Content Encoding + /// + string ContentEncoding { get; set; } + /// + /// The application correlation identifier + /// + string CorrelationId { get; set; } + /// + /// The application correlation identifier, as an array of bytes + /// + byte[] CorrelationIdAsBytes { get; set; } + /// + /// Non-persistent (1) or persistent (2) + /// + DeliveryMode DeliveryMode { get; set; } + /// + /// Message expiration specification + /// + long Expiration { get; set; } + /// + /// The application message identifier + /// + string MessageId { get; set; } + /// + /// The message priority, 0 to 9 + /// + byte Priority { get; set; } + /// + /// True if the message has been redelivered + /// + bool Redelivered { get; set; } + /// + /// Exchange name of the reply-to address + /// + string ReplyToExchangeName { get; set; } + /// + /// Routing key of the reply-to address + /// + string ReplyToRoutingKey { get; set; } + /// + /// The message timestamp + /// + long Timestamp { get; set; } + /// + /// The message type name + /// + string Type { get; set; } + /// + /// Message headers + /// + IHeaders Headers { get; } + /// + /// The creating user id + /// + string UserId { get; set; } + /// + /// The creating application id + /// + string AppId { get; set; } + /// + /// Intra-cluster routing identifier + /// + string ClusterId { get; set; } + + void Acknowledge(); + void Acknowledge(bool ackprevious); + void ClearBody(); + } +} diff --git a/dotnet/Qpid.Messaging/IMessagePublisher.cs b/dotnet/Qpid.Messaging/IMessagePublisher.cs index d895a9749b..f223211729 100644 --- a/dotnet/Qpid.Messaging/IMessagePublisher.cs +++ b/dotnet/Qpid.Messaging/IMessagePublisher.cs @@ -1,92 +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. - * - */ -using System; - -namespace Apache.Qpid.Messaging -{ - /// - /// Defines an object capable of publishing messages - /// to an AMQP broker. - /// - /// - /// A publisher can be created using either - /// or - /// using the builder pattern (preferred) with - /// - /// - public interface IMessagePublisher : IDisposable, ICloseable - { - /// - /// Default delivery mode to use with this publisher - /// - DeliveryMode DeliveryMode { get; set; } - /// - /// Name of exchange messages are published to - /// - string ExchangeName { get; } - /// - /// Routing key used when publishing messages - /// - string RoutingKey { get; } - /// - /// If true, a message ID will not be generated by the publisher - /// when sending the message - /// - bool DisableMessageID { get; set; } - /// - /// If true, no timestamp will be added to the message - /// when publishing it - /// - bool DisableMessageTimestamp { get; set; } - /// - /// Default priority used when publishing messages - /// - int Priority { get; set; } - /// - /// Default time to live used when publishing messages - /// - long TimeToLive { get; set; } - /// - /// Set the default MIME type for messages produced by this producer. - /// This reduces the overhead of each message. - /// - string MimeType { get; set; } - /// - /// Set the default encoding for messages produced by this producer. - /// This reduces the overhead of each message. - /// - string Encoding { get; set; } - - /// - /// Publish a message, using any default values configured - /// - /// Message to publish - void Send(IMessage msg); - /// - /// Publish a message with the specified options - /// - /// Message to publish - /// Delivery mode to use - /// Priority of the message - /// Time to live of the message - void Send(IMessage msg, DeliveryMode deliveryMode, int priority, long timeToLive); - } -} +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +using System; + +namespace Apache.Qpid.Messaging +{ + /// + /// Defines an object capable of publishing messages + /// to an AMQP broker. + /// + /// + /// A publisher can be created using either + /// or + /// using the builder pattern (preferred) with + /// + /// + public interface IMessagePublisher : IDisposable, ICloseable + { + /// + /// Default delivery mode to use with this publisher + /// + DeliveryMode DeliveryMode { get; set; } + /// + /// Name of exchange messages are published to + /// + string ExchangeName { get; } + /// + /// Routing key used when publishing messages + /// + string RoutingKey { get; } + /// + /// If true, a message ID will not be generated by the publisher + /// when sending the message + /// + bool DisableMessageID { get; set; } + /// + /// If true, no timestamp will be added to the message + /// when publishing it + /// + bool DisableMessageTimestamp { get; set; } + /// + /// Default priority used when publishing messages + /// + int Priority { get; set; } + /// + /// Default time to live used when publishing messages + /// + long TimeToLive { get; set; } + /// + /// Set the default MIME type for messages produced by this producer. + /// This reduces the overhead of each message. + /// + string MimeType { get; set; } + /// + /// Set the default encoding for messages produced by this producer. + /// This reduces the overhead of each message. + /// + string Encoding { get; set; } + + /// + /// Get the channel this producer is on + /// + IChannel Channel { get; } + + /// + /// Publish a message, using any default values configured + /// + /// Message to publish + void Send(IMessage msg); + /// + /// Publish a message with the specified options + /// + /// Message to publish + /// Delivery mode to use + /// Priority of the message + /// Time to live of the message + void Send(IMessage msg, DeliveryMode deliveryMode, int priority, long timeToLive); + } +} -- cgit v1.2.1