/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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 com.hazelcast.cache.impl; import com.hazelcast.cache.CacheUtil; import com.hazelcast.cache.HazelcastCacheManager; import com.hazelcast.cache.ICache; import com.hazelcast.config.CacheConfig; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.HazelcastInstanceNotActiveException; import com.hazelcast.core.LifecycleEvent; import com.hazelcast.core.LifecycleListener; import com.hazelcast.core.LifecycleService; import com.hazelcast.util.EmptyStatement; import javax.cache.CacheException; import javax.cache.CacheManager; import javax.cache.configuration.CacheEntryListenerConfiguration; import javax.cache.configuration.CompleteConfiguration; import javax.cache.configuration.Configuration; import javax.cache.spi.CachingProvider; import java.lang.ref.WeakReference; import java.net.URI; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicBoolean; import static com.hazelcast.util.Preconditions.checkNotNull; /** * <p> * Abstract {@link HazelcastCacheManager} (also {@link CacheManager} as indirect) implementation * provides shared functionality to server and client cache managers. * There are two cache managers which can be accessed via their providers. * <ul> * <li>Client: HazelcastClientCacheManager.</li> * <li>Server: HazelcastServerCacheManager.</li> * </ul> * </p> * <p> * {@link AbstractHazelcastCacheManager} manages the lifecycle of the caches created or accessed through itself. * </p> * @see HazelcastCacheManager * @see CacheManager */ public abstract class AbstractHazelcastCacheManager implements HazelcastCacheManager { protected final ConcurrentMap<String, ICacheInternal<?, ?>> caches = new ConcurrentHashMap<String, ICacheInternal<?, ?>>(); protected final URI uri; protected final WeakReference<ClassLoader> classLoaderReference; protected final Properties properties; protected final String cacheNamePrefix; protected final boolean isDefaultURI; protected final boolean isDefaultClassLoader; protected final CachingProvider cachingProvider; protected final HazelcastInstance hazelcastInstance; private final AtomicBoolean isClosed = new AtomicBoolean(false); private final AtomicBoolean isDestroyed = new AtomicBoolean(false); private final String lifecycleListenerRegistrationId; public AbstractHazelcastCacheManager(CachingProvider cachingProvider, HazelcastInstance hazelcastInstance, URI uri, ClassLoader classLoader, Properties properties) { checkNotNull(cachingProvider, "CachingProvider missing"); this.cachingProvider = cachingProvider; checkNotNull(hazelcastInstance, "hazelcastInstance cannot be null"); this.hazelcastInstance = hazelcastInstance; this.isDefaultURI = uri == null || cachingProvider.getDefaultURI().equals(uri); this.uri = isDefaultURI ? cachingProvider.getDefaultURI() : uri; this.isDefaultClassLoader = classLoader == null || cachingProvider.getDefaultClassLoader().equals(classLoader); ClassLoader localClassLoader = isDefaultClassLoader ? cachingProvider.getDefaultClassLoader() : classLoader; this.classLoaderReference = new WeakReference<ClassLoader>(localClassLoader); this.properties = properties == null ? new Properties() : new Properties(properties); this.cacheNamePrefix = cacheNamePrefix(); this.lifecycleListenerRegistrationId = registerLifecycleListener(); } private <K, V, C extends Configuration<K, V>> ICacheInternal<K, V> createCacheInternal(String cacheName, C configuration) throws IllegalArgumentException { checkIfManagerNotClosed(); checkNotNull(cacheName, "cacheName must not be null"); checkNotNull(configuration, "configuration must not be null"); CacheConfig<K, V> newCacheConfig = createCacheConfig(cacheName, configuration); if (caches.containsKey(newCacheConfig.getNameWithPrefix())) { throw new CacheException("A cache named " + cacheName + " already exists."); } // Create cache config on all nodes as sync CacheConfig<K, V> currentCacheConfig = createCacheConfig(cacheName, newCacheConfig, true, true); // Create cache proxy object with cache config ICacheInternal<K, V> cacheProxy = createCacheProxy(newCacheConfig); if (currentCacheConfig == null) { // Put created cache config. // Single thread region because "createConfigOnPartition" is single threaded by partition thread addCacheConfigIfAbsent(newCacheConfig); // Put created cache. No need to a "putIfAbsent" as this is a single threaded region caches.put(newCacheConfig.getNameWithPrefix(), cacheProxy); // Register listeners registerListeners(newCacheConfig, cacheProxy); return cacheProxy; } ICacheInternal<?, ?> cache = getOrPutIfAbsent(currentCacheConfig.getNameWithPrefix(), cacheProxy); CacheConfig config = cache.getConfiguration(CacheConfig.class); if (config.equals(newCacheConfig)) { return (ICacheInternal<K, V>) cache; } throw new CacheException("A cache named " + cacheName + " already exists."); } @Override public HazelcastInstance getHazelcastInstance() { return hazelcastInstance; } @Override public <K, V, C extends Configuration<K, V>> ICache<K, V> createCache(String cacheName, C configuration) throws IllegalArgumentException { return createCacheInternal(cacheName, configuration); } private ICacheInternal<?, ?> getOrPutIfAbsent(String nameWithPrefix, ICacheInternal cacheProxy) { ICacheInternal<?, ?> cache = caches.get(nameWithPrefix); if (cache == null) { ICacheInternal<?, ?> iCache = caches.putIfAbsent(nameWithPrefix, cacheProxy); cache = iCache != null ? iCache : cacheProxy; } return cache; } @Override public CachingProvider getCachingProvider() { return cachingProvider; } @Override public URI getURI() { return this.uri; } @Override public ClassLoader getClassLoader() { return classLoaderReference.get(); } @Override public Properties getProperties() { return properties; } @Override public <K, V> ICache<K, V> getCache(String cacheName, Class<K> keyType, Class<V> valueType) { checkIfManagerNotClosed(); checkNotNull(keyType, "keyType can not be null"); checkNotNull(valueType, "valueType can not be null"); ICacheInternal<?, ?> cache = getCacheUnchecked(cacheName); if (cache != null) { Configuration<?, ?> configuration = cache.getConfiguration(CacheConfig.class); if (configuration.getKeyType() != null && configuration.getKeyType().equals(keyType)) { if (configuration.getValueType() != null && configuration.getValueType().equals(valueType)) { return ensureOpenIfAvailable((ICacheInternal<K, V>) cache); } else { throw new ClassCastException( "Incompatible cache value types specified, expected " + configuration.getValueType() + " but " + valueType + " was specified"); } } else { throw new ClassCastException( "Incompatible cache key types specified, expected " + configuration.getKeyType() + " but " + keyType + " was specified"); } } return null; } public <K, V> ICache<K, V> getOrCreateCache(String cacheName, CacheConfig<K, V> cacheConfig) { checkIfManagerNotClosed(); String cacheNameWithPrefix = getCacheNameWithPrefix(cacheName); ICacheInternal<?, ?> cache = caches.get(cacheNameWithPrefix); if (cache == null) { cache = createCacheInternal(cacheName, cacheConfig); } return ensureOpenIfAvailable((ICacheInternal<K, V>) cache); } @Override public <K, V> ICache<K, V> getCache(String cacheName) { checkIfManagerNotClosed(); ICacheInternal<?, ?> cache = getCacheUnchecked(cacheName); if (cache != null) { Configuration<?, ?> configuration = cache.getConfiguration(CacheConfig.class); if (Object.class.equals(configuration.getKeyType()) && Object.class.equals(configuration.getValueType())) { return ensureOpenIfAvailable((ICacheInternal<K, V>) cache); } else { throw new IllegalArgumentException( "Cache " + cacheName + " was " + "defined with specific types Cache<" + configuration.getKeyType() + ", " + configuration.getValueType() + "> " + "in which case CacheManager.getCache(String, Class, Class) must be used"); } } return null; } protected <K, V> ICacheInternal<K, V> ensureOpenIfAvailable(ICacheInternal<K, V> cache) { if (cache != null && cache.isClosed() && !cache.isDestroyed()) { cache.open(); } return cache; } protected <K, V> ICacheInternal<?, ?> getCacheUnchecked(String cacheName) { String cacheNameWithPrefix = getCacheNameWithPrefix(cacheName); ICacheInternal<?, ?> cache = caches.get(cacheNameWithPrefix); if (cache == null) { CacheConfig<K, V> cacheConfig = findCacheConfig(cacheNameWithPrefix, cacheName, true, true); if (cacheConfig == null) { // No cache found return null; } // Create the cache proxy which already exists in the cluster as config ICacheInternal<K, V> cacheProxy = createCacheProxy(cacheConfig); // Put created cache config. addCacheConfigIfAbsent(cacheConfig); cache = caches.putIfAbsent(cacheNameWithPrefix, cacheProxy); if (cache == null) { registerListeners(cacheConfig, cacheProxy); cache = cacheProxy; } } return cache; } @Override public Iterable<String> getCacheNames() { Set<String> names; if (isClosed()) { names = Collections.emptySet(); } else { names = new LinkedHashSet<String>(); for (Map.Entry<String, ICacheInternal<?, ?>> entry : caches.entrySet()) { String nameWithPrefix = entry.getKey(); int index = nameWithPrefix.indexOf(cacheNamePrefix) + cacheNamePrefix.length(); final String name = nameWithPrefix.substring(index); names.add(name); } } return Collections.unmodifiableCollection(names); } @Override public void destroyCache(String cacheName) { removeCache(cacheName, true); } @Override public void removeCache(String cacheName, boolean destroy) { checkIfManagerNotClosed(); checkNotNull(cacheName, "cacheName cannot be null"); String cacheNameWithPrefix = getCacheNameWithPrefix(cacheName); ICacheInternal<?, ?> cache = caches.remove(cacheNameWithPrefix); if (cache != null && destroy) { cache.destroy(); } removeCacheConfigFromLocal(cacheNameWithPrefix); } /** * Removes the local copy of the cache configuration if one exists. * Default implementation does not require it. But client implementation overrides this to track a local copy * of the config. * * @param cacheName cache name. */ protected void removeCacheConfigFromLocal(String cacheName) { } private String registerLifecycleListener() { return hazelcastInstance.getLifecycleService().addLifecycleListener(new LifecycleListener() { @Override public void stateChanged(LifecycleEvent event) { if (event.getState() == LifecycleEvent.LifecycleState.SHUTTING_DOWN) { onShuttingDown(); } } }); } private void deregisterLifecycleListener() { LifecycleService lifecycleService = hazelcastInstance.getLifecycleService(); try { lifecycleService.removeLifecycleListener(lifecycleListenerRegistrationId); } catch (HazelcastInstanceNotActiveException e) { // if hazelcastInstance is terminated already, // `lifecycleService.removeLifecycleListener` will throw HazelcastInstanceNotActiveException. // We can safely ignore this exception. See TerminatedLifecycleService. EmptyStatement.ignore(e); } } @Override public void close() { if (isDestroyed.get() || !isClosed.compareAndSet(false, true)) { return; } deregisterLifecycleListener(); for (ICacheInternal cache : caches.values()) { cache.close(); } postClose(); // TODO do we need to clear it as "caches.clear();" } /** * Destroy all managed caches. */ @Override public void destroy() { if (!isDestroyed.compareAndSet(false, true)) { return; } deregisterLifecycleListener(); for (ICacheInternal cache : caches.values()) { cache.destroy(); } caches.clear(); isClosed.set(true); postDestroy(); } protected void postDestroy() { } @Override public boolean isClosed() { return isClosed.get() || !hazelcastInstance.getLifecycleService().isRunning(); } protected void checkIfManagerNotClosed() { if (isClosed()) { throw new IllegalStateException(); } } /** * This method calculates a fixed string based on the URI and classloader using the formula: * <p>/hz[/uri][/classloader]/</p> * <p>URI and classloader are dropped if they have default values.</p> * * @return the calculated cache prefix. */ protected String cacheNamePrefix() { String cacheNamePrefix = CacheUtil.getPrefix( isDefaultURI ? null : uri, isDefaultClassLoader ? null : getClassLoader()); if (cacheNamePrefix == null) { return HazelcastCacheManager.CACHE_MANAGER_PREFIX; } else { return HazelcastCacheManager.CACHE_MANAGER_PREFIX + cacheNamePrefix; } } @Override public String getCacheNameWithPrefix(String name) { return cacheNamePrefix + name; } protected <K, V, C extends Configuration<K, V>> CacheConfig<K, V> createCacheConfig(String cacheName, C configuration) { CacheConfig<K, V> cacheConfig; if (configuration instanceof CompleteConfiguration) { cacheConfig = new CacheConfig<K, V>((CompleteConfiguration) configuration); } else { cacheConfig = new CacheConfig<K, V>(); cacheConfig.setStoreByValue(configuration.isStoreByValue()); final Class<K> keyType = configuration.getKeyType(); final Class<V> valueType = configuration.getValueType(); cacheConfig.setTypes(keyType, valueType); } cacheConfig.setName(cacheName); cacheConfig.setManagerPrefix(this.cacheNamePrefix); cacheConfig.setUriString(getURI().toString()); return cacheConfig; } protected <K, V> void registerListeners(CacheConfig<K, V> cacheConfig, ICache<K, V> source) { Iterator<CacheEntryListenerConfiguration<K, V>> iterator = cacheConfig.getCacheEntryListenerConfigurations().iterator(); while (iterator.hasNext()) { CacheEntryListenerConfiguration<K, V> listenerConfig = iterator.next(); ((ICacheInternal<K, V>) source).registerCacheEntryListener(listenerConfig, false); } } @Override public String toString() { return "HazelcastCacheManager{hazelcastInstance=" + hazelcastInstance + ", cachingProvider=" + cachingProvider + '}'; } protected abstract <K, V> void addCacheConfigIfAbsent(CacheConfig<K, V> cacheConfig); protected abstract <K, V> ICacheInternal<K, V> createCacheProxy(CacheConfig<K, V> cacheConfig); protected abstract <K, V> CacheConfig<K, V> findCacheConfig(String cacheName, String simpleCacheName, boolean createAlsoOnOthers, boolean syncCreate); protected abstract <K, V> CacheConfig<K, V> createCacheConfig(String cacheName, CacheConfig<K, V> config, boolean createAlsoOnOthers, boolean syncCreate); protected abstract <K, V> CacheConfig<K, V> getCacheConfig(String cacheName, String simpleCacheName); protected abstract void postClose(); protected abstract void onShuttingDown(); }