com.hazelcast.cache.impl.AbstractHazelcastCacheManager Maven / Gradle / Ivy
Show all versions of hazelcast-all Show documentation
/*
* Copyright (c) 2008-2019, 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.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 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.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.cache.CacheUtil.getPrefix;
import static com.hazelcast.util.EmptyStatement.ignore;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static com.hazelcast.util.SetUtil.createLinkedHashSet;
/**
* Abstract {@link HazelcastCacheManager} (also indirect {@link CacheManager})
* implementation to provide shared functionality to server and client cache
* managers.
*
* There are two cache managers which can be accessed via their providers:
*
* - Client: HazelcastClientCacheManager
* - Server: HazelcastServerCacheManager
*
* {@link AbstractHazelcastCacheManager} manages the lifecycle of the caches
* created or accessed through itself.
*
* @see HazelcastCacheManager
* @see CacheManager
*/
@SuppressWarnings("WeakerAccess")
public abstract class AbstractHazelcastCacheManager implements HazelcastCacheManager {
protected final ConcurrentMap> caches
= new ConcurrentHashMap>();
protected final CachingProvider cachingProvider;
protected final HazelcastInstance hazelcastInstance;
protected final boolean isDefaultURI;
protected final boolean isDefaultClassLoader;
protected final URI uri;
protected final Properties properties;
private final AtomicBoolean isClosed = new AtomicBoolean(false);
private final AtomicBoolean isDestroyed = new AtomicBoolean(false);
private final WeakReference classLoaderReference;
private final String cacheNamePrefix;
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(localClassLoader);
this.properties = properties == null ? new Properties() : new Properties(properties);
this.cacheNamePrefix = getCacheNamePrefix();
this.lifecycleListenerRegistrationId = registerLifecycleListener();
}
@SuppressWarnings("unchecked")
private > ICacheInternal createCacheInternal(String cacheName, C configuration)
throws IllegalArgumentException {
ensureOpen();
checkNotNull(cacheName, "cacheName must not be null");
checkNotNull(configuration, "configuration must not be null");
CacheConfig newCacheConfig = createCacheConfig(cacheName, configuration);
validateCacheConfig(newCacheConfig);
if (caches.containsKey(newCacheConfig.getNameWithPrefix())) {
throw new CacheException("A cache named '" + cacheName + "' already exists");
}
// create cache config on all nodes as sync
createCacheConfig(cacheName, newCacheConfig);
// create cache proxy object with cache config
ICacheInternal cacheProxy = createCacheProxy(newCacheConfig);
// add created cache config to local configurations map
addCacheConfigIfAbsent(newCacheConfig);
ICacheInternal, ?> existingCache = caches.putIfAbsent(newCacheConfig.getNameWithPrefix(), cacheProxy);
if (existingCache == null) {
// register listeners on new cache
registerListeners(newCacheConfig, cacheProxy);
return cacheProxy;
} else {
CacheConfig, ?> config = existingCache.getConfiguration(CacheConfig.class);
if (config.equals(newCacheConfig)) {
return (ICacheInternal) existingCache;
} else {
throw new CacheException("A cache named " + cacheName + " already exists");
}
}
}
@Override
public HazelcastInstance getHazelcastInstance() {
return hazelcastInstance;
}
@Override
public > ICache createCache(String cacheName, C configuration)
throws IllegalArgumentException {
return createCacheInternal(cacheName, configuration);
}
@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
@SuppressWarnings("unchecked")
public ICache getCache(String cacheName, Class keyType, Class valueType) {
ensureOpen();
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) 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;
}
// used in EE
@SuppressWarnings({"unchecked", "unused"})
public ICache getOrCreateCache(String cacheName, CacheConfig cacheConfig) {
ensureOpen();
String cacheNameWithPrefix = getCacheNameWithPrefix(cacheName);
ICacheInternal, ?> cache = caches.get(cacheNameWithPrefix);
if (cache == null) {
cache = createCacheInternal(cacheName, cacheConfig);
}
return ensureOpenIfAvailable((ICacheInternal) cache);
}
@Override
@SuppressWarnings("unchecked")
public ICache getCache(String cacheName) {
ensureOpen();
ICacheInternal, ?> cache = getCacheUnchecked(cacheName);
if (cache != null) {
return ensureOpenIfAvailable((ICacheInternal) cache);
}
return null;
}
private ICacheInternal ensureOpenIfAvailable(ICacheInternal cache) {
if (cache != null && cache.isClosed() && !cache.isDestroyed()) {
cache.open();
}
return cache;
}
private ICacheInternal, ?> getCacheUnchecked(String cacheName) {
String cacheNameWithPrefix = getCacheNameWithPrefix(cacheName);
ICacheInternal, ?> cache = caches.get(cacheNameWithPrefix);
if (cache == null) {
CacheConfig cacheConfig = findCacheConfig(cacheNameWithPrefix, cacheName);
if (cacheConfig == null) {
// no cache found
return null;
}
// create the cache proxy which already exists in the cluster as config
ICacheInternal cacheProxy = createCacheProxy(cacheConfig);
// put created cache config
addCacheConfigIfAbsent(cacheConfig);
cache = caches.putIfAbsent(cacheNameWithPrefix, cacheProxy);
if (cache == null) {
registerListeners(cacheConfig, cacheProxy);
cache = cacheProxy;
}
}
if (cache != null) {
cache.setCacheManager(this);
}
return cache;
}
@Override
public Iterable getCacheNames() {
ensureOpen();
Set names;
names = createLinkedHashSet(caches.size());
for (Map.Entry> entry : caches.entrySet()) {
String nameWithPrefix = entry.getKey();
int index = nameWithPrefix.indexOf(cacheNamePrefix) + cacheNamePrefix.length();
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) {
ensureOpen();
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.
*
* The default implementation does not require it, but client
* implementation overrides this to track a local copy of the
* config.
*
* @param cacheNameWithPrefix the cache name
*/
protected void removeCacheConfigFromLocal(String cacheNameWithPrefix) {
}
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 already terminated,
// `lifecycleService.removeLifecycleListener()` will throw a
// HazelcastInstanceNotActiveException, which we can safely ignore
// (see TerminatedLifecycleService)
ignore(e);
}
}
@Override
public void close() {
if (isDestroyed.get() || !isClosed.compareAndSet(false, true)) {
return;
}
deregisterLifecycleListener();
for (ICacheInternal cache : caches.values()) {
cache.close();
}
postClose();
}
/**
* Destroys 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 ensureOpen() {
if (isClosed()) {
throw new IllegalStateException("CacheManager " + cacheNamePrefix + " is already closed.");
}
}
/**
* Calculates a fixed string based on the URI and classloader.
*
* Uses the formula:
*
* /hz[/uri][/classloader]/
*
*
* URI and classloader are dropped if they have default values.
*
* @return the calculated cache prefix
*/
private String getCacheNamePrefix() {
String cacheNamePrefix = 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;
}
@SuppressWarnings("unchecked")
protected > CacheConfig createCacheConfig(String cacheName, C configuration) {
CacheConfig cacheConfig;
if (configuration instanceof CompleteConfiguration) {
cacheConfig = new CacheConfig((CompleteConfiguration) configuration);
} else {
cacheConfig = new CacheConfig();
cacheConfig.setStoreByValue(configuration.isStoreByValue());
Class keyType = configuration.getKeyType();
Class valueType = configuration.getValueType();
cacheConfig.setTypes(keyType, valueType);
}
cacheConfig.setName(cacheName);
cacheConfig.setManagerPrefix(this.cacheNamePrefix);
cacheConfig.setUriString(getURI().toString());
return cacheConfig;
}
private void registerListeners(CacheConfig cacheConfig, ICache source) {
for (CacheEntryListenerConfiguration listenerConfig : cacheConfig.getCacheEntryListenerConfigurations()) {
((ICacheInternal) source).registerCacheEntryListener(listenerConfig, false);
}
}
@Override
public String toString() {
return "HazelcastCacheManager{hazelcastInstance=" + hazelcastInstance + ", cachingProvider=" + cachingProvider + '}';
}
protected abstract void validateCacheConfig(CacheConfig cacheConfig);
protected abstract void addCacheConfigIfAbsent(CacheConfig cacheConfig);
protected abstract ICacheInternal createCacheProxy(CacheConfig cacheConfig);
protected abstract CacheConfig findCacheConfig(String cacheName, String simpleCacheName);
protected abstract void createCacheConfig(String cacheName, CacheConfig config);
protected abstract CacheConfig getCacheConfig(String cacheName, String simpleCacheName);
protected abstract void postClose();
protected abstract void onShuttingDown();
}