463 lines · 17.3 KB · C#
RawDownload
//******************************************************************************************************//  SynchronizedClientSubscription.cs - Gbtc////  Copyright © 2010, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/eclipse-1.0.php////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  06/24/2011 - Ritchie//       Generated original version of source code.////****************************************************************************************************** using GSF.Parsing;using GSF.TimeSeries.Adapters;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text; namespace GSF.TimeSeries.Transport{    /// <summary>    /// Represents a synchronized client subscription to the <see cref="DataPublisher" />.    /// </summary>    internal class SynchronizedClientSubscription : ActionAdapterBase, IClientSubscription    {        #region [ Members ]         // Events         /// <summary>        /// Indicates to the host that processing for an input adapter (via temporal session) has completed.        /// </summary>        /// <remarks>        /// This event is expected to only be raised when an input adapter has been designed to process        /// a finite amount of data, e.g., reading a historical range of data during temporal procesing.        /// </remarks>        public event EventHandler<EventArgs<IClientSubscription, EventArgs>> ProcessingComplete;         // Fields        private SignalIndexCache m_signalIndexCache;        private DataPublisher m_parent;        private Guid m_clientID;        private Guid m_subscriberID;        private string m_hostName;        private string m_requestedInputFilter;        private volatile bool m_useCompactMeasurementFormat;        private volatile bool m_startTimeSent;        private IaonSession m_iaonSession;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="SynchronizedClientSubscription"/>.        /// </summary>        /// <param name="parent">Reference to parent.</param>        /// <param name="clientID"><see cref="Guid"/> based client connection ID.</param>        /// <param name="subscriberID"><see cref="Guid"/> based subscriber ID.</param>        public SynchronizedClientSubscription(DataPublisher parent, Guid clientID, Guid subscriberID)        {            // Pass parent reference into base class            AssignParentCollection(parent);             m_parent = parent;            m_clientID = clientID;            m_subscriberID = subscriberID;            m_signalIndexCache = new SignalIndexCache()            {                SubscriberID = subscriberID            };        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets the <see cref="Guid"/> client TCP connection identifier of this <see cref="SynchronizedClientSubscription"/>.        /// </summary>        public Guid ClientID        {            get            {                return m_clientID;            }        }         /// <summary>        /// Gets the <see cref="Guid"/> based subscriber ID of this <see cref="SynchronizedClientSubscription"/>.        /// </summary>        public Guid SubscriberID        {            get            {                return m_subscriberID;            }        }         /// <summary>        /// Gets the current signal index cache of this <see cref="SynchronizedClientSubscription"/>.        /// </summary>        public SignalIndexCache SignalIndexCache        {            get            {                return m_signalIndexCache;            }        }         public string RequestedInputFilter        {            get            {                return m_requestedInputFilter;            }        }         /// <summary>        /// Gets or sets flag that determines if the compact measurement format should be used in data packets of this <see cref="SynchronizedClientSubscription"/>.        /// </summary>        public bool UseCompactMeasurementFormat        {            get            {                return m_useCompactMeasurementFormat;            }            set            {                m_useCompactMeasurementFormat = value;            }        }         /// <summary>        /// Gets size of timestamp in bytes.        /// </summary>        public int TimestampSize        {            get            {                return 8;            }        }         /// <summary>        /// Gets or sets host name used to identify connection source of client subscription.        /// </summary>        public string HostName        {            get            {                return m_hostName;            }            set            {                m_hostName = value;            }        }         /// <summary>        /// Gets or sets the desired processing interval, in milliseconds, for the adapter.        /// </summary>        /// <remarks>        /// With the exception of the values of -1 and 0, this value specifies the desired processing interval for data, i.e.,        /// basically a delay, or timer interval, overwhich to process data. A value of -1 means to use the default processing        /// interval while a value of 0 means to process data as fast as possible.        /// </remarks>        public override int ProcessingInterval        {            get            {                return base.ProcessingInterval;            }            set            {                base.ProcessingInterval = value;                 // Update processing interval in private temporal session, if defined                if (m_iaonSession != null && m_iaonSession.AllAdapters != null)                    m_iaonSession.AllAdapters.ProcessingInterval = value;            }        }         /// <summary>        /// Gets or sets primary keys of input measurements the <see cref="SynchronizedClientSubscription"/> expects, if any.        /// </summary>        /// <remarks>        /// We override method so assignment can be synchronized such that dynamic updates won't interfere        /// with filtering in <see cref="QueueMeasurementsForProcessing"/>.        /// </remarks>        public override MeasurementKey[] InputMeasurementKeys        {            get            {                return base.InputMeasurementKeys;            }            set            {                lock (this)                {                    // Update signal index cache unless "detaching" from real-time                    if (value != null && !(value.Length == 1 && value[0] == MeasurementKey.Undefined))                    {                        m_parent.UpdateSignalIndexCache(m_clientID, m_signalIndexCache, value);                         if ((object)DataSource != null && (object)m_signalIndexCache != null)                            value = AdapterBase.ParseInputMeasurementKeys(DataSource, string.Join("; ", m_signalIndexCache.AuthorizedSignalIDs.Select(signalID => signalID.ToString()).ToArray()));                    }                     base.InputMeasurementKeys = value;                }            }        }         /// <summary>        /// Gets the flag indicating if this adapter supports temporal processing.        /// </summary>        /// <remarks>        /// Although this adapter provisions support for temporal processing by proxying historical data to a remote sink, the adapter        /// does not need to be automatically engaged within an actual temporal <see cref="IaonSession"/>, therefore this method returns        /// <c>false</c> to make sure the adapter doesn't get automatically instantiated within a temporal session.        /// </remarks>        public override bool SupportsTemporalProcessing        {            get            {                return false;            }        }         /// <summary>        /// Gets a formatted message describing the status of this <see cref="SynchronizedClientSubscription"/>.        /// </summary>        public override string Status        {            get            {                StringBuilder status = new StringBuilder();                ClientConnection connection;                 if (m_parent.ClientConnections.TryGetValue(m_clientID, out connection))                {                    status.Append(connection.Status);                    status.AppendLine();                }                 status.Append(base.Status);                 if (m_iaonSession != null)                    status.Append(m_iaonSession.Status);                 return status.ToString();            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Releases the unmanaged resources used by the <see cref="SynchronizedClientSubscription"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected override void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                    {                        // Remove reference to parent                        m_parent = null;                         // Dispose Iaon session                        this.DisposeTemporalSession(ref m_iaonSession);                    }                }                finally                {                    m_disposed = true;          // Prevent duplicate dispose.                    base.Dispose(disposing);    // Call base class Dispose().                }            }        }         /// <summary>        /// Initializes <see cref="SynchronizedClientSubscription"/>.        /// </summary>        public override void Initialize()        {            base.Initialize();            base.UsePrecisionTimer = false;             if (!Settings.TryGetValue("inputMeasurementKeys", out m_requestedInputFilter))                m_requestedInputFilter = null;             // Handle temporal session intialization            if (this.TemporalConstraintIsDefined())                m_iaonSession = this.CreateTemporalSession();        }         /// <summary>        /// Starts the <see cref="SynchronizedClientSubscription"/> or restarts it if it is already running.        /// </summary>        public override void Start()        {            if (!Enabled)                m_startTimeSent = false;             base.Start();        }         /// <summary>        /// Queues a collection of measurements for processing.        /// </summary>        /// <param name="measurements">Collection of measurements to queue for processing.</param>        /// <remarks>        /// Measurements are filtered against the defined <see cref="InputMeasurementKeys"/> so we override method        /// so that dynamic updates to keys will be synchronized with filtering to prevent interference.        /// </remarks>        public override void QueueMeasurementsForProcessing(IEnumerable<IMeasurement> measurements)        {            if (!m_startTimeSent && (object)measurements != null && measurements.Any())            {                m_startTimeSent = true;                 IMeasurement measurement = measurements.FirstOrDefault(m => m != null);                Ticks timestamp = 0;                 if (measurement != null)                    timestamp = measurement.Timestamp;                 m_parent.SendDataStartTime(m_clientID, timestamp);            }             if (ProcessMeasurementFilter)            {                lock (this)                {                    base.QueueMeasurementsForProcessing(measurements);                }            }            else            {                base.QueueMeasurementsForProcessing(measurements);            }        }         /// <summary>        /// Publish <see cref="IFrame"/> of time-aligned collection of <see cref="IMeasurement"/> values that arrived within the        /// concentrator's defined <see cref="ConcentratorBase.LagTime"/>.        /// </summary>        /// <param name="frame"><see cref="IFrame"/> of measurements with the same timestamp that arrived within <see cref="ConcentratorBase.LagTime"/> that are ready for processing.</param>        /// <param name="index">Index of <see cref="IFrame"/> within a second ranging from zero to <c><see cref="ConcentratorBase.FramesPerSecond"/> - 1</c>.</param>        protected override void PublishFrame(IFrame frame, int index)        {            if (!m_disposed)            {                List<ISupportBinaryImage> packet = new List<ISupportBinaryImage>();                bool useCompactMeasurementFormat = m_useCompactMeasurementFormat;                long frameLevelTimestamp = frame.Timestamp;                int packetSize = 13;                 foreach (IMeasurement measurement in frame.Measurements.Values)                {                    ISupportBinaryImage binaryMeasurement;                    int binaryLength;                     // Serialize the current measurement.                    if (useCompactMeasurementFormat)                        binaryMeasurement = new CompactMeasurement(measurement, m_signalIndexCache, false, null, 0, false);                    else                        binaryMeasurement = new SerializableMeasurement(measurement, m_parent.GetClientEncoding(ClientID));                     // Determine the size of the measurement in bytes.                    binaryLength = binaryMeasurement.BinaryLength;                     // If the current measurement will not fit in the packet based on                    // the max packet size, process the packet and start a new one.                    if (packetSize + binaryLength > DataPublisher.MaxPacketSize)                    {                        ProcessBinaryMeasurements(packet, frameLevelTimestamp, useCompactMeasurementFormat);                        packet.Clear();                        packetSize = 13;                    }                     // Add the measurement to the packet.                    packet.Add(binaryMeasurement);                    packetSize += binaryLength;                }                 // Process the remaining measurements.                ProcessBinaryMeasurements(packet, frameLevelTimestamp, useCompactMeasurementFormat);            }        }         private void ProcessBinaryMeasurements(IEnumerable<ISupportBinaryImage> measurements, long frameLevelTimestamp, bool useCompactMeasurementFormat)        {            MemoryStream data = new MemoryStream();             // Serialize data packet flags into response            DataPacketFlags flags = DataPacketFlags.Synchronized;             if (useCompactMeasurementFormat)                flags |= DataPacketFlags.Compact;             data.WriteByte((byte)flags);             // Serialize frame timestamp into data packet - this only occurs in synchronized data packets,            // unsynchronized subcriptions always include timestamps in the serialized measurements            data.Write(EndianOrder.BigEndian.GetBytes(frameLevelTimestamp), 0, 8);             // Serialize total number of measurement values to follow            data.Write(EndianOrder.BigEndian.GetBytes(measurements.Count()), 0, 4);             // Serialize measurements to data buffer            foreach (ISupportBinaryImage measurement in measurements)            {                measurement.CopyBinaryImageToStream(data);            }             // Publish data packet to client            if (m_parent != null)                m_parent.SendClientResponse(m_clientID, ServerResponse.DataPacket, ServerCommand.Subscribe, data.ToArray());        }         // Explicitly implement status message event bubbler to satisfy IClientSubscription interface        void IClientSubscription.OnStatusMessage(string status)        {            OnStatusMessage(status);        }         // Explicitly implement process exception event bubbler to satisfy IClientSubscription interface        void IClientSubscription.OnProcessException(Exception ex)        {            OnProcessException(ex);        }         // Explicitly implement processing completed event bubbler to satisfy IClientSubscription interface        void IClientSubscription.OnProcessingCompleted(object sender, EventArgs e)        {            if (ProcessingComplete != null)                ProcessingComplete(sender, new EventArgs<IClientSubscription, EventArgs>(this, e));        }         #endregion    }}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t