+
+ // Always return success from the cancel toast call.
+ return Futures.immediateFuture( RpcResultBuilder.<Void> success().build() );
+ }
+
+ /**
+ * RPC call implemented from the ToasterService interface that attempts to make toast.
+ */
+ @Override
+ public Future<RpcResult<Void>> makeToast(final MakeToastInput input) {
+ LOG.info("makeToast: " + input);
+
+ final SettableFuture<RpcResult<Void>> futureResult = SettableFuture.create();
+
+ 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 ) ) );
+ }
+
+ @Override
+ public void onFailure( final Throwable ex ) {
+ if( ex instanceof OptimisticLockFailedException ) {
+
+ // Another thread is likely trying to make toast simultaneously and updated the
+ // status before us. Try reading the status again - if another make toast is
+ // now in progress, we should get ToasterStatus.Down and fail.
+
+ if( ( tries - 1 ) > 0 ) {
+ LOG.debug( "Got OptimisticLockFailedException - trying again" );
+
+ checkStatusAndMakeToast( input, futureResult, tries - 1 );
+ }
+ else {
+ futureResult.set( RpcResultBuilder.<Void> failed()
+ .withError( ErrorType.APPLICATION, ex.getMessage() ).build() );
+ }
+
+ } else {
+
+ LOG.debug( "Failed to commit Toaster status", ex );
+
+ // Probably already making toast.
+ futureResult.set( RpcResultBuilder.<Void> failed()
+ .withRpcErrors( ((TransactionCommitFailedException)ex).getErrorList() )
+ .build() );
+ }
+ }
+ } );