package org.cache2k.jcache.provider; /* * #%L * cache2k JCache provider * %% * Copyright (C) 2000 - 2017 headissue GmbH, Munich * %% * 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. * #L% */ import org.cache2k.CacheEntry; import org.cache2k.configuration.Cache2kConfiguration; import org.cache2k.configuration.CacheType; import org.cache2k.configuration.CustomizationReferenceSupplier; import org.cache2k.core.Cache2kCoreProviderImpl; import org.cache2k.core.CacheManagerImpl; import org.cache2k.core.InternalCache2kBuilder; import org.cache2k.integration.CacheWriter; import org.cache2k.integration.ExceptionPropagator; import org.cache2k.integration.ExceptionInformation; import org.cache2k.jcache.ExtendedConfiguration; import org.cache2k.jcache.ExtendedMutableConfiguration; import org.cache2k.jcache.JCacheConfiguration; import org.cache2k.jcache.provider.event.EventHandling; import org.cache2k.jcache.provider.generic.storeByValueSimulation.CopyCacheProxy; import org.cache2k.jcache.provider.generic.storeByValueSimulation.ObjectCopyFactory; import org.cache2k.jcache.provider.generic.storeByValueSimulation.ObjectTransformer; import org.cache2k.jcache.provider.generic.storeByValueSimulation.RuntimeCopyTransformer; import org.cache2k.jcache.provider.generic.storeByValueSimulation.SimpleObjectCopyFactory; import javax.cache.Cache; import javax.cache.CacheException; import javax.cache.configuration.CacheEntryListenerConfiguration; import javax.cache.configuration.CompleteConfiguration; import javax.cache.configuration.Configuration; import javax.cache.configuration.MutableConfiguration; import javax.cache.expiry.CreatedExpiryPolicy; import javax.cache.expiry.Duration; import javax.cache.expiry.EternalExpiryPolicy; import javax.cache.expiry.ExpiryPolicy; import javax.cache.expiry.ModifiedExpiryPolicy; import javax.cache.integration.CacheLoader; import javax.cache.integration.CacheLoaderException; import java.util.concurrent.Executors; /** * Constructs a requested JCache. * * @author Jens Wilke */ public class JCacheBuilder<K,V> { private String name; private JCacheManagerAdapter manager; private boolean cache2kConfigurationWasProvided = false; private CompleteConfiguration<K,V> config; private Cache2kConfiguration<K,V> cache2kConfiguration; private JCacheConfiguration extraConfiguration = JCACHE_DEFAULTS; private CacheType<K> keyType; private CacheType<V> valueType; private ExpiryPolicy expiryPolicy; private Cache<K,V> createdCache; private EventHandling<K,V> eventHandling; private boolean needsTouchyWrapper; public JCacheBuilder(String _name, JCacheManagerAdapter _manager) { name = _name; manager = _manager; } public void setConfiguration(Configuration<K,V> cfg) { if (cfg instanceof CompleteConfiguration) { config = (CompleteConfiguration<K,V>) cfg; if (cfg instanceof ExtendedConfiguration) { cache2kConfiguration = ((ExtendedConfiguration<K,V>) cfg).getCache2kConfiguration(); if (cache2kConfiguration != null) { if (cache2kConfiguration.getName() != null && !cache2kConfiguration.getName().equals(name)) { throw new IllegalArgumentException("cache name mismatch."); } cache2kConfigurationWasProvided = true; } } } else { MutableConfiguration<K, V> _cfgCopy = new MutableConfiguration<K, V>(); _cfgCopy.setTypes(cfg.getKeyType(), cfg.getValueType()); _cfgCopy.setStoreByValue(cfg.isStoreByValue()); config = _cfgCopy; } if (cache2kConfiguration == null) { cache2kConfiguration = CacheManagerImpl.PROVIDER.getDefaultConfiguration(manager.getCache2kManager()); if (cfg instanceof ExtendedMutableConfiguration) { ((ExtendedMutableConfiguration) cfg).setCache2kConfiguration(cache2kConfiguration); } } cache2kConfiguration.setName(name); Cache2kCoreProviderImpl.augmentConfiguration(manager.getCache2kManager(), cache2kConfiguration); cache2kConfigurationWasProvided |= cache2kConfiguration.isExternalConfigurationPresent(); if (cache2kConfigurationWasProvided) { extraConfiguration = CACHE2K_DEFAULTS; JCacheConfiguration _extraConfigurationSpecified = cache2kConfiguration.getSections().getSection(JCacheConfiguration.class); if (_extraConfigurationSpecified != null) { extraConfiguration = _extraConfigurationSpecified; } } } public Cache<K,V> build() { setupTypes(); setupDefaults(); setupExceptionPropagator(); setupCacheThrough(); setupExpiryPolicy(); setupEventHandling(); buildAdapterCache(); wrapForExpiryPolicy(); wrapIfCopyIsNeeded(); return createdCache; } public CompleteConfiguration<K,V> getCompleteConfiguration() { return config; } Cache2kConfiguration<K,V> getCache2kConfiguration() { return cache2kConfiguration; } JCacheConfiguration getExtraConfiguration() { return extraConfiguration; } /** * If there is a cache2k configuration, we take the types from there. */ private void setupTypes() { if (!cache2kConfigurationWasProvided) { cache2kConfiguration.setKeyType(config.getKeyType()); cache2kConfiguration.setValueType(config.getValueType()); } else { if (cache2kConfiguration.getKeyType() == null) { cache2kConfiguration.setKeyType(config.getKeyType()); } if (cache2kConfiguration.getValueType() == null) { cache2kConfiguration.setValueType(config.getValueType()); } } keyType = cache2kConfiguration.getKeyType(); valueType = cache2kConfiguration.getValueType(); if (!config.getKeyType().equals(Object.class) && !config.getKeyType().equals(keyType.getType())) { throw new IllegalArgumentException("Key type mismatch between JCache and cache2k configuration"); } if (!config.getValueType().equals(Object.class) && !config.getValueType().equals(valueType.getType())) { throw new IllegalArgumentException("Value type mismatch between JCache and cache2k configuration"); } } /** * Configure conservative defaults, if no cache2k configuration is available. */ private void setupDefaults() { if (!cache2kConfigurationWasProvided) { cache2kConfiguration.setSharpExpiry(true); cache2kConfiguration.setKeepDataAfterExpired(false); } } /** * If an exception propagator is configured, take this one, otherwise go with default that * is providing JCache compatible behavior. */ private void setupExceptionPropagator() { if (cache2kConfiguration.getExceptionPropagator() != null) { return; } cache2kConfiguration.setExceptionPropagator(new CustomizationReferenceSupplier<ExceptionPropagator<K>>(new ExceptionPropagator() { @Override public RuntimeException propagateException(Object key, final ExceptionInformation exceptionInformation) { return new CacheLoaderException("propagate previous loader exception", exceptionInformation.getException()); } })); } /** * Configure loader and writer. */ private void setupCacheThrough() { if (config.getCacheLoaderFactory() != null) { final CacheLoader<K, V> clf = config.getCacheLoaderFactory().create(); cache2kConfiguration.setLoader( new CustomizationReferenceSupplier<org.cache2k.integration.CacheLoader<K, V>>( new org.cache2k.integration.CacheLoader<K,V>() { @Override public V load(K k) { return clf.load(k); } })); } if (config.isWriteThrough()) { final javax.cache.integration.CacheWriter<? super K, ? super V> cw = config.getCacheWriterFactory().create(); cache2kConfiguration.setWriter(new CustomizationReferenceSupplier<CacheWriter<K, V>>(new CacheWriter<K,V>() { @Override public void write(final K key, final V value) throws Exception { Cache.Entry<K, V> ce = new Cache.Entry<K, V>() { @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public <T> T unwrap(Class<T> clazz) { throw new UnsupportedOperationException("unwrap entry not supported"); } }; cw.write(ce); } @Override public void delete(final Object key) throws Exception { cw.delete(key); } })); } } /** * Register a expiry policy to cache2k. * * <p>JSR107 requires that null values are deleted from the cache. We register an expiry policy * to cache2k to provide this behavior. */ private void setupExpiryPolicy() { if (cache2kConfigurationWasProvided) { if (cache2kConfiguration.getExpiryPolicy() != null) { org.cache2k.expiry.ExpiryPolicy<K, V> ep0; try { ep0 = cache2kConfiguration.getExpiryPolicy().supply(manager.getCache2kManager()); } catch (Exception ex) { throw new CacheException("couldn't initialize expiry policy", ex); } final org.cache2k.expiry.ExpiryPolicy<K, V> ep = ep0; cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>( new org.cache2k.expiry.ExpiryPolicy<K, V>() { @Override public long calculateExpiryTime(final K key, final V value, final long loadTime, final CacheEntry<K, V> oldEntry) { if (value == null) { return NO_CACHE; } return ep.calculateExpiryTime(key, value, loadTime, oldEntry); } })); return; } if (cache2kConfiguration.getExpireAfterWrite() >= 0) { return; } } cache2kConfiguration.setEternal(true); if (config.getExpiryPolicyFactory() != null) { expiryPolicy = config.getExpiryPolicyFactory().create(); } if (expiryPolicy == null || expiryPolicy instanceof EternalExpiryPolicy) { cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>(new org.cache2k.expiry.ExpiryPolicy<K, V>() { @Override public long calculateExpiryTime(final K key, final V value, final long loadTime, final CacheEntry<K, V> oldEntry) { if (value == null) { return NO_CACHE; } return ETERNAL; } })); return; } if (expiryPolicy instanceof ModifiedExpiryPolicy) { Duration d = expiryPolicy.getExpiryForCreation(); final long _millisDuration = d.getTimeUnit().toMillis(d.getDurationAmount()); if (_millisDuration == 0) { cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>(new org.cache2k.expiry.ExpiryPolicy<K, V>() { @Override public long calculateExpiryTime(final K key, final V value, final long loadTime, final CacheEntry<K, V> oldEntry) { return NO_CACHE; } })); return; } cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>(new org.cache2k.expiry.ExpiryPolicy<K, V>() { @Override public long calculateExpiryTime(final K key, final V value, final long loadTime, final CacheEntry<K, V> oldEntry) { if (value == null) { return NO_CACHE; } return loadTime + _millisDuration; } })); return; } if (expiryPolicy instanceof CreatedExpiryPolicy) { cache2kConfiguration.setEternal(true); Duration d = expiryPolicy.getExpiryForCreation(); final long _millisDuration = d.getTimeUnit().toMillis(d.getDurationAmount()); if (_millisDuration == 0) { cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>(new org.cache2k.expiry.ExpiryPolicy<K, V>() { @Override public long calculateExpiryTime(final K key, final V value, final long loadTime, final CacheEntry<K, V> oldEntry) { return NO_CACHE; } })); return; } cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>(new org.cache2k.expiry.ExpiryPolicy<K, V>() { @Override public long calculateExpiryTime(final K key, final V value, final long loadTime, final CacheEntry<K, V> oldEntry) { if (value == null) { return NO_CACHE; } if (oldEntry == null) { return loadTime + _millisDuration; } else { return NEUTRAL; } } })); return; } needsTouchyWrapper = true; cache2kConfiguration.setExpiryPolicy(new CustomizationReferenceSupplier<org.cache2k.expiry.ExpiryPolicy<K, V>>(new TouchyJCacheAdapter.ExpiryPolicyAdapter<K,V>(expiryPolicy))); } private void setupEventHandling() { eventHandling = new EventHandling<K, V>(manager, Executors.newCachedThreadPool()); eventHandling.registerCache2kListeners(cache2kConfiguration); for (CacheEntryListenerConfiguration<K,V> cfg : config.getCacheEntryListenerConfigurations()) { eventHandling.registerListener(cfg); } } private void buildAdapterCache() { JCacheAdapter<K, V> _adapter = new JCacheAdapter<K, V>( manager, new InternalCache2kBuilder<K,V>(cache2kConfiguration, manager.getCache2kManager()).buildAsIs()); _adapter.valueType = valueType.getType(); _adapter.keyType = keyType.getType(); if (config.getCacheLoaderFactory() != null) { _adapter.loaderConfigured = true; } _adapter.readThrough = config.isReadThrough(); _adapter.eventHandling = eventHandling; if (config.isStoreByValue()) { _adapter.storeByValue = true; } if (extraConfiguration.isAlwaysFlushJmxStatistics()) { _adapter.flushJmxStatistics = true; } createdCache = _adapter; } private void wrapForExpiryPolicy() { if (needsTouchyWrapper) { createdCache = new TouchyJCacheAdapter<K, V>((JCacheAdapter<K, V>) createdCache, expiryPolicy); } } private void wrapIfCopyIsNeeded() { if (extraConfiguration.isCopyAlwaysIfRequested() && config.isStoreByValue()) { final ObjectTransformer<K, K> _keyTransformer = createCopyTransformer(keyType); final ObjectTransformer<V, V> _valueTransformer = createCopyTransformer(valueType); createdCache = new CopyCacheProxy<K,V>( createdCache, _keyTransformer, _valueTransformer); } } @SuppressWarnings("unchecked") private <T> ObjectTransformer<T, T> createCopyTransformer(final CacheType<T> _type) { ObjectCopyFactory f = new SimpleObjectCopyFactory(); ObjectTransformer<T, T> _keyTransformer = f.createCopyTransformer(_type.getType(), manager.getClassLoader()); if (_keyTransformer == null) { _keyTransformer = (ObjectTransformer<T, T>) new RuntimeCopyTransformer(manager.getClassLoader()); } return _keyTransformer; } /** * Defaults to use if no cache2k configuration is provided. */ private static final JCacheConfiguration JCACHE_DEFAULTS = new JCacheConfiguration.Builder() .alwaysFlushJmxStatistics(true) .copyAlwaysIfRequested(true) .buildConfigurationSection(); /** * Defaults to use if cache2k configuration is provided but no * extra JCacheConfiguration section is added. */ private static final JCacheConfiguration CACHE2K_DEFAULTS = new JCacheConfiguration.Builder() .alwaysFlushJmxStatistics(false) .copyAlwaysIfRequested(false) .buildConfigurationSection(); }