public void ExtractData(INotifier notifier, String soapBody, String mode) { XmlTextReader xtr = null; XmlDocument doc = null; XmlNode node = null; if (soapBody != String.Empty) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Parsing notification SOAP: " + soapBody); } xtr = new XmlTextReader(new System.IO.StringReader(soapBody)); doc = new XmlDocument(); node = doc.ReadNode(xtr); while (xtr.Read()) { if (xtr.IsStartElement()) { // Get element name switch (xtr.Name) { //extract session id case "SessionId": if (xtr.Read()) { this.SessionID = xtr.Value.Trim(); if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("SessionId: " + this.SessionID); } } break; //extract session url case "PartnerUrl": if (xtr.Read()) { this.SessionURL = xtr.Value.Trim(); if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("SessionURL: " + this.SessionURL); } } break; //extract object's name case "sObject": if (xtr["xsi:type"] != null) { string sObjectName = xtr["xsi:type"]; if (sObjectName != null) { this.ObjectName = sObjectName.Split(new char[] { ':' })[1]; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("ObjectName: " + this.ObjectName); } } } break; //extract notification id [note: salesforce can send a notification multiple times. it is, therefore, a good idea to keep track of this id.] case "Id": if (xtr.Read()) { this.NotificationIDs.Add(xtr.Value.Trim()); } break; //extract record id case "sf:Id": if (xtr.Read()) { this.ObjectIDs.Add(xtr.Value.Trim()); if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("ObjectId: " + xtr.Value.Trim()); } } break; } } } } else { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Notification has no data to parse."); } } }
protected override System.Threading.Tasks.Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { WorkflowRuleNotification receivedNotification = null; IAuthenticatedWho authenticatedWho = null; INotifier emailNotifier = null; Guid tenantGuid = Guid.Empty; String tenantId = null; String mode = null; String email = null; try { // Get the mode from the request uri mode = BaseHttpUtils.GetModeFromQuery(request.RequestUri); // Get any provided notification email email = BaseHttpUtils.GetEmailFromQuery(request.RequestUri); // Check to make sure the incoming request has enough segments // Deployed we have an extra segment for the deployment sub-directory, hence this is one more than you would have thought if (request.RequestUri.Segments.Length < 9) { throw new ArgumentNullException("Request.Segments", "The incoming request is not a valid Url for outbound messages."); } // Get the segments from the call so we know which tenant we're executing against tenantId = request.RequestUri.Segments[8].Replace("/", ""); // Check to make sure we've received valid guids if (Guid.TryParse(tenantId, out tenantGuid) == false) { throw new ArgumentNullException("Request.Segments", "The incoming request does not contain a valid tenant identifier. You provided: " + tenantId); } // Create a basic authenticated who for the notifier authenticatedWho = AuthenticationUtils.CreatePublicUser(tenantId); authenticatedWho.Email = email; // Create the notifier emailNotifier = EmailNotifier.GetInstance(tenantId, authenticatedWho, null, "WorkflowRuleListenerMessageHandler"); // ExtractData would populate notification class' variables, which can be used to get desired data. receivedNotification = new WorkflowRuleNotification(); receivedNotification.ExtractData(emailNotifier, request.Content.ReadAsStringAsync().Result, mode); // Now send ManyWho the notification that something has changed on a set of records, but only if ManyWho actually cares about them this.Execute(emailNotifier, tenantId, mode, receivedNotification); // Send the debug log if the user is running in debug mode if (SettingUtils.IsDebugging(mode)) { ErrorUtils.SendAlert(emailNotifier, null, ErrorUtils.ALERT_TYPE_WARNING, "Debug Log Entries"); } // Send a response back to SFDC // Note: since we are not calling base class' SendAsync function, the request will return from here, and will not reach our POST function. return(Task.FromResult(receivedNotification.PrepareResponse(request))); } catch (Exception exception) { // Send the debug log if the user is running in debug mode if (SettingUtils.IsDebugging(mode)) { ErrorUtils.SendAlert(emailNotifier, null, ErrorUtils.ALERT_TYPE_WARNING, BaseHttpUtils.GetExceptionMessage(exception)); } throw BaseHttpUtils.GetWebException(HttpStatusCode.BadRequest, BaseHttpUtils.GetExceptionMessage(exception)); } }
protected override System.Threading.Tasks.Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { WorkflowRuleNotification receivedNotification = null; IAuthenticatedWho authenticatedWho = null; INotifier emailNotifier = null; Guid tenantGuid = Guid.Empty; Guid flowGuid = Guid.Empty; String tenantId = null; String flowId = null; String player = null; String mode = null; String email = null; String reportingMode = null; try { // Get the mode from the request uri mode = BaseHttpUtils.GetModeFromQuery(request.RequestUri); // Get the reporting mode from the request uri reportingMode = BaseHttpUtils.GetReportingModeFromQuery(request.RequestUri); // Get any provided notification email email = BaseHttpUtils.GetEmailFromQuery(request.RequestUri); // Check to make sure the incoming request has enough segments if (request.RequestUri.Segments.Length < 9) { throw new ArgumentNullException("Request.Segments", "The incoming request is not a valid Url for outbound messages."); } // Get the segments from the call so we know which tenant we're executing against tenantId = request.RequestUri.Segments[7].Replace("/", ""); flowId = request.RequestUri.Segments[8].Replace("/", ""); player = request.RequestUri.Segments[9]; // Check to make sure we've received valid guids if (Guid.TryParse(tenantId, out tenantGuid) == false) { throw new ArgumentNullException("Request.Segments", "The incoming request does not contain a valid tenant identifier."); } if (Guid.TryParse(flowId, out flowGuid) == false) { throw new ArgumentNullException("Request.Segments", "The incoming request does not contain a valid flow identifier."); } // If a player has not been provided, we make it the default player if (String.IsNullOrWhiteSpace(player) == true) { player = "default"; } // Create a basic authenticated who for the notifier authenticatedWho = AuthenticationUtils.CreatePublicUser(tenantId); authenticatedWho.Email = email; // Create the notifier emailNotifier = EmailNotifier.GetInstance(tenantId, authenticatedWho, null, "WorkflowRuleMessageHandler"); //ExtractData would populate notification class' variables, which can be used to get desired data. receivedNotification = new WorkflowRuleNotification(); receivedNotification.ExtractData(emailNotifier, request.Content.ReadAsStringAsync().Result, mode); if (SettingUtils.IsDebugging(mode)) { emailNotifier.AddLogEntry("Mode: " + mode); } if (SettingUtils.IsDebugging(mode)) { emailNotifier.AddLogEntry("Email: " + email); } if (SettingUtils.IsDebugging(mode)) { emailNotifier.AddLogEntry("Reporting Mode: " + reportingMode); } // Execute the notifications against ManyWho this.Execute(emailNotifier, tenantId, flowId, player, mode, reportingMode, receivedNotification); // Send the debug log if the user is running in debug mode if (SettingUtils.IsDebugging(mode)) { ErrorUtils.SendAlert(emailNotifier, null, ErrorUtils.ALERT_TYPE_WARNING, "Debug Log Entries"); } //Send a response back to SFDC //Note: since we are not calling base class' SendAsync function, the request will return from here, and will not reach our POST function. return(Task.FromResult(receivedNotification.PrepareResponse(request))); } catch (Exception exception) { // Send the debug log if the user is running in debug mode if (SettingUtils.IsDebugging(mode)) { ErrorUtils.SendAlert(emailNotifier, null, ErrorUtils.ALERT_TYPE_WARNING, BaseHttpUtils.GetExceptionMessage(exception)); } throw BaseHttpUtils.GetWebException(HttpStatusCode.BadRequest, BaseHttpUtils.GetExceptionMessage(exception)); } }
private void Execute(INotifier notifier, String tenantId, String mode, WorkflowRuleNotification workflowRuleNotification) { Dictionary <String, ListenerServiceRequestAPI> salesforceListenerEntries = null; ListenerServiceResponseAPI listenerServiceResponse = null; ListenerServiceRequestAPI listenerServiceRequest = null; SforceService sforceService = null; String authenticationStrategy = null; String authenticationUrl = null; String securityToken = null; String invokeType = null; String username = null; String password = null; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Executing listener notification."); } // Go through each object identifier in the notification if (workflowRuleNotification.ObjectIDs != null && workflowRuleNotification.ObjectIDs.Count > 0) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Workflow event has object identifiers."); } foreach (String objectId in workflowRuleNotification.ObjectIDs) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Processing object identifier: " + objectId); } // Check to see if ManyWho has asked us to listen to any of them salesforceListenerEntries = SalesforceListenerSingleton.GetInstance().GetListenerRequests(tenantId, objectId); // Check to see if we're actually listening if (salesforceListenerEntries != null && salesforceListenerEntries.Count > 0) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Object has listener entries."); } // If it has, send a listen notification back to the workflow engine - one by one at the moment (no bulk!) foreach (KeyValuePair <String, ListenerServiceRequestAPI> pair in salesforceListenerEntries) { // Get the listener request out listenerServiceRequest = pair.Value; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Executing listener entry."); } // Create the service response listenerServiceResponse = new ListenerServiceResponseAPI(); listenerServiceResponse.annotations = listenerServiceRequest.annotations; listenerServiceResponse.culture = listenerServiceRequest.culture; listenerServiceResponse.tenantId = listenerServiceRequest.tenantId; listenerServiceResponse.token = listenerServiceRequest.token; // Apply the settings for the response from the request so the engine has the full details about the object listenerServiceResponse.listeningEventValue = listenerServiceRequest.valueForListening; listenerServiceResponse.listeningEventValue.contentType = listenerServiceRequest.valueForListening.contentType; listenerServiceResponse.listeningEventValue.contentValue = listenerServiceRequest.valueForListening.contentValue; listenerServiceResponse.listeningEventValue.developerName = listenerServiceRequest.valueForListening.developerName; listenerServiceResponse.listeningEventValue.typeElementDeveloperName = listenerServiceRequest.valueForListening.typeElementDeveloperName; listenerServiceResponse.listeningEventValue.typeElementId = listenerServiceRequest.valueForListening.typeElementId; listenerServiceResponse.listeningEventValue.typeElementPropertyDeveloperName = listenerServiceRequest.valueForListening.typeElementPropertyDeveloperName; listenerServiceResponse.listeningEventValue.typeElementPropertyId = listenerServiceRequest.valueForListening.typeElementPropertyId; listenerServiceResponse.listeningEventValue.valueElementId = listenerServiceRequest.valueForListening.valueElementId; // Get the configuration values out that are needed to check the voting status // TODO: we should smart cache the login info and connection authenticationUrl = ValueUtils.GetContentValue(SalesforceServiceSingleton.SERVICE_VALUE_AUTHENTICATION_URL, listenerServiceRequest.configurationValues, true); username = ValueUtils.GetContentValue(SalesforceServiceSingleton.SERVICE_VALUE_USERNAME, listenerServiceRequest.configurationValues, true); password = ValueUtils.GetContentValue(SalesforceServiceSingleton.SERVICE_VALUE_PASSWORD, listenerServiceRequest.configurationValues, true); securityToken = ValueUtils.GetContentValue(SalesforceServiceSingleton.SERVICE_VALUE_SECURITY_TOKEN, listenerServiceRequest.configurationValues, false); authenticationStrategy = ValueUtils.GetContentValue(SalesforceServiceSingleton.SERVICE_VALUE_AUTHENTICATION_STRATEGY, listenerServiceRequest.configurationValues, false); if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Logging into salesforce using: " + username); } if (String.IsNullOrWhiteSpace(authenticationStrategy) == true || authenticationStrategy.Equals(SalesforceServiceSingleton.AUTHENTICATION_STRATEGY_STANDARD, StringComparison.OrdinalIgnoreCase) == true || authenticationStrategy.Equals(SalesforceServiceSingleton.AUTHENTICATION_STRATEGY_ACTIVE_USER, StringComparison.OrdinalIgnoreCase) == true) { sforceService = SalesforceDataSingleton.GetInstance().LogUserInBasedOnSession(listenerServiceRequest.configurationValues, workflowRuleNotification.SessionID, workflowRuleNotification.SessionURL); } else if (authenticationStrategy.Equals(SalesforceServiceSingleton.AUTHENTICATION_STRATEGY_SUPER_USER, StringComparison.OrdinalIgnoreCase) == true) { // Login to salesforce using the details in the service request sforceService = SalesforceDataSingleton.GetInstance().LoginUsingCredentials(authenticationUrl, username, password, securityToken); } else { throw new ArgumentNullException("ConfigurationValues", String.Format("The provided authentication strategy is not supported: '{0}'", authenticationStrategy)); } if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Getting full sObject for: " + objectId); } // Load the latest object from salesforce so we have the data to send back listenerServiceResponse.listeningEventValue.objectData = SalesforceDataSingleton.GetInstance().LoadSObjectByIdentifier(sforceService, workflowRuleNotification.ObjectName, objectId, true); try { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Executing event against ManyWho"); } // Dispatch a listen response to the engine as an event has occurred invokeType = RunSingleton.GetInstance().Event(notifier, null, tenantId, listenerServiceRequest.callbackUri, listenerServiceResponse); } catch (Exception exception) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Event execution failed with: " + BaseHttpUtils.GetExceptionMessage(exception)); } // Something went wrong - but we ignore it for now invokeType = ManyWhoConstants.INVOKE_TYPE_FORWARD; } if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Service returned invoke type of: " + invokeType); } // If the engine returns nothing, errors or returns an response other than a "WAIT", we delete the listener for now // TODO: make this a bit more intelligent so we can handle things like retry if (String.IsNullOrWhiteSpace(invokeType) == false && invokeType.IndexOf(ManyWhoConstants.INVOKE_TYPE_WAIT, StringComparison.InvariantCultureIgnoreCase) < 0) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Removing entry from listeners for invoke type: " + invokeType); } SalesforceListenerSingleton.GetInstance().UnregisterListener(tenantId, objectId, listenerServiceRequest); } } } } } }
public void Execute(INotifier notifier, String tenantId, String flowId, String player, String mode, String reportingMode, WorkflowRuleNotification workflowRuleNotification) { FlowResponseAPI flowResponse = null; EngineInvokeRequestAPI engineInvokeRequest = null; EngineInvokeResponseAPI engineInvokeResponse = null; EngineInitializationRequestAPI engineInitializationRequest = null; EngineInitializationResponseAPI engineInitializationResponse = null; AuthenticationCredentialsAPI authenticationCredentials = null; String authenticationToken = null; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Executing notification."); } // Check to see if we have object identifiers to process if (workflowRuleNotification.ObjectIDs != null && workflowRuleNotification.ObjectIDs.Count > 0) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Notification has object identifiers."); } if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry(String.Format("Loading flow for tenant ({0}) and identifier ({1}).", tenantId, flowId)); } // Now we have the data from the message, we can execute the workflow // Load the flow by the unique identifier flowResponse = RunSingleton.GetInstance().LoadFlowById(notifier, authenticationToken, tenantId, flowId); // Check to make sure we have a flow response if (flowResponse == null) { throw new ArgumentNullException("FlowResponse", "The flow is null for the provided tenant and flow identifier."); } foreach (String objectID in workflowRuleNotification.ObjectIDs) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Sending initialization request to ManyWho."); } // Create an engine initialization request to kick off the flow engineInitializationRequest = new EngineInitializationRequestAPI(); engineInitializationRequest.flowId = new FlowIdAPI(); engineInitializationRequest.flowId.id = flowResponse.id.id; engineInitializationRequest.flowId.versionId = flowResponse.id.versionId; engineInitializationRequest.mode = mode; engineInitializationRequest.reportingMode = reportingMode; // If we're not using the default player, change the urls if (player != "default") { engineInitializationRequest.joinPlayerUrl = "https://flow.manywho.com/" + tenantId + "/play/" + player; engineInitializationRequest.playerUrl = "https://flow.manywho.com/" + tenantId + "/play/" + player; } // Initialize the workflow with the values provided engineInitializationRequest.inputs = new List <EngineValueAPI>(); engineInitializationRequest.inputs.Add(new EngineValueAPI() { developerName = "SalesforceNotificationRecordId", contentValue = objectID, contentType = ManyWhoConstants.CONTENT_TYPE_STRING }); engineInitializationRequest.inputs.Add(new EngineValueAPI() { developerName = "SalesforceNotificationObjectName", contentValue = workflowRuleNotification.ObjectName, contentType = ManyWhoConstants.CONTENT_TYPE_STRING }); if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("SalesforceNotificationRecordId: " + objectID); } if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("SalesforceNotificationObjectName: " + workflowRuleNotification.ObjectName); } // Initialize the engine with the bare basics engineInitializationResponse = RunSingleton.GetInstance().Initialize(notifier, authenticationToken, tenantId, engineInitializationRequest); // Check to see if the workflow is authorized to execute - if not, we need to login using the session if (engineInitializationResponse.statusCode.Equals(ManyWhoConstants.AUTHORIZATION_STATUS_NOT_AUTHORIZED, StringComparison.OrdinalIgnoreCase) == true) { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Event not authorized, attempting a login using session info."); } if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("SessionId: " + workflowRuleNotification.SessionID); } if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("SessionURL: " + workflowRuleNotification.SessionURL); } // Create the authentication credentials for the service authenticationCredentials = new AuthenticationCredentialsAPI(); authenticationCredentials.loginUrl = engineInitializationResponse.authorizationContext.loginUrl; authenticationCredentials.sessionToken = workflowRuleNotification.SessionID; authenticationCredentials.sessionUrl = workflowRuleNotification.SessionURL; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Logging into service again using session info."); } // Login to the system authenticationToken = RunSingleton.GetInstance().Login(notifier, tenantId, engineInitializationResponse.stateId, authenticationCredentials); // Apply the state back engineInitializationRequest.stateId = engineInitializationResponse.stateId; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Initializing engine again for state identifier: " + engineInitializationResponse.stateId); } // Initialize the engine again - re-using the state identifier engineInitializationResponse = RunSingleton.GetInstance().Initialize(notifier, authenticationToken, tenantId, engineInitializationRequest); } // Now create the fist engine invoke request so we can get the content of the first ivr engineInvokeRequest = new EngineInvokeRequestAPI(); engineInvokeRequest.currentMapElementId = engineInitializationResponse.currentMapElementId; engineInvokeRequest.invokeType = ManyWhoConstants.INVOKE_TYPE_FORWARD; engineInvokeRequest.mapElementInvokeRequest = new MapElementInvokeRequestAPI(); engineInvokeRequest.currentMapElementId = engineInitializationResponse.currentMapElementId; engineInvokeRequest.stateId = engineInitializationResponse.stateId; engineInvokeRequest.stateToken = engineInitializationResponse.stateToken; engineInvokeRequest.mode = mode; if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Sending invoke request to ManyWho."); } // Invoke the engine with the first request engineInvokeResponse = RunSingleton.GetInstance().Execute(notifier, authenticationToken, tenantId, engineInvokeRequest); // If we're running in step through mode, we notify the author with the join identifier so they can debug the workflow if (SettingUtils.IsDebugging(engineInvokeRequest.mode) == true && engineInvokeRequest.mode.Equals(ManyWhoConstants.MODE_DEBUG_STEPTHROUGH, StringComparison.OrdinalIgnoreCase) == true) { notifier.AddLogEntry("Flow is waiting to be joined in order to be debugged."); notifier.AddLogEntry("JoinUrl: " + engineInvokeResponse.joinFlowUri + "&mode=" + engineInvokeRequest.mode); } } } else { if (SettingUtils.IsDebugging(mode)) { notifier.AddLogEntry("Notification does not have object identifiers."); } } }