/* * Copyright Terracotta, Inc. * * Licensed 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.ehcache.impl.internal.store.tiering; import org.ehcache.config.ResourceType; import org.ehcache.core.CacheConfigurationChangeListener; import org.ehcache.core.collections.ConcurrentWeakIdentityHashMap; import org.ehcache.core.spi.function.BiFunction; import org.ehcache.core.spi.function.Function; import org.ehcache.core.spi.store.Store; import org.ehcache.core.spi.store.StoreAccessException; import org.ehcache.core.spi.store.tiering.CachingTier; import org.ehcache.core.spi.store.tiering.HigherCachingTier; import org.ehcache.core.spi.store.tiering.LowerCachingTier; import org.ehcache.spi.service.Service; import org.ehcache.spi.service.ServiceConfiguration; import org.ehcache.spi.service.ServiceDependencies; import org.ehcache.spi.service.ServiceProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terracotta.statistics.StatisticsManager; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import static java.util.Collections.unmodifiableSet; import static org.ehcache.config.ResourceType.Core.HEAP; import static org.ehcache.config.ResourceType.Core.OFFHEAP; /** * A {@link CachingTier} implementation supporting a cache hierarchy. */ public class CompoundCachingTier<K, V> implements CachingTier<K, V> { private static final Logger LOGGER = LoggerFactory.getLogger(CompoundCachingTier.class); private final HigherCachingTier<K, V> higher; private final LowerCachingTier<K, V> lower; private volatile InvalidationListener<K, V> invalidationListener; public CompoundCachingTier(HigherCachingTier<K, V> higher, final LowerCachingTier<K, V> lower) { this.higher = higher; this.lower = lower; this.higher.setInvalidationListener(new InvalidationListener<K, V>() { @Override public void onInvalidation(final K key, final Store.ValueHolder<V> valueHolder) { try { CompoundCachingTier.this.lower.installMapping(key, new Function<K, Store.ValueHolder<V>>() { @Override public Store.ValueHolder<V> apply(K k) { return valueHolder; } }); } catch (StoreAccessException cae) { notifyInvalidation(key, valueHolder); LOGGER.warn("Error overflowing '{}' into lower caching tier {}", key, lower, cae); } } }); StatisticsManager.associate(higher).withParent(this); StatisticsManager.associate(lower).withParent(this); } private void notifyInvalidation(K key, Store.ValueHolder<V> p) { final InvalidationListener<K, V> invalidationListener = this.invalidationListener; if (invalidationListener != null) { invalidationListener.onInvalidation(key, p); } } static class ComputationException extends RuntimeException { public ComputationException(StoreAccessException cause) { super(cause); } public StoreAccessException getStoreAccessException() { return (StoreAccessException) getCause(); } @Override public synchronized Throwable fillInStackTrace() { return this; } } @Override public Store.ValueHolder<V> getOrComputeIfAbsent(K key, final Function<K, Store.ValueHolder<V>> source) throws StoreAccessException { try { return higher.getOrComputeIfAbsent(key, new Function<K, Store.ValueHolder<V>>() { @Override public Store.ValueHolder<V> apply(K k) { try { Store.ValueHolder<V> valueHolder = lower.getAndRemove(k); if (valueHolder != null) { return valueHolder; } return source.apply(k); } catch (StoreAccessException cae) { throw new ComputationException(cae); } } }); } catch (ComputationException ce) { throw ce.getStoreAccessException(); } } @Override public void invalidate(final K key) throws StoreAccessException { try { higher.silentInvalidate(key, new Function<Store.ValueHolder<V>, Void>() { @Override public Void apply(Store.ValueHolder<V> mappedValue) { try { if (mappedValue != null) { notifyInvalidation(key, mappedValue); } else { lower.invalidate(key); } } catch (StoreAccessException cae) { throw new ComputationException(cae); } return null; } }); } catch (ComputationException ce) { throw ce.getStoreAccessException(); } } @Override public void invalidateAll() throws StoreAccessException { try { higher.silentInvalidateAll(new BiFunction<K, Store.ValueHolder<V>, Void>() { @Override public Void apply(K key, Store.ValueHolder<V> mappedValue) { if (mappedValue != null) { notifyInvalidation(key, mappedValue); } return null; } }); } finally { lower.invalidateAll(); } } @Override public void invalidateAllWithHash(long hash) throws StoreAccessException { try { higher.silentInvalidateAllWithHash(hash, new BiFunction<K, Store.ValueHolder<V>, Void>() { @Override public Void apply(K key, Store.ValueHolder<V> mappedValue) { if (mappedValue != null) { notifyInvalidation(key, mappedValue); } return null; } }); } finally { lower.invalidateAllWithHash(hash); } } @Override public void clear() throws StoreAccessException { try { higher.clear(); } finally { lower.clear(); } } @Override public void setInvalidationListener(InvalidationListener<K, V> invalidationListener) { this.invalidationListener = invalidationListener; lower.setInvalidationListener(invalidationListener); } @Override public List<CacheConfigurationChangeListener> getConfigurationChangeListeners() { List<CacheConfigurationChangeListener> listeners = new ArrayList<CacheConfigurationChangeListener>(); listeners.addAll(higher.getConfigurationChangeListeners()); listeners.addAll(lower.getConfigurationChangeListeners()); return listeners; } @ServiceDependencies({HigherCachingTier.Provider.class, LowerCachingTier.Provider.class}) public static class Provider implements CachingTier.Provider { private volatile ServiceProvider<Service> serviceProvider; private final ConcurrentMap<CachingTier<?, ?>, Map.Entry<HigherCachingTier.Provider, LowerCachingTier.Provider>> providersMap = new ConcurrentWeakIdentityHashMap<CachingTier<?, ?>, Map.Entry<HigherCachingTier.Provider, LowerCachingTier.Provider>>(); @Override public <K, V> CachingTier<K, V> createCachingTier(Store.Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) { if (serviceProvider == null) { throw new RuntimeException("ServiceProvider is null."); } Collection<HigherCachingTier.Provider> higherProviders = serviceProvider.getServicesOfType(HigherCachingTier.Provider.class); if (higherProviders.size() != 1) { throw new IllegalStateException("Cannot handle multiple higher tier providers"); } HigherCachingTier.Provider higherProvider = higherProviders.iterator().next(); HigherCachingTier<K, V> higherCachingTier = higherProvider.createHigherCachingTier(storeConfig, serviceConfigs); Collection<LowerCachingTier.Provider> lowerProviders = serviceProvider.getServicesOfType(LowerCachingTier.Provider.class); if (lowerProviders.size() != 1) { throw new IllegalStateException("Cannot handle multiple lower tier providers"); } LowerCachingTier.Provider lowerProvider = lowerProviders.iterator().next(); LowerCachingTier<K, V> lowerCachingTier = lowerProvider.createCachingTier(storeConfig, serviceConfigs); CompoundCachingTier<K, V> compoundCachingTier = new CompoundCachingTier<K, V>(higherCachingTier, lowerCachingTier); providersMap.put(compoundCachingTier, new AbstractMap.SimpleEntry<HigherCachingTier.Provider, LowerCachingTier.Provider>(higherProvider, lowerProvider)); return compoundCachingTier; } @Override public void releaseCachingTier(CachingTier<?, ?> resource) { if (!providersMap.containsKey(resource)) { throw new IllegalArgumentException("Given caching tier is not managed by this provider : " + resource); } CompoundCachingTier compoundCachingTier = (CompoundCachingTier) resource; Map.Entry<HigherCachingTier.Provider, LowerCachingTier.Provider> entry = providersMap.get(resource); entry.getKey().releaseHigherCachingTier(compoundCachingTier.higher); entry.getValue().releaseCachingTier(compoundCachingTier.lower); } @Override public void initCachingTier(CachingTier<?, ?> resource) { if (!providersMap.containsKey(resource)) { throw new IllegalArgumentException("Given caching tier is not managed by this provider : " + resource); } CompoundCachingTier compoundCachingTier = (CompoundCachingTier) resource; Map.Entry<HigherCachingTier.Provider, LowerCachingTier.Provider> entry = providersMap.get(resource); entry.getValue().initCachingTier(compoundCachingTier.lower); entry.getKey().initHigherCachingTier(compoundCachingTier.higher); } @Override public int rankCachingTier(Set<ResourceType<?>> resourceTypes, Collection<ServiceConfiguration<?>> serviceConfigs) { return resourceTypes.equals(unmodifiableSet(EnumSet.of(HEAP, OFFHEAP))) ? 2 : 0; } @Override public void start(ServiceProvider<Service> serviceProvider) { this.serviceProvider = serviceProvider; } @Override public void stop() { this.serviceProvider = null; this.providersMap.clear(); } } }