Example #1
0
        /// <summary>
        /// Sends a batch of commands to the server and processes the results.
        /// </summary>
        private static void SendBatch()
        {
            // Create a new web client that will serve as the connection point to the server.
            WebClient webClient = new WebClient();

            webClient.Timeout = TableLoader.timeout;

            // Call the web services to execute the command batch.
            remoteBatch.Merge(webClient.Execute(remoteBatch));

            // Any error during the batch will terminate the execution of the remaining records in the data file.
            if (remoteBatch.HasExceptions)
            {
                // Display each error on the console.
                foreach (RemoteException remoteException in remoteBatch.Exceptions)
                {
                    Console.WriteLine(remoteException.Message);
                }

                // This will signal the error exit from this loader.
                hasErrors = true;
            }

            // Clearing out the batch indicates that a new header should be created for the next set of commands.
            remoteBatch       = null;
            remoteTransaction = null;
            remoteAssembly    = null;
            remoteType        = null;
        }
Example #2
0
        /// <summary>
        /// Execute a command batch and synchronizes the client data model with the newer records from the server.
        /// </summary>
        public static void Execute(RemoteBatch remoteBatch)
        {
            // This 'try' block will insure that the mutual exclusion locks are released.
            try
            {
                // Maure sure only one thread at a time tries to refresh the data model.
                ClientMarketData.refreshMutex.WaitOne();

                // IMPORTANT CONCEPT: The rowVersion keeps track of the state of the client in-memory database. The server returns
                // a value for us to use on the next cycle.  If, for any reason, the merge of the data on the client should fail,
                // we won't use the new value on the next cycle. That is, we're going to keep on asking for the same incremental
                // set of records until we've successfully merged them.  This guarantees that the client and server databases stay
                // consistent with each other.
                DateTime timeStamp  = ClientMarketData.timeStamp;
                long     rowVersion = ClientMarketData.rowVersion;

                // IMPORTANT CONCEPT:  Executing the 'Reconcile' Web Service with a rowVersion returns to the client a DataSet with
                // only records that are the same age or younger than the rowVersion. This reduces the traffic on the network to
                // include only the essential records.  This set also contains the deleted records.  When the DataSet that is
                // returned from this Web Service is merged with the current data, the client will be reconciled with the server as
                // of the 'rowVersion' returned from the server.
                DataSet   resultSet;
                WebClient webClient = new WebClient();
                DataSet   dataSet   = webClient.ExecuteAndReconcile(ref timeStamp, ref rowVersion, remoteBatch, out resultSet);
                remoteBatch.Merge(resultSet);

                // If the time stamps are out of sync, then the server has reset since our last refresh (or this is the first time
                // refreshing the data mdoel).  This will reset the client side of the data model so that, after this refresh, the
                // client and the server will be in sync again.
                if (ClientMarketData.timeStamp != timeStamp)
                {
                    ClientMarketData.rowVersion = 0;
                    ClientMarketData.timeStamp  = timeStamp;
                    ClientMarketData.Clear();
                }

                // Optimization: Don't merge the results if there's nothing to merge.
                if (rowVersion > ClientMarketData.rowVersion)
                {
                    // IMPORTANT CONCEPT: This broadcast can be used to set up conditions for the data event handlers.  Often,
                    // optimizing algorithms will be used to consolidate the results of a merge.  This will allow the event driven
                    // logic to clear previous results and set up initial states for handling the bulk update of data.
                    ClientMarketData.OnBeginMerge(typeof(ClientMarketData));

                    // This 'try' block will insure that the locks are released if there's an error.
                    try
                    {
                        // IMPORTANT CONCEPT: Make sure that there aren't any nested locks.  Table locks have to be aquired in the
                        // same order for every thread that uses the shared data model.  It's possible that the current thread may
                        // have locked table 'B' if the preamble, then try to lock table 'A' during the processing of a command
                        // batch.  If another thread aquires the lock on table 'A' and blocks on table 'B', we will have a
                        // deadlock.  This is designed to catch situations where the 'Execute' method is called while tables are
                        // already locked.
                        Debug.Assert(!ClientMarketData.AreLocksHeld);

                        // IMPORTANT CONCEPT: Since the results will still be on the server if the client misses a refresh cycle,
                        // we take the attitude that this update process doesn't have to wait for locks. That is, if we can't get
                        // all the tables locked quickly, we'll just wait until the next refresh period to get the results. This
                        // effectively prevents deadlocking on the client. Make sure all the tables are locked before populating
                        // them.
                        foreach (TableLock tableLock in ClientMarketData.TableLocks)
                        {
                            tableLock.AcquireWriterLock(ClientTimeout.LockWait);
                        }

                        // IMPORANT CONCEPT:  Once all the write locks have been obtained, we can merge the results.  This will
                        // trigger the events associated with the tables for updated and deleted rows.  Also, if
                        // "AcceptChanges" is invoked for a DataSet or a Table, every single record in the DataSet or DataTable
                        // will be "Committed", even though they are unchanged.  This is a VERY inefficient operation.  To get
                        // around this, an ArrayList of modified records is constructed during the Merge operation.  After the
                        // merge, only the new records are committed.
                        ClientMarketData.mergedRows.Clear();
                        ClientMarketData.Merge(dataSet);
                        for (int index = 0; index < ClientMarketData.mergedRows.Count; index++)
                        {
                            ((DataRow)ClientMarketData.mergedRows[index]).AcceptChanges();
                        }

                        // If the merge operation was successful, then we can use the new rowVersion for the next cycle. Any
                        // exception before this point will result in a request of the same set of data becaue the rowVersion was
                        // never updated.
                        ClientMarketData.rowVersion = rowVersion;
                    }
                    catch (ConstraintException)
                    {
                        // Write out the exact location of the error.
                        foreach (DataTable dataTable in ClientMarketData.Tables)
                        {
                            foreach (DataRow dataRow in dataTable.Rows)
                            {
                                if (dataRow.HasErrors)
                                {
                                    Console.WriteLine("Error in '{0}': {1}", dataRow.Table.TableName, dataRow.RowError);
                                }
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        // Write the error and stack trace out to the debug listener
                        Debug.WriteLine(String.Format("{0}, {1}", exception.Message, exception.StackTrace));
                    }
                    finally
                    {
                        // No matter what happens above, we need to release the locks acquired above.
                        foreach (TableLock tableLock in ClientMarketData.TableLocks)
                        {
                            if (tableLock.IsWriterLockHeld)
                            {
                                tableLock.ReleaseWriterLock();
                            }
                        }
                    }

                    // IMPORTANT CONCEPT: When the merge is complete and the tables are unlocked, this will broadcast an event
                    // which allows optimization code to consolidate the results, examine the changed values and update reports
                    // based on the changed data.
                    ClientMarketData.OnEndMerge(typeof(ClientMarketData));
                }
            }
            finally
            {
                // Other threads can now request a refresh of the data model.
                ClientMarketData.refreshMutex.ReleaseMutex();
            }

            // Throw a specialized exception if the server returned any errors in the RemoteBatch structure.
            if (remoteBatch.HasExceptions)
            {
                throw new BatchException(remoteBatch);
            }
        }
Example #3
0
        static int Main(string[] args)
        {
            // If this flag is set during the processing of the file, the program will exit with an error code.
            bool hasErrors = false;

            try
            {
                // Defaults
                assemblyName              = "Service.External";
                namespaceName             = "Shadows.WebService.External";
                externalConfigurationCode = "DEFAULT";
                argumentState             = ArgumentState.FileName;

                // Parse the command line for arguments.
                foreach (string argument in args)
                {
                    // Decode the current argument into a state.
                    if (argument == "-c")
                    {
                        argumentState = ArgumentState.ConfigurationCode; continue;
                    }
                    if (argument == "-i")
                    {
                        argumentState = ArgumentState.FileName; continue;
                    }
                    if (argument == "-id")
                    {
                        argumentState = ArgumentState.StylesheetId; continue;
                    }
                    if (argument == "-t")
                    {
                        argumentState = ArgumentState.StylesheetTypeCode; continue;
                    }

                    // The parsing state will determine which variable is read next.
                    switch (argumentState)
                    {
                    // Read the command line argument into the proper variable based on the parsing state.
                    case ArgumentState.ConfigurationCode: externalConfigurationCode = argument; break;

                    case ArgumentState.StylesheetTypeCode: stylesheetTypeCode = argument; break;

                    case ArgumentState.FileName: fileName = argument; break;

                    case ArgumentState.Name: name = argument; break;

                    case ArgumentState.StylesheetId: StylesheetId = argument; break;
                    }

                    // The default state for the parser is to look for a file name.
                    argumentState = ArgumentState.FileName;
                }

                // If no file name was specified, we return an error.
                if (fileName == null)
                {
                    throw new Exception("Usage: Loader.Stylesheet -i <FileName>");
                }

                // Load up an XML document with the contents of the file specified on the command line.
                XmlDocument stylesheet = new XmlDocument();
                stylesheet.Load(fileName);

                // The XML document has several nodes that need to be read -- and then removed -- that contain attributes of the
                // stylesheet.  These nodes can be found easily using the XSL Path functions which need a namespace manager to sort
                // out the tag prefixes.
                XmlNamespaceManager namespaceManager = new XmlNamespaceManager(stylesheet.NameTable);
                namespaceManager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
                namespaceManager.AddNamespace("sss", "urn:schemas-shadows-com:shadows:stylesheet");

                // The spreadhseet source has several nodes which contain information about how the data in the XML document should
                // be loaded into the server, such as the stylesheet identifier, the name and the stylesheet style.  They are found
                // at this node.  After these information nodes have been read, they are removed from the stylesheet source.
                XmlNode stylesheetNode = stylesheet.SelectSingleNode("xsl:stylesheet", namespaceManager);
                if (stylesheetNode == null)
                {
                    throw new Exception("Syntax Error: missing stylesheet declaration.");
                }

                // Find the StylesheetId node.
                XmlNode StylesheetIdNode = stylesheetNode.SelectSingleNode("sss:stylesheetId", namespaceManager);
                if (StylesheetIdNode == null)
                {
                    throw new Exception("Syntax Error: missing StylesheetId declaration.");
                }

                // Find the StylesheetStyle node.
                XmlNode stylesheetTypeCodeNode = stylesheetNode.SelectSingleNode("sss:stylesheetTypeCode", namespaceManager);
                if (stylesheetTypeCodeNode == null)
                {
                    throw new Exception("Syntax Error: missing StylesheetStyle declaration.");
                }

                // Find the name node.
                XmlNode nameNode = stylesheetNode.SelectSingleNode("sss:name", namespaceManager);
                if (nameNode == null)
                {
                    throw new Exception("Syntax Error: missing name declaration.");
                }

                // Extract the data from the XML nodes.
                StylesheetId       = StylesheetIdNode.InnerText;
                stylesheetTypeCode = stylesheetTypeCodeNode.InnerText;
                name = nameNode.InnerText;

                // Remove the stylesheet nodes from the XSL spreadsheet before loading it into the server.
                stylesheetNode.RemoveChild(StylesheetIdNode);
                stylesheetNode.RemoveChild(stylesheetTypeCodeNode);
                stylesheetNode.RemoveChild(nameNode);

                // Create a command to load the stylesheet from the data loaded from the file.
                RemoteBatch    remoteBatch    = new RemoteBatch();
                RemoteAssembly remoteAssembly = remoteBatch.Assemblies.Add(assemblyName);
                RemoteType     remoteType     = remoteAssembly.Types.Add(string.Format("{0}.{1}", namespaceName, "Stylesheet"));
                RemoteMethod   remoteMethod   = remoteType.Methods.Add("Load");
                remoteMethod.Parameters.Add("StylesheetId", StylesheetId);
                remoteMethod.Parameters.Add("stylesheetTypeCode", stylesheetTypeCode);
                remoteMethod.Parameters.Add("name", name);
                remoteMethod.Parameters.Add("text", stylesheet.InnerXml);

                // Create a new web client that will serve as the connection point to the server and call the web services to
                // execute the command batch.
                WebClient webClient = new WebClient();
                remoteBatch.Merge(webClient.Execute(remoteBatch));

                // Display the each of the exceptions and set a global flag that shows that there was an exception to the normal
                // execution.
                if (remoteBatch.HasExceptions)
                {
                    foreach (RemoteException exception in remoteBatch.Exceptions)
                    {
                        Console.WriteLine(String.Format("{0}: {1}", remoteMethod.Parameters["StylesheetId"].Value, exception.Message));
                    }
                    hasErrors = true;
                }
            }
            catch (Exception exception)
            {
                // Show the system error and exit with an error.
                Console.WriteLine(exception.Message);
                hasErrors = true;
            }

            // Any errors will cause an abnormal exit.
            if (hasErrors)
            {
                return(1);
            }

            // Display the template that was loaded and exit with a successful code.
            Console.WriteLine(String.Format("{0} Stylesheet: {1}, Loaded", DateTime.Now.ToString("u"), name));
            return(0);
        }
Example #4
0
        static int Main(string[] args)
        {
            // If this flag is set during the processing of the file, the program will exit with an error code.
            bool hasErrors = false;

            try
            {
                // Defaults
                batchSize     = 100;
                assemblyName  = "Service.External";
                nameSpaceName = "Shadows.WebService.External";

                // The command line parser is driven by different states that are triggered by the flags read.  Unless a flag has been
                // read, the command line parser assumes that it's reading the file name from the command line.
                argumentState = ArgumentState.FileName;

                // Parse the command line for arguments.
                foreach (string argument in args)
                {
                    // Decode the current argument into a state change (or some other action).
                    if (argument == "-a")
                    {
                        argumentState = ArgumentState.Assembly; continue;
                    }
                    if (argument == "-b")
                    {
                        argumentState = ArgumentState.BatchSize; continue;
                    }
                    if (argument == "-n")
                    {
                        argumentState = ArgumentState.NameSpace; continue;
                    }
                    if (argument == "-i")
                    {
                        argumentState = ArgumentState.FileName; continue;
                    }

                    // The parsing state will determine which variable is read next.
                    switch (argumentState)
                    {
                    case ArgumentState.Assembly: assemblyName = argument; break;

                    case ArgumentState.BatchSize: batchSize = Convert.ToInt32(argument); break;

                    case ArgumentState.FileName: fileName = argument; break;

                    case ArgumentState.NameSpace: nameSpaceName = argument; break;
                    }

                    // The default state is to look for the input file name on the command line.
                    argumentState = ArgumentState.FileName;
                }

                // Throw a usage message back at the user if no file name was given.
                if (fileName == null)
                {
                    throw new Exception("Usage: Loader.Algorithm -i <FileName>");
                }

                // Open up the file containing all the broker.
                BrokerReader brokerReader = new BrokerReader(fileName);

                // Create a new web client that will serve as the connection point to the server.
                WebClient webClient = new WebClient();

                // Loading the database involves creating a batch of commands and sending them off as a transaction.  This gives
                // the server a chance to pipeline a large chunk of processing, without completely locking up the server for the
                // entire set of data.  This will construct a header for the command batch which gives information about which
                // assembly contains the class that is used to load the data.
                RemoteBatch       remoteBatch       = new RemoteBatch();
                RemoteTransaction remoteTransaction = remoteBatch.Transactions.Add();
                RemoteAssembly    remoteAssembly    = remoteBatch.Assemblies.Add(assemblyName);
                RemoteType        remoteType        = remoteAssembly.Types.Add(string.Format("{0}.{1}", nameSpaceName, "Broker"));

                // Read the file until an EOF is reached.
                while (true)
                {
                    // This counter keeps track of the number of records sent.  When the batch is full, it's sent to the server to be
                    // executed as a single transaction.
                    int batchCounter = 0;

                    // Read the next broker from the input stream.  A 'null' is returned when we've read past the end of file.
                    Broker broker = brokerReader.ReadBroker();
                    if (broker != null)
                    {
                        // Construct a call to the 'Load' method to populate the broker record.
                        RemoteMethod remoteMethod = remoteType.Methods.Add("Load");
                        remoteMethod.Transaction = remoteTransaction;
                        remoteMethod.Parameters.Add("brokerId", broker.Symbol);
                        remoteMethod.Parameters.Add("name", broker.Name);
                        remoteMethod.Parameters.Add("symbol", broker.Symbol);
                    }

                    // This will check to see if it's time to send the batch.  A batch is sent when the 'batchSize' has been
                    // reached, or if the last record has just been converted into a command.
                    if (++batchCounter % batchSize == 0 || broker == null)
                    {
                        // Call the web services to execute the command batch.
                        remoteBatch.Merge(webClient.Execute(remoteBatch));

                        // Any error during the batch will terminate the execution of the remaining records in the data file.
                        if (remoteBatch.HasExceptions)
                        {
                            // Display each error on the console.
                            foreach (RemoteMethod remoteMethod in remoteBatch.Methods)
                            {
                                foreach (RemoteException remoteException in remoteMethod.Exceptions)
                                {
                                    Console.WriteLine(String.Format("{0}: {1}", remoteMethod.Parameters["brokerId"].Value,
                                                                    remoteException.Message));
                                }
                            }

                            // This will signal the error exit from this loader.
                            hasErrors = true;
                        }

                        // If the end of file was reached, break out of the loop and exit the application.
                        if (broker == null)
                        {
                            break;
                        }

                        // After each batch has been send, check to see if there are additional records to be send.  If so,
                        // regenerate the remote batch and set up the header.
                        remoteBatch       = new RemoteBatch();
                        remoteTransaction = remoteBatch.Transactions.Add();
                        remoteAssembly    = remoteBatch.Assemblies.Add(assemblyName);
                        remoteType        = remoteAssembly.Types.Add(string.Format("{0}.{1}", nameSpaceName, "Broker"));
                    }
                }
            }
            catch (Exception exception)
            {
                // Show the system error and exit with an error.
                Console.WriteLine(exception.Message);
                hasErrors = true;
            }

            // Any errors will cause an abnormal exit.
            if (hasErrors)
            {
                return(1);
            }

            // Write a status message when a the file is loaded successfully.
            Console.WriteLine(String.Format("{0} Data: Brokers, Loaded", DateTime.Now.ToString("u")));

            // If we reached here, the file was imported without issue.
            return(0);
        }