You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2016/01/19 12:04:57 UTC

[1/2] jclouds git commit: JCLOUDS-512: Implement the ImageCache

Repository: jclouds
Updated Branches:
  refs/heads/master 0f6ab3944 -> 40f31786c


http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/core/src/test/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest.java b/core/src/test/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest.java
index 868e726..8b5a954 100644
--- a/core/src/test/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest.java
+++ b/core/src/test/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest.java
@@ -21,22 +21,26 @@ import static com.google.common.util.concurrent.Atomics.newReference;
 import static org.testng.Assert.assertEquals;
 
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.jclouds.rest.AuthorizationException;
 import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.SetAndThrowAuthorizationExceptionSupplierBackedLoader;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
 import com.google.common.util.concurrent.UncheckedExecutionException;
+import com.google.common.util.concurrent.Uninterruptibles;
 
 @Test(groups = "unit", testName = "MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest")
 public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest {
    @Test
    public void testLoaderNormal() {
       AtomicReference<AuthorizationException> authException = newReference();
-      assertEquals(new SetAndThrowAuthorizationExceptionSupplierBackedLoader<String>(ofInstance("foo"),
-            authException).load("KEY").get(), "foo");
+      assertEquals(new SetAndThrowAuthorizationExceptionSupplierBackedLoader<String>(ofInstance("foo"), authException, new ValueLoadedCallback.NoOpCallback<String>()).load("KEY").get(), "foo");
       assertEquals(authException.get(), null);
    }
 
@@ -48,7 +52,7 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest {
             public String get() {
                throw new AuthorizationException();
             }
-         }, authException).load("KEY");
+         }, authException, new ValueLoadedCallback.NoOpCallback<String>()).load("KEY");
       } finally {
          assertEquals(authException.get().getClass(), AuthorizationException.class);
       }
@@ -62,7 +66,7 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest {
             public String get() {
                throw new RuntimeException(new ExecutionException(new AuthorizationException()));
             }
-         }, authException).load("KEY");
+         }, authException, new ValueLoadedCallback.NoOpCallback<String>()).load("KEY");
       } finally {
          assertEquals(authException.get().getClass(), AuthorizationException.class);
       }
@@ -76,7 +80,7 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest {
             public String get() {
                throw new UncheckedExecutionException(new AuthorizationException());
             }
-         }, authException).load("KEY");
+         }, authException, new ValueLoadedCallback.NoOpCallback<String>()).load("KEY");
       } finally {
          assertEquals(authException.get().getClass(), AuthorizationException.class);
       }
@@ -90,9 +94,43 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplierTest {
             public String get() {
                throw new RuntimeException(new IllegalArgumentException("foo"));
             }
-         }, authException).load("KEY");
+         }, authException, new ValueLoadedCallback.NoOpCallback<String>()).load("KEY");
       } finally {
          assertEquals(authException.get().getClass(), RuntimeException.class);
       }
    }
+
+   @Test
+   public void testLoaderNotifiesAfterReloading() {
+      AtomicReference<AuthorizationException> authException = newReference();
+      ValueLoadedEventHandler handler = new ValueLoadedEventHandler();
+
+      Supplier<String> supplier = MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
+            Suppliers.<String> ofInstance("foo"), 3, TimeUnit.SECONDS, handler);
+
+      // The supplier loads the value initially and returns the cached values
+      assertEquals(handler.count.get(), 0);
+      supplier.get();
+      assertEquals(handler.count.get(), 1);
+      supplier.get();
+      assertEquals(handler.count.get(), 1);
+      
+      // Once expired, it reloads the values, notified the event, and updated the cache
+      Uninterruptibles.sleepUninterruptibly(4, TimeUnit.SECONDS);
+      supplier.get();
+      assertEquals(handler.count.get(), 2);
+      supplier.get();
+      assertEquals(handler.count.get(), 2);
+      supplier.get();
+      assertEquals(handler.count.get(), 2);
+   }
+
+   static class ValueLoadedEventHandler implements ValueLoadedCallback<String>{
+      AtomicInteger count = new AtomicInteger(0);
+
+      @Override
+      public void valueLoaded(Optional<String> value) {
+         count.incrementAndGet();
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderImpl.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderImpl.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderImpl.java
index 160c37f..aca7141 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderImpl.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/AWSEC2TemplateBuilderImpl.java
@@ -27,8 +27,6 @@ import org.jclouds.compute.domain.Hardware;
 import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.options.TemplateOptions;
-import org.jclouds.compute.strategy.GetImageStrategy;
-import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.domain.Location;
 import org.jclouds.ec2.compute.domain.RegionAndName;
 import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl;
@@ -40,11 +38,11 @@ public class AWSEC2TemplateBuilderImpl extends EC2TemplateBuilderImpl {
 
    @Inject
    protected AWSEC2TemplateBuilderImpl(@Memoized Supplier<Set<? extends Location>> locations,
-         ImageCacheSupplier images, @Memoized Supplier<Set<? extends Hardware>> sizes,
+         @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
          Supplier<Location> defaultLocation, @Named("DEFAULT") Provider<TemplateOptions> optionsProvider,
-         @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider, GetImageStrategy getImageStrategy,
+         @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider,
          Supplier<LoadingCache<RegionAndName, ? extends Image>> imageMap) {
-      super(locations, images, sizes, defaultLocation, optionsProvider, defaultTemplateProvider, getImageStrategy, imageMap);
+      super(locations, images, sizes, defaultLocation, optionsProvider, defaultTemplateProvider, imageMap);
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
index 4d3d6c9..e1a184f 100644
--- a/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
+++ b/providers/aws-ec2/src/main/java/org/jclouds/aws/ec2/compute/config/AWSEC2ComputeServiceContextModule.java
@@ -16,14 +16,11 @@
  */
 package org.jclouds.aws.ec2.compute.config;
 
-import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
-
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicReference;
 
-import javax.inject.Named;
 import javax.inject.Singleton;
 
 import org.jclouds.aws.ec2.compute.AWSEC2TemplateBuilderImpl;
@@ -38,8 +35,6 @@ import org.jclouds.aws.ec2.compute.strategy.CreateKeyPairPlacementAndSecurityGro
 import org.jclouds.aws.ec2.compute.suppliers.AWSEC2HardwareSupplier;
 import org.jclouds.compute.config.BaseComputeServiceContextModule;
 import org.jclouds.compute.domain.Image;
-import org.jclouds.compute.extensions.ImageExtension;
-import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.ec2.compute.config.EC2BindComputeStrategiesByClass;
 import org.jclouds.ec2.compute.domain.RegionAndName;
@@ -59,7 +54,6 @@ import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier;
 import org.jclouds.rest.AuthorizationException;
 import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
 
-import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
 import com.google.common.base.Throwables;
@@ -107,9 +101,8 @@ public class AWSEC2ComputeServiceContextModule extends BaseComputeServiceContext
    // duplicates EC2ComputeServiceContextModule; but that's easiest thing to do with guice; could extract to common util
    // TODO: have a another look at this (Adrian)
    @Override
-   protected Supplier<Set<? extends Image>> supplyNonParsingImageCache(
-            AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds,
-            final Supplier<Set<? extends Image>> imageSupplier, Injector injector) {
+   protected Supplier<Set<? extends Image>> supplyNonParsingImages(final Supplier<Set<? extends Image>> imageSupplier,
+         Injector injector) {
       final Supplier<LoadingCache<RegionAndName, ? extends Image>> cache = injector.getInstance(Key
                .get(new TypeLiteral<Supplier<LoadingCache<RegionAndName, ? extends Image>>>() {
                }));
@@ -170,14 +163,4 @@ public class AWSEC2ComputeServiceContextModule extends BaseComputeServiceContext
    protected TemplateOptions provideTemplateOptions(Injector injector, TemplateOptions options) {
       return options.as(EC2TemplateOptions.class).userData("#cloud-config\nrepo_upgrade: none\n".getBytes());
    }
-
-   @Override
-   protected Optional<ImageExtension> provideImageExtension(Injector i) {
-      return Optional.of(i.getInstance(ImageExtension.class));
-   }
-
-   @Override
-   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.of(i.getInstance(SecurityGroupExtension.class));
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2ImageExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2ImageExtensionLiveTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2ImageExtensionLiveTest.java
index ef0d539..2f6b962 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2ImageExtensionLiveTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/extensions/AWSEC2ImageExtensionLiveTest.java
@@ -21,7 +21,8 @@ import static com.google.common.collect.Iterables.transform;
 import org.jclouds.aws.ec2.AWSEC2Api;
 import org.jclouds.aws.util.AWSUtils;
 import org.jclouds.compute.domain.Image;
-import org.jclouds.compute.extensions.internal.BaseImageExtensionLiveTest;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.ec2.compute.extensions.EC2ImageExtensionLiveTest;
 import org.jclouds.ec2.compute.functions.EC2ImageParser;
 import org.jclouds.ec2.options.DescribeImagesOptions;
 import org.jclouds.sshj.config.SshjSshClientModule;
@@ -33,7 +34,7 @@ import com.google.inject.Module;
  * Live test for aws-ec2 {@link ImageExtension} implementation
  */
 @Test(groups = "live", singleThreaded = true, testName = "AWSEC2ImageExtensionLiveTest")
-public class AWSEC2ImageExtensionLiveTest extends BaseImageExtensionLiveTest {
+public class AWSEC2ImageExtensionLiveTest extends EC2ImageExtensionLiveTest {
 
    public AWSEC2ImageExtensionLiveTest() {
       provider = "aws-ec2";

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
----------------------------------------------------------------------
diff --git a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
index 4951b05..baaec3f 100644
--- a/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
+++ b/providers/google-compute-engine/src/main/java/org/jclouds/googlecomputeengine/compute/config/GoogleComputeEngineServiceContextModule.java
@@ -25,26 +25,14 @@ import static org.jclouds.googlecomputeengine.config.GoogleComputeEngineProperti
 import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
 import static org.jclouds.util.Predicates2.retry;
 
-import javax.inject.Named;
-import javax.inject.Singleton;
 import java.net.URI;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
-import com.google.common.base.Function;
-import com.google.common.base.Functions;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.base.Supplier;
-import com.google.common.cache.CacheBuilder;
-import com.google.common.cache.CacheLoader;
-import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableMap;
-import com.google.inject.Injector;
-import com.google.inject.Provides;
-import com.google.inject.Scopes;
-import com.google.inject.TypeLiteral;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
 import org.jclouds.collect.Memoized;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.ComputeServiceAdapter;
@@ -53,8 +41,6 @@ import org.jclouds.compute.domain.Hardware;
 import org.jclouds.compute.domain.NodeMetadata;
 import org.jclouds.compute.domain.OperatingSystem;
 import org.jclouds.compute.domain.OsFamily;
-import org.jclouds.compute.extensions.ImageExtension;
-import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LoginCredentials;
@@ -80,6 +66,19 @@ import org.jclouds.googlecomputeengine.domain.Operation;
 import org.jclouds.location.suppliers.ImplicitLocationSupplier;
 import org.jclouds.location.suppliers.implicit.FirstZone;
 
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Injector;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+import com.google.inject.TypeLiteral;
+
 public final class GoogleComputeEngineServiceContextModule
       extends ComputeServiceAdapterContextModule<Instance, MachineType, Image, Location> {
 
@@ -197,15 +196,6 @@ public final class GoogleComputeEngineServiceContextModule
       return CacheBuilder.newBuilder().build(in);
    }
 
-
-   @Override protected Optional<ImageExtension> provideImageExtension(Injector i) {
-      return Optional.absent();
-   }
-
-   @Override protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.absent();
-   }
-
    private static final Map<Instance.Status, NodeMetadata.Status> toPortableNodeStatus =
          ImmutableMap.<Instance.Status, NodeMetadata.Status>builder()
                      .put(Instance.Status.PROVISIONING, NodeMetadata.Status.PENDING)


[2/2] jclouds git commit: JCLOUDS-512: Implement the ImageCache

Posted by na...@apache.org.
JCLOUDS-512: Implement the ImageCache

This commit refactors the ImageCacheSupplier to act as a
proper cache. It is used by the ImageExtesion and all operations
on the images are propagated to the cache.

A method has also been added to the TemplateBuilder to let users
force a cache refresh. There have been several requests to provide a way
to disable image caching in the compute abstraction, and this new method
should fix that.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/40f31786
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/40f31786
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/40f31786

Branch: refs/heads/master
Commit: 40f31786c59e10f8ea74c2bf4463954361d5f68e
Parents: 0f6ab39
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Oct 22 09:38:14 2015 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Tue Jan 19 11:25:09 2016 +0100

----------------------------------------------------------------------
 .../CloudStackComputeServiceContextModule.java  |  11 --
 .../config/EC2ComputeServiceContextModule.java  |  19 +-
 .../internal/EC2TemplateBuilderImpl.java        |   8 +-
 .../ec2/compute/EC2TemplateBuilderTest.java     |   8 +-
 .../extensions/EC2ImageExtensionLiveTest.java   |  48 ++++-
 .../internal/EC2TemplateBuilderImplTest.java    |   9 +-
 .../config/NovaComputeServiceContextModule.java |  11 --
 .../config/BaseComputeServiceContextModule.java |  50 ++----
 .../jclouds/compute/domain/TemplateBuilder.java |  16 +-
 .../compute/domain/TemplateBuilderSpec.java     |  25 ++-
 .../domain/internal/TemplateBuilderImpl.java    |  81 +++------
 .../internal/DelegatingImageExtension.java      |  74 ++++++++
 .../compute/internal/BaseComputeService.java    |   9 +-
 .../config/StubComputeServiceContextModule.java |   9 -
 .../compute/suppliers/ImageCacheSupplier.java   | 175 +++++++++++++++----
 .../compute/domain/TemplateBuilderSpecTest.java |  51 +++++-
 .../internal/TemplateBuilderImplTest.java       |   9 +-
 .../internal/BaseImageExtensionLiveTest.java    |  45 +++--
 .../suppliers/ImageCacheSupplierTest.java       |  79 ++++++++-
 ...tButNotOnAuthorizationExceptionSupplier.java |  45 ++++-
 .../rest/suppliers/ValueLoadedCallback.java     |  37 ++++
 ...NotOnAuthorizationExceptionSupplierTest.java |  50 +++++-
 .../ec2/compute/AWSEC2TemplateBuilderImpl.java  |   8 +-
 .../AWSEC2ComputeServiceContextModule.java      |  21 +--
 .../AWSEC2ImageExtensionLiveTest.java           |   5 +-
 ...GoogleComputeEngineServiceContextModule.java |  42 ++---
 26 files changed, 677 insertions(+), 268 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
index 7ef7805..f46937d 100644
--- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
+++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/compute/config/CloudStackComputeServiceContextModule.java
@@ -89,7 +89,6 @@ import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExc
 
 import com.google.common.base.Function;
 import com.google.common.base.Objects;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.cache.CacheBuilder;
@@ -277,14 +276,4 @@ public class CloudStackComputeServiceContextModule extends
          NetworkType.ADVANCED, new AdvancedNetworkOptionsConverter(),
          NetworkType.BASIC, new BasicNetworkOptionsConverter());
    }
-
-   @Override
-   protected Optional<ImageExtension> provideImageExtension(Injector i) {
-      return Optional.of(i.getInstance(ImageExtension.class));
-   }
-
-   @Override
-   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.of(i.getInstance(SecurityGroupExtension.class));
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceContextModule.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceContextModule.java
index 97eca73..ce889a3 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceContextModule.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceContextModule.java
@@ -17,7 +17,6 @@
 package org.jclouds.ec2.compute.config;
 
 import static com.google.common.collect.Iterables.toArray;
-import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
 import static org.jclouds.ec2.reference.EC2Constants.PROPERTY_EC2_AMI_OWNERS;
 
 import java.util.Set;
@@ -30,8 +29,6 @@ import javax.inject.Singleton;
 import org.jclouds.compute.ComputeServiceContext;
 import org.jclouds.compute.config.BaseComputeServiceContextModule;
 import org.jclouds.compute.domain.Image;
-import org.jclouds.compute.extensions.ImageExtension;
-import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.ec2.compute.EC2ComputeService;
 import org.jclouds.ec2.compute.domain.RegionAndName;
 import org.jclouds.ec2.compute.loaders.RegionAndIdToImage;
@@ -39,7 +36,6 @@ import org.jclouds.ec2.compute.suppliers.RegionAndNameToImageSupplier;
 import org.jclouds.rest.AuthorizationException;
 import org.jclouds.rest.suppliers.SetAndThrowAuthorizationExceptionSupplier;
 
-import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
@@ -78,9 +74,8 @@ public class EC2ComputeServiceContextModule extends BaseComputeServiceContextMod
    }
 
    @Override
-   protected Supplier<Set<? extends Image>> supplyNonParsingImageCache(
-            AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds,
-            final Supplier<Set<? extends Image>> imageSupplier, Injector injector) {
+   protected Supplier<Set<? extends Image>> supplyNonParsingImages(final Supplier<Set<? extends Image>> imageSupplier,
+         Injector injector) {
       final Supplier<LoadingCache<RegionAndName, ? extends Image>> cache = injector.getInstance(Key.get(new TypeLiteral<Supplier<LoadingCache<RegionAndName, ? extends Image>>>() {}));
       return new Supplier<Set<? extends Image>>() {
          @Override
@@ -135,15 +130,5 @@ public class EC2ComputeServiceContextModule extends BaseComputeServiceContextMod
          return new String[] {};
       return toArray(Splitter.on(',').split(amiOwners), String.class);
    }
-
-   @Override
-   protected Optional<ImageExtension> provideImageExtension(Injector i) {
-      return Optional.of(i.getInstance(ImageExtension.class));
-   }
-
-   @Override
-   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.of(i.getInstance(SecurityGroupExtension.class));
-   }
 }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/ec2/src/main/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImpl.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImpl.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImpl.java
index 180bd62..ea76c03 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImpl.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImpl.java
@@ -32,8 +32,6 @@ import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.domain.internal.TemplateBuilderImpl;
 import org.jclouds.compute.options.TemplateOptions;
-import org.jclouds.compute.strategy.GetImageStrategy;
-import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.domain.Location;
 import org.jclouds.ec2.compute.domain.RegionAndName;
 import org.jclouds.util.Throwables2;
@@ -50,11 +48,11 @@ public class EC2TemplateBuilderImpl extends TemplateBuilderImpl {
 
    @Inject
    protected EC2TemplateBuilderImpl(@Memoized Supplier<Set<? extends Location>> locations,
-         ImageCacheSupplier images, @Memoized Supplier<Set<? extends Hardware>> sizes,
+         @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> sizes,
          Supplier<Location> defaultLocation, @Named("DEFAULT") Provider<TemplateOptions> optionsProvider,
-         @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider, GetImageStrategy getImageStrategy,
+         @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider,
          Supplier<LoadingCache<RegionAndName, ? extends Image>> imageMap) {
-      super(locations, images, sizes, defaultLocation, optionsProvider, defaultTemplateProvider, getImageStrategy);
+      super(locations, images, sizes, defaultLocation, optionsProvider, defaultTemplateProvider);
       this.lazyImageCache = imageMap;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2TemplateBuilderTest.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2TemplateBuilderTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2TemplateBuilderTest.java
index 2522a1f..8f752eb 100644
--- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2TemplateBuilderTest.java
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/EC2TemplateBuilderTest.java
@@ -58,6 +58,7 @@ import org.jclouds.ec2.compute.domain.RegionAndName;
 import org.jclouds.ec2.compute.functions.ImagesToRegionAndIdMap;
 import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl;
 import org.jclouds.ec2.domain.VirtualizationType;
+import org.jclouds.rest.AuthorizationException;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Function;
@@ -69,6 +70,8 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Atomics;
+import com.google.inject.util.Providers;
 
 /**
  * Tests compute service specifically to EC2.
@@ -236,8 +239,9 @@ public class EC2TemplateBuilderTest {
                         m1_small().build(), m1_xlarge().build(), m2_xlarge().build(), m2_2xlarge().build(),
                         m2_4xlarge().build(), g2_2xlarge().build(), HARDWARE_SUPPORTING_BOGUS));
 
-      return new EC2TemplateBuilderImpl(locations, new ImageCacheSupplier(images, 60), sizes, Suppliers.ofInstance(location), optionsProvider,
-               templateBuilderProvider, getImageStrategy, imageCache) {
+      return new EC2TemplateBuilderImpl(locations, new ImageCacheSupplier(images, 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy)), sizes,
+            Suppliers.ofInstance(location), optionsProvider, templateBuilderProvider, imageCache) {
       };
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/ec2/src/test/java/org/jclouds/ec2/compute/extensions/EC2ImageExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/extensions/EC2ImageExtensionLiveTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/extensions/EC2ImageExtensionLiveTest.java
index 3be04db..be8abb6 100644
--- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/extensions/EC2ImageExtensionLiveTest.java
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/extensions/EC2ImageExtensionLiveTest.java
@@ -17,16 +17,31 @@
 package org.jclouds.ec2.compute.extensions;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.getOnlyElement;
 
+import java.lang.reflect.Field;
 import java.util.Properties;
+import java.util.Set;
 
+import org.jclouds.collect.Memoized;
+import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilderSpec;
+import org.jclouds.compute.extensions.ImageExtension;
 import org.jclouds.compute.extensions.internal.BaseImageExtensionLiveTest;
+import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.sshj.config.SshjSshClientModule;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Supplier;
+import com.google.common.base.Throwables;
+import com.google.common.cache.LoadingCache;
+import com.google.inject.Key;
 import com.google.inject.Module;
+import com.google.inject.TypeLiteral;
 
 /**
  * Live test for ec2 {@link ImageExtension} implementation
@@ -43,11 +58,42 @@ public class EC2ImageExtensionLiveTest extends BaseImageExtensionLiveTest {
    protected Properties setupProperties() {
       Properties overrides = super.setupProperties();
       String ebsSpec = checkNotNull(setIfTestSystemPropertyPresent(overrides, provider + ".ebs-template"), provider
-              + ".ebs-template");
+            + ".ebs-template");
       ebsTemplate = TemplateBuilderSpec.parse(ebsSpec);
       return overrides;
    }
 
+   // Getting an image from the cache is tricky in EC2, as images are filtered
+   // by default by owner, and the image being created will be owned by the
+   // current user. If the cache needs to refresh the list, the just added image
+   // will be removed as the image list will be refreshed (potentially) without
+   // taking into account the current user owner id. Instead of using the
+   // TempalteBuilder, just inspect the ImageCacheSupplier directly
+   @SuppressWarnings("unchecked")
+   @Override
+   protected Optional<Image> findImageWithNameInCache(String name) {
+      ImageCacheSupplier imageCache = (ImageCacheSupplier) context.utils().injector()
+            .getInstance(Key.get(new TypeLiteral<Supplier<Set<? extends Image>>>() {
+            }, Memoized.class));
+
+      try {
+         Field field = imageCache.getClass().getDeclaredField("imageCache");
+         field.setAccessible(true);
+         LoadingCache<String, Image> cache = (LoadingCache<String, Image>) field.get(imageCache);
+
+         return Optional.fromNullable(getOnlyElement(filter(cache.asMap().values(), new Predicate<Image>() {
+            @Override
+            public boolean apply(Image input) {
+               return imageGroup.equals(input.getName());
+            }
+         }), null));
+      } catch (NoSuchFieldException ex) {
+         throw Throwables.propagate(ex);
+      } catch (IllegalAccessException ex) {
+         throw Throwables.propagate(ex);
+      }
+   }
+
    @Override
    public Template getNodeTemplate() {
       return view.getComputeService().templateBuilder().from(ebsTemplate).build();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/ec2/src/test/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImplTest.java
----------------------------------------------------------------------
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImplTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImplTest.java
index 0502175..7086978 100644
--- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImplTest.java
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/internal/EC2TemplateBuilderImplTest.java
@@ -44,6 +44,7 @@ import org.jclouds.domain.Location;
 import org.jclouds.ec2.compute.domain.RegionAndName;
 import org.jclouds.ec2.compute.functions.ImagesToRegionAndIdMap;
 import org.jclouds.ec2.compute.options.EC2TemplateOptions;
+import org.jclouds.rest.AuthorizationException;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Functions;
@@ -54,6 +55,8 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.Atomics;
+import com.google.inject.util.Providers;
 
 @Test(groups = "unit", singleThreaded = true)
 public class EC2TemplateBuilderImplTest extends TemplateBuilderImplTest {
@@ -86,8 +89,10 @@ public class EC2TemplateBuilderImplTest extends TemplateBuilderImplTest {
                   ImagesToRegionAndIdMap.imagesToMap(images.get()))));
       }
 
-      return new EC2TemplateBuilderImpl(locations, new ImageCacheSupplier(images, 60), sizes, Suppliers.ofInstance(defaultLocation),
-            optionsProvider, templateBuilderProvider, getImageStrategy, Suppliers.<LoadingCache<RegionAndName, ? extends Image>>ofInstance(imageMap));
+      return new EC2TemplateBuilderImpl(locations, new ImageCacheSupplier(images, 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy)), sizes,
+            Suppliers.ofInstance(defaultLocation), optionsProvider, templateBuilderProvider,
+            Suppliers.<LoadingCache<RegionAndName, ? extends Image>> ofInstance(imageMap));
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
index f03c456..338dee7 100644
--- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/compute/config/NovaComputeServiceContextModule.java
@@ -80,7 +80,6 @@ import org.jclouds.openstack.nova.v2_0.predicates.FindSecurityGroupWithNameAndRe
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
-import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Supplier;
 import com.google.common.base.Suppliers;
@@ -280,14 +279,4 @@ public class NovaComputeServiceContextModule extends
    protected final Map<org.jclouds.openstack.nova.v2_0.domain.Image.Status, Image.Status> toPortableImageStatus() {
       return toPortableImageStatus;
    }
-
-   @Override
-   protected Optional<ImageExtension> provideImageExtension(Injector i) {
-      return Optional.of(i.getInstance(ImageExtension.class));
-   }
-
-   @Override
-   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.of(i.getInstance(SecurityGroupExtension.class));
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java b/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
index c1bdf38..ab5fb0f 100644
--- a/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
+++ b/compute/src/main/java/org/jclouds/compute/config/BaseComputeServiceContextModule.java
@@ -52,6 +52,7 @@ import org.jclouds.compute.options.RunScriptOptions;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants;
 import org.jclouds.compute.strategy.CustomizeNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
+import org.jclouds.compute.strategy.GetImageStrategy;
 import org.jclouds.compute.strategy.InitializeRunScriptOnNodeOrPlaceInBadMap;
 import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.config.ValueOfConfigurationKeyOrNull;
@@ -71,8 +72,10 @@ import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.inject.AbstractModule;
+import com.google.inject.Binding;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
+import com.google.inject.Key;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
@@ -112,9 +115,6 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
       }, InitializeRunScriptOnNodeOrPlaceInBadMap.class).build(InitializeRunScriptOnNodeOrPlaceInBadMap.Factory.class));
 
       install(new FactoryModuleBuilder().build(BlockUntilInitScriptStatusIsZeroThenReturnOutput.Factory.class));
-
-      bind(new TypeLiteral<Supplier<Set<? extends Image>>>() {
-      }).annotatedWith(Memoized.class).to(ImageCacheSupplier.class);
    }
 
    protected void bindCredentialsOverriderFunction() {
@@ -238,35 +238,27 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
 
       }, images);
    }
-
+   
    @Provides
    @Singleton
-   @Named("imageCache")
-   protected final Supplier<Set<? extends Image>> supplyImageCache(AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds,
-         final Supplier<Set<? extends Image>> imageSupplier, Injector injector) {
-      if (shouldEagerlyParseImages(injector)) {
-         return supplyImageCache(authException, seconds, imageSupplier);
-      } else {
-         return supplyNonParsingImageCache(authException, seconds, imageSupplier, injector);
-      }
+   @Memoized
+   protected final Supplier<Set<? extends Image>> supplyImageCache(
+         AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds,
+         final Supplier<Set<? extends Image>> imageSupplier, com.google.inject.Provider<GetImageStrategy> imageLoader, Injector injector) {
+      Supplier<Set<? extends Image>> parsingImageSupplier = shouldEagerlyParseImages(injector) ? imageSupplier
+            : supplyNonParsingImages(imageSupplier, injector);
+      return new ImageCacheSupplier(parsingImageSupplier, seconds, authException, imageLoader);
    }
 
    protected boolean shouldEagerlyParseImages(Injector injector) {
       return true;
    }
 
-   protected Supplier<Set<? extends Image>> supplyImageCache(AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds,
-         final Supplier<Set<? extends Image>> imageSupplier) {
-      return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException, imageSupplier, seconds,
-               TimeUnit.SECONDS);
-   }
-
    /**
-    * For overriding; default impl is same as {@link supplyImageCache(seconds, imageSupplier)}
+    * For overriding; default impl just returns the image supplier.
     */
-   protected Supplier<Set<? extends Image>> supplyNonParsingImageCache(AtomicReference<AuthorizationException> authException, @Named(PROPERTY_SESSION_INTERVAL) long seconds,
-            final Supplier<Set<? extends Image>> imageSupplier, Injector injector) {
-      return supplyImageCache(authException, seconds, imageSupplier);
+   protected Supplier<Set<? extends Image>> supplyNonParsingImages(final Supplier<Set<? extends Image>> imageSupplier, Injector injector) {
+      return imageSupplier;
    }
 
    @Provides
@@ -312,21 +304,15 @@ public abstract class BaseComputeServiceContextModule extends AbstractModule {
    @Provides
    @Singleton
    public final Optional<ImageExtension> guiceProvideImageExtension(Injector i) {
-      return provideImageExtension(i);
-   }
-
-   protected Optional<ImageExtension> provideImageExtension(Injector i) {
-      return Optional.absent();
+      Binding<ImageExtension> binding = i.getExistingBinding(Key.get(ImageExtension.class));
+      return binding == null ? Optional.<ImageExtension> absent() : Optional.of(binding.getProvider().get());
    }
 
    @Provides
    @Singleton
    protected final Optional<SecurityGroupExtension> guiceProvideSecurityGroupExtension(Injector i) {
-      return provideSecurityGroupExtension(i);
-   }
-
-   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.absent();
+      Binding<SecurityGroupExtension> binding = i.getExistingBinding(Key.get(SecurityGroupExtension.class));
+      return binding == null ? Optional.<SecurityGroupExtension> absent() : Optional.of(binding.getProvider().get());
    }
    
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilder.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilder.java b/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilder.java
index cea193d..e2adadc 100644
--- a/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilder.java
+++ b/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilder.java
@@ -56,7 +56,6 @@ public interface TemplateBuilder {
     *
     * @since 1.5
     */
-   @Beta
    TemplateBuilder from(TemplateBuilderSpec spec);
    
    /**
@@ -66,7 +65,6 @@ public interface TemplateBuilder {
     * @param spec a String in the format specified by {@link TemplateBuilderSpec}
     * @since 1.5
     */
-   @Beta
    TemplateBuilder from(String spec);
    
    /**
@@ -202,5 +200,19 @@ public interface TemplateBuilder {
     * Normal usage is to build up all options and pass them to the builder with a single call to this method.
     */
    TemplateBuilder options(TemplateOptions options);
+   
+   /**
+    * Forces an image lookup against the provider to reload the image cache.
+    * <p>
+    * Use with caution. In some providers getting the list of images is an
+    * expensive operation, and the use of the image cache is recommended. If
+    * there is a need to minimize the amount of time the images are cached,
+    * consider configuring the cache expiration time by setting the
+    * {@link org.jclouds.Constants#PROPERTY_SESSION_INTERVAL} property.
+    * 
+    * @since 2.0
+    */
+   @Beta
+   TemplateBuilder forceCacheReload();
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilderSpec.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilderSpec.java b/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilderSpec.java
index 5a073e0..d00cb3b 100644
--- a/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilderSpec.java
+++ b/compute/src/main/java/org/jclouds/compute/domain/TemplateBuilderSpec.java
@@ -145,6 +145,7 @@ public class TemplateBuilderSpec {
          .put("loginUser", new LoginUserParser())
          .put("authenticateSudo", new AuthenticateSudoParser())
          .put("locationId", new LocationIdParser())
+         .put("forceCacheReload", new ForceCacheReloadParser())
          .build();
 
    @VisibleForTesting
@@ -177,6 +178,8 @@ public class TemplateBuilderSpec {
    Boolean authenticateSudo;
    @VisibleForTesting
    String locationId;
+   @VisibleForTesting
+   Boolean forceCacheReload;
    
    /** Specification; used for toParseableString(). */
    // transient in case people using serializers don't want this to show up
@@ -279,6 +282,9 @@ public class TemplateBuilderSpec {
       if (locationId != null) {
          builder.locationId(locationId);
       }
+      if (forceCacheReload != null && forceCacheReload) {
+         builder.forceCacheReload();
+      }
       return builder;
    }
 
@@ -304,7 +310,8 @@ public class TemplateBuilderSpec {
    @Override
    public int hashCode() {
       return Objects.hashCode(hardwareId, minCores, minRam, hypervisorMatches, imageId, imageNameMatches, osFamily,
-            osVersionMatches, os64Bit, osArchMatches, osDescriptionMatches, loginUser, authenticateSudo, locationId);
+            osVersionMatches, os64Bit, osArchMatches, osDescriptionMatches, loginUser, authenticateSudo, locationId,
+            forceCacheReload);
    }
 
    @Override
@@ -322,7 +329,7 @@ public class TemplateBuilderSpec {
             && equal(osVersionMatches, that.osVersionMatches) && equal(os64Bit, that.os64Bit)
             && equal(osArchMatches, that.osArchMatches) && equal(osDescriptionMatches, that.osDescriptionMatches)
             && equal(loginUser, that.loginUser) && equal(authenticateSudo, that.authenticateSudo)
-            && equal(locationId, that.locationId);
+            && equal(locationId, that.locationId) && equal(forceCacheReload, that.forceCacheReload);
    }
    
    /** Base class for parsing doubles. */
@@ -563,6 +570,15 @@ public class TemplateBuilderSpec {
       }
    }
    
+   /** Parse forceCacheReload */
+   static class ForceCacheReloadParser extends BooleanParser {
+      @Override
+      protected void parseBoolean(TemplateBuilderSpec spec, boolean value) {
+         checkArgument(spec.forceCacheReload == null, "forceCacheReload was already set to ", spec.forceCacheReload);
+         spec.forceCacheReload = value;
+      }
+   }
+   
    public String getHardwareId() {
       return hardwareId;
    }
@@ -622,4 +638,9 @@ public class TemplateBuilderSpec {
    public String getSpecification() {
       return specification;
    }
+
+   public Boolean getForceCacheReload() {
+      return forceCacheReload;
+   }
+   
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/domain/internal/TemplateBuilderImpl.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/domain/internal/TemplateBuilderImpl.java b/compute/src/main/java/org/jclouds/compute/domain/internal/TemplateBuilderImpl.java
index 72a4b47..2865409 100644
--- a/compute/src/main/java/org/jclouds/compute/domain/internal/TemplateBuilderImpl.java
+++ b/compute/src/main/java/org/jclouds/compute/domain/internal/TemplateBuilderImpl.java
@@ -16,6 +16,7 @@
  */
 package org.jclouds.compute.domain.internal;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.base.Predicates.and;
@@ -52,8 +53,8 @@ import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.domain.TemplateBuilderSpec;
 import org.jclouds.compute.options.TemplateOptions;
+import org.jclouds.compute.predicates.ImagePredicates;
 import org.jclouds.compute.reference.ComputeServiceConstants;
-import org.jclouds.compute.strategy.GetImageStrategy;
 import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.domain.Location;
 import org.jclouds.logging.Logger;
@@ -84,7 +85,6 @@ public class TemplateBuilderImpl implements TemplateBuilder {
    protected final Supplier<Location> defaultLocation;
    protected final Provider<TemplateOptions> optionsProvider;
    protected final Provider<TemplateBuilder> defaultTemplateProvider;
-   protected final GetImageStrategy getImageStrategy;
 
    @VisibleForTesting
    protected Location location;
@@ -128,19 +128,21 @@ public class TemplateBuilderImpl implements TemplateBuilder {
    protected boolean fastest;
    @VisibleForTesting
    protected TemplateOptions options;
+   @VisibleForTesting
+   protected Boolean forceCacheReload;
 
    @Inject
    protected TemplateBuilderImpl(@Memoized Supplier<Set<? extends Location>> locations,
-         ImageCacheSupplier images, @Memoized Supplier<Set<? extends Hardware>> hardwares,
+         @Memoized Supplier<Set<? extends Image>> images, @Memoized Supplier<Set<? extends Hardware>> hardwares,
          Supplier<Location> defaultLocation, @Named("DEFAULT") Provider<TemplateOptions> optionsProvider,
-         @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider, GetImageStrategy getImageStrategy) {
+         @Named("DEFAULT") Provider<TemplateBuilder> defaultTemplateProvider) {
       this.locations = checkNotNull(locations, "locations");
-      this.images = checkNotNull(images, "images");
+      checkArgument(images instanceof ImageCacheSupplier, "an instance of the ImageCacheSupplier is needed");
+      this.images = ImageCacheSupplier.class.cast(images);
       this.hardwares = checkNotNull(hardwares, "hardwares");
       this.defaultLocation = checkNotNull(defaultLocation, "defaultLocation");
       this.optionsProvider = checkNotNull(optionsProvider, "optionsProvider");
       this.defaultTemplateProvider = checkNotNull(defaultTemplateProvider, "defaultTemplateProvider");
-      this.getImageStrategy = checkNotNull(getImageStrategy, "getImageStrategy");
    }
 
    static Predicate<Hardware> supportsImagesPredicate(final Iterable<? extends Image> images) {
@@ -175,26 +177,6 @@ public class TemplateBuilderImpl implements TemplateBuilder {
 
    });
 
-   private final Predicate<Image> idPredicate = new Predicate<Image>() {
-      @Override
-      public boolean apply(Image input) {
-         boolean returnVal = true;
-         if (imageId != null) {
-            returnVal = imageId.equals(input.getId());
-            // match our input params so that the later predicates pass.
-            if (returnVal) {
-               fromImage(input);
-            }
-         }
-         return returnVal;
-      }
-
-      @Override
-      public String toString() {
-         return "imageId(" + imageId + ")";
-      }
-   };
-
    private final Predicate<OperatingSystem> osFamilyPredicate = new Predicate<OperatingSystem>() {
 
       @Override
@@ -691,7 +673,7 @@ public class TemplateBuilderImpl implements TemplateBuilder {
 
       Image image = null;
       if (imageId != null) {
-         image = findImageWithId(images);
+         image = loadImageWithId(images);
          if (currentLocationWiderThan(image.getLocation()))
             this.location = image.getLocation();
       }
@@ -733,27 +715,16 @@ public class TemplateBuilderImpl implements TemplateBuilder {
       return supportedImages;
    }
 
-   private Image findImageWithId(Set<? extends Image> images) {
-      // Try to find the image in the cache and fallback to the GetImageStrategy
-      // see https://issues.apache.org/jira/browse/JCLOUDS-570
-      Optional<? extends Image> image = tryFind(images, idPredicate);
-      if (image.isPresent()) {
-         return image.get();
-      }
-
-      logger.info("Image %s not found in the image cache. Trying to get it from the provider...", imageId);
-      // Note that this will generate make a call to the provider instead of using a cache, but
-      // this will be executed rarely, only when an image is not present in the image list but
-      // it actually exists in the provider. It shouldn't be an expensive call so using a cache just for
-      // this corner case is overkill.
-      Image imageFromProvider = getImageStrategy.getImage(imageId);
-      if (imageFromProvider == null) {
-         throwNoSuchElementExceptionAfterLoggingImageIds(format("%s not found", idPredicate), images);
+   private Image loadImageWithId(Iterable<? extends Image> images) {
+      Optional<? extends Image> image = tryFind(images, ImagePredicates.idEquals(imageId));
+      if (!image.isPresent()) {
+         image = this.images.get(imageId); // Load the image from the cache, and refresh if missing
+         if (!image.isPresent()) {
+            throw throwNoSuchElementExceptionAfterLoggingImageIds(format("imageId(%s) not found", imageId), images);
+         }
       }
-      // Register the just found image in the image cache, so subsequent uses of the TemplateBuilder and
-      // the ComptueService find it.
-      this.images.registerImage(imageFromProvider);
-      return imageFromProvider;
+      fromImage(image.get());
+      return image.get();
    }
 
    private Hardware findHardwareWithId(Set<? extends Hardware> hardwaresToSearch) {
@@ -856,10 +827,8 @@ public class TemplateBuilderImpl implements TemplateBuilder {
             logger.trace("<<   matched images(%s)", transform(matchingImages, imageToId));
          return imageChooser().apply(matchingImages);
       } catch (NoSuchElementException exception) {
-         throwNoSuchElementExceptionAfterLoggingImageIds(format("no image matched params: %s", toString()),
-                  supportedImages);
-         assert false;
-         return null;
+         throw throwNoSuchElementExceptionAfterLoggingImageIds(format("no image matched params: %s", toString()),
+               supportedImages);
       }
    }
 
@@ -885,7 +854,7 @@ public class TemplateBuilderImpl implements TemplateBuilder {
       return maxes;
    }
    protected Set<? extends Image> getImages() {
-      return images.get();
+      return forceCacheReload != null && forceCacheReload ? images.rebuildCache() : images.get();
    }
 
    private Predicate<Image> buildImagePredicate() {
@@ -1157,6 +1126,7 @@ public class TemplateBuilderImpl implements TemplateBuilder {
       toString.add("os64Bit", os64Bit);
       toString.add("hardwareId", hardwareId);
       toString.add("hypervisor", hypervisor);
+      toString.add("forceCacheReload", forceCacheReload);
       return toString;
    }
 
@@ -1176,4 +1146,11 @@ public class TemplateBuilderImpl implements TemplateBuilder {
       return from(TemplateBuilderSpec.parse(spec));
    }
 
+   @Override
+   public TemplateBuilder forceCacheReload() {
+      this.forceCacheReload = true;
+      return this;
+   }
+
+   
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/extensions/internal/DelegatingImageExtension.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/extensions/internal/DelegatingImageExtension.java b/compute/src/main/java/org/jclouds/compute/extensions/internal/DelegatingImageExtension.java
new file mode 100644
index 0000000..e0ecf8b
--- /dev/null
+++ b/compute/src/main/java/org/jclouds/compute/extensions/internal/DelegatingImageExtension.java
@@ -0,0 +1,74 @@
+/*
+ * 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.compute.extensions.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.domain.ImageTemplate;
+import org.jclouds.compute.extensions.ImageExtension;
+import org.jclouds.compute.suppliers.ImageCacheSupplier;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Delegates to the provider specific {@link ImageExtension} and takes care of
+ * propagating the changes made to the images to the image cache.
+ */
+@Beta
+public class DelegatingImageExtension implements ImageExtension {
+
+   private final ImageCacheSupplier imageCache;
+   private final ImageExtension delegate;
+
+   public DelegatingImageExtension(ImageCacheSupplier imageCache, ImageExtension delegate) {
+      this.imageCache = checkNotNull(imageCache, "imageCache");
+      this.delegate = checkNotNull(delegate, "delegate");
+   }
+
+   public ImageTemplate buildImageTemplateFromNode(String name, String id) {
+      return delegate.buildImageTemplateFromNode(name, id);
+   }
+
+   public ListenableFuture<Image> createImage(ImageTemplate template) {
+      ListenableFuture<Image> future = delegate.createImage(template);
+      Futures.addCallback(future, new FutureCallback<Image>() {
+         @Override
+         public void onSuccess(Image result) {
+            imageCache.registerImage(result);
+         }
+
+         @Override
+         public void onFailure(Throwable t) {
+
+         }
+      });
+      return future;
+   }
+
+   public boolean deleteImage(String id) {
+      boolean success = delegate.deleteImage(id);
+      if (success) {
+         imageCache.removeImage(id);
+      }
+      return success;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
index 3ba198c..6cde18d 100644
--- a/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
+++ b/compute/src/main/java/org/jclouds/compute/internal/BaseComputeService.java
@@ -66,6 +66,7 @@ import org.jclouds.compute.domain.Template;
 import org.jclouds.compute.domain.TemplateBuilder;
 import org.jclouds.compute.extensions.ImageExtension;
 import org.jclouds.compute.extensions.SecurityGroupExtension;
+import org.jclouds.compute.extensions.internal.DelegatingImageExtension;
 import org.jclouds.compute.options.RunScriptOptions;
 import org.jclouds.compute.options.TemplateOptions;
 import org.jclouds.compute.reference.ComputeServiceConstants;
@@ -80,6 +81,7 @@ import org.jclouds.compute.strategy.RebootNodeStrategy;
 import org.jclouds.compute.strategy.ResumeNodeStrategy;
 import org.jclouds.compute.strategy.RunScriptOnNodeAndAddToGoodMapOrPutExceptionIntoBadMap;
 import org.jclouds.compute.strategy.SuspendNodeStrategy;
+import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.domain.Credentials;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LoginCredentials;
@@ -180,8 +182,13 @@ public class BaseComputeService implements ComputeService {
       this.runScriptOnNodeFactory = checkNotNull(runScriptOnNodeFactory, "runScriptOnNodeFactory");
       this.persistNodeCredentials = checkNotNull(persistNodeCredentials, "persistNodeCredentials");
       this.userExecutor = checkNotNull(userExecutor, "userExecutor");
-      this.imageExtension = checkNotNull(imageExtension, "imageExtension");
       this.securityGroupExtension = checkNotNull(securityGroupExtension, "securityGroupExtension");
+      if (imageExtension.isPresent() && images instanceof ImageCacheSupplier) {
+         this.imageExtension = Optional.<ImageExtension> of(new DelegatingImageExtension(ImageCacheSupplier.class
+               .cast(images), imageExtension.get()));
+      } else {
+         this.imageExtension = checkNotNull(imageExtension, "imageExtension");
+      }
    }
 
    /**

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
index 6ddc436..be419d5 100644
--- a/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
+++ b/compute/src/main/java/org/jclouds/compute/stub/config/StubComputeServiceContextModule.java
@@ -17,12 +17,8 @@
 package org.jclouds.compute.stub.config;
 
 import org.jclouds.compute.config.JCloudsNativeComputeServiceAdapterContextModule;
-import org.jclouds.compute.extensions.SecurityGroupExtension;
 import org.jclouds.concurrent.SingleThreaded;
 
-import com.google.common.base.Optional;
-import com.google.inject.Injector;
-
 @SingleThreaded
 public class StubComputeServiceContextModule extends JCloudsNativeComputeServiceAdapterContextModule {
 
@@ -36,9 +32,4 @@ public class StubComputeServiceContextModule extends JCloudsNativeComputeService
       super.configure();
    }
 
-   @Override
-   protected Optional<SecurityGroupExtension> provideSecurityGroupExtension(Injector i) {
-      return Optional.of(i.getInstance(SecurityGroupExtension.class));
-   }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/main/java/org/jclouds/compute/suppliers/ImageCacheSupplier.java
----------------------------------------------------------------------
diff --git a/compute/src/main/java/org/jclouds/compute/suppliers/ImageCacheSupplier.java b/compute/src/main/java/org/jclouds/compute/suppliers/ImageCacheSupplier.java
index 400525b..d2d111d 100644
--- a/compute/src/main/java/org/jclouds/compute/suppliers/ImageCacheSupplier.java
+++ b/compute/src/main/java/org/jclouds/compute/suppliers/ImageCacheSupplier.java
@@ -17,68 +17,181 @@
 package org.jclouds.compute.suppliers;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.concat;
-import static org.jclouds.Constants.PROPERTY_SESSION_INTERVAL;
 
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
+import javax.annotation.Resource;
 import javax.inject.Named;
-import javax.inject.Singleton;
 
 import org.jclouds.compute.domain.Image;
+import org.jclouds.compute.reference.ComputeServiceConstants;
+import org.jclouds.compute.strategy.GetImageStrategy;
+import org.jclouds.logging.Logger;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
+import org.jclouds.rest.suppliers.ValueLoadedCallback;
 
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Supplier;
-import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
-import com.google.inject.Inject;
+import com.google.common.collect.Maps;
+import com.google.inject.Provider;
 
 /**
- * Image supplier that allows new images to be registered.
+ * Memoized image supplier that allows new images to be registered at runtime.
  * <p>
- * This is a wrapper for the memoized image supplier (the actual image cache), to provide a way to register new images as
- * needed. Once a new image is created by the {@link org.jclouds.compute.extensions.ImageExtension}, or discovered by
- * other means (see https://issues.apache.org/jira/browse/JCLOUDS-570) this supplier will allow the image to be appended
- * to the cached list, so it can be properly used normally.
+ * The memoized <code>Supplier<Set<? extends Image>></code> is a static data
+ * structure that can't be properly modified at runtime. This class is a wrapper
+ * for the image supplier to provide a way to register new images as needed.
+ * Once a new image is created by the
+ * {@link org.jclouds.compute.extensions.ImageExtension}, or discovered by other
+ * means (see https://issues.apache.org/jira/browse/JCLOUDS-570) this supplier
+ * will allow the image to be appended to the cached list.
  */
-@Singleton
-public class ImageCacheSupplier implements Supplier<Set<? extends Image>> {
+@Beta
+public class ImageCacheSupplier implements Supplier<Set<? extends Image>>, ValueLoadedCallback<Set<? extends Image>> {
 
-   private final Supplier<Set<? extends Image>> imageCache;
-
-   private final Cache<String, Image> uncachedImages;
+   /**
+    * The image supplier that fetches the images from the provider.
+    */
+   private final Supplier<Set<? extends Image>> liveImageSupplier;
+   
+   /**
+    * The image supplier that loads the images and caches them for the duration
+    * of the session. Delegates to the {@link #liveImageSupplier}.
+    */
+   private final Supplier<Set<? extends Image>> memoizedImageSupplier;
+   
+   /**
+    * The actual image cache. It acts as a view over the memoized image supplier
+    * and allows to add and remove images at runtime.
+    */
+   private final LoadingCache<String, Image> imageCache;
+   
+   @Resource
+   @Named(ComputeServiceConstants.COMPUTE_LOGGER)
+   protected Logger logger = Logger.NULL;
 
-   @Inject
-   public ImageCacheSupplier(@Named("imageCache") Supplier<Set<? extends Image>> imageCache,
-         @Named(PROPERTY_SESSION_INTERVAL) long sessionIntervalSeconds) {
-      this.imageCache = checkNotNull(imageCache, "imageCache");
-      // We use a cache to let the entries in the "uncached" set expire as soon as the image cache expires. We want the
-      // uncached set to be regenerated when the original cache is also regenerated.
-      this.uncachedImages = CacheBuilder.newBuilder().expireAfterWrite(sessionIntervalSeconds, TimeUnit.SECONDS)
-            .build();
+   public ImageCacheSupplier(Supplier<Set<? extends Image>> imageSupplier, long sessionIntervalSeconds,
+         AtomicReference<AuthorizationException> authException, final Provider<GetImageStrategy> imageLoader) {
+      liveImageSupplier = imageSupplier;
+      memoizedImageSupplier = MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
+            imageSupplier, sessionIntervalSeconds, TimeUnit.SECONDS, this);
+      imageCache = CacheBuilder.newBuilder().expireAfterWrite(sessionIntervalSeconds, TimeUnit.SECONDS)
+            .build(new CacheLoader<String, Image>() {
+               @Override
+               public Image load(String key) throws Exception {
+                  return imageLoader.get().getImage(key);
+               }
+            });
    }
-
+   
    @Override
    public Set<? extends Image> get() {
-      return ImmutableSet.copyOf(concat(imageCache.get(), uncachedImages.asMap().values()));
+      // Call the memoized supplier. The "imageCache" is subscribed to the
+      // reloads of the supplier once it expires. For this reason we ignore the
+      // value returned by the supplier: every time it is reloaded, the cache
+      // will be notified and re-populated with the fresh values. Any other call
+      // to the supplier that returns a cached value will be ignored and the
+      // values in the cache will be returned, as the cache properly handles
+      // individual image additions and deletions (introduced, for example, by
+      // the usage of the ImageExtension).
+      memoizedImageSupplier.get();
+      return ImmutableSet.copyOf(imageCache.asMap().values());
+   }
+
+   /**
+    * The cache is subscribed to value loading events generated by the
+    * {@link MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier}.
+    * <p>
+    * Every time the memoized supplier reloads a value, an event will be
+    * populated and this method will handle it. This makes it possible to
+    * refresh the cache with the last values everytime they are reloaded.
+    */
+   @Override
+   public void valueLoaded(Optional<Set<? extends Image>> value) {
+      if (value.isPresent()) {
+         reset(value.get());
+      }
+   }
+   
+   /**
+    * Resets the cache to the given set of images.
+    * <p>
+    * This method is called when the memoized image supplier is reloaded, or
+    * when the cache needs to be refreshed (for example when the TempalteBuilder
+    * is invoked forcing a fresh image lookup.
+    */
+   public void reset(Set<? extends Image> images) {
+      imageCache.invalidateAll();
+      imageCache.putAll(Maps.uniqueIndex(images, new Function<Image, String>() {
+         @Override
+         public String apply(Image input) {
+            return input.getId();
+         }
+      }));
+   }
+   
+   /**
+    * Calls the {@link #liveImageSupplier} to get the current images and
+    * rebuilds the cache with them.
+    */
+   public Set<? extends Image> rebuildCache() {
+      Set<? extends Image> images = liveImageSupplier.get();
+      reset(images);
+      return images;
+   }
+
+   /**
+    * Loads an image by id.
+    * <p>
+    * This methods returns the cached image, or performs a call to retrieve it
+    * if the image is still not cached.
+    */
+   public Optional<? extends Image> get(String id) {
+      try {
+         return Optional.fromNullable(imageCache.getUnchecked(id));
+      } catch (Exception ex) {
+         logger.error(ex, "Unexpected error loading image %s", id);
+         return Optional.absent();
+      }
    }
 
    /**
     * Registers a new image in the image cache.
     * <p>
-    * This method should be called to register new images into the image cache, when some image that is known to exist
-    * in the provider is still not cached. For example, this can happen when an image is created after the image cache
-    * has been populated for the first time.
+    * This method should be called to register new images into the image cache
+    * when some image that is known to exist in the provider is still not
+    * cached. For example, this can happen when an image is created after the
+    * image cache has been populated for the first time.
     * <p>
-    * Note that this method does not check if the image is already cached, to avoid loading all images if the image
-    * cache is still not populated.
+    * Note that this method does not check if the image is already cached, to
+    * avoid loading all images if the image cache is still not populated.
     *
     * @param image The image to be registered to the cache.
     */
    public void registerImage(Image image) {
       checkNotNull(image, "image");
-      uncachedImages.put(image.getId(), image);
+      imageCache.put(image.getId(), image);
+   }
+
+   /**
+    * Removes an image from the image cache.
+    * <p>
+    * This method should be called to invalidate an already cached image, when
+    * some image known to not exist in the provider is still cached.
+    * 
+    * @param imageId The id of the image to invalidate.
+    */
+   public void removeImage(String imageId) {
+      imageCache.invalidate(checkNotNull(imageId, "imageId"));
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/test/java/org/jclouds/compute/domain/TemplateBuilderSpecTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/compute/domain/TemplateBuilderSpecTest.java b/compute/src/test/java/org/jclouds/compute/domain/TemplateBuilderSpecTest.java
index cd51b33..8c11751 100644
--- a/compute/src/test/java/org/jclouds/compute/domain/TemplateBuilderSpecTest.java
+++ b/compute/src/test/java/org/jclouds/compute/domain/TemplateBuilderSpecTest.java
@@ -61,6 +61,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get(), templateBuilders.get().from(spec));
    }
 
@@ -81,6 +82,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().hardwareId("m1.small"),
             templateBuilders.get().from(spec));
    }
@@ -132,6 +134,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().minCores(32), templateBuilders.get().from(spec));
    }
 
@@ -160,6 +163,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().minRam(10), templateBuilders.get().from(spec));
    }
 
@@ -180,6 +184,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().minRam(10), templateBuilders.get().from(spec));
    }
 
@@ -216,6 +221,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().hypervisorMatches("OpenVZ"),
             templateBuilders.get().from(spec));
    }
@@ -246,6 +252,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().imageId("us-east-1/ami-fffffff"),
             templateBuilders.get().from(spec));
    }
@@ -315,6 +322,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().imageNameMatches(".*w/ None.*"),
             templateBuilders.get().from(spec));
    }
@@ -345,6 +353,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().osFamily(OsFamily.UBUNTU),
             templateBuilders.get().from(spec));
    }
@@ -374,6 +383,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().osVersionMatches(".*[Aa]utomated SSH Access.*"),
             templateBuilders.get().from(spec));
    }
@@ -403,6 +413,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().os64Bit(true),
             templateBuilders.get().from(spec));
    }
@@ -432,6 +443,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().osArchMatches("x86"),
             templateBuilders.get().from(spec));
    }
@@ -461,6 +473,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().osDescriptionMatches("^((?!MGC).)*$"),
             templateBuilders.get().from(spec));
    }
@@ -491,6 +504,7 @@ public class TemplateBuilderSpecTest {
       assertEquals(spec.loginUser, "ubuntu");
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(
             templateBuilders.get().options(overrideLoginUser("ubuntu")), templateBuilders
                   .get().from(spec));
@@ -522,6 +536,7 @@ public class TemplateBuilderSpecTest {
       assertEquals(spec.loginUser, "root:toor");
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(
             templateBuilders.get().options(
                   overrideLoginCredentials(LoginCredentials.builder().user("root").password("toor").build())),
@@ -554,12 +569,13 @@ public class TemplateBuilderSpecTest {
       assertEquals(spec.loginUser, "root:toor");
       assertEquals(spec.authenticateSudo.booleanValue(), true);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(
             templateBuilders.get().options(
                   overrideLoginCredentials(LoginCredentials.builder().user("root").password("toor")
                         .authenticateSudo(true).build())), templateBuilders.get().from(spec));
    }
-
+   
    public void testParse_authenticateSudoRepeated() {
       try {
          parse("loginUser=root:toor,authenticateSudo=true,authenticateSudo=false");
@@ -568,6 +584,36 @@ public class TemplateBuilderSpecTest {
          // expected
       }
    }
+   
+   public void testParse_forceCacheReload() {
+      TemplateBuilderSpec spec = parse("forceCacheReload=true");
+      assertNull(spec.hardwareId);
+      assertNull(spec.minCores);
+      assertNull(spec.minRam);
+      assertNull(spec.minDisk);
+      assertNull(spec.imageId);
+      assertNull(spec.imageNameMatches);
+      assertNull(spec.hypervisorMatches);
+      assertNull(spec.osFamily);
+      assertNull(spec.osVersionMatches);
+      assertNull(spec.os64Bit);
+      assertNull(spec.osArchMatches);
+      assertNull(spec.osDescriptionMatches);
+      assertNull(spec.loginUser);
+      assertNull(spec.authenticateSudo);
+      assertNull(spec.locationId);
+      assertEquals(spec.forceCacheReload.booleanValue(), true);
+      assertTemplateBuilderEquivalence(templateBuilders.get().forceCacheReload(), templateBuilders.get().from(spec));
+   }
+
+   public void testParse_forceCacheReloadRepeated() {
+      try {
+         parse("forceCacheReload=true,forceCacheReload=false");
+         fail("Expected exception");
+      } catch (IllegalArgumentException expected) {
+         // expected
+      }
+   }
 
    public void testParse_locationId() {
       TemplateBuilderSpec spec = parse("locationId=stub");
@@ -586,6 +632,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertEquals(spec.locationId, "stub");
+      assertNull(spec.forceCacheReload);
       assertTemplateBuilderEquivalence(templateBuilders.get().locationId("stub"),
             templateBuilders.get().from(spec));
    }
@@ -616,6 +663,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       TemplateBuilder expected = templateBuilders.get().osVersionMatches("1[012].[01][04]").imageNameMatches(".*w/ None.*").osFamily(OsFamily.UBUNTU);
       assertTemplateBuilderEquivalence(expected, templateBuilders.get().from(spec));
    }
@@ -637,6 +685,7 @@ public class TemplateBuilderSpecTest {
       assertNull(spec.loginUser);
       assertNull(spec.authenticateSudo);
       assertNull(spec.locationId);
+      assertNull(spec.forceCacheReload);
       TemplateBuilder expected = templateBuilders.get().minRam(10).osFamily(OsFamily.UBUNTU);
       assertTemplateBuilderEquivalence(expected, templateBuilders.get().from(spec));
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java b/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java
index eaaf17e..cc2708c 100644
--- a/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java
+++ b/compute/src/test/java/org/jclouds/compute/domain/internal/TemplateBuilderImplTest.java
@@ -50,6 +50,7 @@ import org.jclouds.compute.suppliers.ImageCacheSupplier;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LocationBuilder;
 import org.jclouds.domain.LocationScope;
+import org.jclouds.rest.AuthorizationException;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Function;
@@ -62,6 +63,8 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Ordering;
+import com.google.common.util.concurrent.Atomics;
+import com.google.inject.util.Providers;
 
 @Test(groups = "unit", singleThreaded = true, testName = "TemplateBuilderImplTest")
 public class TemplateBuilderImplTest {
@@ -468,8 +471,10 @@ public class TemplateBuilderImplTest {
             Supplier<Set<? extends Image>> images, Supplier<Set<? extends Hardware>> hardwares,
             Location defaultLocation, Provider<TemplateOptions> optionsProvider,
             Provider<TemplateBuilder> templateBuilderProvider, GetImageStrategy getImageStrategy) {
-      TemplateBuilderImpl template = new TemplateBuilderImpl(locations, new ImageCacheSupplier(images, 60), hardwares, Suppliers
-               .ofInstance(defaultLocation), optionsProvider, templateBuilderProvider, getImageStrategy);
+      TemplateBuilderImpl template = new TemplateBuilderImpl(locations, new ImageCacheSupplier(images, 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy)), hardwares,
+            Suppliers.ofInstance(defaultLocation),
+            optionsProvider, templateBuilderProvider);
       return template;
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseImageExtensionLiveTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseImageExtensionLiveTest.java b/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseImageExtensionLiveTest.java
index 8c0476e..878768e 100644
--- a/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseImageExtensionLiveTest.java
+++ b/compute/src/test/java/org/jclouds/compute/extensions/internal/BaseImageExtensionLiveTest.java
@@ -20,8 +20,10 @@ import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.jclouds.compute.predicates.NodePredicates.inGroup;
 import static org.jclouds.util.Predicates2.retry;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
+import java.util.NoSuchElementException;
 import java.util.concurrent.ExecutionException;
 
 import javax.annotation.Resource;
@@ -90,46 +92,40 @@ public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceConte
 
    @Test(groups = { "integration", "live" }, singleThreaded = true)
    public void testCreateImage() throws RunNodesException, InterruptedException, ExecutionException {
-
       ComputeService computeService = view.getComputeService();
-
       Optional<ImageExtension> imageExtension = computeService.getImageExtension();
-
       assertTrue(imageExtension.isPresent(), "image extension was not present");
 
       Template template = getNodeTemplate();
-
       NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup(imageGroup, 1, template));
-
       checkReachable(node);
 
       logger.info("Creating image from node %s, started with template: %s", node, template);
-
       ImageTemplate newImageTemplate = imageExtension.get().buildImageTemplateFromNode(imageGroup,
               node.getId());
-
       Image image = imageExtension.get().createImage(newImageTemplate).get();
-
       logger.info("Image created: %s", image);
 
       assertEquals(imageGroup, image.getName());
 
       imageId = image.getId();
-
       computeService.destroyNode(node.getId());
 
       Optional<? extends Image> optImage = getImage();
-
       assertTrue(optImage.isPresent());
    }
+   
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testCreateImage")
+   public void testImageIsCachedAfterBeingCreated() {
+      Optional<Image> imageInCache = findImageWithNameInCache(imageGroup);
+      assertTrue(imageInCache.isPresent());
+      assertEquals(imageInCache.get().getId(), imageId);
+   }
 
    @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testCreateImage")
    public void testSpawnNodeFromImage() throws RunNodesException {
-
       ComputeService computeService = view.getComputeService();
-
       Optional<? extends Image> optImage = getImage();
-
       assertTrue(optImage.isPresent());
 
       NodeMetadata node = Iterables.getOnlyElement(computeService.createNodesInGroup(imageGroup, 1, view
@@ -138,28 +134,30 @@ public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceConte
                .templateBuilder().imageId(optImage.get().getId()).fromImage(optImage.get()).build()));
 
       checkReachable(node);
-
       view.getComputeService().destroyNode(node.getId());
-
    }
 
    @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = { "testCreateImage",
-            "testSpawnNodeFromImage" })
+            "testSpawnNodeFromImage", "testImageIsCachedAfterBeingCreated" })
    public void testDeleteImage() {
-
       ComputeService computeService = view.getComputeService();
 
       Optional<ImageExtension> imageExtension = computeService.getImageExtension();
       assertTrue(imageExtension.isPresent(), "image extension was not present");
 
       Optional<? extends Image> optImage = getImage();
-
       assertTrue(optImage.isPresent());
 
       Image image = optImage.get();
-
       assertTrue(imageExtension.get().deleteImage(image.getId()));
    }
+   
+   @Test(groups = { "integration", "live" }, singleThreaded = true, dependsOnMethods = "testDeleteImage")
+   public void testImageIsRemovedFromCacheAfterDeletion() {
+      Optional<Image> imageInCache = findImageWithNameInCache(imageGroup);
+      assertFalse(imageInCache.isPresent());
+      assertFalse(getImage().isPresent());
+   }
 
    private Optional<? extends Image> getImage() {
       return Optional.fromNullable(view.getComputeService().getImage(imageId));
@@ -179,6 +177,15 @@ public abstract class BaseImageExtensionLiveTest extends BaseComputeServiceConte
       }, getSpawnNodeMaxWait(), 1l, SECONDS).apply(client));
    }
 
+   protected Optional<Image> findImageWithNameInCache(String name) {
+      try {
+         Template template = view.getComputeService().templateBuilder().imageNameMatches(name).build();
+         return Optional.of(template.getImage());
+      } catch (NoSuchElementException ex) {
+         return Optional.absent();
+      }
+   }
+
    @AfterClass(groups = { "integration", "live" })
    @Override
    protected void tearDownContext() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/compute/src/test/java/org/jclouds/compute/suppliers/ImageCacheSupplierTest.java
----------------------------------------------------------------------
diff --git a/compute/src/test/java/org/jclouds/compute/suppliers/ImageCacheSupplierTest.java b/compute/src/test/java/org/jclouds/compute/suppliers/ImageCacheSupplierTest.java
index 29a7caf..1e0befc 100644
--- a/compute/src/test/java/org/jclouds/compute/suppliers/ImageCacheSupplierTest.java
+++ b/compute/src/test/java/org/jclouds/compute/suppliers/ImageCacheSupplierTest.java
@@ -16,20 +16,31 @@
  */
 package org.jclouds.compute.suppliers;
 
+import static com.google.common.collect.Iterables.any;
+import static org.jclouds.compute.predicates.ImagePredicates.idEquals;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 
 import org.jclouds.compute.domain.Image;
 import org.jclouds.compute.domain.ImageBuilder;
 import org.jclouds.compute.domain.OperatingSystem;
+import org.jclouds.compute.strategy.GetImageStrategy;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LocationBuilder;
 import org.jclouds.domain.LocationScope;
+import org.jclouds.rest.AuthorizationException;
 import org.testng.annotations.Test;
 
+import com.google.common.base.Optional;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Atomics;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.inject.util.Providers;
 
 /**
  * Unit tests for the {@link ImageCacheSupplier} class.
@@ -49,15 +60,25 @@ public class ImageCacheSupplierTest {
 
    private Set<? extends Image> images = ImmutableSet.of(image);
 
+   private GetImageStrategy getImageStrategy = new GetImageStrategy() {
+      @Override
+      public Image getImage(String id) {
+         return new ImageBuilder().id(id).providerId(id).name("imageName-" + id).description("imageDescription")
+               .version("imageVersion").operatingSystem(os).status(Image.Status.AVAILABLE).location(location).build();
+      }
+   };
+
    @Test(expectedExceptions = NullPointerException.class)
    public void testRegisterNullImageIsNotAllowed() {
-      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60);
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
       imageCache.registerImage(null);
    }
 
    @Test
    public void testRegisterImageIgnoresDuplicates() {
-      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60);
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
       assertEquals(imageCache.get().size(), 1);
 
       imageCache.registerImage(image);
@@ -67,11 +88,63 @@ public class ImageCacheSupplierTest {
 
    @Test
    public void testRegisterNewImage() {
-      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60);
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
       assertEquals(imageCache.get().size(), 1);
 
       imageCache.registerImage(ImageBuilder.fromImage(image).id("newimage").build());
 
       assertEquals(imageCache.get().size(), 2);
    }
+
+   @Test(expectedExceptions = NullPointerException.class)
+   public void testRemoveNullImageIsNotAllowed() {
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
+      imageCache.removeImage(null);
+   }
+
+   @Test
+   public void testRemoveImage() {
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
+      assertEquals(imageCache.get().size(), 1);
+
+      imageCache.removeImage(image.getId());
+
+      assertEquals(imageCache.get().size(), 0);
+   }
+
+   @Test
+   public void testLoadImage() {
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 60,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
+      assertEquals(imageCache.get().size(), 1);
+
+      Optional<? extends Image> image = imageCache.get("foo");
+
+      assertTrue(image.isPresent());
+      assertEquals(image.get().getName(), "imageName-foo");
+      assertEquals(imageCache.get().size(), 2);
+   }
+
+   @Test
+   public void testSupplierExpirationReloadsTheCache() {
+      ImageCacheSupplier imageCache = new ImageCacheSupplier(Suppliers.<Set<? extends Image>> ofInstance(images), 3,
+            Atomics.<AuthorizationException> newReference(), Providers.of(getImageStrategy));
+      assertEquals(imageCache.get().size(), 1);
+
+      Optional<? extends Image> image = imageCache.get("foo");
+
+      // Load an image into the cache
+      assertTrue(image.isPresent());
+      assertEquals(image.get().getName(), "imageName-foo");
+      assertEquals(imageCache.get().size(), 2);
+
+      // Once the supplier expires, reloading it will laod the initial values
+      // (it is a hardcoded supplier), so the just loaded image should be gone
+      Uninterruptibles.sleepUninterruptibly(3, TimeUnit.SECONDS);
+      assertEquals(imageCache.get().size(), 1);
+      assertFalse(any(imageCache.get(), idEquals("foo")));
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java b/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java
index 0750a1f..a481e12 100644
--- a/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java
+++ b/core/src/main/java/org/jclouds/rest/suppliers/MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.java
@@ -53,16 +53,18 @@ import com.google.common.util.concurrent.UncheckedExecutionException;
  */
 public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> extends ForwardingObject implements
       Supplier<T> {
-
+   
    static class SetAndThrowAuthorizationExceptionSupplierBackedLoader<V> extends CacheLoader<String, Optional<V>> {
 
       private final Supplier<V> delegate;
       private final AtomicReference<AuthorizationException> authException;
+      private final ValueLoadedCallback<V> valueLoadedCallback;
 
       public SetAndThrowAuthorizationExceptionSupplierBackedLoader(Supplier<V> delegate,
-            AtomicReference<AuthorizationException> authException) {
+            AtomicReference<AuthorizationException> authException, ValueLoadedCallback<V> valueLoadedCallback) {
          this.delegate = checkNotNull(delegate, "delegate");
          this.authException = checkNotNull(authException, "authException");
+         this.valueLoadedCallback = checkNotNull(valueLoadedCallback, "valueLoadedCallback");
       }
 
       @Override
@@ -70,7 +72,9 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> ext
          if (authException.get() != null)
             throw authException.get();
          try {
-            return Optional.fromNullable(delegate.get());
+            Optional<V> value = Optional.fromNullable(delegate.get());
+            valueLoadedCallback.valueLoaded(value);
+            return value;
          } catch (Exception e) {
             AuthorizationException aex = getFirstThrowableOfType(e, AuthorizationException.class);
             if (aex != null) {
@@ -85,7 +89,24 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> ext
       public String toString() {
          return Objects.toStringHelper(this).add("delegate", delegate).toString();
       }
+   }
+   
+   public static class ValueLoadedEvent<V> {
+      private final Object eventKey;
+      private final Optional<V> value;
+
+      public ValueLoadedEvent(Object eventKey, Optional<V> value) {
+         this.eventKey = checkNotNull(eventKey, "eventKey");
+         this.value = checkNotNull(value, "value");
+      }
+
+      public Object getEventKey() {
+         return eventKey;
+      }
 
+      public Optional<V> getValue() {
+         return value;
+      }
    }
 
    private final Supplier<T> delegate;
@@ -96,16 +117,26 @@ public class MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> ext
    public static <T> MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> create(
          AtomicReference<AuthorizationException> authException, Supplier<T> delegate, long duration, TimeUnit unit) {
       return new MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T>(authException, delegate, duration,
-            unit);
+            unit, new ValueLoadedCallback.NoOpCallback<T>());
    }
-
+   
+   /**
+    * Creates a memoized supplier that calls the given callback each time values are loaded.
+    */
+   public static <T> MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T> create(
+         AtomicReference<AuthorizationException> authException, Supplier<T> delegate, long duration, TimeUnit unit,
+         ValueLoadedCallback<T> valueLoadedCallback) {
+      return new MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier<T>(authException, delegate, duration,
+            unit, valueLoadedCallback);
+   }
+   
    MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier(AtomicReference<AuthorizationException> authException,
-         Supplier<T> delegate, long duration, TimeUnit unit) {
+         Supplier<T> delegate, long duration, TimeUnit unit, ValueLoadedCallback<T> valueLoadedCallback) {
       this.delegate = delegate;
       this.duration = duration;
       this.unit = unit;
       this.cache = CacheBuilder.newBuilder().expireAfterWrite(duration, unit)
-            .build(new SetAndThrowAuthorizationExceptionSupplierBackedLoader<T>(delegate, authException));
+            .build(new SetAndThrowAuthorizationExceptionSupplierBackedLoader<T>(delegate, authException, valueLoadedCallback));
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/40f31786/core/src/main/java/org/jclouds/rest/suppliers/ValueLoadedCallback.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/rest/suppliers/ValueLoadedCallback.java b/core/src/main/java/org/jclouds/rest/suppliers/ValueLoadedCallback.java
new file mode 100644
index 0000000..dc45f0f
--- /dev/null
+++ b/core/src/main/java/org/jclouds/rest/suppliers/ValueLoadedCallback.java
@@ -0,0 +1,37 @@
+/*
+ * 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.rest.suppliers;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+
+/**
+ * Callback that gets called every time the supplier loads new values.
+ */
+@Beta
+public interface ValueLoadedCallback<V> {
+   
+   void valueLoaded(Optional<V> value);
+
+   /** The default implementation does nothing */
+   public static class NoOpCallback<V> implements ValueLoadedCallback<V> {
+      @Override
+      public void valueLoaded(Optional<V> value) {
+         // Do nothing
+      }
+   }
+}