org.jclouds.compute.suppliers.ImageCacheSupplier Maven / Gradle / Ivy
Show all versions of jclouds-compute Show documentation
/*
* 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.suppliers;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Resource;
import javax.inject.Named;
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.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Provider;
/**
* Memoized image supplier that allows new images to be registered at runtime.
*
* The memoized Supplier>
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.
*/
@Beta
public class ImageCacheSupplier implements Supplier>, ValueLoadedCallback> {
/**
* The image supplier that fetches the images from the provider.
*/
private final Supplier> 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> 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 imageCache;
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
public ImageCacheSupplier(Supplier> imageSupplier, long sessionIntervalSeconds,
AtomicReference authException, final Provider imageLoader) {
liveImageSupplier = imageSupplier;
memoizedImageSupplier = MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(authException,
imageSupplier, sessionIntervalSeconds, TimeUnit.SECONDS, this);
imageCache = CacheBuilder.newBuilder().expireAfterWrite(sessionIntervalSeconds, TimeUnit.SECONDS)
.build(new CacheLoader() {
@Override
public Image load(String key) throws Exception {
return imageLoader.get().getImage(key);
}
});
}
@Override
public Set extends Image> get() {
// 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}.
*
* 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> value) {
if (value.isPresent()) {
reset(value.get());
}
}
/**
* Resets the cache to the given set of images.
*
* 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() {
@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.
*
* 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.
*
* 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.
*
* 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");
imageCache.put(image.getId(), image);
}
/**
* Removes an image from the image cache.
*
* 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"));
}
}