using System; using System.Collections; using System.Data; using System.IO; using System.Net; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web.Services; using System.Web.Services.Description; using System.Web.Services.Protocols; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using System.Data.SqlClient; using UDDI; using UDDI.API; using UDDI.Diagnostics; namespace UDDI.Replication { /// ******************************************************************** /// class ReplicationHelper /// -------------------------------------------------------------------- /// /// /// ******************************************************************** /// public class ReplicationHelper { private static OperatorNode LocalOperator = null; private static X509Certificate LocalCertificate = null; private static OperatorNodeCollection RemoteOperators = null; private static ChangeRecordVectorCollection ChangesAlreadySeen = null; private static ReplicationSoapClient SoapClient = null; private static Hashtable NodeChangeRecordCounts = null; private static Hashtable SessionChangeRecordCounts = null; /// **************************************************************** /// private Initialize [static] /// ---------------------------------------------------------------- /// /// Initializes replication variables. /// /// **************************************************************** /// private static void Initialize() { Debug.Enter(); Debug.VerifySetting( "OperatorKey" ); // // Get information about the local operator node. // LocalOperator = new OperatorNode( Config.GetString( "OperatorKey" ) ); LocalOperator.Get(); // // Get the local certificate. // LocalCertificate = new X509Certificate( LocalOperator.Certificate ); // // Get the list of operator nodes to which we can send // get_changeRecords messages. Remove ourself from the // list! // RemoteOperators = new OperatorNodeCollection(); RemoteOperators.Get(); RemoteOperators.Remove( LocalOperator.OperatorNodeID ); // // Get a ChangesAlreadySeen list for each operator node (not // just the ones we are replicating with). // GetHighWaterMarks(); // // Initialize our soap client. // string proxy = Config.GetString( "Proxy", null ); SoapClient = new ReplicationSoapClient(); SoapClient.ClientCertificates.Add( LocalCertificate ); if( !Utility.StringEmpty( proxy ) ) SoapClient.Proxy = new WebProxy( proxy, true ); // // Setup the context for a replication session. // Context.ApiVersionMajor = 2; Context.ContextType = ContextType.Replication; Context.LogChangeRecords = false; // // Setup logging. // SessionChangeRecordCounts = new Hashtable(); foreach( int payloadType in Enum.GetValues( typeof( ChangeRecordPayloadType ) ) ) { SessionChangeRecordCounts[ payloadType ] = 0; } Debug.Leave(); } private static void GetHighWaterMarks() { try { GetHighWaterMarks getHighWaterMarks = new GetHighWaterMarks(); HighWaterMarkDetail detail = getHighWaterMarks.Get(); ChangesAlreadySeen = detail.HighWaterMarks; } catch( Exception e ) { #if never Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.CannotRetrieveHighWaterMarks, "Could not retrive high water marks for operator nodes.\r\n\r\nDetail:\r\n" + e.ToString() ); #endif Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.CannotRetrieveHighWaterMarks, new UDDIText( "UDDI_ERROR_REPLICATION_HIGHWATER_MARKS", e.ToString() ).GetText() ); throw new UDDIException( ErrorType.E_fatalError, "UDDI_ERROR_REPLICATION_HIGHWATER_MARKS", e ); } } /// **************************************************************** /// public Replicate [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public static void Replicate() { // // Initialize the replication session. // Initialize(); // // Verify that we do in fact have operators to replicate // with. // if( RemoteOperators.Count < 1 ) { #if never Debug.OperatorMessage( SeverityType.Info, CategoryType.Replication, OperatorMessageType.NoReplicationOperators, "There are no operators to replicate with. Please check the replication configuration." ); #endif Debug.OperatorMessage( SeverityType.Info, CategoryType.Replication, OperatorMessageType.NoReplicationOperators, new UDDIText( "UDDI_ERROR_REPLICATION_NO_OPERATORS" ).GetText() ); return; } // // TODO: Get the node id of the last operator we replicated // with. // // // TODO: Re-order the operator node list so that we replicate // in a round-robin fashion. // // // Create the start replication session log entry. // LogSessionStart(); // // Replicate with each operator node. // foreach( OperatorNode remoteNode in RemoteOperators ) { ReplicationResult result = null; // // Log the node replication start. // LogOperatorStart( remoteNode ); // // Get the branch list for the node. The branch list is // the remote node itself, plus any alternatives that we // may try if we have trouble communicating with the first // node. For now, we don't support alternate nodes, so // the remote node is the only node we add to this list. // OperatorNodeCollection branchNodes = new OperatorNodeCollection(); branchNodes.Add( remoteNode ); foreach( OperatorNode branchNode in branchNodes ) { // // Get the last replication status for this node. // result = ReplicateWithNode( branchNode ); if( ReplicationStatus.Success == result.ReplicationStatus ) break; // // Determine what we should do based on this attempts result // and the result of the last replication cycle with this // node. TODO: if it is a communication error, we'll need // to try alternate edges. This is reserved until alternate // edges are actually used. // } // // Log the node replication end. // LogOperatorEnd( remoteNode, result ); } // // Create the end replication session log entry. // LogSessionEnd(); Debug.Leave(); } /// **************************************************************** /// public ReplicateWithNode [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public static ReplicationResult ReplicateWithNode( string operatorNodeID ) { OperatorNode remoteNode = null; // // Initialize the replication session. // Initialize(); // // Replicate with the specified node. // try { remoteNode = new OperatorNode( operatorNodeID ); remoteNode.Get(); } catch( Exception e ) { #if never string message = String.Format( "Could not get details for operator {{{0}}}.\r\n\r\nDetails:\r\n{1}", operatorNodeID, e.ToString() ); Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.UnknownOperator, message ); throw new Exception( message, e ); #endif UDDIText uddiText = new UDDIText( "UDDI_ERROR_REPLICATION_OPERATOR_DETAILS", operatorNodeID, e.ToString() ); Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.UnknownOperator, uddiText.GetText() ); throw new UDDIException( ErrorType.E_fatalError, uddiText.GetText() ); } // // Log the node replication start. // LogOperatorStart( remoteNode ); // // Replicate // ReplicationResult result = ReplicateWithNode( remoteNode ); // // Log the node replication end. // LogOperatorEnd( remoteNode, result ); return result; } /// **************************************************************** /// private ReplicateWithNode [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static ReplicationResult ReplicateWithNode( OperatorNode remoteNode ) { // // Retrieve the change records from the remote node and process // them. // ChangeRecordCollection changeRecords = null; // // Keep track of the number of times we call GetChangeRecords on each operator. We // don't want to be in a situation where we call them infinitely if they keep updating // their data. // int maxIterations = Config.GetInt( "Replication.MaxGetChangeIterations", 100 ); int iterations = 0; // // Keep getting change records until there are no more; we need to do this because some // operators will limit the number of change records that they send back. // do { try { changeRecords = GetChangeRecords( remoteNode ); } catch( Exception e ) { return ReplicationResult.LogCommunicationError( remoteNode, e, ChangesAlreadySeen ); } foreach( ChangeRecord changeRecord in changeRecords ) { // // Make sure we haven't seen this change record before. // if( ChangesAlreadySeen.IsProcessed( changeRecord.ChangeID ) ) { Debug.Write( SeverityType.Info, CategoryType.Replication, String.Format( "Change already seen.\r\nSkipping record {0}:{1} retrieved from node {2}", changeRecord.ChangeID.NodeID, changeRecord.ChangeID.OriginatingUSN, remoteNode.OperatorNodeID ) ); continue; } // // Make sure we aren't trying to replicate one of our own // change records. // if( 0 == String.Compare( changeRecord.ChangeID.NodeID, LocalOperator.OperatorNodeID, true ) ) { Debug.Write( SeverityType.Info, CategoryType.Replication, String.Format( "Skipping replication of local change record.\r\nSkipping record {0}:{1} retrieved from node {2}", changeRecord.ChangeID.NodeID, changeRecord.ChangeID.OriginatingUSN, remoteNode.OperatorNodeID ) ); continue; } // // Begin a transaction. // ConnectionManager.BeginTransaction(); try { // // Set user context information and process the // change record. // Context.User.ID = changeRecord.ChangeID.NodeID; Context.TimeStamp = DateTime.Now; // // Validate the change record, then process it. There is a very good // reason for doing this validation on a change record by change record // basis: if there is one error in the incoming change record stream, // we would reject the entire stream, not just the errant change record. // SchemaCollection.Validate( changeRecord ); try { changeRecord.Process(); } catch( Exception innerException ) { // // Exceptions that are related to IN 60 are ignored. // if( Context.ExceptionSource == ExceptionSource.PublisherAssertion || Context.ExceptionSource == ExceptionSource.BrokenServiceProjection ) { // // Log the error // ReplicationResult.LogInvalidKeyWarning( remoteNode, changeRecord, innerException ); } else { // // Otherwise, throw the exception for real. // throw innerException; } } // // Commit the transaction. // ConnectionManager.Commit(); // // Update our changeAlreadySeen vector for the // originating node. // ChangesAlreadySeen.MarkAsProcessed( changeRecord.ChangeID ); // // Update log counts. // LogCounts( changeRecord ); } catch( Exception e ) { ConnectionManager.Abort(); return ReplicationResult.LogValidationError( remoteNode, changeRecord, e, ChangesAlreadySeen ); } } // // Refresh our highwater mark vector. We do this instead of calling gethighwatermarks on the // operator because hitting our DB is faster then making the SOAP call across HTTP. // GetHighWaterMarks(); iterations++; }while( 0 != changeRecords.Count && iterations < maxIterations ); // // Log success! // return ReplicationResult.LogSuccess( remoteNode, null ); } /// **************************************************************** /// private GetChangeRecords [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static ChangeRecordCollection GetChangeRecords( OperatorNode remoteNode ) { GetChangeRecords getChangeRecords = new GetChangeRecords(); getChangeRecords.RequestingNode = LocalOperator.OperatorNodeID; getChangeRecords.ChangesAlreadySeen = ChangesAlreadySeen; SoapClient.Url = remoteNode.SoapReplicationURL; ChangeRecordDetail detail = SoapClient.GetChangeRecords( getChangeRecords ); return detail.ChangeRecords; } /// **************************************************************** /// private LogSessionStart [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static void LogSessionStart() { StringWriter log = new StringWriter(); try { // log.WriteLine( "Replication cycle started." ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_START" ).GetText() ); log.WriteLine(); // log.WriteLine( "REPLICATION NODES:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_NODES" ).GetText() ); foreach( OperatorNode operatorNode in RemoteOperators ) { log.Write( " " ); log.WriteLine( operatorNode.Name ); log.Write( " {" ); log.Write( operatorNode.OperatorNodeID ); log.WriteLine( "}" ); log.Write( " " ); log.WriteLine( operatorNode.SoapReplicationURL ); log.WriteLine(); } // log.WriteLine( "CURRENT HIGHWATER MARKS:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_CURRENT_HIGHWATER_MARKS" ).GetText() ); foreach( ChangeRecordVector vector in ChangesAlreadySeen ) { log.Write( " {" ); log.Write( vector.NodeID ); log.Write( "} : " ); log.WriteLine( vector.OriginatingUSN ); } Debug.OperatorMessage( SeverityType.Info, CategoryType.Replication, OperatorMessageType.StartingReplicationSession, log.ToString() ); } finally { log.Close(); } } /// **************************************************************** /// private LogOperatorStart [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static void LogOperatorStart( OperatorNode remoteNode ) { NodeChangeRecordCounts = new Hashtable(); foreach( int payloadType in Enum.GetValues( typeof( ChangeRecordPayloadType ) ) ) { NodeChangeRecordCounts[ payloadType ] = 0; } StringWriter log = new StringWriter(); try { // log.WriteLine( "Starting replication with node.\r\n" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_NODE_START" ).GetText() ); // log.WriteLine( "REPLICATION NODE:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_NODE" ).GetText() ); log.Write( " " ); log.WriteLine( remoteNode.Name ); log.Write( " {" ); log.Write( remoteNode.OperatorNodeID ); log.WriteLine( "}" ); log.Write( " " ); log.WriteLine( remoteNode.SoapReplicationURL ); log.WriteLine(); // log.WriteLine( "CURRENT HIGHWATER MARKS:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_CURRENT_HIGHWATER_MARKS" ).GetText() ); foreach( ChangeRecordVector vector in ChangesAlreadySeen ) { log.Write( " {" ); log.Write( vector.NodeID ); log.Write( "} : " ); log.WriteLine( vector.OriginatingUSN ); } Debug.OperatorMessage( SeverityType.Info, CategoryType.Replication, OperatorMessageType.StartingReplicationWithNode, log.ToString() ); } finally { log.Close(); } } /// **************************************************************** /// private LogOperatorEnd [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static void LogOperatorEnd( OperatorNode remoteNode, ReplicationResult result ) { StringWriter log = new StringWriter(); try { if( ReplicationStatus.Success == result.ReplicationStatus ) { // log.WriteLine( "Replication with node complete." ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_COMPLETE" ).GetText() ); } else { // log.Write( "Replication with node interrupted\r\n ERROR: " ); log.Write( new UDDIText( "UDDI_MSG_REPLICATION_INTERRUPTED" ).GetText() ); log.WriteLine( result.ReplicationStatus.ToString() ); } log.WriteLine(); // log.WriteLine( "REPLICATION NODE:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_NODE" ).GetText() ); log.Write( " " ); log.WriteLine( remoteNode.Name ); log.Write( " {" ); log.Write( remoteNode.OperatorNodeID ); log.WriteLine( "}" ); log.Write( " " ); log.WriteLine( remoteNode.SoapReplicationURL ); log.WriteLine(); // log.WriteLine( "CHANGE RECORDS PROCESSED:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_CHANGE_RECORDS_PROCESSED" ).GetText() ); foreach( int payloadType in Enum.GetValues( typeof( ChangeRecordPayloadType ) ) ) { string name = Enum.GetName( typeof( ChangeRecordPayloadType ), payloadType ) + ":"; log.Write( " " ); log.Write( name.PadRight( 30 ) ); log.Write( "\t" ); log.WriteLine( NodeChangeRecordCounts[ payloadType ] ); } log.WriteLine(); //log.WriteLine( "NEW HIGHWATER MARKS:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_NEW_HIGHWATER_MARKS" ).GetText() ); foreach( ChangeRecordVector vector in ChangesAlreadySeen ) { log.Write( " {" ); log.Write( vector.NodeID ); log.Write( "} : " ); log.WriteLine( vector.OriginatingUSN ); } SeverityType severity = SeverityType.Info; if( ReplicationStatus.Success != result.ReplicationStatus ) severity = SeverityType.Error; string message = log.ToString(); // // Log results // Debug.OperatorMessage( severity, CategoryType.Replication, OperatorMessageType.EndingReplicationWithNode, message ); // // Only send mail on success; if there was a failure, mail will be sent at the point of failure. // if( ReplicationStatus.Success == result.ReplicationStatus ) { Debug.OperatorMail( SeverityType.None, CategoryType.Replication, OperatorMessageType.EndingReplicationWithNode, message ); } } finally { log.Close(); } // // Add the operator node counts to the totals. // foreach( int payloadType in Enum.GetValues( typeof( ChangeRecordPayloadType ) ) ) { SessionChangeRecordCounts[ payloadType ] = (int)SessionChangeRecordCounts[ payloadType ] + (int)NodeChangeRecordCounts[ payloadType ]; } } /// **************************************************************** /// private LogSessionEnd [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static void LogSessionEnd() { StringWriter log = new StringWriter(); try { // log.WriteLine( "Replication cycle complete." ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_END" ).GetText() ); log.WriteLine(); //log.WriteLine( "CHANGE RECORDS PROCESSED:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_CHANGERECORDS_PROCESSED" ).GetText() ); foreach( int payloadType in Enum.GetValues( typeof( ChangeRecordPayloadType ) ) ) { string name = Enum.GetName( typeof( ChangeRecordPayloadType ), payloadType ) + ":"; log.Write( " " ); log.Write( name.PadRight( 30 ) ); log.Write( "\t" ); log.WriteLine( SessionChangeRecordCounts[ payloadType ] ); } log.WriteLine(); // log.WriteLine( "NEW HIGHWATER MARKS:" ); log.WriteLine( new UDDIText( "UDDI_MSG_REPLICATION_NEW_HIGHWATER_MARKS" ).GetText() ); foreach( ChangeRecordVector vector in ChangesAlreadySeen ) { log.Write( " {" ); log.Write( vector.NodeID ); log.Write( "} : " ); log.WriteLine( vector.OriginatingUSN ); } Debug.OperatorMessage( SeverityType.Info, CategoryType.Replication, OperatorMessageType.EndingReplicationWithNode, log.ToString() ); } finally { log.Close(); } } /// **************************************************************** /// private LogCounts [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// private static void LogCounts( ChangeRecord changeRecord ) { int payloadType = (int)changeRecord.Payload.ChangeRecordPayloadType; NodeChangeRecordCounts[ payloadType ] = (int)NodeChangeRecordCounts[ payloadType ] + 1; } } /// ******************************************************************** /// class ReplicationResult /// -------------------------------------------------------------------- /// /// /// ******************************************************************** /// public class ReplicationResult { public UDDI.Replication.ReplicationStatus ReplicationStatus; public string OperatorNodeID; public string Description; public string LastNodeID; public long LastUSN; public long LastChange; /// **************************************************************** /// public GetLast /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public void GetLast( string operatorNodeID, bool inboundStatus ) { Debug.Enter(); SqlStoredProcedureAccessor sp = new SqlStoredProcedureAccessor(); sp.ProcedureName = "net_operatorLogLast_get"; sp.Parameters.Add( "@operatorKey", SqlDbType.UniqueIdentifier ); sp.Parameters.Add( "@inboundStatus", SqlDbType.Bit ); sp.Parameters.Add( "@replStatusID", SqlDbType.TinyInt, ParameterDirection.Output ); sp.Parameters.Add( "@description", SqlDbType.NVarChar, UDDI.Constants.Lengths.Description, ParameterDirection.Output ); sp.Parameters.Add( "@lastOperatorKey", SqlDbType.UniqueIdentifier, ParameterDirection.Output ); sp.Parameters.Add( "@lastUSN", SqlDbType.BigInt, ParameterDirection.Output ); sp.Parameters.Add( "@lastChange", SqlDbType.BigInt, ParameterDirection.Output ); sp.Parameters.SetGuidFromString( "@operatorKey", operatorNodeID ); sp.Parameters.SetBool( "@inboundStatus", inboundStatus ); sp.ExecuteNonQuery(); this.OperatorNodeID = operatorNodeID; this.ReplicationStatus = (ReplicationStatus)sp.Parameters.GetShort( "@replStatusID" ); this.Description = sp.Parameters.GetString( "@description" ); this.LastNodeID = sp.Parameters.GetGuidString( "@lastOperatorKey" ); this.LastUSN = sp.Parameters.GetLong( "@lastUSN" ); this.LastChange = sp.Parameters.GetLong( "@lastChange" ); Debug.Leave(); } /// **************************************************************** /// public Save /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public void Save() { Debug.Enter(); try { SqlStoredProcedureAccessor sp = new SqlStoredProcedureAccessor(); sp.ProcedureName = "net_operatorLog_save"; sp.Parameters.Add( "@operatorKey", SqlDbType.UniqueIdentifier ); sp.Parameters.Add( "@replStatusID", SqlDbType.TinyInt ); sp.Parameters.Add( "@description", SqlDbType.NVarChar, UDDI.Constants.Lengths.Description ); sp.Parameters.Add( "@lastOperatorKey", SqlDbType.UniqueIdentifier ); sp.Parameters.Add( "@lastUSN", SqlDbType.BigInt ); sp.Parameters.Add( "@lastChange", SqlDbType.BigInt ); sp.Parameters.SetGuidFromString( "@operatorKey", OperatorNodeID ); sp.Parameters.SetShort( "@replStatusID", (short)ReplicationStatus ); sp.Parameters.SetString( "@description", Description ); sp.Parameters.SetGuidFromString( "@lastOperatorKey", LastNodeID ); sp.Parameters.SetLong( "@lastUSN", LastUSN ); sp.Parameters.SetLong( "@lastChange", LastChange ); sp.ExecuteNonQuery(); } catch { #if never Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, String.Format( "Could not store last replication result.\r\n\r\n" + "REPLICATION NODE:\r\n" + " {{{0}}}\r\n\r\n" + "CHANGE RECORD:\r\n" + " {{{1}}} : {2}", OperatorNodeID, LastNodeID, LastUSN ) ); #endif Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, new UDDIText( "UDDI_ERROR_REPLICATION_COULD_NOT_STORE_RESULT", OperatorNodeID, LastNodeID, LastUSN ).GetText() ); } Debug.Leave(); } /// **************************************************************** /// public LogSuccess [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public static ReplicationResult LogSuccess( OperatorNode remoteNode, string description ) { ReplicationResult result = new ReplicationResult(); result.ReplicationStatus = ReplicationStatus.Success; result.Description = description; result.OperatorNodeID = remoteNode.OperatorNodeID; result.LastNodeID = null; result.LastUSN = 0; result.LastChange = DateTime.UtcNow.Ticks; result.Save(); return result; } /// **************************************************************** /// public LogCommunicationError [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public static ReplicationResult LogCommunicationError( OperatorNode remoteNode, Exception e, ChangeRecordVectorCollection changesAlreadySeen ) { ReplicationResult result = new ReplicationResult(); result.ReplicationStatus = ReplicationStatus.CommunicationError; result.Description = e.ToString(); result.OperatorNodeID = remoteNode.OperatorNodeID; result.LastNodeID = null; result.LastUSN = 0; result.LastChange = DateTime.UtcNow.Ticks; result.Save(); string response = e.ToString(); if( e is WebException ) { WebException we = (WebException)e; if( null != we.Response ) { StreamReader reader = null; try { reader = new StreamReader( we.Response.GetResponseStream() ); response += "\r\nRESPONSE:\r\n\r\n"; response += reader.ReadToEnd(); } catch { } finally { if( null != reader ) reader.Close(); } } } else if( e is SoapException ) { SoapException se = (SoapException)e; if( null != se.Detail ) { response += "\r\nSOAP FAULT DETAILS:\r\n\r\n"; response += se.Detail.OuterXml; } } // // Also send the highwater marks. // StringWriter highWaterMarks = new StringWriter(); foreach( ChangeRecordVector vector in changesAlreadySeen ) { highWaterMarks.Write( " {" ); highWaterMarks.Write( vector.NodeID ); highWaterMarks.Write( "} : " ); highWaterMarks.WriteLine( vector.OriginatingUSN ); } #if never // // Communications Error // string message = String.Format( "Error communicating with operator.\r\n\r\n" + "REPLICATION NODE:\r\n" + " {0}\r\n" + " {{{1}}}\r\n" + " {2}\r\n\r\n" + "DETAILS:\r\n\r\n{3}\r\n\r\nCURRENT HIGHWATER MARKS:\r\n\r\n{4}", remoteNode.Name, remoteNode.OperatorNodeID, remoteNode.SoapReplicationURL, response, highWaterMarks.ToString() ); #endif // // Communications Error // UDDIText message = new UDDIText( "UDDI_ERROR_REPLICATION_OPERATOR_COMMUNICATION_ERROR", remoteNode.Name, remoteNode.OperatorNodeID, remoteNode.SoapReplicationURL, response, highWaterMarks.ToString() ); Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ErrorCommunicatingWithNode, message.GetText() ); Debug.OperatorMail( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ErrorCommunicatingWithNode, message.GetText() ); return result; } /// **************************************************************** /// public LogValidationError [static] /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// public static ReplicationResult LogValidationError( OperatorNode remoteNode, ChangeRecord changeRecord, Exception e, ChangeRecordVectorCollection changesAlreadySeen ) { ReplicationResult result = new ReplicationResult(); result.ReplicationStatus = ReplicationStatus.ValidationError; result.Description = e.ToString(); result.OperatorNodeID = remoteNode.OperatorNodeID; result.LastNodeID = changeRecord.ChangeID.NodeID; result.LastUSN = changeRecord.ChangeID.OriginatingUSN; result.LastChange = DateTime.UtcNow.Ticks; result.Save(); // // Also send the highwater marks. // StringWriter highWaterMarks = new StringWriter(); foreach( ChangeRecordVector vector in changesAlreadySeen ) { highWaterMarks.Write( " {" ); highWaterMarks.Write( vector.NodeID ); highWaterMarks.Write( "} : " ); highWaterMarks.WriteLine( vector.OriginatingUSN ); } #if never string message = String.Format( "Could not process entity from remote operator.\r\n\r\n" + "REPLICATION NODE:\r\n" + " {0}\r\n" + " {{{1}}}\r\n" + " {2}\r\n\r\n" + "DETAILS:\r\n\r\n" + "{3}\r\n\r\n" + "CHANGE RECORD:\r\n" + " {{{4}}} : {5}\r\n\r\n" + "{6}\r\n\r\nCURRENT HIGHWATER MARKS:\r\n\r\n{7}", remoteNode.Name, remoteNode.OperatorNodeID, remoteNode.SoapReplicationURL, e.ToString(), changeRecord.ChangeID.NodeID, changeRecord.ChangeID.OriginatingUSN, changeRecord.ToString(), highWaterMarks.ToString() ); #endif UDDIText message = new UDDIText( "UDDI_ERROR_REPLICATION_COULD_NOT_PROCESS_ENTITY", remoteNode.Name, remoteNode.OperatorNodeID, remoteNode.SoapReplicationURL, e.ToString(), changeRecord.ChangeID.NodeID, changeRecord.ChangeID.OriginatingUSN, changeRecord.ToString(), highWaterMarks.ToString() ); Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, message.GetText() ); Debug.OperatorMail( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, message.GetText() ); return result; } public static void LogInvalidKeyWarning( OperatorNode remoteNode, ChangeRecord changeRecord, Exception e ) { ReplicationResult result = new ReplicationResult(); result.ReplicationStatus = ReplicationStatus.CommunicationError; result.Description = e.ToString(); result.OperatorNodeID = remoteNode.OperatorNodeID; result.LastNodeID = changeRecord.ChangeID.NodeID; result.LastUSN = changeRecord.ChangeID.OriginatingUSN; result.LastChange = DateTime.UtcNow.Ticks; result.Save(); #if never string message = String.Format( "Invalid key from remote operator.\r\n\r\n" + "REPLICATION NODE:\r\n" + " {0}\r\n" + " {{{1}}}\r\n" + " {2}\r\n\r\n" + "DETAILS:\r\n\r\n" + "{3}\r\n\r\n" + "CHANGE RECORD:\r\n" + " {{{4}}} : {5}\r\n\r\n" + "{6}", remoteNode.Name, remoteNode.OperatorNodeID, remoteNode.SoapReplicationURL, e.ToString(), changeRecord.ChangeID.NodeID, changeRecord.ChangeID.OriginatingUSN, changeRecord.ToString() ); #endif UDDIText message = new UDDIText( "UDDI_ERROR_REPLICATION_INVALID_KEY", remoteNode.Name, remoteNode.OperatorNodeID, remoteNode.SoapReplicationURL, e.ToString(), changeRecord.ChangeID.NodeID, changeRecord.ChangeID.OriginatingUSN, changeRecord.ToString() ); Debug.OperatorMessage( SeverityType.Warning, CategoryType.Replication, OperatorMessageType.InvalidKey, message.GetText() ); // // TODO for now don't send mail on a warning. // #if never Debug.OperatorMail( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, message ); #endif } } /// ******************************************************************** /// class ReplicationSoapClient /// -------------------------------------------------------------------- /// /// /// ******************************************************************** /// [ WebServiceBinding( Name="ExternalMessagesSoap", Namespace=UDDI.Replication.Constants.Namespace ) ] internal sealed class ReplicationSoapClient : SoapHttpClientProtocol { /// **************************************************************** /// public GetChangeRecords /// ---------------------------------------------------------------- /// /// /// **************************************************************** /// [ System.Diagnostics.DebuggerStepThrough ] [ SoapDocumentMethod( "", Use=SoapBindingUse.Literal, ParameterStyle=SoapParameterStyle.Bare ) ] [ return: XmlElement( "changeRecords", Namespace=UDDI.Replication.Constants.Namespace, IsNullable=false ) ] public ChangeRecordDetail GetChangeRecords( GetChangeRecords get_changeRecords ) { object[] results = Invoke( "GetChangeRecords", new object[] { get_changeRecords } ); return (ChangeRecordDetail)results[ 0 ]; } protected override WebRequest GetWebRequest( Uri uri ) { WebRequest innerWebRequest = base.GetWebRequest( uri ); UDDIWebRequest webRequest = new UDDIWebRequest( innerWebRequest ); return webRequest; } } /// /// UDDIWebResponse allows us to return our own Stream object. /// internal class UDDIWebResponse : WebResponse { WebResponse innerWebResponse; UDDIResponseStream uddiResponseStream; /// /// Constructor /// /// This object should come from the WebResponse created by HttpSoapClientProtocol. public UDDIWebResponse( WebResponse innerWebResponse ) { this.innerWebResponse = innerWebResponse; } /// /// Return our response stream (UDDIResponseStream) instead of the Stream associated with our inner response. /// /// public override Stream GetResponseStream() { if( null == uddiResponseStream ) { uddiResponseStream = new UDDIResponseStream( innerWebResponse.GetResponseStream() ); } return uddiResponseStream; } /// /// Delegates to the inner web response. /// public override void Close() { innerWebResponse.Close(); } /// /// Delegates to the inner web response. /// public override long ContentLength { get { return innerWebResponse.ContentLength; } set { innerWebResponse.ContentLength = value; } } /// /// Delegates to the inner web response. /// public override string ContentType { get { return innerWebResponse.ContentType; } set { innerWebResponse.ContentType = value; } } /// /// Delegates to the inner web response. /// public override Uri ResponseUri { get { return innerWebResponse.ResponseUri; } } /// /// Delegates to the inner web response. /// public override WebHeaderCollection Headers { get { return innerWebResponse.Headers; } } } /// /// UDDIResponseStream allows us to manipulate the XML sent back from the web service. /// internal class UDDIResponseStream : MemoryStream { // // TODO: at some point it may be necessary to pass in the current version as a parameter if the transforms become // more complicated. // /// /// Constructor. We read all the XML sent from the server, do our version manipulation, then write the new XML /// into our inner responseStream object. /// /// This object should be the responseStream from a WebResponse object. public UDDIResponseStream( Stream responseStream ) { try { // // Get the XML the server sent to us. // StreamReader reader = new StreamReader( responseStream ); string responseXml = reader.ReadToEnd(); reader.Close(); // // Write this transformed XML into ourselves. // StreamUtility.WriteStringToStream( this, responseXml ); // // Rewind ourselves // this.Position = 0; // // Validate the incoming XML. We have to read the data from the stream first because it is not seekable // try { SchemaCollection.Validate( this ); } catch( XmlSchemaException schemaException ) { string message = schemaException.ToString() + "\r\n\r\nResponse XML:\r\n\r\n" + responseXml; Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, message ); throw schemaException; } } finally { // // Rewind ourselves // this.Position = 0; } } } /// /// UDDIWebRequest allows us to return our own request and response objects. /// /// /// UDDIWebRequest allows us to return our own request and response objects. /// internal class UDDIWebRequest : WebRequest { WebRequest innerWebRequest; UDDIRequestStream uddiRequestStream; UDDIWebResponse uddiWebResponse; /// /// Constructor /// /// Uri to the web service we are calling /// UDDI version to use for requests public UDDIWebRequest( WebRequest innerWebRequest ) { this.innerWebRequest = innerWebRequest; } /// /// Return a UDDIRequestStream object instead of the default one. /// /// UDDIRequestStream object public override Stream GetRequestStream() { if( null == uddiRequestStream ) { uddiRequestStream = new UDDIRequestStream( innerWebRequest.GetRequestStream() ); } return uddiRequestStream; } /// /// Return a UDDIWebRequest object instead of the default one. /// /// UDDIWebResponse object public override WebResponse GetResponse() { if( null == uddiWebResponse ) { uddiWebResponse = new UDDIWebResponse( innerWebRequest.GetResponse() ); } return uddiWebResponse; } /// /// Delegates to our inner WebRequest /// public override string Method { get { return innerWebRequest.Method; } set { innerWebRequest.Method = value; } } /// /// Delegates to our inner WebRequest /// public override Uri RequestUri { get { return innerWebRequest.RequestUri; } } /// /// Delegates to our inner WebRequest /// public override string ConnectionGroupName { get { return innerWebRequest.ConnectionGroupName; } set { innerWebRequest.ConnectionGroupName = value; } } /// /// Delegates to our inner WebRequest /// public override WebHeaderCollection Headers { get { return innerWebRequest.Headers; } set { innerWebRequest.Headers = value; } } /// /// Delegates to our inner WebRequest /// public override long ContentLength { get { return innerWebRequest.ContentLength; } set { innerWebRequest.ContentLength = value; } } /// /// Delegates to our inner WebRequest /// public override string ContentType { get { return innerWebRequest.ContentType; } set { innerWebRequest.ContentType = value; } } /// /// Delegates to our inner WebRequest /// public override ICredentials Credentials { get { return innerWebRequest.Credentials; } set { innerWebRequest.Credentials = value; } } /// /// Delegates to our inner WebRequest /// public override IWebProxy Proxy { get { return innerWebRequest.Proxy; } set { innerWebRequest.Proxy = value; } } /// /// Delegates to our inner WebRequest /// public override bool PreAuthenticate { get { return innerWebRequest.PreAuthenticate; } set { innerWebRequest.PreAuthenticate = value; } } /// /// Delegates to our inner WebRequest /// public override int Timeout { get { return innerWebRequest.Timeout; } set { innerWebRequest.Timeout = value; } } /// /// Delegates to our inner WebRequest /// public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) { return innerWebRequest.BeginGetResponse( callback, state ); } /// /// Delegates to our inner WebRequest /// public override WebResponse EndGetResponse(IAsyncResult asyncResult) { return innerWebRequest.EndGetResponse( asyncResult ); } /// /// Delegates to our inner WebRequest /// public override IAsyncResult BeginGetRequestStream(AsyncCallback callback, Object state) { return innerWebRequest.BeginGetRequestStream( callback, state ); } /// /// Delegates to our inner WebRequest /// public override Stream EndGetRequestStream(IAsyncResult asyncResult) { return innerWebRequest.EndGetRequestStream( asyncResult ); } /// /// Delegates to our inner WebRequest /// public override void Abort() { innerWebRequest.Abort(); } } /// /// UDDIRequestStream allows us to manipulate the XML before we send it to the client. This class will accept all data that /// is written to it from the ASP.NET web service framework. When the framework closes the stream (ie. wants to send the data), we /// will manipulate this XML so that it has the right UDDI version, then send it out using our innerStream object. /// internal class UDDIRequestStream : MemoryStream { Stream innerStream; /// /// Constructor /// /// Should be from a WebRequest object. /// The UDD version we should use to send to the server public UDDIRequestStream( Stream innerStream ) { this.innerStream = innerStream; } /// /// Before we actually close the request stream, we want to manipulate the XML. /// public override void Close() { try { // // Rewind ourselves. // this.Position = 0; // // Read the XML that was written; this is the XML request that will be sent to the UDDI server. // StreamReader reader = new StreamReader( this ); string requestXml = reader.ReadToEnd(); // // Rewind ourselves. // this.Position = 0; #if DEBUG try { // // Validate the XML that we are about to send. // SchemaCollection.Validate( this ); } catch( XmlSchemaException schemaException ) { string message = schemaException.ToString() + "\r\n\r\nRequest XML:\r\n\r\n" + requestXml; Debug.OperatorMessage( SeverityType.Error, CategoryType.Replication, OperatorMessageType.ValidationError, message ); throw schemaException; } #endif // // Write the transformed data to the 'real' stream. // StreamUtility.WriteStringToStream( innerStream, requestXml ); } finally { // // Make sure we clean up properly. // innerStream.Close(); base.Close(); } } } /// /// Simple utility class to help us write string data to Stream objects. /// internal sealed class StreamUtility { public static void WriteStringToStream( Stream stream, string stringToWrite ) { StreamWriter writer = new StreamWriter( stream ); writer.Write( stringToWrite ); writer.Flush(); } } }