- public Future<RpcResult<Void>> makeToast(MakeToastInput input) {
- LOG.info("makeToast: " + input);
-
- synchronized (taskLock) {
- if (currentTask != null) {
- // return an error since we are already toasting some toast.
- LOG.info( "Toaster is already making toast" );
-
- 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));
+ public ListenableFuture<RpcResult<MakeToastOutput>> makeToast(final MakeToastInput input) {
+ LOG.info("makeToast: {}", input);
+
+ final SettableFuture<RpcResult<MakeToastOutput>> futureResult = SettableFuture.create();
+
+ checkStatusAndMakeToast(input, futureResult, maxMakeToastTries);
+
+ return futureResult;
+ }
+
+ private static RpcError makeToasterOutOfBreadError() {
+ return RpcResultBuilder.newError(APPLICATION, ErrorTag.RESOURCE_DENIED, "Toaster is out of bread",
+ "out-of-stock", null, null);
+ }
+
+ private static RpcError makeToasterInUseError() {
+ return RpcResultBuilder.newWarning(APPLICATION, ErrorTag.IN_USE, "Toaster is busy", null, null, null);
+ }
+
+ private void checkStatusAndMakeToast(final MakeToastInput input,
+ final SettableFuture<RpcResult<MakeToastOutput>> 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 = dataBroker.newReadWriteTransaction();
+ FluentFuture<Optional<Toaster>> readFuture = tx.read(OPERATIONAL, TOASTER_IID);
+
+ final ListenableFuture<? extends CommitInfo> commitFuture =
+ Futures.transformAsync(readFuture, toasterData -> {
+ 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");
+ tx.cancel();
+ return Futures.immediateFailedFuture(
+ 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(OPERATIONAL, TOASTER_IID, buildToaster(ToasterStatus.Down));
+ return tx.commit();
+ }
+
+ 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.
+ tx.cancel();
+ return Futures.immediateFailedFuture(
+ new TransactionCommitFailedException("", makeToasterInUseError()));
+ }, MoreExecutors.directExecutor());
+
+ Futures.addCallback(commitFuture, new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ // OK to make toast
+ currentMakeToastTask.set(executor.submit(new MakeToastTask(input, futureResult)));