/* * 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.CacheNotExistsException; import com.hazelcast.cache.HazelcastCacheManager; import com.hazelcast.cache.impl.event.CachePartitionLostEventFilter; import com.hazelcast.cache.impl.operation.PostJoinCacheOperation; import com.hazelcast.config.CacheConfig; import com.hazelcast.config.CacheSimpleConfig; import com.hazelcast.config.InMemoryFormat; import com.hazelcast.core.DistributedObject; import com.hazelcast.core.Member; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.IOUtil; import com.hazelcast.nio.serialization.Data; import com.hazelcast.spi.EventFilter; import com.hazelcast.spi.EventRegistration; import com.hazelcast.spi.EventService; import com.hazelcast.spi.NodeEngine; import com.hazelcast.spi.Operation; import com.hazelcast.spi.PartitionAwareService; import com.hazelcast.spi.PartitionMigrationEvent; import com.hazelcast.spi.PostJoinAwareService; import com.hazelcast.spi.QuorumAwareService; import com.hazelcast.spi.SplitBrainHandlerService; import com.hazelcast.spi.partition.IPartitionLostEvent; import com.hazelcast.spi.partition.MigrationEndpoint; import com.hazelcast.util.Clock; import com.hazelcast.util.ConcurrencyUtil; import com.hazelcast.util.ConstructorFunction; import com.hazelcast.util.ContextMutexFactory; import com.hazelcast.util.ExceptionUtil; import javax.cache.CacheException; import javax.cache.configuration.CacheEntryListenerConfiguration; import javax.cache.event.CacheEntryListener; import java.io.Closeable; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static com.hazelcast.cache.impl.AbstractCacheRecordStore.SOURCE_NOT_AVAILABLE; import static com.hazelcast.config.InMemoryFormat.NATIVE; @SuppressWarnings("checkstyle:classdataabstractioncoupling") public abstract class AbstractCacheService implements ICacheService, PostJoinAwareService, PartitionAwareService, QuorumAwareService, SplitBrainHandlerService { private static final String SETUP_REF = "setupRef"; protected final ConcurrentMap<String, CacheConfig> configs = new ConcurrentHashMap<String, CacheConfig>(); protected final ConcurrentMap<String, CacheContext> cacheContexts = new ConcurrentHashMap<String, CacheContext>(); protected final ConcurrentMap<String, CacheStatisticsImpl> statistics = new ConcurrentHashMap<String, CacheStatisticsImpl>(); protected final ConcurrentMap<String, Set<Closeable>> resources = new ConcurrentHashMap<String, Set<Closeable>>(); protected final ConcurrentMap<String, Closeable> closeableListeners = new ConcurrentHashMap<String, Closeable>(); protected final ConcurrentMap<String, CacheOperationProvider> operationProviderCache = new ConcurrentHashMap<String, CacheOperationProvider>(); protected final ConstructorFunction<String, CacheContext> cacheContextsConstructorFunction = new ConstructorFunction<String, CacheContext>() { @Override public CacheContext createNew(String name) { return new CacheContext(); } }; protected final ConstructorFunction<String, CacheStatisticsImpl> cacheStatisticsConstructorFunction = new ConstructorFunction<String, CacheStatisticsImpl>() { @Override public CacheStatisticsImpl createNew(String name) { return new CacheStatisticsImpl( Clock.currentTimeMillis(), CacheEntryCountResolver.createEntryCountResolver(getOrCreateCacheContext(name))); } }; // mutex factory ensures each Set<Closeable> of cache resources is only constructed and inserted in resources map once protected final ContextMutexFactory cacheResourcesMutexFactory = new ContextMutexFactory(); protected final ConstructorFunction<String, Set<Closeable>> cacheResourcesConstructorFunction = new ConstructorFunction<String, Set<Closeable>>() { @Override public Set<Closeable> createNew(String name) { return Collections.newSetFromMap(new ConcurrentHashMap<Closeable, Boolean>()); } }; protected NodeEngine nodeEngine; protected CachePartitionSegment[] segments; protected CacheEventHandler cacheEventHandler; protected CacheSplitBrainHandler cacheSplitBrainHandler; protected ILogger logger; @Override public final void init(NodeEngine nodeEngine, Properties properties) { this.nodeEngine = nodeEngine; int partitionCount = nodeEngine.getPartitionService().getPartitionCount(); this.segments = new CachePartitionSegment[partitionCount]; for (int i = 0; i < partitionCount; i++) { segments[i] = newPartitionSegment(i); } this.cacheEventHandler = new CacheEventHandler(nodeEngine); this.cacheSplitBrainHandler = new CacheSplitBrainHandler(nodeEngine, configs, segments); this.logger = nodeEngine.getLogger(getClass()); postInit(nodeEngine, properties); } protected void postInit(NodeEngine nodeEngine, Properties properties) { } protected abstract CachePartitionSegment newPartitionSegment(int partitionId); protected abstract ICacheRecordStore createNewRecordStore(String name, int partitionId); @Override public void reset() { reset(false); } private void reset(boolean onShutdown) { for (String objectName : configs.keySet()) { deleteCache(objectName, null, false); } CachePartitionSegment[] partitionSegments = segments; for (CachePartitionSegment partitionSegment : partitionSegments) { if (partitionSegment != null) { if (onShutdown) { partitionSegment.shutdown(); } else { partitionSegment.clear(); partitionSegment.init(); } } } for (String objectName : configs.keySet()) { sendInvalidationEvent(objectName, null, SOURCE_NOT_AVAILABLE); } } @Override public void shutdown(boolean terminate) { if (!terminate) { cacheEventHandler.shutdown(); reset(true); } } @Override public DistributedObject createDistributedObject(String fullCacheName) { try { /* * In here, `fullCacheName` is the full cache name. * Full cache name contains, Hazelcast prefix, cache name prefix and pure cache name. */ if (fullCacheName.equals(SETUP_REF)) { // workaround to make clients older than 3.7 to work with 3.7+ servers due to changes in the cache init! CacheSimpleConfig cacheSimpleConfig = new CacheSimpleConfig(); cacheSimpleConfig.setName("setupRef"); CacheConfig cacheConfig = new CacheConfig(cacheSimpleConfig); cacheConfig.setManagerPrefix(HazelcastCacheManager.CACHE_MANAGER_PREFIX); return new CacheProxy(cacheConfig, nodeEngine, this); } else { // At first, lookup cache name in the created cache configs. CacheConfig cacheConfig = getCacheConfig(fullCacheName); if (cacheConfig == null) { /* * Prefixed cache name contains cache name prefix and pure cache name, but not Hazelcast prefix (`/hz/`). * Cache name prefix is generated by using specified URI and classloader scopes. * This means, if there is no specified URI and classloader, prefixed cache name is pure cache name. * This means, if there is no specified URI and classloader, prefixed cache name is pure cache name. */ // If cache config is not created yet, remove Hazelcast prefix and get prefixed cache name. String cacheName = fullCacheName.substring(HazelcastCacheManager.CACHE_MANAGER_PREFIX.length()); // Lookup prefixed cache name in the config. cacheConfig = findCacheConfig(cacheName); checkCacheSimpleConfig(cacheName, cacheConfig); cacheConfig.setManagerPrefix(HazelcastCacheManager.CACHE_MANAGER_PREFIX); } checkCacheConfig(fullCacheName, cacheConfig); putCacheConfigIfAbsent(cacheConfig); return new CacheProxy(cacheConfig, nodeEngine, this); } } catch (Throwable t) { throw ExceptionUtil.rethrow(t); } } protected boolean isNativeInMemoryFormatSupported() { return false; } protected void checkCacheSimpleConfig(String cacheName, CacheConfig cacheConfig) { if (cacheConfig == null) { throw new CacheNotExistsException("Couldn't find cache config with name " + cacheName); } if (NATIVE == cacheConfig.getInMemoryFormat() && !isNativeInMemoryFormatSupported()) { throw new IllegalArgumentException("NATIVE storage format is supported in Hazelcast Enterprise only. " + "Make sure you have Hazelcast Enterprise JARs on your classpath!"); } } protected void checkCacheConfig(String cacheName, CacheConfig cacheConfig) { if (cacheConfig == null) { throw new CacheNotExistsException("Couldn't find cache config with name " + cacheName); } if (NATIVE == cacheConfig.getInMemoryFormat() && !isNativeInMemoryFormatSupported()) { throw new IllegalArgumentException("NATIVE storage format is supported in Hazelcast Enterprise only. " + "Make sure you have Hazelcast Enterprise JARs on your classpath!"); } } @Override public void destroyDistributedObject(String objectName) { deleteCache(objectName, null, true); } @Override public void beforeMigration(PartitionMigrationEvent event) { } @Override public void commitMigration(PartitionMigrationEvent event) { if (event.getMigrationEndpoint() == MigrationEndpoint.SOURCE) { clearCachesHavingLesserBackupCountThan(event.getPartitionId(), event.getNewReplicaIndex()); } initPartitionReplica(event.getPartitionId()); } @Override public void rollbackMigration(PartitionMigrationEvent event) { if (event.getMigrationEndpoint() == MigrationEndpoint.DESTINATION) { clearCachesHavingLesserBackupCountThan(event.getPartitionId(), event.getCurrentReplicaIndex()); } initPartitionReplica(event.getPartitionId()); } private void clearCachesHavingLesserBackupCountThan(int partitionId, int thresholdReplicaIndex) { if (thresholdReplicaIndex == -1) { clearPartitionReplica(partitionId); return; } CachePartitionSegment segment = segments[partitionId]; segment.clearHavingLesserBackupCountThan(thresholdReplicaIndex); } private void initPartitionReplica(int partitionId) { segments[partitionId].init(); } private void clearPartitionReplica(int partitionId) { segments[partitionId].clear(); } @Override public ICacheRecordStore getOrCreateRecordStore(String name, int partitionId) { return segments[partitionId].getOrCreateRecordStore(name); } @Override public ICacheRecordStore getRecordStore(String name, int partitionId) { return segments[partitionId].getRecordStore(name); } @Override public CachePartitionSegment getSegment(int partitionId) { return segments[partitionId]; } protected void destroySegments(String name) { for (CachePartitionSegment segment : segments) { segment.deleteRecordStore(name, true); } } protected void closeSegments(String name) { for (CachePartitionSegment segment : segments) { segment.deleteRecordStore(name, false); } } @Override public void deleteCache(String name, String callerUuid, boolean destroy) { CacheConfig config = deleteCacheConfig(name); if (destroy) { destroySegments(name); sendInvalidationEvent(name, null, SOURCE_NOT_AVAILABLE); } else { closeSegments(name); } cacheContexts.remove(name); operationProviderCache.remove(name); deregisterAllListener(name); setStatisticsEnabled(config, name, false); setManagementEnabled(config, name, false); deleteCacheStat(name); deleteCacheResources(name); } @Override public CacheConfig putCacheConfigIfAbsent(CacheConfig config) { CacheConfig localConfig = configs.putIfAbsent(config.getNameWithPrefix(), config); if (localConfig == null) { if (config.isStatisticsEnabled()) { setStatisticsEnabled(config, config.getNameWithPrefix(), true); } if (config.isManagementEnabled()) { setManagementEnabled(config, config.getNameWithPrefix(), true); } } if (localConfig == null) { logger.info("Added cache config: " + config); } return localConfig; } @Override public CacheConfig deleteCacheConfig(String name) { CacheConfig config = configs.remove(name); if (config != null) { logger.info("Removed cache config: " + config); } return config; } @Override public CacheStatisticsImpl createCacheStatIfAbsent(String name) { return ConcurrencyUtil.getOrPutIfAbsent(statistics, name, cacheStatisticsConstructorFunction); } public CacheContext getCacheContext(String name) { return cacheContexts.get(name); } @Override public CacheContext getOrCreateCacheContext(String name) { return ConcurrencyUtil.getOrPutIfAbsent(cacheContexts, name, cacheContextsConstructorFunction); } @Override public void deleteCacheStat(String name) { statistics.remove(name); } @Override public void setStatisticsEnabled(CacheConfig cacheConfig, String cacheNameWithPrefix, boolean enabled) { cacheConfig = cacheConfig != null ? cacheConfig : configs.get(cacheNameWithPrefix); if (cacheConfig != null) { String cacheManagerName = cacheConfig.getUriString(); cacheConfig.setStatisticsEnabled(enabled); if (enabled) { CacheStatisticsImpl cacheStatistics = createCacheStatIfAbsent(cacheNameWithPrefix); CacheStatisticsMXBeanImpl mxBean = new CacheStatisticsMXBeanImpl(cacheStatistics); MXBeanUtil.registerCacheObject(mxBean, cacheManagerName, cacheConfig.getName(), true); } else { MXBeanUtil.unregisterCacheObject(cacheManagerName, cacheConfig.getName(), true); deleteCacheStat(cacheNameWithPrefix); } } } @Override public void setManagementEnabled(CacheConfig cacheConfig, String cacheNameWithPrefix, boolean enabled) { cacheConfig = cacheConfig != null ? cacheConfig : configs.get(cacheNameWithPrefix); if (cacheConfig != null) { String cacheManagerName = cacheConfig.getUriString(); cacheConfig.setManagementEnabled(enabled); if (enabled) { CacheMXBeanImpl mxBean = new CacheMXBeanImpl(cacheConfig); MXBeanUtil.registerCacheObject(mxBean, cacheManagerName, cacheConfig.getName(), false); } else { MXBeanUtil.unregisterCacheObject(cacheManagerName, cacheConfig.getName(), false); deleteCacheStat(cacheNameWithPrefix); } } } @Override public CacheConfig getCacheConfig(String name) { return configs.get(name); } @Override public CacheConfig findCacheConfig(String simpleName) { if (simpleName == null) { return null; } CacheSimpleConfig cacheSimpleConfig = nodeEngine.getConfig().findCacheConfig(simpleName); if (cacheSimpleConfig == null) { return null; } try { // Set name explicitly, because found config might have a wildcard name. return new CacheConfig(cacheSimpleConfig).setName(simpleName); } catch (Exception e) { throw new CacheException(e); } } @Override public Collection<CacheConfig> getCacheConfigs() { return configs.values(); } public Object toObject(Object data) { if (data == null) { return null; } if (data instanceof Data) { return nodeEngine.toObject(data); } else { return data; } } public Data toData(Object object) { if (object == null) { return null; } if (object instanceof Data) { return (Data) object; } else { return nodeEngine.getSerializationService().toData(object); } } @Override public void publishEvent(CacheEventContext cacheEventContext) { cacheEventHandler.publishEvent(cacheEventContext); } @Override public void publishEvent(String cacheName, CacheEventSet eventSet, int orderKey) { cacheEventHandler.publishEvent(cacheName, eventSet, orderKey); } @Override public NodeEngine getNodeEngine() { return nodeEngine; } @Override public void dispatchEvent(Object event, CacheEventListener listener) { listener.handleEvent(event); } @Override public String registerListener(String name, CacheEventListener listener, boolean isLocal) { return registerListenerInternal(name, listener, null, isLocal); } @Override public String registerListener(String name, CacheEventListener listener, EventFilter eventFilter, boolean isLocal) { return registerListenerInternal(name, listener, eventFilter, isLocal); } protected String registerListenerInternal(String name, CacheEventListener listener, EventFilter eventFilter, boolean isLocal) { EventService eventService = getNodeEngine().getEventService(); EventRegistration reg; if (isLocal) { if (eventFilter == null) { reg = eventService.registerLocalListener(AbstractCacheService.SERVICE_NAME, name, listener); } else { reg = eventService.registerLocalListener(AbstractCacheService.SERVICE_NAME, name, eventFilter, listener); } } else { if (eventFilter == null) { reg = eventService.registerListener(AbstractCacheService.SERVICE_NAME, name, listener); } else { reg = eventService.registerListener(AbstractCacheService.SERVICE_NAME, name, eventFilter, listener); } } String id = reg.getId(); if (listener instanceof Closeable) { closeableListeners.put(id, (Closeable) listener); } else if (listener instanceof CacheEntryListenerProvider) { CacheEntryListener cacheEntryListener = ((CacheEntryListenerProvider) listener).getCacheEntryListener(); if (cacheEntryListener instanceof Closeable) { closeableListeners.put(id, (Closeable) cacheEntryListener); } } return id; } @Override public boolean deregisterListener(String name, String registrationId) { EventService eventService = getNodeEngine().getEventService(); boolean result = eventService.deregisterListener(SERVICE_NAME, name, registrationId); Closeable listener = closeableListeners.remove(registrationId); if (listener != null) { IOUtil.closeResource(listener); } return result; } @Override public void deregisterAllListener(String name) { EventService eventService = getNodeEngine().getEventService(); Collection<EventRegistration> registrations = eventService.getRegistrations(SERVICE_NAME, name); if (registrations != null) { for (EventRegistration registration : registrations) { Closeable listener = closeableListeners.remove(registration.getId()); if (listener != null) { IOUtil.closeResource(listener); } } } eventService.deregisterAllListeners(AbstractCacheService.SERVICE_NAME, name); CacheContext cacheContext = cacheContexts.get(name); if (cacheContext != null) { cacheContext.resetCacheEntryListenerCount(); cacheContext.resetInvalidationListenerCount(); } } @Override public CacheStatisticsImpl getStatistics(String name) { return statistics.get(name); } @Override public CacheOperationProvider getCacheOperationProvider(String nameWithPrefix, InMemoryFormat inMemoryFormat) { if (InMemoryFormat.NATIVE.equals(inMemoryFormat)) { throw new IllegalArgumentException("Native memory is available only in Hazelcast Enterprise." + "Make sure you have Hazelcast Enterprise JARs on your classpath!"); } CacheOperationProvider cacheOperationProvider = operationProviderCache.get(nameWithPrefix); if (cacheOperationProvider != null) { return cacheOperationProvider; } cacheOperationProvider = createOperationProvider(nameWithPrefix, inMemoryFormat); CacheOperationProvider current = operationProviderCache.putIfAbsent(nameWithPrefix, cacheOperationProvider); return current == null ? cacheOperationProvider : current; } protected abstract CacheOperationProvider createOperationProvider(String nameWithPrefix, InMemoryFormat inMemoryFormat); public void addCacheResource(String name, Closeable resource) { Set<Closeable> cacheResources = ConcurrencyUtil.getOrPutSynchronized(resources, name, cacheResourcesMutexFactory, cacheResourcesConstructorFunction); cacheResources.add(resource); } private void deleteCacheResources(String name) { Set<Closeable> cacheResources; ContextMutexFactory.Mutex mutex = cacheResourcesMutexFactory.mutexFor(name); try { synchronized (mutex) { cacheResources = resources.remove(name); } } finally { mutex.close(); } if (cacheResources != null) { for (Closeable resource : cacheResources) { IOUtil.closeResource(resource); } cacheResources.clear(); } } @Override public Operation getPostJoinOperation() { PostJoinCacheOperation postJoinCacheOperation = new PostJoinCacheOperation(); for (Map.Entry<String, CacheConfig> cacheConfigEntry : configs.entrySet()) { postJoinCacheOperation.addCacheConfig(cacheConfigEntry.getValue()); } return postJoinCacheOperation; } protected void publishCachePartitionLostEvent(String cacheName, int partitionId) { Collection<EventRegistration> registrations = new LinkedList<EventRegistration>(); for (EventRegistration registration : getRegistrations(cacheName)) { if (registration.getFilter() instanceof CachePartitionLostEventFilter) { registrations.add(registration); } } if (registrations.isEmpty()) { return; } Member member = nodeEngine.getLocalMember(); CacheEventData eventData = new CachePartitionEventData(cacheName, partitionId, member); EventService eventService = nodeEngine.getEventService(); eventService.publishEvent(SERVICE_NAME, registrations, eventData, partitionId); } Collection<EventRegistration> getRegistrations(String cacheName) { EventService eventService = nodeEngine.getEventService(); return eventService.getRegistrations(SERVICE_NAME, cacheName); } @Override public void onPartitionLost(IPartitionLostEvent partitionLostEvent) { int partitionId = partitionLostEvent.getPartitionId(); for (CacheConfig config : getCacheConfigs()) { final String cacheName = config.getName(); if (config.getTotalBackupCount() <= partitionLostEvent.getLostReplicaIndex()) { publishCachePartitionLostEvent(cacheName, partitionId); } } } public void cacheEntryListenerRegistered(String name, CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { CacheConfig cacheConfig = getCacheConfig(name); if (cacheConfig == null) { throw new IllegalStateException("CacheConfig does not exist for cache " + name); } cacheConfig.addCacheEntryListenerConfiguration(cacheEntryListenerConfiguration); } public void cacheEntryListenerDeregistered(String name, CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { CacheConfig cacheConfig = getCacheConfig(name); if (cacheConfig == null) { throw new IllegalStateException("CacheConfig does not exist for cache " + name); } cacheConfig.removeCacheEntryListenerConfiguration(cacheEntryListenerConfiguration); } /** * Gets the name of the quorum associated with specified cache * * @param cacheName name of the cache * @return name of the associated quorum * null if there is no associated quorum */ @Override public String getQuorumName(String cacheName) { CacheConfig cacheConfig = configs.get(cacheName); if (cacheConfig == null) { return null; } return cacheConfig.getQuorumName(); } /** * Registers and {@link com.hazelcast.cache.impl.CacheEventListener} for specified <code>cacheName</code>. * * @param name the name of the cache that {@link com.hazelcast.cache.impl.CacheEventListener} will be registered for * @param listener the {@link com.hazelcast.cache.impl.CacheEventListener} to be registered for specified <code>cache</code> * @param localOnly true if only events originated from this member wants be listened, false if all invalidation events in the * cluster wants to be listened * @return the id which is unique for current registration */ @Override public String addInvalidationListener(String name, CacheEventListener listener, boolean localOnly) { EventService eventService = nodeEngine.getEventService(); EventRegistration registration; if (localOnly) { registration = eventService.registerLocalListener(SERVICE_NAME, name, listener); } else { registration = eventService.registerListener(SERVICE_NAME, name, listener); } return registration.getId(); } /** * Sends an invalidation event for given <code>cacheName</code> with specified <code>key</code> * from mentioned source with <code>sourceUuid</code>. * * @param name the name of the cache that invalidation event is sent for * @param key the {@link com.hazelcast.nio.serialization.Data} represents the invalidation event * @param sourceUuid an id that represents the source for invalidation event */ @Override public void sendInvalidationEvent(String name, Data key, String sourceUuid) { cacheEventHandler.sendInvalidationEvent(name, key, sourceUuid); } @Override public Runnable prepareMergeRunnable() { return cacheSplitBrainHandler.prepareMergeRunnable(); } public CacheEventHandler getCacheEventHandler() { return cacheEventHandler; } }