JCLOUDS-624 - Fixed bug in ListNodes

To fix this bug, I used the approach debated in the above issue: create
new methods to do the operation using an ExecutorService provided by the
user.The the old methods are still working, but now the operations in
those methods are not concurrent anymore.
diff --git a/core/src/main/java/org/jclouds/chef/ChefService.java b/core/src/main/java/org/jclouds/chef/ChefService.java
index bae2e1b..040107a 100644
--- a/core/src/main/java/org/jclouds/chef/ChefService.java
+++ b/core/src/main/java/org/jclouds/chef/ChefService.java
@@ -16,10 +16,8 @@
  */
 package org.jclouds.chef;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
+import com.google.common.io.InputSupplier;
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.BootstrapConfig;
 import org.jclouds.chef.domain.Client;
 import org.jclouds.chef.domain.CookbookVersion;
@@ -30,8 +28,10 @@
 import org.jclouds.rest.annotations.SinceApiVersion;
 import org.jclouds.scriptbuilder.domain.Statement;
 
-import com.google.common.io.InputSupplier;
-import com.google.inject.ImplementedBy;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
 
 /**
  * Provides high level Chef operations.
@@ -41,7 +41,7 @@
 
    /**
     * Gets the context that created this service.
-    * 
+    *
     * @return The context that created the service.
     */
    ChefContext getContext();
@@ -50,7 +50,7 @@
 
    /**
     * Encrypts the given input stream.
-    * 
+    *
     * @param supplier The input stream to encrypt.
     * @return The encrypted bytes for the given input stream.
     * @throws IOException If there is an error reading from the input stream.
@@ -59,7 +59,7 @@
 
    /**
     * Decrypts the given input stream.
-    * 
+    *
     * @param supplier The input stream to decrypt.
     * @return The decrypted bytes for the given input stream.
     * @throws IOException If there is an error reading from the input stream.
@@ -70,27 +70,27 @@
 
    /**
     * Creates all steps necessary to bootstrap the node.
-    * 
+    *
     * @param group corresponds to a configured
-    *        {@link ChefProperties#CHEF_BOOTSTRAP_DATABAG} data bag where
-    *        run_list and other information are stored.
+    *              {@link ChefProperties#CHEF_BOOTSTRAP_DATABAG} data bag where
+    *              run_list and other information are stored.
     * @return The script used to bootstrap the node.
     */
    Statement createBootstrapScriptForGroup(String group);
 
    /**
     * Configures how the nodes of a certain group will be bootstrapped
-    * 
-    * @param group The group where the given bootstrap configuration will be
-    *        applied.
+    *
+    * @param group           The group where the given bootstrap configuration will be
+    *                        applied.
     * @param bootstrapConfig The configuration to be applied to the nodes in the
-    *        group.
+    *                        group.
     */
    void updateBootstrapConfigForGroup(String group, BootstrapConfig bootstrapConfig);
 
    /**
     * Gets the run list for the given group.
-    * 
+    *
     * @param The group to get the configured run list for.
     * @return run list for all nodes bootstrapped with a certain group
     */
@@ -98,10 +98,10 @@
 
    /**
     * Gets the bootstrap configuration for a given group.
-    * <p>
+    * <p/>
     * The bootstrap configuration is a Json object containing the run list and
     * the configured attributes.
-    * 
+    *
     * @param group The name of the group.
     * @return The bootstrap configuration for the given group.
     */
@@ -111,9 +111,9 @@
 
    /**
     * Creates a new node and populates the automatic attributes.
-    * 
+    *
     * @param nodeName The name of the node to create.
-    * @param runList The run list for the created node.
+    * @param runList  The run list for the created node.
     * @return The created node with the automatic attributes populated.
     * @see OhaiModule
     * @see ChefUtils#ohaiAutomaticAttributeBinder(com.google.inject.Binder)
@@ -122,7 +122,7 @@
 
    /**
     * Updates and populate the automatic attributes of the given node.
-    * 
+    *
     * @param nodeName The node to update.
     */
    void updateAutomaticAttributesOnNode(String nodeName);
@@ -130,37 +130,44 @@
    /**
     * Removes the nodes and clients that have been inactive for a given amount of
     * time.
-    * 
-    * @param prefix The prefix for the nodes and clients to delete.
+    *
+    * @param prefix       The prefix for the nodes and clients to delete.
     * @param secondsStale The seconds of inactivity to consider a node and
-    *        client obsolete.
+    *                     client obsolete.
     */
    void cleanupStaleNodesAndClients(String prefix, int secondsStale);
 
    /**
     * Deletes the given nodes.
-    * 
+    *
     * @param names The names of the nodes to delete.
     */
    void deleteAllNodesInList(Iterable<String> names);
 
    /**
     * Deletes the given clients.
-    * 
+    *
     * @param names The names of the client to delete.
     */
    void deleteAllClientsInList(Iterable<String> names);
 
    /**
     * Lists the details of all existing nodes.
-    * 
+    *
     * @return The details of all existing nodes.
     */
    Iterable<? extends Node> listNodes();
 
    /**
+    * Lists the details of all existing nodes, executing concurrently using the executorService.
+    *
+    * @return The details of all existing nodes.
+    */
+   Iterable<? extends Node> listNodes(ExecutorService executorService);
+
+   /**
     * Lists the details of all existing nodes in the given environment.
-    * 
+    *
     * @param environmentName The name fo the environment.
     * @return The details of all existing nodes in the given environment.
     */
@@ -168,20 +175,44 @@
    Iterable<? extends Node> listNodesInEnvironment(String environmentName);
 
    /**
+    * Lists the details of all existing nodes in the given environment, using the ExecutorService to paralleling the execution.
+    *
+    * @param executorService The thread pool used in this operation
+    * @param environmentName The name fo the environment.
+    * @return The details of all existing nodes in the given environment.
+    */
+   @SinceApiVersion("0.10.0")
+   Iterable<? extends Node> listNodesInEnvironment(String environmentName, ExecutorService executorService);
+
+   /**
     * Lists the details of all existing clients.
-    * 
+    *
     * @return The details of all existing clients.
     */
    Iterable<? extends Client> listClients();
 
    /**
+    * Lists the details of all existing clients, but executing concurrently using the threads available in the ExecutorService.
+    *
+    * @return The details of all existing clients.
+    */
+   Iterable<? extends Client> listClients(ExecutorService executorService);
+
+   /**
     * Lists the details of all existing cookbooks.
-    * 
+    *
     * @return The details of all existing cookbooks.
     */
    Iterable<? extends CookbookVersion> listCookbookVersions();
 
    /**
+    * Lists the details of all existing cookbooks. This method is executed concurrently, using the threads available in the ExecutorService.
+    *
+    * @return The details of all existing cookbooks.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersions(ExecutorService executorService);
+
+   /**
     * Lists the details of all existing cookbooks in an environment.
     *
     * @param environmentName The environment name.
@@ -190,19 +221,40 @@
    Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName);
 
    /**
+    * Lists the details of all existing cookbooks in an environment.
+
+    * @param executorService The thread pool to do the concurrent execution.
+    * @param environmentName The environment name.
+    * @return The details of all existing cookbooks in an environment.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, ExecutorService executorService);
+
+   /**
     * Lists the details of all existing cookbooks in an environment
     * limiting number of versions.
     *
     * @param environmentName The environment name.
-    * @param numVersions The number of cookbook versions to include.
-    *                    Use 'all' to return all cookbook versions.
+    * @param numVersions     The number of cookbook versions to include.
+    *                        Use 'all' to return all cookbook versions.
     * @return The details of all existing cookbooks in environment.
     */
    Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, String numVersions);
 
    /**
+    * Lists the details of all existing cookbooks in an environment
+    * limiting number of versions.
+    *
+    * @param executorService The executorService used to do this operation concurrently.
+    * @param environmentName The environment name.
+    * @param numVersions     The number of cookbook versions to include.
+    *                        Use 'all' to return all cookbook versions.
+    * @return The details of all existing cookbooks in environment.
+    */
+   Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, String numVersions, ExecutorService executorService);
+
+   /**
     * Lists the details of all existing environments.
-    * 
+    *
     * @return The details of all existing environments.
     */
    @SinceApiVersion("0.10.0")
diff --git a/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java b/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java
index c22e697..d390555 100644
--- a/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java
+++ b/core/src/main/java/org/jclouds/chef/internal/BaseChefService.java
@@ -24,6 +24,7 @@
 import java.security.PrivateKey;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
 
 import javax.annotation.Resource;
 import javax.inject.Inject;
@@ -50,9 +51,9 @@
 import org.jclouds.chef.strategy.ListClients;
 import org.jclouds.chef.strategy.ListCookbookVersions;
 import org.jclouds.chef.strategy.ListCookbookVersionsInEnvironment;
-import org.jclouds.chef.strategy.ListNodesInEnvironment;
 import org.jclouds.chef.strategy.ListEnvironments;
 import org.jclouds.chef.strategy.ListNodes;
+import org.jclouds.chef.strategy.ListNodesInEnvironment;
 import org.jclouds.chef.strategy.UpdateAutomaticAttributesOnNode;
 import org.jclouds.crypto.Crypto;
 import org.jclouds.domain.JsonBall;
@@ -92,7 +93,7 @@
    private final ListNodesInEnvironment listNodesInEnvironment;
    private final Json json;
    private final Crypto crypto;
-   
+
    @Resource
    @Named(ChefProperties.CHEF_LOGGER)
    protected Logger logger = Logger.NULL;
@@ -127,7 +128,7 @@
       this.runListForGroup = checkNotNull(runListForGroup, "runListForGroup");
       this.listEnvironments = checkNotNull(listEnvironments, "listEnvironments");
       this.listNodesInEnvironment = checkNotNull(listNodesInEnvironment, "listNodesInEnvironment");
-      this.listCookbookVersionsInEnvironment = checkNotNull(listCookbookVersionsInEnvironment, "listCookbookVersionsInEnvironment");
+      this.listCookbookVersionsInEnvironment = checkNotNull(listCookbookVersionsInEnvironment,"listCookbookVersionsInEnvironment");
       this.json = checkNotNull(json, "json");
       this.crypto = checkNotNull(crypto, "crypto");
    }
@@ -140,13 +141,13 @@
    @Override
    public byte[] encrypt(InputSupplier<? extends InputStream> supplier) throws IOException {
       return ByteStreams.toByteArray(new RSAEncryptingPayload(crypto, Payloads.newPayload(supplier.getInput()), privateKey
-            .get()));
+                  .get()));
    }
 
    @Override
    public byte[] decrypt(InputSupplier<? extends InputStream> supplier) throws IOException {
       return ByteStreams.toByteArray(new RSADecryptingPayload(crypto, Payloads.newPayload(supplier.getInput()), privateKey
-            .get()));
+                  .get()));
    }
 
    @VisibleForTesting
@@ -233,26 +234,54 @@
    }
 
    @Override
+   public Iterable<? extends Node> listNodes(ExecutorService executorService) {
+      return listNodes.execute(executorService);
+   }
+
+   @Override
    public Iterable<? extends Client> listClients() {
       return listClients.execute();
    }
 
    @Override
+   public Iterable<? extends Client> listClients(ExecutorService executorService) {
+      return listClients.execute(executorService);
+   }
+
+   @Override
    public Iterable<? extends CookbookVersion> listCookbookVersions() {
       return listCookbookVersions.execute();
    }
 
+   @Override public Iterable<? extends CookbookVersion> listCookbookVersions(
+         ExecutorService executorService) {
+      return listCookbookVersions.execute(executorService);
+   }
+
    @Override
    public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName) {
       return listCookbookVersionsInEnvironment.execute(environmentName);
    }
 
    @Override
-   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName, String numVersions) {
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName,
+         ExecutorService executorService) {
+      return listCookbookVersionsInEnvironment.execute(executorService, environmentName);
+   }
+
+   @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName,
+         String numVersions) {
       return listCookbookVersionsInEnvironment.execute(environmentName, numVersions);
    }
 
    @Override
+   public Iterable<? extends CookbookVersion> listCookbookVersionsInEnvironment(String environmentName,
+         String numVersions, ExecutorService executorService) {
+      return listCookbookVersionsInEnvironment.execute(executorService, environmentName, numVersions);
+   }
+
+   @Override
    public Iterable<? extends Environment> listEnvironments() {
       return listEnvironments.execute();
    }
@@ -262,4 +291,9 @@
       return listNodesInEnvironment.execute(environmentName);
    }
 
+   @Override
+   public Iterable<? extends Node> listNodesInEnvironment(String environmentName, ExecutorService executorService) {
+      return listNodesInEnvironment.execute(executorService, environmentName);
+   }
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/ListClients.java b/core/src/main/java/org/jclouds/chef/strategy/ListClients.java
index f531a32..aa40c2a 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/ListClients.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/ListClients.java
@@ -16,16 +16,17 @@
  */
 package org.jclouds.chef.strategy;
 
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.Client;
 import org.jclouds.chef.strategy.internal.ListClientsImpl;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.ImplementedBy;
+import java.util.concurrent.ExecutorService;
 
 @ImplementedBy(ListClientsImpl.class)
 public interface ListClients {
 
    Iterable<? extends Client> execute();
 
-   Iterable<? extends Client> execute(ListeningExecutorService executor);
+   Iterable<? extends Client> execute(ExecutorService executor);
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java b/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java
index 4409e4e..45663a3 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersions.java
@@ -16,16 +16,17 @@
  */
 package org.jclouds.chef.strategy;
 
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.CookbookVersion;
 import org.jclouds.chef.strategy.internal.ListCookbookVersionsImpl;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.ImplementedBy;
+import java.util.concurrent.ExecutorService;
 
 @ImplementedBy(ListCookbookVersionsImpl.class)
 public interface ListCookbookVersions {
 
    Iterable<? extends CookbookVersion> execute();
 
-   Iterable<? extends CookbookVersion> execute(ListeningExecutorService executor);
+   Iterable<? extends CookbookVersion> execute(ExecutorService executor);
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java b/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java
index b0e1c54..188d29f 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/ListCookbookVersionsInEnvironment.java
@@ -16,11 +16,11 @@
  */
 package org.jclouds.chef.strategy;
 
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.CookbookVersion;
 import org.jclouds.chef.strategy.internal.ListCookbookVersionsInEnvironmentImpl;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.ImplementedBy;
+import java.util.concurrent.ExecutorService;
 
 @ImplementedBy(ListCookbookVersionsInEnvironmentImpl.class)
 public interface ListCookbookVersionsInEnvironment {
@@ -29,7 +29,9 @@
 
    Iterable<? extends CookbookVersion> execute(String environmentName, String numVersions);
 
-   Iterable<? extends CookbookVersion> execute(ListeningExecutorService executor, String environmentName);
+   Iterable<? extends CookbookVersion> execute(ExecutorService executor, String environmentName);
 
-   Iterable<? extends CookbookVersion> execute(ListeningExecutorService executor, String environmentName, String numVersions);
+   Iterable<? extends CookbookVersion> execute(ExecutorService executor, String environmentName, String numVersions);
+
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java b/core/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java
index 3e5b11f..553e8d4 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/ListEnvironments.java
@@ -16,16 +16,16 @@
  */
 package org.jclouds.chef.strategy;
 
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.Environment;
 import org.jclouds.chef.strategy.internal.ListEnvironmentsImpl;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.ImplementedBy;
+import java.util.concurrent.ExecutorService;
 
 @ImplementedBy(ListEnvironmentsImpl.class)
 public interface ListEnvironments {
 
    Iterable<? extends Environment> execute();
 
-   Iterable<? extends Environment> execute(ListeningExecutorService executor);
+   Iterable<? extends Environment> execute(ExecutorService executor);
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/ListNodes.java b/core/src/main/java/org/jclouds/chef/strategy/ListNodes.java
index a1a4093..660eed0 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/ListNodes.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/ListNodes.java
@@ -16,16 +16,17 @@
  */
 package org.jclouds.chef.strategy;
 
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.Node;
 import org.jclouds.chef.strategy.internal.ListNodesImpl;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.ImplementedBy;
+import java.util.concurrent.ExecutorService;
 
 @ImplementedBy(ListNodesImpl.class)
 public interface ListNodes {
 
    Iterable<? extends Node> execute();
 
-   Iterable<? extends Node> execute(ListeningExecutorService executor);
+   Iterable<? extends Node> execute(ExecutorService executor);
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java b/core/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java
index 6a46fab..efeffe6 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/ListNodesInEnvironment.java
@@ -16,16 +16,17 @@
  */
 package org.jclouds.chef.strategy;
 
+import com.google.inject.ImplementedBy;
 import org.jclouds.chef.domain.Node;
 import org.jclouds.chef.strategy.internal.ListNodesInEnvironmentImpl;
 
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.ImplementedBy;
+import java.util.concurrent.ExecutorService;
 
 @ImplementedBy(ListNodesInEnvironmentImpl.class)
 public interface ListNodesInEnvironment {
 
    Iterable<? extends Node> execute(String environmentName);
 
-   Iterable<? extends Node> execute(ListeningExecutorService executor, String environmentName);
+   Iterable<? extends Node> execute(ExecutorService executor, String environmentName);
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java
new file mode 100644
index 0000000..94cf79a
--- /dev/null
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/BaseListCookbookVersionsImpl.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.CookbookVersion;
+import org.jclouds.logging.Logger;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.concat;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+public abstract class BaseListCookbookVersionsImpl {
+
+   protected final ChefApi api;
+
+   protected Logger logger = Logger.NULL;
+
+   BaseListCookbookVersionsImpl(ChefApi api) {
+      this.api = checkNotNull(api, "api");
+   }
+
+   protected Iterable<? extends CookbookVersion> execute(Iterable<String> toGet) {
+      return concat(transform(toGet, new Function<String, Iterable<? extends CookbookVersion>>() {
+
+         @Override
+         public Iterable<? extends CookbookVersion> apply(final String cookbook) {
+            // TODO getting each version could also go parallel
+            Set<String> cookbookVersions = api.listVersionsOfCookbook(cookbook);
+            Iterable<? extends CookbookVersion> cookbooksVersions = transform(cookbookVersions,
+                  new Function<String, CookbookVersion>() {
+                     @Override
+                     public CookbookVersion apply(final String version) {
+                        return api.getCookbook(cookbook, version);
+                     }
+                  }
+            );
+
+            logger.trace(String.format("getting versions of cookbook: %s", cookbook));
+            return cookbooksVersions;
+         }
+      }));
+
+   }
+
+   protected Iterable<? extends CookbookVersion> executeConcurrently(final ListeningExecutorService executor,
+         Iterable<String> cookbookNames) {
+      return concat(transform(cookbookNames, new Function<String, Iterable<? extends CookbookVersion>>() {
+
+         @Override
+         public Iterable<? extends CookbookVersion> apply(final String cookbook) {
+            // TODO getting each version could also go parallel
+            Set<String> cookbookVersions = api.listVersionsOfCookbook(cookbook);
+            ListenableFuture<List<CookbookVersion>> futures = allAsList(transform(cookbookVersions,
+                  new Function<String, ListenableFuture<CookbookVersion>>() {
+                     @Override
+                     public ListenableFuture<CookbookVersion> apply(final String version) {
+                        return executor.submit(new Callable<CookbookVersion>() {
+                           @Override
+                           public CookbookVersion call() throws Exception {
+                              return api.getCookbook(cookbook, version);
+                           }
+                        });
+                     }
+                  }
+            ));
+
+            logger.trace(String.format("getting versions of cookbook: %s", cookbook));
+            return getUnchecked(futures);
+         }
+      }));
+   }
+
+}
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java
new file mode 100644
index 0000000..a426b8d
--- /dev/null
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/BaseListNodesImpl.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.chef.strategy.internal;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import org.jclouds.chef.ChefApi;
+import org.jclouds.chef.domain.Node;
+import org.jclouds.logging.Logger;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.util.concurrent.Futures.allAsList;
+import static com.google.common.util.concurrent.Futures.getUnchecked;
+
+public abstract class BaseListNodesImpl {
+
+   protected final ChefApi api;
+
+   protected Logger logger = Logger.NULL;
+
+   BaseListNodesImpl(ChefApi api) {
+      this.api = checkNotNull(api, "api");
+   }
+
+   protected Iterable<? extends Node> execute(Iterable<String> toGet) {
+      Iterable<? extends Node> nodes = transform(toGet, new Function<String, Node>() {
+               @Override
+               public Node apply(final String input) {
+                  return api.getNode(input);
+               }
+            }
+      );
+
+      logger.trace(String.format("getting nodes: %s", Joiner.on(',').join(toGet)));
+      return nodes;
+
+   }
+
+   protected Iterable<? extends Node> executeConcurrently(final ListeningExecutorService executor,
+         Iterable<String> toGet) {
+      ListenableFuture<List<Node>> futures = allAsList(transform(toGet, new Function<String, ListenableFuture<Node>>() {
+         @Override
+         public ListenableFuture<Node> apply(final String input) {
+            return executor.submit(new Callable<Node>() {
+               @Override
+               public Node call() throws Exception {
+                  return api.getNode(input);
+               }
+            });
+         }
+      }));
+
+      logger.trace(String.format("getting nodes: %s", Joiner.on(',').join(toGet)));
+      return getUnchecked(futures);
+   }
+
+}
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java
index 6acda30..105be2f 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/ListClientsImpl.java
@@ -28,45 +28,66 @@
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import org.jclouds.Constants;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefProperties;
 import org.jclouds.chef.domain.Client;
 import org.jclouds.chef.strategy.ListClients;
 import org.jclouds.logging.Logger;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.Inject;
+import java.util.concurrent.ExecutorService;
+
 
 @Singleton
 public class ListClientsImpl implements ListClients {
 
    protected final ChefApi api;
-   protected final ListeningExecutorService userExecutor;
    @Resource
    @Named(ChefProperties.CHEF_LOGGER)
    protected Logger logger = Logger.NULL;
 
    @Inject
-   ListClientsImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
-      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
+   ListClientsImpl(ChefApi api) {
       this.api = checkNotNull(api, "api");
    }
 
    @Override
    public Iterable<? extends Client> execute() {
-      return execute(userExecutor);
+
+      Iterable<String> toGet = api.listClients();
+      Iterable<? extends Client> clients = transform(toGet,
+            new Function<String, Client>() {
+               @Override
+               public Client apply(final String input) {
+
+                  return api.getClient(input);
+               }
+
+            }
+      );
+
+      logger.trace(String.format("getting clients: %s", Joiner.on(',').join(toGet)));
+      return clients;
+
    }
 
    @Override
-   public Iterable<? extends Client> execute(ListeningExecutorService executor) {
-      return execute(executor, api.listClients());
+   public Iterable<? extends Client> execute(ExecutorService executorService) {
+      return this.execute(MoreExecutors.listeningDecorator(executorService));
    }
 
-   private Iterable<? extends Client> execute(final ListeningExecutorService executor, Iterable<String> toGet) {
+
+   private Iterable<? extends Client> execute(ListeningExecutorService listeningExecutor) {
+      return executeConcurrently(listeningExecutor, api.listClients());
+   }
+
+   private Iterable<? extends Client> executeConcurrently(final ListeningExecutorService executor,
+         Iterable<String> toGet) {
       ListenableFuture<List<Client>> futures = allAsList(transform(toGet,
             new Function<String, ListenableFuture<Client>>() {
                @Override
@@ -78,7 +99,8 @@
                      }
                   });
                }
-            }));
+            }
+      ));
 
       logger.trace(String.format("getting clients: %s", Joiner.on(',').join(toGet)));
       return getUnchecked(futures);
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java
index 75794d9..d109038 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsImpl.java
@@ -16,81 +16,45 @@
  */
 package org.jclouds.chef.strategy.internal;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.concat;
-import static com.google.common.collect.Iterables.transform;
-import static com.google.common.util.concurrent.Futures.allAsList;
-import static com.google.common.util.concurrent.Futures.getUnchecked;
-
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-
-import javax.annotation.Resource;
-import javax.inject.Named;
-import javax.inject.Singleton;
-
-import org.jclouds.Constants;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefProperties;
 import org.jclouds.chef.domain.CookbookVersion;
 import org.jclouds.chef.strategy.ListCookbookVersions;
 import org.jclouds.logging.Logger;
 
-import com.google.common.base.Function;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.Inject;
+import javax.annotation.Resource;
+import javax.inject.Named;
+import javax.inject.Singleton;
+import java.util.concurrent.ExecutorService;
 
 @Singleton
-public class ListCookbookVersionsImpl implements ListCookbookVersions {
+public class ListCookbookVersionsImpl extends BaseListCookbookVersionsImpl implements ListCookbookVersions {
 
-   protected final ChefApi api;
-   protected final ListeningExecutorService userExecutor;
    @Resource
    @Named(ChefProperties.CHEF_LOGGER)
    protected Logger logger = Logger.NULL;
 
    @Inject
-   ListCookbookVersionsImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
-      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
-      this.api = checkNotNull(api, "api");
+   ListCookbookVersionsImpl(ChefApi api) {
+      super(api);
    }
 
    @Override
    public Iterable<? extends CookbookVersion> execute() {
-      return execute(userExecutor);
+      return super.execute(api.listCookbooks());
    }
 
    @Override
-   public Iterable<? extends CookbookVersion> execute(ListeningExecutorService executor) {
-      return execute(executor, api.listCookbooks());
+   public Iterable<? extends CookbookVersion> execute(ExecutorService executor) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor));
    }
 
-   private Iterable<? extends CookbookVersion> execute(final ListeningExecutorService executor,
-         Iterable<String> cookbookNames) {
-      return concat(transform(cookbookNames, new Function<String, Iterable<? extends CookbookVersion>>() {
 
-         @Override
-         public Iterable<? extends CookbookVersion> apply(final String cookbook) {
-            // TODO getting each version could also go parallel
-            Set<String> cookbookVersions = api.listVersionsOfCookbook(cookbook);
-            ListenableFuture<List<CookbookVersion>> futures = allAsList(transform(cookbookVersions,
-                  new Function<String, ListenableFuture<CookbookVersion>>() {
-                     @Override
-                     public ListenableFuture<CookbookVersion> apply(final String version) {
-                        return executor.submit(new Callable<CookbookVersion>() {
-                           @Override
-                           public CookbookVersion call() throws Exception {
-                              return api.getCookbook(cookbook, version);
-                           }
-                        });
-                     }
-                  }));
-
-            logger.trace(String.format("getting versions of cookbook: %s", cookbook));
-            return getUnchecked(futures);
-         }
-      }));
+   private Iterable<? extends CookbookVersion> executeConcurrently(ListeningExecutorService executor) {
+      return super.executeConcurrently(executor, api.listCookbooks());
    }
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java
index ccc2992..a7142dc 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImpl.java
@@ -16,21 +16,16 @@
  */
 package org.jclouds.chef.strategy.internal;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.concat;
 import static com.google.common.collect.Iterables.transform;
-import static com.google.common.util.concurrent.Futures.allAsList;
-import static com.google.common.util.concurrent.Futures.getUnchecked;
-
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
 
 import javax.annotation.Resource;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import org.jclouds.Constants;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefProperties;
 import org.jclouds.chef.domain.CookbookDefinition;
@@ -38,69 +33,85 @@
 import org.jclouds.chef.strategy.ListCookbookVersionsInEnvironment;
 import org.jclouds.logging.Logger;
 
-import com.google.common.base.Function;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.Inject;
+import java.util.concurrent.ExecutorService;
 
 @Singleton
-public class ListCookbookVersionsInEnvironmentImpl implements ListCookbookVersionsInEnvironment {
+public class ListCookbookVersionsInEnvironmentImpl extends BaseListCookbookVersionsImpl
+      implements ListCookbookVersionsInEnvironment {
 
-   protected final ChefApi api;
-   protected final ListeningExecutorService userExecutor;
    @Resource
    @Named(ChefProperties.CHEF_LOGGER)
    protected Logger logger = Logger.NULL;
 
    @Inject
-   ListCookbookVersionsInEnvironmentImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
-      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
-      this.api = checkNotNull(api, "api");
+   ListCookbookVersionsInEnvironmentImpl(ChefApi api) {
+      super(api);
    }
 
    @Override
    public Iterable<? extends CookbookVersion> execute(String environmentName) {
-      return execute(userExecutor, environmentName);
+      return super.execute(transform(api.listCookbooksInEnvironment(environmentName),
+            new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            }
+      ));
    }
 
    @Override
    public Iterable<? extends CookbookVersion> execute(String environmentName, String numVersions) {
-      return execute(userExecutor, environmentName, numVersions);
-   }
+      return super.execute(transform(api.listCookbooksInEnvironment(environmentName, numVersions),
+            new Function<CookbookDefinition, String>() {
 
-   public Iterable<? extends CookbookVersion> execute(ListeningExecutorService executor, String environmentName) {
-      return execute(executor, api.listCookbooksInEnvironment(environmentName));
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            }
+      ));
    }
 
    @Override
-   public Iterable<? extends CookbookVersion> execute(ListeningExecutorService executor, String environmentName, String numVersions) {
-      return execute(executor, api.listCookbooksInEnvironment(environmentName, numVersions));
+   public Iterable<? extends CookbookVersion> execute(ExecutorService executor,
+         String environmentName) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor), environmentName);
    }
 
-   private Iterable<? extends CookbookVersion> execute(final ListeningExecutorService executor,
-         Iterable<CookbookDefinition> cookbookDefs) {
-      return concat(transform(cookbookDefs, new Function<CookbookDefinition, Iterable<? extends CookbookVersion>>() {
-
-         @Override
-         public Iterable<? extends CookbookVersion> apply(final CookbookDefinition cookbookDef) {
-            // TODO getting each version could also go parallel
-            Set<CookbookDefinition.Version> cookbookVersions = cookbookDef.getVersions();
-            ListenableFuture<List<CookbookVersion>> futures = allAsList(transform(cookbookVersions,
-                  new Function<CookbookDefinition.Version, ListenableFuture<CookbookVersion>>() {
-                     @Override
-                     public ListenableFuture<CookbookVersion> apply(final CookbookDefinition.Version version) {
-                        return executor.submit(new Callable<CookbookVersion>() {
-                           @Override
-                           public CookbookVersion call() throws Exception {
-                              return api.getCookbook(cookbookDef.getName(), version.getVersion());
-                           }
-                        });
-                     }
-                  }));
-
-            logger.trace(String.format("getting versions of cookbook %s: ", cookbookDef.getName()));
-            return getUnchecked(futures);
-         }
-      }));
+   @Override
+   public Iterable<? extends CookbookVersion> execute(ExecutorService executor,
+         String environmentName, String numVersions) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor), environmentName, numVersions);
    }
+
+
+   private Iterable<? extends CookbookVersion> executeConcurrently(ListeningExecutorService executor,
+         String environmentName) {
+      return super.execute(
+            transform(api.listCookbooksInEnvironment(environmentName), new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            })
+      );
+   }
+
+
+   private Iterable<? extends CookbookVersion> executeConcurrently(ListeningExecutorService executor,
+         String environmentName, String numVersions) {
+      return super.execute(transform(api.listCookbooksInEnvironment(environmentName, numVersions),
+            new Function<CookbookDefinition, String>() {
+
+               @Override
+               public String apply(CookbookDefinition cookbookDefinition) {
+                  return cookbookDefinition.getName();
+               }
+            }
+      ));
+   }
+
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java
index 9b79de2..0ed792e 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/ListEnvironmentsImpl.java
@@ -40,6 +40,12 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.inject.Inject;
+import com.google.common.util.concurrent.MoreExecutors;
+
+
+import java.util.concurrent.ExecutorService;
+
+
 
 @Singleton
 public class ListEnvironmentsImpl implements ListEnvironments {
@@ -62,7 +68,11 @@
    }
 
    @Override
-   public Iterable<? extends Environment> execute(ListeningExecutorService executor) {
+   public Iterable<? extends Environment> execute(ExecutorService executor) {
+      return this.execute(MoreExecutors.listeningDecorator(executor));
+   }
+
+   private Iterable<? extends Environment> execute(ListeningExecutorService executor) {
       return execute(executor, api.listEnvironments());
    }
 
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java
index be9b652..8d95965 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesImpl.java
@@ -16,71 +16,47 @@
  */
 package org.jclouds.chef.strategy.internal;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.transform;
-import static com.google.common.util.concurrent.Futures.allAsList;
-import static com.google.common.util.concurrent.Futures.getUnchecked;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
 import javax.annotation.Resource;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import org.jclouds.Constants;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefProperties;
 import org.jclouds.chef.domain.Node;
 import org.jclouds.chef.strategy.ListNodes;
 import org.jclouds.logging.Logger;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.Inject;
+import java.util.concurrent.ExecutorService;
 
 @Singleton
-public class ListNodesImpl implements ListNodes {
+public class ListNodesImpl extends BaseListNodesImpl implements ListNodes {
 
-   protected final ChefApi api;
-   protected final ListeningExecutorService userExecutor;
+
    @Resource
    @Named(ChefProperties.CHEF_LOGGER)
    protected Logger logger = Logger.NULL;
 
    @Inject
-   ListNodesImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
-      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
-      this.api = checkNotNull(api, "api");
+   ListNodesImpl(ChefApi api) {
+      super(api);
    }
 
    @Override
    public Iterable<? extends Node> execute() {
-      return execute(userExecutor);
+      return super.execute(api.listNodes());
    }
 
    @Override
-   public Iterable<? extends Node> execute(ListeningExecutorService executor) {
-      return execute(executor, api.listNodes());
+   public Iterable<? extends Node> execute(ExecutorService executor) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor));
    }
 
-   private Iterable<? extends Node> execute(final ListeningExecutorService executor, Iterable<String> toGet) {
-      ListenableFuture<List<Node>> futures = allAsList(transform(toGet, new Function<String, ListenableFuture<Node>>() {
-         @Override
-         public ListenableFuture<Node> apply(final String input) {
-            return executor.submit(new Callable<Node>() {
-               @Override
-               public Node call() throws Exception {
-                  return api.getNode(input);
-               }
-            });
-         }
-      }));
 
-      logger.trace(String.format("getting nodes: %s", Joiner.on(',').join(toGet)));
-      return getUnchecked(futures);
+   private Iterable<? extends Node> executeConcurrently(ListeningExecutorService executor) {
+      return super.executeConcurrently(executor, api.listNodes());
    }
 
 }
diff --git a/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java b/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java
index 8ae747e..58ecaaa 100644
--- a/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java
+++ b/core/src/main/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImpl.java
@@ -16,71 +16,47 @@
  */
 package org.jclouds.chef.strategy.internal;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.transform;
-import static com.google.common.util.concurrent.Futures.allAsList;
-import static com.google.common.util.concurrent.Futures.getUnchecked;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
 import javax.annotation.Resource;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
-import org.jclouds.Constants;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.inject.Inject;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.config.ChefProperties;
 import org.jclouds.chef.domain.Node;
 import org.jclouds.chef.strategy.ListNodesInEnvironment;
 import org.jclouds.logging.Logger;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.inject.Inject;
+import java.util.concurrent.ExecutorService;
 
 @Singleton
-public class ListNodesInEnvironmentImpl implements ListNodesInEnvironment {
+public class ListNodesInEnvironmentImpl extends BaseListNodesImpl implements ListNodesInEnvironment {
 
-   protected final ChefApi api;
-   protected final ListeningExecutorService userExecutor;
    @Resource
    @Named(ChefProperties.CHEF_LOGGER)
    protected Logger logger = Logger.NULL;
 
    @Inject
-   ListNodesInEnvironmentImpl(@Named(Constants.PROPERTY_USER_THREADS) ListeningExecutorService userExecutor, ChefApi api) {
-      this.userExecutor = checkNotNull(userExecutor, "userExecuor");
-      this.api = checkNotNull(api, "api");
+   ListNodesInEnvironmentImpl(ChefApi api) {
+      super(api);
    }
 
    @Override
    public Iterable<? extends Node> execute(String environmentName) {
-      return execute(userExecutor, environmentName);
+      return super.execute(api.listNodesInEnvironment(environmentName));
    }
 
    @Override
-   public Iterable<? extends Node> execute(ListeningExecutorService executor, String environmentName) {
-      return execute(executor, environmentName, api.listNodesInEnvironment(environmentName));
+   public Iterable<? extends Node> execute(ExecutorService executor, String environmentName) {
+      return this.executeConcurrently(MoreExecutors.listeningDecorator(executor), environmentName);
    }
 
-   private Iterable<? extends Node> execute(final ListeningExecutorService executor, String environmentName, Iterable<String> toGet) {
-      ListenableFuture<List<Node>> futures = allAsList(transform(toGet, new Function<String, ListenableFuture<Node>>() {
-         @Override
-         public ListenableFuture<Node> apply(final String input) {
-            return executor.submit(new Callable<Node>() {
-               @Override
-               public Node call() throws Exception {
-                  return api.getNode(input);
-               }
-            });
-         }
-      }));
 
-      logger.trace(String.format("getting nodes in environment %s: %s", environmentName, Joiner.on(',').join(toGet)));
-      return getUnchecked(futures);
+   private Iterable<? extends Node> executeConcurrently(ListeningExecutorService executor,
+         String environmentName) {
+      return super.executeConcurrently(executor, api.listNodesInEnvironment(environmentName));
    }
 
 }
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java
index a9b667d..5f68fcf 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/ListCookbookVersionsInEnvironmentImplLiveTest.java
@@ -22,7 +22,11 @@
 
 import java.io.File;
 import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.domain.ChecksumStatus;
 import org.jclouds.chef.domain.CookbookVersion;
@@ -51,6 +55,9 @@
    private ListCookbookVersionsInEnvironmentImpl strategy;
    private CreateNodeAndPopulateAutomaticAttributesImpl creator;
 
+   private ExecutorService testExecutorService;
+   private ListeningExecutorService testListeningExecutorService;
+
    @Override
    protected void initialize() {
       super.initialize();
@@ -63,6 +70,8 @@
       }
 
       this.strategy = injector.getInstance(ListCookbookVersionsInEnvironmentImpl.class);
+      this.testExecutorService = Executors.newFixedThreadPool(5);
+      this.testListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
    }
 
    @AfterClass(groups = { "integration", "live" })
@@ -72,6 +81,10 @@
       api.deleteCookbook(PREFIX, "1.0.0");
       api.deleteCookbook(PREFIX + 1, "0.0.0");
       api.deleteCookbook(PREFIX + 1, "1.0.0");
+
+      this.testExecutorService.shutdown();
+      this.testListeningExecutorService.shutdown();
+
       super.tearDown();
    }
 
@@ -81,15 +94,51 @@
    }
 
    @Test
+   public void testExecuteConcurrentlyWithExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
    public void testExecuteWithNumVersions() {
       assertTrue(size(strategy.execute("_default", "2")) > 0, "Expected one or more elements");
    }
 
    @Test
+   public void testExecuteConcurrentlyWithNumVersionsAndExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default", "2")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAndListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default", "2")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
    public void testExecuteWithNumVersionsAll() {
       assertTrue(size(strategy.execute("_default", "all")) > 0, "Expected one or more elements");
    }
 
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAllAndExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default", "all")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithNumVersionsAllAndListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default", "all")) > 0,
+            "Expected one or more elements");
+   }
+
    private FilePayload uploadContent(String fileName) throws Exception {
       // Define the file you want in the cookbook
       File file = new File(System.getProperty("user.dir"), fileName);
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java
index b9de2b8..903b998 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesImplLiveTest.java
@@ -19,6 +19,8 @@
 import static com.google.common.collect.Iterables.size;
 import static org.testng.Assert.assertTrue;
 
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.internal.BaseChefLiveTest;
 import org.testng.annotations.AfterClass;
@@ -26,6 +28,9 @@
 
 import com.google.common.collect.ImmutableSet;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 /**
  * Tests behavior of {@code ListNodesImpl} strategies
  */
@@ -35,6 +40,9 @@
    private ListNodesImpl strategy;
    private CreateNodeAndPopulateAutomaticAttributesImpl creator;
 
+   private ExecutorService testExecutorService;
+   private ListeningExecutorService testListeningExecutorService;
+
    @Override
    protected void initialize() {
       super.initialize();
@@ -42,6 +50,9 @@
       this.strategy = injector.getInstance(ListNodesImpl.class);
       creator.execute(prefix, ImmutableSet.<String> of());
       creator.execute(prefix + 1, ImmutableSet.<String> of());
+
+      this.testExecutorService = Executors.newFixedThreadPool(5);
+      this.testListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
    }
 
    @AfterClass(groups = { "integration", "live" })
@@ -49,6 +60,10 @@
    protected void tearDown() {
       api.deleteNode(prefix);
       api.deleteNode(prefix + 1);
+
+      this.testExecutorService.shutdown();
+      this.testListeningExecutorService.shutdown();
+
       super.tearDown();
    }
 
@@ -56,4 +71,12 @@
    public void testExecute() {
       assertTrue(size(strategy.execute()) > 0, "Expected one or more elements");
    }
+
+   public void testExecuteConcurrentlyWithExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService)) > 0, "Expected one or more elements");
+   }
+
+   public void testExecuteConcurrentlyWithListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService)) > 0, "Expected one or more elements");
+   }
 }
diff --git a/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java b/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java
index 81dd6d8..f655d52 100644
--- a/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java
+++ b/core/src/test/java/org/jclouds/chef/strategy/internal/ListNodesInEnvironmentImplLiveTest.java
@@ -19,6 +19,8 @@
 import static com.google.common.collect.Iterables.size;
 import static org.testng.Assert.assertTrue;
 
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
 import org.jclouds.chef.ChefApi;
 import org.jclouds.chef.internal.BaseChefLiveTest;
 import org.testng.annotations.AfterClass;
@@ -26,6 +28,9 @@
 
 import com.google.common.collect.ImmutableSet;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
 /**
  * Tests behavior of {@code ListNodesInEnvironmentImpl} strategies
  */
@@ -35,13 +40,19 @@
    private ListNodesInEnvironmentImpl strategy;
    private CreateNodeAndPopulateAutomaticAttributesImpl creator;
 
+   private ExecutorService testExecutorService;
+   private ListeningExecutorService testListeningExecutorService;
+
    @Override
    protected void initialize() {
       super.initialize();
       this.creator = injector.getInstance(CreateNodeAndPopulateAutomaticAttributesImpl.class);
       this.strategy = injector.getInstance(ListNodesInEnvironmentImpl.class);
-      creator.execute(prefix, ImmutableSet.<String> of());
-      creator.execute(prefix + 1, ImmutableSet.<String> of());
+      creator.execute(prefix, ImmutableSet.<String>of());
+      creator.execute(prefix + 1, ImmutableSet.<String>of());
+
+      this.testExecutorService = Executors.newFixedThreadPool(5);
+      this.testListeningExecutorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
    }
 
    @AfterClass(groups = { "integration", "live" })
@@ -49,6 +60,10 @@
    protected void tearDown() {
       api.deleteNode(prefix);
       api.deleteNode(prefix + 1);
+
+      this.testExecutorService.shutdown();
+      this.testListeningExecutorService.shutdown();
+
       super.tearDown();
    }
 
@@ -56,4 +71,16 @@
    public void testExecute() {
       assertTrue(size(strategy.execute("_default")) > 0, "Expected one or more elements");
    }
+
+   @Test
+   public void testExecuteConcurrentlyWithExecutorService() {
+      assertTrue(size(strategy.execute(testExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
+
+   @Test
+   public void testExecuteConcurrentlyWithListeningExecutorService() {
+      assertTrue(size(strategy.execute(testListeningExecutorService, "_default")) > 0,
+            "Expected one or more elements");
+   }
 }