/// <summary> /// Reset the negotiation to a "in negotiation" state. /// </summary> /// <param name="consumerTrustNegotiations">The the negotiations to reset.</param> internal static void Reject(ConsumerTrustNegotiationInfo[] consumerTrustNegotiations) { // An instance of the shared data model is required to use its methods. DataModel dataModel = new DataModel(); // The business logic requires the current time and the user identifier for auditing. Guid createUserId = TradingSupport.UserId; DateTime createDateTime = DateTime.UtcNow; DateTime modifiedTime = createDateTime; Guid modifiedUserId = createUserId; // This Web Method comes with an implicit transaction that is linked to its execution. DataModelTransaction dataModelTransaction = DataModelTransaction.Current; // This method can handle a batch of updates in a single transaction. foreach (ConsumerTrustNegotiationInfo consumerTrustNegotiationInfo in consumerTrustNegotiations) { List <ConsumerTrustNegotiationPaymentMethodTypeInfo> counterItems = new List <ConsumerTrustNegotiationPaymentMethodTypeInfo>(); // The blotter is not passed in from the client but is used Guid blotterId = Guid.Empty; Status negotiationStatus; TrustNegotiationInfo trustNegotiationInfo = null; // This is the next negotiation in the batch to be updated. ConsumerTrustNegotiationRow consumerTrustNegotiationRow = DataModel.ConsumerTrustNegotiation.ConsumerTrustNegotiationKey.Find(consumerTrustNegotiationInfo.ConsumerTrustNegotiationId); try { // Lock the current negotation record for reading. The data model doesn't support reader lock promotion, so the programming model is to // lock the database, collect the data, release the locks and then write. This model is especially important when iterating through a // large batch to prevent the number of locks from growing to large. consumerTrustNegotiationRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // The blotter identifier is used for access control and is not passed in by the client. blotterId = consumerTrustNegotiationRow.BlotterId; negotiationStatus = StatusMap.FromId(consumerTrustNegotiationRow.StatusId); trustNegotiationInfo = new TrustNegotiationInfo(consumerTrustNegotiationRow); // Determine whether the client has the right to modify this record. if (!TradingSupport.HasAccess(dataModelTransaction, blotterId, AccessRight.Write)) { throw new FaultException <FluidTrade.Core.SecurityFault>(new SecurityFault("You do not have write access to the selected object.")); } // The payment methods are maintained as a vector associated with the negotiation record. This will lock each of the records and read the // payment methods into a data structure so the locks don't need to be held when it is time to write foreach (var consumerTrustNegotiationOfferPaymentMethodRow in consumerTrustNegotiationRow.GetConsumerTrustNegotiationCounterPaymentMethodRows()) { try { // Temporarily lock the record containing the payment method. consumerTrustNegotiationOfferPaymentMethodRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); // This list is used to delete the payment methods that are no longer part of this negotiation. counterItems.Add( new ConsumerTrustNegotiationPaymentMethodTypeInfo( consumerTrustNegotiationOfferPaymentMethodRow.PaymentMethodTypeId, consumerTrustNegotiationOfferPaymentMethodRow.ConsumerTrustNegotiationCounterPaymentMethodId, consumerTrustNegotiationOfferPaymentMethodRow.RowVersion)); } finally { // At this point the payment method isn't needed. consumerTrustNegotiationOfferPaymentMethodRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } MatchRow matchRow = DataModel.Match.MatchKey.Find(trustNegotiationInfo.MatchId); try { matchRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); trustNegotiationInfo.MatchRowVersion = matchRow.RowVersion; trustNegotiationInfo.ContraMatchId = matchRow.ContraMatchId; } finally { matchRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } MatchRow contraMatchRow = DataModel.Match.MatchKey.Find(trustNegotiationInfo.ContraMatchId); try { contraMatchRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); trustNegotiationInfo.ContraMatchRowVersion = contraMatchRow.RowVersion; } finally { contraMatchRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } } finally { // At this point, the negotiation record isn't needed. It is critical to release the reader locks before attempting a write. consumerTrustNegotiationRow.ReleaseReaderLock(dataModelTransaction.TransactionId); } // At this point, all the data for this operation has been collected and the CRUD operations can be invoked to finish the update. Note that // the counter party information is not modified here, but is done through the Chinese wall. Guid newNegotiationId = Guid.NewGuid(); dataModel.CreateConsumerTrustNegotiation( trustNegotiationInfo.AccountBalance, trustNegotiationInfo.BlotterId, newNegotiationId, trustNegotiationInfo.CounterPaymentLength, trustNegotiationInfo.CounterPaymentStartDateLength, trustNegotiationInfo.CounterPaymentStartDateUnitId, trustNegotiationInfo.CounterSettlementUnitId, trustNegotiationInfo.CounterSettlementValue, createDateTime, createUserId, trustNegotiationInfo.CreditCardId, trustNegotiationInfo.IsRead, trustNegotiationInfo.IsReply, trustNegotiationInfo.MatchId, modifiedTime, modifiedUserId, consumerTrustNegotiationInfo.PaymentLength, consumerTrustNegotiationInfo.PaymentStartDateLength, consumerTrustNegotiationInfo.PaymentStartDateUnitId, consumerTrustNegotiationInfo.SettlementUnitId, consumerTrustNegotiationInfo.SettlementValue, StatusMap.FromCode(Status.Rejected), out trustNegotiationInfo.Version); // This will add the payment methods to the negotiation that are not already there. foreach (Guid paymentMethodTypeId in consumerTrustNegotiationInfo.PaymentMethodTypes) { dataModel.CreateConsumerTrustNegotiationOfferPaymentMethod( blotterId, newNegotiationId, Guid.NewGuid(), paymentMethodTypeId); } foreach (ConsumerTrustNegotiationPaymentMethodTypeInfo consumerTrustNegotiationPaymentMethodTypeInfo in counterItems) { dataModel.UpdateConsumerTrustNegotiationCounterPaymentMethod( blotterId, null, new Object[] { consumerTrustNegotiationPaymentMethodTypeInfo.ConsumerTrustNegotiationOfferPaymentMethodId }, newNegotiationId, consumerTrustNegotiationPaymentMethodTypeInfo.PaymentMethodInfoId, consumerTrustNegotiationPaymentMethodTypeInfo.RowVersion); } //Reset the Match Status Id. This is required so the match engine will redo the match. The //match engine does not recalculate if it is not in the initial three stages of - Valid, Partial, ValidwithFunds dataModel.UpdateMatch( null, null, null, null, null, null, null, new object[] { trustNegotiationInfo.MatchId }, trustNegotiationInfo.MatchRowVersion, StatusMap.FromCode(Status.ValidMatch), null); //Reset the Contra Match Status Id dataModel.UpdateMatch( null, null, null, null, null, null, null, new object[] { trustNegotiationInfo.ContraMatchId }, trustNegotiationInfo.ContraMatchRowVersion, StatusMap.FromCode(Status.ValidMatch), null); } }
/// <summary> /// Handler for validating Destination Order records. /// </summary> /// <param name="sender">The object that originated the event.</param> /// <param name="e">The event arguments.</param> internal static void OnDestinationOrderRowValidate(object sender, DestinationOrderRowChangeEventArgs e) { Int32 currentStatusCode; Decimal destinationOrderQuantity; DataModelTransaction dataModelTransaction; Int32 previousStatusCode; Decimal sourceOrderQuantity; WorkingOrderRow workingOrderRow; // The Business Rules will be enforced on this Destination Order. Note that it is locked at the point this handler is called. DestinationOrderRow destinationOrderRow = e.Row; // The action on the row determines which rule to evaluate. switch (destinationOrderRow.RowState) { case DataRowState.Added: // This rule will reject the operation if the Working Order is overcommitted with a destination. dataModelTransaction = DataModelTransaction.Current; // This rule will throw an exception if the quantity sent to a destination is greater than the quantity ordered. The quantity ordered and // quantity sent can only be calcuated from the owning Working Order which must be locked in order to carry out the calculations. workingOrderRow = e.Row.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); sourceOrderQuantity = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); destinationOrderQuantity = WorkingOrder.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } break; case DataRowState.Modified: // Reject the operation if the Working Order is overcommitted with a destination. Note that the order of the evaluation is important for // efficiency. There is no need to sum up the source and Destination Orders if the quantity has been reduced as there's no chance it will be // over committed. Decimal originalQuantity = (Decimal)destinationOrderRow[DataModel.DestinationOrder.OrderedQuantityColumn, DataRowVersion.Original]; Decimal currentQuantity = (Decimal)destinationOrderRow[DataModel.DestinationOrder.OrderedQuantityColumn, DataRowVersion.Current]; if (originalQuantity < currentQuantity) { // Once it's determined that the order can be overcommitted, a middle tier context is required to lock the rows so the quantities can be // aggregated. dataModelTransaction = DataModelTransaction.Current; // This rule will throw an exception if the quantity sent to a destination is greater than the quantity ordered. The quantity ordered and // quantity sent can only be calcuated from the owning Working Order which must be locked in order to carry out the calculations. workingOrderRow = e.Row.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); sourceOrderQuantity = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); destinationOrderQuantity = WorkingOrder.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } } // This rule will examine the effects of a state change of the Destination Order. The stateChangeMatrix contains delegates to methods that // will determine what, if any, stats change should be applied to the Working Order due to the change in state of this Destination Order. previousStatusCode = Convert.ToInt32(StatusMap.FromId((Guid)destinationOrderRow[DataModel.DestinationOrder.StatusIdColumn, DataRowVersion.Original])); currentStatusCode = Convert.ToInt32(StatusMap.FromId((Guid)destinationOrderRow[DataModel.DestinationOrder.StatusIdColumn, DataRowVersion.Current])); if (previousStatusCode != currentStatusCode) { DestinationOrder.statusChangeMatrix[previousStatusCode, currentStatusCode](new Object[] { destinationOrderRow.WorkingOrderId }); } break; case DataRowState.Deleted: // This rule will examine the effects of a state change of the Destination Order. The stateChangeMatrix contains delegates to methods that // will determine what, if any, stats change should be applied to the Working Order due to the change in state of this Destination Order. previousStatusCode = Convert.ToInt32(StatusMap.FromId((Guid)destinationOrderRow[DataModel.DestinationOrder.StatusIdColumn, DataRowVersion.Original])); currentStatusCode = (Int32)Status.Deleted; Guid workingOrderId = (Guid)destinationOrderRow[DataModel.DestinationOrder.WorkingOrderIdColumn, DataRowVersion.Original]; DestinationOrder.statusChangeMatrix[previousStatusCode, currentStatusCode](new Object[] { workingOrderId }); break; } }