/* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT 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(); } /// /// Construct a new field table. /// /// the buffer from which to read data. The length byte must be read already /// the length of the field table. Must be > 0. public FieldTable(ByteBuffer buffer, uint length) : this() { _encodedForm = buffer.Slice(); _encodedForm.Limit = (int)length; _encodedSize = length; buffer.Skip((int)length); } /// /// The set of all property names /// public ICollection Keys { get { InitMapIfNecessary(); return _properties.Keys; } } /// /// Calculated size of this field table once encoded /// public uint EncodedSize { get { return _encodedSize; } } /// /// Number of properties in the field table /// public int Count { get { InitMapIfNecessary(); return _properties.Count; } } /// /// Gets or sets the specified property. /// /// Property name /// The specified property value 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 // /// /// Removes the property with the specified name /// /// The name of the property to remove /// The previous value of the property or null 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; } /// /// Remove the property with the specified name /// /// The name of the property to remove public void Remove(string key) { RemoveKey(key); } /// /// Remove all properties from the table /// public void Clear() { InitMapIfNecessary(); _encodedForm = null; _properties.Clear(); _encodedSize = 0; } /// /// Adds all the items from one field table in this one. Will overwrite any items in the current table /// with the same key. /// /// the source field table public void AddAll(IFieldTable ft) { foreach ( DictionaryEntry dictionaryEntry in ft ) { this[(string)dictionaryEntry.Key] = dictionaryEntry.Value; } } /// /// Get a enumerator over the internal property set. /// Notice the enumerator will DictionaryEntry objects with /// a string as the Key and an instance as the value /// /// The enumerator object public IEnumerator GetEnumerator() { InitMapIfNecessary(); return _properties.GetEnumerator(); } /// /// Indicates if a property with the given name exists /// /// Property name to check /// True if the property exists public bool Contains(string s) { InitMapIfNecessary(); return _properties.Contains(s); } /// /// Returns a dictionary mapping Property Names to the corresponding /// value /// /// The internal dictionary public IDictionary AsDictionary() { InitMapIfNecessary(); return _properties; } /// /// Returns a string representation of this field table /// /// A string 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(); } /// /// Serializes this instance to the specified . /// /// The buffer to write to public void WriteToBuffer(ByteBuffer buffer) { if ( _log.IsDebugEnabled ) { _log.Debug("FieldTable::writeToBuffer: Writing encoded length of " + EncodedSize + "..."); } EncodingUtils.WriteUnsignedInteger(buffer, EncodedSize); WritePayload(buffer); } /// /// Returns a byte array with the serialized representation /// of this field table /// /// An array of bytes 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 } }