Bug 1112: Update toaster to use async best practices
[controller.git] / opendaylight / md-sal / samples / toaster-consumer / src / main / java / org / opendaylight / controller / sample / kitchen / impl / KitchenServiceImpl.java
index 911a8c87d7166edc38c7faa2597678c8f55c611a..50ae8fd04fcce09c66a9eb7dd512af433df058d7 100644 (file)
@@ -1,10 +1,18 @@
 package org.opendaylight.controller.sample.kitchen.impl;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
-
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import org.opendaylight.controller.config.yang.config.kitchen_service.impl.KitchenServiceRuntimeMXBean;
+import org.opendaylight.controller.sal.common.util.RpcErrors;
+import org.opendaylight.controller.sal.common.util.Rpcs;
 import org.opendaylight.controller.sample.kitchen.api.EggsType;
 import org.opendaylight.controller.sample.kitchen.api.KitchenService;
+import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInput;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.MakeToastInputBuilder;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToastType;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterListener;
@@ -12,16 +20,31 @@ import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterRestocked;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.ToasterService;
 import org.opendaylight.yang.gen.v1.http.netconfcentral.org.ns.toaster.rev091120.WheatBread;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
+import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.JdkFutureAdapters;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+
 public class KitchenServiceImpl implements KitchenService, KitchenServiceRuntimeMXBean, ToasterListener {
 
     private static final Logger log = LoggerFactory.getLogger( KitchenServiceImpl.class );
 
     private final ToasterService toaster;
 
+    private final ListeningExecutorService executor =
+                                   MoreExecutors.listeningDecorator( Executors.newCachedThreadPool() );
+
     private volatile boolean toasterOutOfBread;
 
     public KitchenServiceImpl(ToasterService toaster) {
@@ -29,39 +52,102 @@ public class KitchenServiceImpl implements KitchenService, KitchenServiceRuntime
     }
 
     @Override
-    public boolean makeBreakfast( EggsType eggs, Class<? extends ToastType> toast, int toastDoneness ) {
+    public Future<RpcResult<Void>> makeBreakfast( EggsType eggsType, Class<? extends ToastType> toastType,
+                                                  int toastDoneness ) {
+
+        // Call makeToast and use JdkFutureAdapters to convert the Future to a ListenableFuture,
+        // The OpendaylightToaster impl already returns a ListenableFuture so the conversion is
+        // actually a no-op.
+
+        ListenableFuture<RpcResult<Void>> makeToastFuture = JdkFutureAdapters.listenInPoolThread(
+                makeToast( toastType, toastDoneness ), executor );
+
+        ListenableFuture<RpcResult<Void>> makeEggsFuture = makeEggs( eggsType );
+
+        // Combine the 2 ListenableFutures into 1 containing a list of RpcResults.
+
+        ListenableFuture<List<RpcResult<Void>>> combinedFutures =
+                Futures.allAsList( ImmutableList.of( makeToastFuture, makeEggsFuture ) );
+
+        // Then transform the RpcResults into 1.
+
+        return Futures.transform( combinedFutures,
+            new AsyncFunction<List<RpcResult<Void>>,RpcResult<Void>>() {
+                @Override
+                public ListenableFuture<RpcResult<Void>> apply( List<RpcResult<Void>> results )
+                                                                                 throws Exception {
+                    boolean atLeastOneSucceeded = false;
+                    Builder<RpcError> errorList = ImmutableList.builder();
+                    for( RpcResult<Void> result: results ) {
+                        if( result.isSuccessful() ) {
+                            atLeastOneSucceeded = true;
+                        }
+
+                        if( result.getErrors() != null ) {
+                            errorList.addAll( result.getErrors() );
+                        }
+                    }
+
+                    return Futures.immediateFuture(
+                              Rpcs.<Void> getRpcResult( atLeastOneSucceeded, errorList.build() ) );
+                }
+        } );
+    }
+
+    private ListenableFuture<RpcResult<Void>> makeEggs( EggsType eggsType ) {
+
+        return executor.submit( new Callable<RpcResult<Void>>() {
+
+            @Override
+            public RpcResult<Void> call() throws Exception {
+
+                // We don't actually do anything here - just return a successful result.
+                return Rpcs.<Void> getRpcResult( true, Collections.<RpcError>emptyList() );
+            }
+        } );
+    }
+
+    private Future<RpcResult<Void>> makeToast( Class<? extends ToastType> toastType,
+                                               int toastDoneness ) {
 
         if( toasterOutOfBread )
         {
             log.info( "We're out of toast but we can make eggs" );
-            return true;
+            return Futures.immediateFuture( Rpcs.<Void> getRpcResult( true,
+                       Arrays.asList( RpcErrors.getRpcError( "", "partial-operation", null,
+                                          ErrorSeverity.WARNING,
+                                          "Toaster is out of bread but we can make you eggs",
+                                          ErrorType.APPLICATION, null ) ) ) );
         }
 
         // Access the ToasterService to make the toast.
-        // We don't actually make the eggs for this example - sorry.
-        MakeToastInputBuilder toastInput = new MakeToastInputBuilder();
-        toastInput.setToasterDoneness( (long) toastDoneness);
-        toastInput.setToasterToastType( toast );
 
-        try {
-            RpcResult<Void> result = toaster.makeToast( toastInput.build() ).get();
+        MakeToastInput toastInput = new MakeToastInputBuilder()
+            .setToasterDoneness( (long) toastDoneness )
+            .setToasterToastType( toastType )
+            .build();
+
+        return toaster.makeToast( toastInput );
+    }
 
+    @Override
+    public Boolean makeScrambledWithWheat() {
+        try {
+            // This call has to block since we must return a result to the JMX client.
+            RpcResult<Void> result = makeBreakfast( EggsType.SCRAMBLED, WheatBread.class, 2 ).get();
             if( result.isSuccessful() ) {
-                log.info( "makeToast succeeded" );
+                log.info( "makeBreakfast succeeded" );
             } else {
-                log.warn( "makeToast failed: " + result.getErrors() );
+                log.warn( "makeBreakfast failed: " + result.getErrors() );
             }
 
             return result.isSuccessful();
+
         } catch( InterruptedException | ExecutionException e ) {
-            log.warn( "Error occurred during toast creation" );
+            log.warn( "An error occurred while maing breakfast: " + e );
         }
-        return false;
-    }
 
-    @Override
-    public Boolean makeScrambledWithWheat() {
-        return makeBreakfast( EggsType.SCRAMBLED, WheatBread.class, 2 );
+        return Boolean.FALSE;
     }
 
     /**