| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT 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.Buffer; |
| using Apache.Qpid.Collections; |
| using Apache.Qpid.Messaging; |
| |
| namespace Apache.Qpid.Framing |
| { |
| public class FieldTable : IFieldTable, IEnumerable |
| { |
| private static readonly ILog _log = LogManager.GetLogger(typeof(FieldTable)); |
| |
| IDictionary _properties; |
| private ByteBuffer _encodedForm; |
| private object _syncLock; |
| private uint _encodedSize; |
| |
| public FieldTable() |
| { |
| _syncLock = new object(); |
| } |
| |
| /// <summary> |
| /// Construct a new field table. |
| /// </summary> |
| /// <param name="buffer">the buffer from which to read data. The length byte must be read already</param> |
| /// <param name="length">the length of the field table. Must be > 0.</param> |
| public FieldTable(ByteBuffer buffer, uint length) : this() |
| { |
| _encodedForm = buffer.Slice(); |
| _encodedForm.Limit = (int)length; |
| _encodedSize = length; |
| buffer.Skip((int)length); |
| } |
| |
| /// <summary> |
| /// The set of all property names |
| /// </summary> |
| public ICollection Keys |
| { |
| get |
| { |
| InitMapIfNecessary(); |
| return _properties.Keys; |
| } |
| } |
| |
| /// <summary> |
| /// Calculated size of this field table once encoded |
| /// </summary> |
| public uint EncodedSize |
| { |
| get { return _encodedSize; } |
| } |
| |
| /// <summary> |
| /// Number of properties in the field table |
| /// </summary> |
| public int Count |
| { |
| get |
| { |
| InitMapIfNecessary(); |
| return _properties.Count; |
| } |
| } |
| |
| /// <summary> |
| /// Gets or sets the specified property. |
| /// </summary> |
| /// <param name="key">Property name</param> |
| /// <returns>The specified property value</returns> |
| public object this[string key] |
| { |
| get { return GetObject(key); } |
| set { SetObject(key, value); } |
| } |
| |
| #region Typed Setters and Getters |
| // |
| // Typed Setters and Getters |
| // |
| public bool GetBoolean(string key) |
| { |
| return (bool)this[key]; |
| } |
| public void SetBoolean(string key, bool value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.BOOLEAN.AsTypedValue(value)); |
| } |
| public byte GetByte(string key) |
| { |
| return (byte)this[key]; |
| } |
| public void SetByte(string key, byte value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.BYTE.AsTypedValue(value)); |
| } |
| public sbyte GetSByte(string key) |
| { |
| return (sbyte)this[key]; |
| } |
| public void SetSByte(string key, sbyte value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.SBYTE.AsTypedValue(value)); |
| } |
| public short GetInt16(string key) |
| { |
| return (short)this[key]; |
| } |
| public void SetInt16(string key, short value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.INT16.AsTypedValue(value)); |
| } |
| public int GetInt32(string key) |
| { |
| return (int)this[key]; |
| } |
| public void SetInt32(string key, int value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.INT32.AsTypedValue(value)); |
| } |
| public long GetInt64(string key) |
| { |
| return (long)this[key]; |
| } |
| public void SetInt64(string key, long value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.INT64.AsTypedValue(value)); |
| } |
| public char GetChar(string key) |
| { |
| return (char)this[key]; |
| } |
| public void SetChar(string key, char value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.ASCII_CHARACTER.AsTypedValue(value)); |
| } |
| public float GetFloat(string key) |
| { |
| return (float)this[key]; |
| } |
| public void SetFloat(string key, float value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.FLOAT.AsTypedValue(value)); |
| } |
| public double GetDouble(string key) |
| { |
| return (double)this[key]; |
| } |
| public void SetDouble(string key, double value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.DOUBLE.AsTypedValue(value)); |
| } |
| public decimal GetDecimal(string key) |
| { |
| return (decimal)this[key]; |
| } |
| public void SetDecimal(string key, decimal value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.DECIMAL.AsTypedValue(value)); |
| } |
| public string GetString(string key) |
| { |
| return (string)this[key]; |
| } |
| public void SetString(string key, string value) |
| { |
| CheckPropertyName(key); |
| if ( value == null ) |
| SetProperty(key, AMQType.VOID.AsTypedValue(null)); |
| else |
| SetProperty(key, AMQType.LONG_STRING.AsTypedValue(value)); |
| } |
| public byte[] GetBytes(string key) |
| { |
| return (byte[])this[key]; |
| } |
| public void SetBytes(string key, byte[] value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.BINARY.AsTypedValue(value)); |
| } |
| public ushort GetUInt16(string key) |
| { |
| return (ushort)this[key]; |
| } |
| public void SetUInt16(string key, ushort value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.UINT16.AsTypedValue(value)); |
| } |
| public uint GetUInt32(string key) |
| { |
| return (uint)this[key]; |
| } |
| public void SetUInt32(string key, uint value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.UINT32.AsTypedValue(value)); |
| } |
| public ulong GetUInt64(string key) |
| { |
| return (ulong)this[key]; |
| } |
| public void SetUInt64(string key, ulong value) |
| { |
| CheckPropertyName(key); |
| SetProperty(key, AMQType.UINT64.AsTypedValue(value)); |
| } |
| |
| #endregion // Typed Setters and Getters |
| |
| #region Public Methods |
| // |
| // Public Methods |
| // |
| |
| /// <summary> |
| /// Removes the property with the specified name |
| /// </summary> |
| /// <param name="key">The name of the property to remove</param> |
| /// <returns>The previous value of the property or null</returns> |
| public AMQTypedValue RemoveKey(string key) |
| { |
| InitMapIfNecessary(); |
| _encodedForm = null; |
| AMQTypedValue value = (AMQTypedValue)_properties[key]; |
| if ( value != null ) |
| { |
| _properties.Remove(key); |
| _encodedSize -= EncodingUtils.EncodedShortStringLength(key); |
| _encodedSize--; |
| _encodedSize -= value.EncodingLength; |
| |
| } |
| return value; |
| } |
| |
| |
| /// <summary> |
| /// Remove the property with the specified name |
| /// </summary> |
| /// <param name="key">The name of the property to remove</param> |
| public void Remove(string key) |
| { |
| RemoveKey(key); |
| } |
| |
| /// <summary> |
| /// Remove all properties from the table |
| /// </summary> |
| public void Clear() |
| { |
| InitMapIfNecessary(); |
| _encodedForm = null; |
| _properties.Clear(); |
| _encodedSize = 0; |
| } |
| |
| /// <summary> |
| /// Adds all the items from one field table in this one. Will overwrite any items in the current table |
| /// with the same key. |
| /// </summary> |
| /// <param name="ft">the source field table</param> |
| public void AddAll(IFieldTable ft) |
| { |
| foreach ( DictionaryEntry dictionaryEntry in ft ) |
| { |
| this[(string)dictionaryEntry.Key] = dictionaryEntry.Value; |
| } |
| } |
| |
| /// <summary> |
| /// Get a enumerator over the internal property set. |
| /// Notice the enumerator will DictionaryEntry objects with |
| /// a string as the Key and an <see cref="AMQTypedValue"/> instance as the value |
| /// </summary> |
| /// <returns>The enumerator object</returns> |
| public IEnumerator GetEnumerator() |
| { |
| InitMapIfNecessary(); |
| return _properties.GetEnumerator(); |
| } |
| |
| /// <summary> |
| /// Indicates if a property with the given name exists |
| /// </summary> |
| /// <param name="s">Property name to check</param> |
| /// <returns>True if the property exists</returns> |
| public bool Contains(string s) |
| { |
| InitMapIfNecessary(); |
| return _properties.Contains(s); |
| } |
| |
| /// <summary> |
| /// Returns a dictionary mapping Property Names to the corresponding |
| /// <see cref="AMQTypedValue"/> value |
| /// </summary> |
| /// <returns>The internal dictionary</returns> |
| public IDictionary AsDictionary() |
| { |
| InitMapIfNecessary(); |
| return _properties; |
| } |
| |
| /// <summary> |
| /// Returns a string representation of this field table |
| /// </summary> |
| /// <returns>A string</returns> |
| public override string ToString() |
| { |
| StringBuilder sb = new StringBuilder("FieldTable {"); |
| |
| bool first = true; |
| InitMapIfNecessary(); |
| foreach ( DictionaryEntry entry in _properties ) |
| { |
| if ( !first ) |
| { |
| sb.Append(", "); |
| } |
| first = false; |
| sb.Append(entry.Key).Append(" => ").Append(entry.Value); |
| } |
| |
| sb.Append("}"); |
| return sb.ToString(); |
| } |
| |
| /// <summary> |
| /// Serializes this instance to the specified <see cref="ByteBuffer"/>. |
| /// </summary> |
| /// <param name="buffer">The buffer to write to</param> |
| public void WriteToBuffer(ByteBuffer buffer) |
| { |
| if ( _log.IsDebugEnabled ) |
| { |
| _log.Debug("FieldTable::writeToBuffer: Writing encoded length of " + EncodedSize + "..."); |
| } |
| |
| EncodingUtils.WriteUnsignedInteger(buffer, EncodedSize); |
| WritePayload(buffer); |
| } |
| |
| /// <summary> |
| /// Returns a byte array with the serialized representation |
| /// of this field table |
| /// </summary> |
| /// <returns>An array of bytes</returns> |
| public byte[] GetDataAsBytes() |
| { |
| ByteBuffer buffer = ByteBuffer.Allocate((int)_encodedSize); |
| WritePayload(buffer); |
| byte[] result = new byte[_encodedSize]; |
| buffer.Flip(); |
| buffer.GetBytes(result); |
| //buffer.Release(); |
| return result; |
| } |
| |
| #endregion // Public Methods |
| |
| #region Private Methods |
| // |
| // Private Methods |
| // |
| |
| private static void CheckPropertyName(string propertyName) |
| { |
| if ( propertyName == null || propertyName.Length == 0 ) |
| throw new ArgumentNullException("propertyName"); |
| CheckIdentifierFormat(propertyName); |
| } |
| |
| private static void CheckIdentifierFormat(string propertyName) |
| { |
| // AMQP Spec: 4.2.5.5 Field Tables |
| // Guidelines for implementers: |
| // * Field names MUST start with a letter, '$' or '#' and may continue with |
| // letters, '$' or '#', digits, or underlines, to a maximum length of 128 |
| // characters. |
| // * The server SHOULD validate field names and upon receiving an invalid |
| // field name, it SHOULD signal a connection exception with reply code |
| // 503 (syntax error). Conformance test: amq_wlp_table_01. |
| // * A peer MUST handle duplicate fields by using only the first instance. |
| |
| |
| // AMQP length limit |
| if ( propertyName.Length > 128 ) |
| { |
| throw new ArgumentException("AMQP limits property names to 128 characters"); |
| } |
| |
| // AMQ start character |
| if ( !(Char.IsLetter(propertyName[0]) |
| || propertyName[0] == '$' |
| || propertyName[0] == '#' |
| || propertyName[0] == '_' ) )// Not official AMQP added for JMS. |
| { |
| throw new ArgumentException("Identifier '" + propertyName + "' does not start with a valid AMQP start character"); |
| } |
| } |
| |
| private object GetObject(string key) |
| { |
| AMQTypedValue value = GetProperty(key); |
| return value != null ? value.Value : null; |
| } |
| |
| private void SetObject(string key, object value) |
| { |
| if ( value is bool ) |
| { |
| SetBoolean(key, (bool)value); |
| } else if ( value is byte ) |
| { |
| SetByte(key, (byte)value); |
| } else if ( value is sbyte ) |
| { |
| SetSByte(key, (sbyte)value); |
| } else if ( value is short ) |
| { |
| SetInt16(key, (short)value); |
| } else if ( value is ushort ) |
| { |
| SetUInt16(key, (ushort)value); |
| } else if ( value is int ) |
| { |
| SetInt32(key, (int) value); |
| } else if ( value is uint ) |
| { |
| SetUInt32(key, (uint)value); |
| } else if ( value is long ) |
| { |
| SetInt64(key, (long) value); |
| } else if ( value is ulong ) |
| { |
| SetUInt64(key, (ulong)value); |
| } else if ( value is char ) |
| { |
| SetChar(key, (char) value); |
| } else if ( value is float ) |
| { |
| SetFloat(key, (float) value); |
| } else if ( value is double ) |
| { |
| SetDouble(key, (double) value); |
| } else if ( value is decimal ) |
| { |
| SetDecimal(key, (decimal) value); |
| } else if ( value is string ) |
| { |
| SetString(key, (string) value); |
| } else if ( value is byte[] ) |
| { |
| SetBytes(key, (byte[])value); |
| } else |
| { |
| throw new ArgumentException("Data type not supported yet"); |
| } |
| } |
| |
| private AMQTypedValue GetProperty(string name) |
| { |
| InitMapIfNecessary(); |
| return (AMQTypedValue) _properties[name]; |
| } |
| |
| private void PopulateFromBuffer() |
| { |
| try |
| { |
| ByteBuffer buffer = _encodedForm; |
| _encodedForm = null; |
| if ( buffer != null ) |
| SetFromBuffer(buffer, _encodedSize); |
| } catch ( AMQFrameDecodingException e ) |
| { |
| _log.Error("Error decoding FieldTable in deferred decoding mode ", e); |
| throw; |
| } |
| } |
| |
| private void SetFromBuffer(ByteBuffer buffer, uint length) |
| { |
| bool trace = _log.IsDebugEnabled; |
| if ( length > 0 ) |
| { |
| int expectedRemaining = buffer.Remaining - (int)length; |
| _properties = new LinkedHashtable(); |
| |
| do |
| { |
| string key = EncodingUtils.ReadShortString(buffer); |
| AMQTypedValue value = AMQTypedValue.ReadFromBuffer(buffer); |
| if ( trace ) |
| { |
| _log.Debug(string.Format("FieldTable::PropFieldTable(buffer,{0}): Read type '{1}', key '{2}', value '{3}'", length, value.Type, key, value.Value)); |
| } |
| _properties.Add(key, value); |
| |
| } while ( buffer.Remaining > expectedRemaining ); |
| _encodedSize = length; |
| } |
| if ( trace ) |
| { |
| _log.Debug("FieldTable::FieldTable(buffer," + length + "): Done."); |
| } |
| } |
| |
| private void InitMapIfNecessary() |
| { |
| lock ( _syncLock ) |
| { |
| if ( _properties == null ) |
| { |
| if ( _encodedForm == null ) |
| { |
| _properties = new LinkedHashtable(); |
| } else |
| { |
| PopulateFromBuffer(); |
| } |
| } |
| } |
| } |
| |
| private AMQTypedValue SetProperty(string key, AMQTypedValue value) |
| { |
| InitMapIfNecessary(); |
| _encodedForm = null; |
| if ( value == null ) |
| { |
| RemoveKey(key); |
| } |
| AMQTypedValue oldVal = (AMQTypedValue)_properties[key]; |
| _properties.Add(key, value); |
| if ( oldVal != null ) |
| { |
| _encodedSize -= oldVal.EncodingLength; |
| } else |
| { |
| _encodedSize += EncodingUtils.EncodedShortStringLength(key) + (uint)1; |
| } |
| if ( value != null ) |
| { |
| _encodedSize += value.EncodingLength; |
| } |
| |
| return oldVal; |
| } |
| |
| public void WritePayload(ByteBuffer buffer) |
| { |
| if ( _encodedForm != null ) |
| { |
| lock ( _syncLock ) |
| { |
| buffer.Put(_encodedForm); |
| _encodedForm.Flip(); |
| } |
| } else if ( _properties != null ) |
| { |
| foreach ( DictionaryEntry de in _properties ) |
| { |
| string key = (string)de.Key; |
| AMQTypedValue value = (AMQTypedValue)de.Value; |
| try |
| { |
| if ( _log.IsDebugEnabled ) |
| { |
| _log.Debug("Writing Property:" + key + |
| " Type:" + value.Type + |
| " Value:" + value.Value); |
| _log.Debug("Buffer Position:" + buffer.Position + |
| " Remaining:" + buffer.Remaining); |
| } |
| //Write the actual parameter name |
| EncodingUtils.WriteShortStringBytes(buffer, key); |
| value.WriteToBuffer(buffer); |
| } catch ( Exception ex ) |
| { |
| if ( _log.IsDebugEnabled ) |
| { |
| _log.Debug("Exception thrown:" + ex); |
| _log.Debug("Writing Property:" + key + |
| " Type:" + value.Type + |
| " Value:" + value.Value); |
| _log.Debug("Buffer Position:" + buffer.Position + |
| " Remaining:" + buffer.Remaining); |
| } |
| throw; |
| } |
| } |
| } |
| } |
| #endregion // Private Methods |
| } |
| } |