- RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
- RpcErrors.getRpcError( null, null, null, null,
- "Toaster is busy", null, null ) ) );
- return Futures.immediateFuture(result);
- }
- else if( outOfBread() ) {
- RpcResult<Void> result = Rpcs.<Void> getRpcResult(false, null, Arrays.asList(
- RpcErrors.getRpcError( null, null, null, null,
- "Toaster is out of bread", null, null ) ) );
- return Futures.immediateFuture(result);
- }
- else {
- // Notice that we are moving the actual call to another thread,
- // allowing this thread to return immediately.
- // The MD-SAL design encourages asynchronus programming. If the
- // caller needs to block until the call is
- // complete then they can leverage the blocking methods on the
- // Future interface.
- currentTask = executor.submit(new MakeToastTask(input));
+ checkStatusAndMakeToast( input, futureResult, 2 );
+
+ return futureResult;
+ }
+
+ private RpcError makeToasterOutOfBreadError() {
+ return RpcResultBuilder.newError( ErrorType.APPLICATION, "resource-denied",
+ "Toaster is out of bread", "out-of-stock", null, null );
+ }
+
+ private RpcError makeToasterInUseError() {
+ return RpcResultBuilder.newWarning( ErrorType.APPLICATION, "in-use",
+ "Toaster is busy", null, null, null );
+ }
+
+ private void checkStatusAndMakeToast( final MakeToastInput input,
+ final SettableFuture<RpcResult<Void>> futureResult,
+ final int tries ) {
+
+ // Read the ToasterStatus and, if currently Up, try to write the status to Down.
+ // If that succeeds, then we essentially have an exclusive lock and can proceed
+ // to make toast.
+
+ final ReadWriteTransaction tx = dataProvider.newReadWriteTransaction();
+ ListenableFuture<Optional<Toaster>> readFuture =
+ tx.read( LogicalDatastoreType.OPERATIONAL, TOASTER_IID );
+
+ final ListenableFuture<Void> commitFuture =
+ Futures.transform( readFuture, new AsyncFunction<Optional<Toaster>,Void>() {
+
+ @Override
+ public ListenableFuture<Void> apply(
+ final Optional<Toaster> toasterData ) throws Exception {
+
+ ToasterStatus toasterStatus = ToasterStatus.Up;
+ if( toasterData.isPresent() ) {
+ toasterStatus = toasterData.get().getToasterStatus();
+ }
+
+ LOG.debug( "Read toaster status: {}", toasterStatus );
+
+ if( toasterStatus == ToasterStatus.Up ) {
+
+ if( outOfBread() ) {
+ LOG.debug( "Toaster is out of bread" );
+
+ return Futures.immediateFailedCheckedFuture(
+ new TransactionCommitFailedException( "", makeToasterOutOfBreadError() ) );
+ }
+
+ LOG.debug( "Setting Toaster status to Down" );
+
+ // We're not currently making toast - try to update the status to Down
+ // to indicate we're going to make toast. This acts as a lock to prevent
+ // concurrent toasting.
+ tx.put( LogicalDatastoreType.OPERATIONAL, TOASTER_IID,
+ buildToaster( ToasterStatus.Down ) );
+ return tx.submit();
+ }
+
+ LOG.debug( "Oops - already making toast!" );
+
+ // Return an error since we are already making toast. This will get
+ // propagated to the commitFuture below which will interpret the null
+ // TransactionStatus in the RpcResult as an error condition.
+ return Futures.immediateFailedCheckedFuture(
+ new TransactionCommitFailedException( "", makeToasterInUseError() ) );
+ }
+ } );
+
+ Futures.addCallback( commitFuture, new FutureCallback<Void>() {
+ @Override
+ public void onSuccess( final Void result ) {
+ // OK to make toast
+ currentMakeToastTask.set( executor.submit( new MakeToastTask( input, futureResult ) ) );