Exemple #1
0
        public async Task <IHttpActionResult> FulfillOrderWebHook(string partner,
                                                                  [FromBody] OrderFulfillmentMessage order)
        {
            // Verify that the order was sent as the request body.

            if (order == null)
            {
                var errorSet = new ErrorSet(ErrorCode.ValueIsRequired, nameof(order), "The order body is required.");

                try
                {
                    var body = await this.Request.SafeReadContentAsStringAsync();

                    this.Log
                    .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId())
                    .Information($"Response: {{Response}} { Environment.NewLine } Missing order detected for {{Controller}}::{{ActionMethod}}({{Partner}}) with Headers: [{{Headers}}] { Environment.NewLine } Body: {{RequestBody}} { Environment.NewLine } The following errors were observed: { Environment.NewLine }{{ErrorSet}}",
                                 HttpStatusCode.BadRequest,
                                 nameof(OrderSubmissionController),
                                 nameof(FulfillOrderWebHook),
                                 partner,
                                 this.Request.Headers,
                                 body,
                                 errorSet);
                }

                catch
                {
                    // Do nothing; logging is a non-critical operation that should not cause
                    // cascading failures.
                }

                return(this.BadRequest(errorSet));
            }

            // If the emulation data was provided and the caller is not priviledged, then reject the request.

            var principal = this.User as ClaimsPrincipal;

            if ((order.Emulation != null) &&
                ((principal == null) || (!principal.HasClaim(claim => claim.Type == CustomClaimTypes.MayAccessPriviledgedOperations))))
            {
                try
                {
                    this.Log
                    .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId())
                    .Warning($"Response: {{Response}} { Environment.NewLine } Unauthorized request detected for {{Controller}}::{{ActionMethod}}({{Partner}}) with Headers: [{{Headers}}] { Environment.NewLine }  The caller does not have permission to perform a priviledged operation.",
                             HttpStatusCode.Forbidden,
                             nameof(OrderSubmissionController),
                             nameof(FulfillOrderWebHook),
                             partner,
                             this.Request.Headers);
                }

                catch
                {
                    // Do nothing; logging is a non-critical operation that should not cause
                    // cascading failures.
                }

                return(this.Content <object>(HttpStatusCode.Forbidden, null));
            }

            var assets = order.LineItems
                         .SelectMany(item => item.Assets)
                         .Where(asset => asset != null)
                         .ToDictionary(asset => asset.Name, asset => this.ParseAssetLocationToUrl(asset.Location));

            // Create the command to trigger order processing and place it on the command queue

            var command = new ProcessOrder
            {
                OrderId         = order.OrderRequestHeader.OrderId,
                PartnerCode     = partner,
                Assets          = assets,
                Emulation       = order.Emulation,
                Id              = Guid.NewGuid(),
                CorrelationId   = this.Request.GetOrderFulfillmentCorrelationId(),
                OccurredTimeUtc = this.clock.GetCurrentInstant().ToDateTimeUtc(),
                CurrentUser     = principal?.Identity?.Name,
                Sequence        = 0
            };

            if (!(await this.processOrderCommandPublisher.TryPublishAsync(command)))
            {
                this.Log
                .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId())
                .Error("Unable to publish the {CommandName} for {Partner}//{Order}", nameof(ProcessOrder), partner, command.OrderId);

                return(this.ServiceUnavailable(this.CaclulateRetryAfter(this.config.ServiceUnavailableeRetryAfterInSeconds)));
            }

            // This can't be fire-and-forget, as completing the handler with the outstanding request causes an exception.

            try
            {
                await this.eventPublisher.TryPublishAsync(command.CreateNewOrderEvent <OrderReceived>());
            }

            catch (Exception ex)
            {
                this.Log
                .WithCorrelationId(this.Request.GetOrderFulfillmentCorrelationId())
                .Error(ex, "The event {EventName} for {Partner}//{Order} could not be published.  This is non-critical for fulfillment processing.", nameof(OrderReceived), partner, command.OrderId);
            }

            return(this.Accepted(new OrderFulfillmentAccepted(command.OrderId), this.CaclulateRetryAfter(this.config.OrderAcceptedRetryAfterInSeconds)));
        }
Exemple #2
0
        /// <summary>
        ///   Performs the tasks needed to handle a <see cref="ProcessOrder" /> command.
        /// </summary>
        ///
        /// <param name="processOrderMessage">The brokered message containing the command to process.</param>
        ///
        public async Task HandleProcessOrderAsync([ServiceBusTrigger(TriggerQueueNames.ProcessOrderCommandQueue)] BrokeredMessage processOrderMessage)
        {
            if (processOrderMessage == null)
            {
                this.Log.Error("The {CommandType} brokered message was null.", nameof(ProcessOrder));
                throw new ArgumentNullException(nameof(processOrderMessage));
            }

            ILogger      log     = null;
            ProcessOrder command = null;

            try
            {
                // Attempt to retrieve the command from the brokered message.

                using (var bodyStream = processOrderMessage.GetBody <Stream>())
                    using (var reader = new StreamReader(bodyStream))
                        using (var jsonReader = new JsonTextReader(reader))
                        {
                            command = this.jsonSerializer.Deserialize <ProcessOrder>(jsonReader);

                            jsonReader.Close();
                            reader.Close();
                            bodyStream.Close();
                        };

                // If the body was not a proper ProcessOrder command, an empty command will be
                // returned.  Verify that the Id and OrderId are not in their default states.

                if ((command.Id == Guid.Empty) && (command.OrderId == null))
                {
                    throw new MissingDependencyException("The command could not be extracted from the brokered message");
                }

                // Process the order identified by the command.

                var correlationId = command.CorrelationId ?? processOrderMessage.CorrelationId;

                log = this.Log.WithCorrelationId(correlationId);
                log.Information("A {CommandType} command was received and is being handled.  {Command}", nameof(ProcessOrder), command);

                var result = await this.orderProcessor.ProcessOrderAsync(command.PartnerCode, command.OrderId, (IReadOnlyDictionary <string, string>) command.Assets, command.Emulation, correlationId);

                // Consider the result to complete handling.  If processing was successful, then the message should be completed to
                // ensure that it is not retried.

                if (result?.Outcome == Outcome.Success)
                {
                    await this.submitOrderForProductionPublisher.PublishAsync(command.CreateNewOrderCommand <SubmitOrderForProduction>(cmd => cmd.Emulation = command.Emulation));

                    await this.CompleteMessageAsync(processOrderMessage);

                    this.eventPublisher.TryPublishAsync(command.CreateNewOrderEvent <OrderProcessed>()).FireAndForget();
                    log.Information("A {CommandType} command was successfully handled.  The order was staged for submission with the key {CreateOrderMessageKey}.", nameof(ProcessOrder), result?.Payload);
                }
                else
                {
                    log.Warning("A {CommandType} command was successfully handled.  The order procesing was not successful, however.", nameof(ProcessOrder));

                    // Attempt to schedule the command for a retry using a backoff policy.  If scheduled, complete the current message so that it is removed
                    // from the queue.  Otherwise, throw to expose the failure to the WebJob infrastructure so that the command will be moved to the dead letter
                    // queue after retries are exhausted.

                    if (await this.ScheduleCommandForRetryIfEligibleAsync(command, this.retryThresholds, this.rng, this.clock, this.processOrderPublisher))
                    {
                        await this.CompleteMessageAsync(processOrderMessage);
                    }
                    else
                    {
                        await this.notifyOfFatalFailurePublisher.TryPublishAsync(command.CreateNewOrderCommand <NotifyOfFatalFailure>());

                        throw new FailedtoHandleCommandException();
                    }
                }
            }

            catch (Exception ex) when(!(ex is FailedtoHandleCommandException))
            {
                (log ?? this.Log).Error(ex, "An exception occurred while handling the {CommandType} comand", nameof(ProcessOrder));

                var failedEvent = (command?.CreateNewOrderEvent <OrderProcessingFailed>() ?? new OrderProcessingFailed
                {
                    Id = Guid.NewGuid(),
                    CorrelationId = processOrderMessage?.CorrelationId,
                    OccurredTimeUtc = this.clock.GetCurrentInstant().ToDateTimeUtc()
                });

                this.eventPublisher.TryPublishAsync(failedEvent).FireAndForget();

                // Attempt to schedule the command for a retry using a backoff policy.  If scheduled, complete the current message so that it is removed
                // from the queue.  Otherwise, throw to expose the failure to the WebJob infrastructure so that the command will be moved to the dead letter
                // queue after retries are exhausted.

                if (await this.ScheduleCommandForRetryIfEligibleAsync(command, this.retryThresholds, this.rng, this.clock, this.processOrderPublisher))
                {
                    await this.CompleteMessageAsync(processOrderMessage);
                }
                else
                {
                    await this.notifyOfFatalFailurePublisher.TryPublishAsync(command.CreateNewOrderCommand <NotifyOfFatalFailure>());

                    throw;
                };
            }
        }