/// <summary> /// Sets the destination of a message according to distribution strategy. /// </summary> /// <param name="message">Message to set it's destination</param> public void SetDestination(MDSDataTransferMessage message) { //Return, if no destination exists if (_totalCount == 0 || RoutingRule.Destinations.Length <= 0) { return; } //Find next destination and set var currentTotal = 0; foreach (var destination in RoutingRule.Destinations) { currentTotal += destination.RouteFactor; if (_currentNumber < currentTotal) { SetMessageDestination(message, destination); break; } } //Increase _currentNumber if ((++_currentNumber) >= _totalCount) { _currentNumber = 0; } }
/// <summary> /// Checks all routing rules and apply proper rule to the message /// </summary> /// <param name="message">Message to apply routing</param> public void ApplyRouting(MDSDataTransferMessage message) { if (Rules.Any(rule => rule.ApplyRule(message))) { return; } }
/// <summary> /// Creates a MDSMessageRecord object using a MDSDataTransferMessage. /// </summary> /// <param name="message">Message object</param> public MDSMessageRecord(MDSDataTransferMessage message) { Message = message; MessageId = message.MessageId; DestServer = message.DestinationServerName; DestApplication = message.DestinationApplicationName; RecordDate = DateTime.Now; }
/// <summary> /// Checks if a message matches with this filter. /// </summary> /// <param name="message">Message to check</param> /// <returns>True, if message matches with this rule</returns> public bool Matches(MDSDataTransferMessage message) { if ((string.IsNullOrEmpty(SourceServer) || SourceServer == message.SourceServerName) && (string.IsNullOrEmpty(SourceApplication) || SourceApplication == message.SourceApplicationName) && (string.IsNullOrEmpty(DestinationServer) || DestinationServer == message.DestinationServerName) && (string.IsNullOrEmpty(DestinationApplication) || DestinationApplication == message.DestinationApplicationName) && (string.IsNullOrEmpty(TransmitRule) || TransmitRule == message.TransmitRule.ToString())) { return true; } return false; }
/// <summary> /// Tries to appliy this rule to a data transfer message. /// </summary> /// <param name="message">Message to apply rule</param> /// <returns>True, if rule applied</returns> public bool ApplyRule(MDSDataTransferMessage message) { for (var i = 0; i < Filters.Length; i++) { if (Filters[i].Matches(message)) { DistributionStrategy.SetDestination(message); return true; } } return false; }
/// <summary> /// Sets the destination of a message. /// </summary> /// <param name="message">Message to set it's destination</param> /// <param name="destination">Destination to set to message</param> protected static void SetMessageDestination(MDSDataTransferMessage message, RoutingDestination destination) { //Sets destination server if (!string.IsNullOrEmpty(destination.Server)) { message.DestinationServerName = destination.Server.Equals("this", StringComparison.OrdinalIgnoreCase) ? MDSSettings.Instance.ThisServerName : destination.Server; } //Sets destination application if (!string.IsNullOrEmpty(destination.Application)) { message.DestinationApplicationName = destination.Application; } }
/// <summary> /// Sets the destination of a message according to distribution strategy. /// </summary> /// <param name="message">Message to set it's destination</param> public void SetDestination(MDSDataTransferMessage message) { //Return, if no destination exists if (_maxCount == 0 || RoutingRule.Destinations.Length <= 0) { return; } //Create a random number var randomNumber = _rnd.Next(_maxCount); //Find destination according to random number and set the destination. var currentTotal = 0; foreach (var destination in RoutingRule.Destinations) { currentTotal += destination.RouteFactor; if (randomNumber < currentTotal) { SetMessageDestination(message, destination); return; } } }
/// <summary> /// This method is used to process a MDSDataTransferMessage that is gotten from a mds server or client application. /// Message is sent to destination or next server in one of these three conditions: /// - Destination server is this server and application exists on this server /// - Destination server is an adjacent server of this server /// - Destination server in server graph and there is a path from this server to destination server /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="senderCommunicator">Sender communicator</param> /// <param name="message">Message</param> private void ProcessDataTransferMessage(MDSRemoteApplication senderApplication, ICommunicator senderCommunicator, MDSDataTransferMessage message) { //Check for duplicate messages if (senderApplication.LastAcknowledgedMessageId == message.MessageId) { SendOperationResultMessage(senderApplication, senderCommunicator, message, true, "Duplicate message."); return; } try { AddThisServerToPassedServerList(message); FillEmptyMessageFields(message, senderApplication, senderCommunicator); _routingTable.ApplyRouting(message); //If Destination server is this server then deliver message to the destination application if (message.DestinationServerName.Equals(_settings.ThisServerName, StringComparison.OrdinalIgnoreCase)) { SentToClientApplication(senderApplication, senderCommunicator, message); } //Else, if destination server is an adjacent of this server (so they can communicate directly) else if (_serverGraph.AdjacentServers.ContainsKey(message.DestinationServerName)) { SentToAdjacentServer(senderApplication, senderCommunicator, message); } //Else, if destination server is not adjacent but in server graph (so, send message to next server) else if (_serverGraph.ServerNodes.ContainsKey(message.DestinationServerName)) { SendToNextServer(senderApplication, senderCommunicator, message); } else { //return error to sender SendOperationResultMessage(senderApplication, senderCommunicator, message, false, "Destination does not exists."); } } catch (Exception ex) { SendOperationResultMessage(senderApplication, senderCommunicator, message, false, ex.Message); } }
/// <summary> /// Finds Next server for a message. /// </summary> /// <returns>Next server</returns> protected override string GetNextServerForMessage(MDSDataTransferMessage message) { //Next server is this MDSAdjacentServer because message is being sent to that now. return Name; }
/// <summary> /// This method is used to send a MDSDataTransferMessage to an available communicator of application. /// It just blocks caller thread until a communicator comes available and message is sent or until timeout, /// but receives result (ACK/Reject) message asynchronous. It sends result (ACK/Reject) message to OnResponseReceived() method. /// </summary> /// <param name="message">Message to send</param> /// <param name="timeOut">Timeout value to wait if all receivers are busy. Set 0 to do not use timeout.</param> public void SendDataMessage(MDSDataTransferMessage message, int timeOut) { _messageDeliverer.SendDataMessage(message, timeOut); }
/// <summary> /// Deserializes this message. /// </summary> /// <param name="deserializer">Deserializer used to deserialize objects</param> public override void Deserialize(IMDSDeserializer deserializer) { base.Deserialize(deserializer); Result = deserializer.ReadObject(() => new MDSOperationResultMessage()); Message = deserializer.ReadObject(() => new MDSDataTransferMessage()); }
/// <summary> /// This method is called by ProcessDataTransferMessage when a message must be sent to a server /// that is not an adjacent of this server. Message is forwarded to next server. /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="senderCommunicator">Sender communicator</param> /// <param name="message">Message</param> private void SendToNextServer(MDSRemoteApplication senderApplication, ICommunicator senderCommunicator, MDSDataTransferMessage message) { //If there is a path from this server to destination server... if (_serverGraph.ThisServerNode.BestPathsToServers.ContainsKey(message.DestinationServerName)) { //Find best path to destination server var bestPath = _serverGraph.ThisServerNode.BestPathsToServers[message.DestinationServerName]; //If path is regular (a path must consist of 2 nodes at least)... if (bestPath.Count > 1) { //Next server var nextServerName = bestPath[1].Name; /* On one of these conditions, message is stored: * - TransmitRule = StoreAndForward * - (TransmitRule = StoreOnSource OR StoreOnEndPoints) AND (This server is the source server) */ if (message.TransmitRule == MessageTransmitRules.StoreAndForward || message.TransmitRule == MessageTransmitRules.NonPersistent) { EnqueueMessage( senderApplication, senderCommunicator, _serverGraph.AdjacentServers[nextServerName], message ); } /* Else, message is not stored in these conditions: * - TransmitRule = DirectlySend OR StoreOnDestination (this server can not be destination because message is being sent to another server right now) * - All Other conditions */ else { SendMessageDirectly( senderApplication, senderCommunicator, _serverGraph.AdjacentServers[nextServerName], message ); } } //Server graph may be wrong (this is just for checking case, normally this situation must not become) else { SendOperationResultMessage(senderApplication, senderCommunicator, message, false, "Server graph is wrong."); } } //No path from this server to destination server else { SendOperationResultMessage(senderApplication, senderCommunicator, message, false, "There is no path from this server to destination."); } }
/// <summary> /// This method is called by ProcessDataTransferMessage when a message must be sent to a aclient application /// that is running on this server. /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="senderCommunicator">Sender communicator</param> /// <param name="message">Message</param> private void SentToClientApplication(MDSRemoteApplication senderApplication, ICommunicator senderCommunicator, MDSDataTransferMessage message) { MDSClientApplication destinationApplication = null; //If application exists on this server, get it lock (_clientApplicationList.Applications) { if (_clientApplicationList.Applications.ContainsKey(message.DestinationApplicationName)) { destinationApplication = _clientApplicationList.Applications[message.DestinationApplicationName]; } } //If application doesn't exist on this server... if (destinationApplication == null) { SendOperationResultMessage(senderApplication, senderCommunicator, message, false, "Application does not exists on this server (" + _settings.ThisServerName + ")."); return; } //Send message according TransmitRule switch (message.TransmitRule) { case MessageTransmitRules.DirectlySend: SendMessageDirectly( senderApplication, senderCommunicator, destinationApplication, message ); break; default: // case MessageTransmitRules.StoreAndForward: // case MessageTransmitRules.NonPersistent: EnqueueMessage( senderApplication, senderCommunicator, destinationApplication, message ); break; } }
/// <summary> /// This method is called when a MDSDataTransferMessage sent to this application. /// Stores message and adds to queue to send to remote application. /// </summary> /// <param name="message">Message</param> public void EnqueueMessage(MDSDataTransferMessage message) { //Create MDSMessageRecord from MDSDataTransferMessage to save message to database var messageRecord = new MDSMessageRecord(message) {NextServer = GetNextServerForMessage(message)}; //Lock collection to be synchronized. lock (_waitingMessages) { if (message.TransmitRule == MessageTransmitRules.StoreAndForward) { //Save message to database StorageManager.StoreMessage(messageRecord); /* If these conditions are true, then message also added to _waitingMessages * and message is sent to appliction from this queue instead of read again from database (for performance reasons): * - _waitingMessages's message count is smaller than a maximum count (_waitingMessages.Count < MaxMessagesInQueue). * - All messages in database is also in _waitingMessages (_biggestWaitingMessageIdInList >= _biggestWaitingMessageId) * That means there is no message that is in database but not in _waitingMessages list. */ if (_waitingMessages.Count < MaxMessagesInQueue && _biggestWaitingMessageIdInList >= _biggestWaitingMessageId) { //Add message to queue. _waitingMessages.AddLast(new WaitingMessage(messageRecord)); //This message's Id is new biggest id on queue. _biggestWaitingMessageIdInList = messageRecord.Id; } //This message's id is new biggest id in database, so update _biggestWaitingMessageId value _biggestWaitingMessageId = messageRecord.Id; } else { //Add message to queue. _waitingMessages.AddFirst(new WaitingMessage(messageRecord)); } //Pulse waiting thread that is in wait state because of no message to process. if (!_waitingForAnError) { Monitor.PulseAll(_waitingMessages); } Logger.Debug("EnqueueMessage - WaitingMessages.Count = " + _waitingMessages.Count + ", Application = " + Name); } }
/// <summary> /// Deserializes this message. /// </summary> /// <param name="deserializer">Deserializer used to deserialize objects</param> public override void Deserialize(IMDSDeserializer deserializer) { base.Deserialize(deserializer); Result = deserializer.ReadObject(() => new MDSOperationResultMessage()); Message = deserializer.ReadObject(() => new MDSDataTransferMessage()); }
/// <summary> /// Finds Next server for a message. /// </summary> /// <returns>Next server</returns> protected override string GetNextServerForMessage(MDSDataTransferMessage message) { //Next server and destination server is same (this server) because message is being delivered to application now. return message.DestinationServerName; }
/// <summary> /// Makes web service call, receives result and raises MessageReceived event. /// </summary> /// <param name="message"></param> private void SendMessageToWebService(MDSDataTransferMessage message) { using (var appService = new MDSAppService()) { appService.Url = _url; var responseMessageBytes = appService.ReceiveMDSMessage(MDSSerializationHelper.SerializeToByteArray(message)); if (responseMessageBytes == null) { throw new MDSException("Response byte array from web service call is null."); } var responseMessage = MDSSerializationHelper.DeserializeFromByteArray(() => new MDSDataTransferResponseMessage(), responseMessageBytes); if (responseMessage.Result != null) { OnMessageReceived(responseMessage.Result); } if (responseMessage.Message != null) { OnMessageReceived(responseMessage.Message); } } }
/// <summary> /// This method is called when a MDSDataTransferMessage sent to this application. /// It does not store message, adds it as first item of sending queue. /// </summary> /// <param name="message">Message</param> public void AddMessageToHeadOfQueue(MDSDataTransferMessage message) { //Lock collection to be synchronized. lock (_waitingMessages) { //Add message to queue. _waitingMessages.AddFirst( new WaitingMessage( new MDSMessageRecord(message) { NextServer = GetNextServerForMessage(message), Id = -1 })); //Pulse waiting thread that is in wait state because of no message to process. if (!_waitingForAnError) { Monitor.PulseAll(_waitingMessages); } Logger.Debug("AddMessageToHeadOfQueue - WaitingMessages.Count = " + _waitingMessages.Count + ", Application = " + Name); } }
/// <summary> /// Adds this server to the list of passed servers of message. /// </summary> /// <param name="message">Message object</param> private void AddThisServerToPassedServerList(MDSDataTransferMessage message) { //Create new transmit report for this server var transmitReport = new ServerTransmitReport { ArrivingTime = DateTime.Now, ServerName = _settings.ThisServerName }; if (message.PassedServers == null) { //Create array message.PassedServers = new ServerTransmitReport[1]; } else { //Create new array (that has item one more than original array) var newArray = new ServerTransmitReport[message.PassedServers.Length + 1]; //Copy old items to new array if (message.PassedServers.Length > 1) { Array.Copy(message.PassedServers, 0, newArray, 0, message.PassedServers.Length); } //Replace old array by new array message.PassedServers = newArray; } //Add transmit report to array message.PassedServers[message.PassedServers.Length - 1] = transmitReport; }
/// <summary> /// Adds message to destination's send queue. /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="senderCommunicator">Sender communicator</param> /// <param name="destApplication">Destination application/server</param> /// <param name="message">Message</param> private static void EnqueueMessage(MDSRemoteApplication senderApplication, ICommunicator senderCommunicator, MDSPersistentRemoteApplicationBase destApplication, MDSDataTransferMessage message) { destApplication.EnqueueMessage(message); SendOperationResultMessage(senderApplication, senderCommunicator, message, true, "Success."); }
/// <summary> /// Checks a MDSDataTransferMessage and fills it's empty fields by default values. /// </summary> /// <param name="dataTransferMessage">Message</param> /// <param name="senderApplication">Sender application</param> /// <param name="communicator">Sender communicator of application</param> private void FillEmptyMessageFields(MDSDataTransferMessage dataTransferMessage, MDSRemoteApplication senderApplication, ICommunicator communicator) { //Default SourceApplicationName: Name of the sender application. if (string.IsNullOrEmpty(dataTransferMessage.SourceApplicationName)) { dataTransferMessage.SourceApplicationName = senderApplication.Name; } //Default SourceServerName: Name of this server. if (string.IsNullOrEmpty(dataTransferMessage.SourceServerName)) { dataTransferMessage.SourceServerName = _settings.ThisServerName; } //Default DestinationApplicationName: Name of the sender application. if (string.IsNullOrEmpty(dataTransferMessage.DestinationApplicationName)) { dataTransferMessage.DestinationApplicationName = senderApplication.Name; } //Default DestinationServerName: Name of this server. if (string.IsNullOrEmpty(dataTransferMessage.DestinationServerName)) { dataTransferMessage.DestinationServerName = _settings.ThisServerName; } if (dataTransferMessage.SourceServerName == _settings.ThisServerName) { //Sender communicator id is being set. dataTransferMessage.SourceCommunicatorId = communicator.ComminicatorId; } }
/// <summary> /// Sets Leaving time for last passed server (this server) of message. /// </summary> /// <param name="message">Message object</param> private static void SetLeavingTime(MDSDataTransferMessage message) { if (message.PassedServers == null || message.PassedServers.Length <= 0) { return; } message.PassedServers[message.PassedServers.Length - 1].LeavingTime = DateTime.Now; }
/// <summary> /// This method is called by ProcessDataTransferMessage when a message must be sent to an adjacent server of this server. /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="senderCommunicator">Sender communicator</param> /// <param name="message">Message</param> private void SentToAdjacentServer(MDSRemoteApplication senderApplication, ICommunicator senderCommunicator, MDSDataTransferMessage message) { /* On one of these conditions, message is stored: * - TransmitRule = StoreAndForward * - (TransmitRule = StoreOnSource OR StoreOnEndPoints) AND (This server is the source server) */ if (message.TransmitRule == MessageTransmitRules.StoreAndForward || message.TransmitRule == MessageTransmitRules.NonPersistent) { EnqueueMessage( senderApplication, senderCommunicator, _serverGraph.AdjacentServers[message.DestinationServerName], message ); } /* Else, message is not stored in these conditions: * - TransmitRule = DirectlySend OR StoreOnDestination (this server can not be destination because message is being sent to another server right now) * - All Other conditions */ else { SendMessageDirectly( senderApplication, senderCommunicator, _serverGraph.AdjacentServers[message.DestinationServerName], message ); } }
/// <summary> /// Creates a new IncomingDataMessage object from a MDSDataTransferMessage object. /// </summary> /// <param name="message">MDSDataTransferMessage object to create IncomingDataMessage</param> public IncomingDataMessage(MDSDataTransferMessage message) { DestinationApplicationName = message.DestinationApplicationName; DestinationCommunicatorId = message.DestinationCommunicatorId; DestinationServerName = message.DestinationServerName; MessageData = message.MessageData; MessageId = message.MessageId; PassedServers = message.PassedServers; RepliedMessageId = message.RepliedMessageId; SourceApplicationName = message.SourceApplicationName; SourceCommunicatorId = message.SourceCommunicatorId; SourceServerName = message.SourceServerName; TransmitRule = message.TransmitRule; }
/// <summary> /// Sends message directly to application (not stores) and waits ACK. /// This method adds message to queue by MDSPersistentRemoteApplicationBase.AddMessageToHeadOfQueue method /// and waits a signal/pulse from RemoteApplication_MessageReceived method to get ACK/Reject. /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="senderCommunicator">Sender communicator</param> /// <param name="destApplication">Destination application/server</param> /// <param name="message">Message</param> private void SendMessageDirectly(MDSRemoteApplication senderApplication, ICommunicator senderCommunicator, MDSPersistentRemoteApplicationBase destApplication, MDSDataTransferMessage message) { //Create a WaitingMessage to wait and get ACK/Reject message and add it to waiting messages var waitingMessage = new WaitingMessage(); lock (_waitingMessages) { _waitingMessages[message.MessageId] = waitingMessage; } try { //Add message to head of queue of remote application destApplication.AddMessageToHeadOfQueue(message); //Wait until thread is signalled by another thread to get response (Signalled by RemoteApplication_MessageReceived method) waitingMessage.WaitEvent.WaitOne((int) (_settings.MessageResponseTimeout*1.2)); //Evaluate response if (waitingMessage.ResponseMessage.Success) { SendOperationResultMessage(senderApplication, senderCommunicator, message, true, "Success."); } else { SendOperationResultMessage(senderApplication, senderCommunicator, message, false, "Message is not acknowledged. Reason: " + waitingMessage.ResponseMessage.ResultText); } } finally { //Remove message from waiting messages lock (_waitingMessages) { _waitingMessages.Remove(message.MessageId); } } }
/// <summary> /// Finds Next server for a message. /// This method is designed, because it is different to get next server's name for client applications and mds servers. /// </summary> /// <returns>Next server</returns> protected abstract string GetNextServerForMessage(MDSDataTransferMessage message);
/// <summary> /// To send a MDSOperationResultMessage to remote application's spesific communicator. /// </summary> /// <param name="senderApplication">Sender application/server</param> /// <param name="communicator">Communicator to send message</param> /// <param name="repliedMessage">Replied Message</param> /// <param name="success">Operation result</param> /// <param name="resultText">Details</param> private static void SendOperationResultMessage(MDSRemoteApplication senderApplication, ICommunicator communicator, MDSDataTransferMessage repliedMessage, bool success, string resultText) { try { if (success) { //Save MessageId of acknowledged message to do not receive same message again senderApplication.LastAcknowledgedMessageId = repliedMessage.MessageId; } senderApplication.SendMessage(new MDSOperationResultMessage { RepliedMessageId = repliedMessage.MessageId, Success = success, ResultText = resultText }, communicator); } catch (Exception ex) { Logger.Warn(ex.Message, ex); } }
/// <summary> /// This method is used to send a MDSDataTransferMessage to an available communicator of application. /// It just blocks caller thread until a communicator comes available and message is sent or until timeout, /// but receives result (ACK/Reject) message asynchronous. It sends result (ACK/Reject) message to OnResponseReceived() method. /// </summary> /// <param name="message">Message to send</param> /// <param name="timeOut">Timeout value to wait if all receivers are busy. Set 0 to do not use timeout.</param> /// <returns>True, if message sent to communicator.</returns> public void SendDataMessage(MDSDataTransferMessage message, int timeOut) { //Get a free/available communicator from _receiverCommunicators list ConnectedCommunicator receiver = null; lock (_remoteApplication._communicators) { var startTime = DateTime.Now; var passedTime = 0; while (receiver == null) { //If no receiver is connected to server, throw exception if (_remoteApplication._communicators.Count <= 0) { throw new MDSNoCommunicatorException("There is no communicator for remote application '" + _remoteApplication.Name + "' to send message."); } //If timeout is set and timeout occurs, throw an exception if ((timeOut > 0) && ((passedTime = (int)DateTime.Now.Subtract(startTime).TotalMilliseconds) >= timeOut)) { throw new MDSTimeoutException("All communicators for remote application '" + _remoteApplication.Name + "' are busy. Waited for " + timeOut + " ms."); } //Get a free communicator that can receive message //TODO: This check is working but not proper for here, move in the future. receiver = message.DestinationServerName == _remoteApplication.Settings.ThisServerName ? GetFreeReceiverCommunicator(message.DestinationCommunicatorId) : GetFreeReceiverCommunicator(0); //If no communicator is free, than wait for a free communicator.. if (receiver == null) { if (timeOut > 0) { //If there is not free communicator, wait until a communicator is available Monitor.Wait(_remoteApplication._communicators, timeOut - passedTime); } else { //If there is not free communicator, wait until a communicator is available Monitor.Wait(_remoteApplication._communicators); } } else { receiver.ProcessingMessage = message; receiver.ProcessingMessageExpireDate = DateTime.Now.AddMilliseconds(_remoteApplication.Settings.MessageResponseTimeout); } } } //Send message to communicator SetLeavingTime(message); SendMessage(message, receiver.Communicator); }