All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.cache.impl.CacheProxySupport Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, 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.impl.event.CachePartitionLostEventFilter;
import com.hazelcast.cache.impl.event.CachePartitionLostListener;
import com.hazelcast.cache.impl.event.InternalCachePartitionLostListenerAdapter;
import com.hazelcast.cache.impl.operation.CacheListenerRegistrationOperation;
import com.hazelcast.cache.impl.operation.MutableOperation;
import com.hazelcast.cluster.Member;
import com.hazelcast.config.CacheConfig;
import com.hazelcast.config.CachePartitionLostListenerConfig;
import com.hazelcast.config.ListenerConfig;
import com.hazelcast.core.ManagedContext;
import com.hazelcast.internal.namespace.NamespaceUtil;
import com.hazelcast.internal.nio.ClassLoaderUtil;
import com.hazelcast.internal.serialization.SerializationService;
import com.hazelcast.internal.util.ExceptionUtil;
import com.hazelcast.internal.util.FutureUtil;
import com.hazelcast.internal.util.collection.PartitionIdSet;
import com.hazelcast.internal.util.executor.CompletableFutureTask;
import com.hazelcast.logging.ILogger;
import com.hazelcast.internal.serialization.Data;
import com.hazelcast.spi.impl.AbstractDistributedObject;
import com.hazelcast.spi.impl.NodeEngine;
import com.hazelcast.spi.impl.eventservice.EventFilter;
import com.hazelcast.spi.impl.executionservice.ExecutionService;
import com.hazelcast.spi.impl.operationservice.Operation;
import com.hazelcast.spi.impl.operationservice.OperationFactory;
import com.hazelcast.spi.impl.operationservice.OperationService;
import com.hazelcast.spi.impl.operationservice.impl.InvocationFuture;
import com.hazelcast.internal.partition.IPartitionService;

import javax.annotation.Nonnull;
import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.configuration.CacheEntryListenerConfiguration;
import javax.cache.expiry.ExpiryPolicy;
import javax.cache.integration.CompletionListener;
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.EntryProcessorException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.hazelcast.cache.impl.CacheProxyUtil.validateNotNull;
import static com.hazelcast.cache.impl.operation.MutableOperation.IGNORE_COMPLETION;
import static com.hazelcast.internal.util.ConcurrencyUtil.CALLER_RUNS;
import static com.hazelcast.internal.util.ExceptionUtil.rethrow;
import static com.hazelcast.internal.util.ExceptionUtil.rethrowAllowedTypeFirst;
import static com.hazelcast.internal.util.SetUtil.createHashSet;
import com.hazelcast.spi.tenantcontrol.DestroyEventContext;

/**
 * Abstract {@link com.hazelcast.cache.ICache} implementation which provides shared internal implementations
 * of cache operations like put, replace, remove and invoke. These internal implementations are delegated
 * by actual cache methods.
 * 

*

Note: this partial implementation is used by server or embedded mode cache.

* * @param the type of key. * @param the type of value. * @see com.hazelcast.cache.impl.CacheProxy * @see com.hazelcast.cache.ICache */ @SuppressWarnings({"checkstyle:methodcount", "checkstyle:classfanoutcomplexity"}) abstract class CacheProxySupport extends AbstractDistributedObject implements ICacheInternal, CacheSyncListenerCompleter { private static final int TIMEOUT = 10; protected final ILogger logger; protected CacheConfig cacheConfig; protected final String name; protected final String nameWithPrefix; protected final ICacheService cacheService; protected final SerializationService serializationService; protected final CacheOperationProvider operationProvider; protected final IPartitionService partitionService; private final CopyOnWriteArrayList loadAllTasks = new CopyOnWriteArrayList<>(); private final AtomicReference cacheManagerRef = new AtomicReference<>(); private final AtomicBoolean isClosed = new AtomicBoolean(false); private final AtomicBoolean isDestroyed = new AtomicBoolean(false); private final CacheProxySyncListenerCompleter listenerCompleter = new CacheProxySyncListenerCompleter(this); CacheProxySupport(CacheConfig cacheConfig, NodeEngine nodeEngine, ICacheService cacheService) { super(nodeEngine, cacheService); this.name = cacheConfig.getName(); this.nameWithPrefix = cacheConfig.getNameWithPrefix(); this.cacheConfig = cacheConfig; this.logger = nodeEngine.getLogger(getClass()); this.partitionService = nodeEngine.getPartitionService(); this.cacheService = cacheService; this.serializationService = nodeEngine.getSerializationService(); this.operationProvider = cacheService.getCacheOperationProvider(nameWithPrefix, cacheConfig.getInMemoryFormat()); List configs = cacheConfig.getPartitionLostListenerConfigs(); for (CachePartitionLostListenerConfig listenerConfig : configs) { CachePartitionLostListener listener = initializeListener(listenerConfig); if (listener != null) { EventFilter filter = new CachePartitionLostEventFilter(); CacheEventListener listenerAdapter = new InternalCachePartitionLostListenerAdapter(listener); getService().getNodeEngine().getEventService() .registerListener(AbstractCacheService.SERVICE_NAME, name, filter, listenerAdapter); } } } @Override public String getName() { return name; } @Override protected String getDistributedObjectName() { return nameWithPrefix; } @Override public String getPrefixedName() { return nameWithPrefix; } @Override public String getServiceName() { return ICacheService.SERVICE_NAME; } @Override public void open() { if (isDestroyed.get()) { throw new IllegalStateException("Cache is already destroyed! Cannot be reopened"); } isClosed.compareAndSet(true, false); } @Override public void close() { close0(false); } @Override public @Nonnull DestroyEventContext getDestroyContextForTenant() { return () -> cacheConfig = ((CacheService) cacheService).reSerializeCacheConfig(cacheConfig); } @Override protected boolean preDestroy() { close0(true); if (!isDestroyed.compareAndSet(false, true)) { return false; } isClosed.set(true); return true; } @Override public boolean isClosed() { return isClosed.get(); } @Override public boolean isDestroyed() { return isDestroyed.get(); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CacheProxySupport that = (CacheProxySupport) o; if (nameWithPrefix != null ? !nameWithPrefix.equals(that.nameWithPrefix) : that.nameWithPrefix != null) { return false; } return true; } @Override public int hashCode() { return nameWithPrefix != null ? nameWithPrefix.hashCode() : 0; } @Override public String toString() { return getClass().getName() + '{' + "name=" + name + ", nameWithPrefix=" + nameWithPrefix + '}'; } @Override public CacheManager getCacheManager() { return cacheManagerRef.get(); } @Override public void setCacheManager(HazelcastCacheManager cacheManager) { assert cacheManager instanceof HazelcastServerCacheManager; // optimistically assume the CacheManager is already set if (cacheManagerRef.get() == cacheManager) { return; } if (!this.cacheManagerRef.compareAndSet(null, (HazelcastServerCacheManager) cacheManager)) { if (cacheManagerRef.get() == cacheManager) { // some other thread managed to set the same CacheManager, we are good return; } throw new IllegalStateException("Cannot overwrite a Cache's CacheManager."); } } @Override public void resetCacheManager() { cacheManagerRef.set(null); } @Override protected void postDestroy() { CacheManager cacheManager = cacheManagerRef.get(); if (cacheManager != null) { cacheManager.destroyCache(getName()); } resetCacheManager(); } @Override public void countDownCompletionLatch(int countDownLatchId) { listenerCompleter.countDownCompletionLatch(countDownLatchId); } protected void ensureOpen() { if (isClosed()) { throw new IllegalStateException("Cache operations can not be performed. The cache closed"); } } @SuppressWarnings("unchecked") protected void createAndSubmitLoadAllTask(Set keysData, boolean replaceExistingValues, CompletionListener completionListener) { try { CacheProxyLoadAllTask loadAllTask = new CacheProxyLoadAllTask(getNodeEngine(), operationProvider, keysData, replaceExistingValues, completionListener, getServiceName()); ExecutionService executionService = getNodeEngine().getExecutionService(); final CompletableFutureTask future = (CompletableFutureTask) executionService .submit("loadAll-" + nameWithPrefix, loadAllTask); loadAllTasks.add(future); future.whenCompleteAsync((response, t) -> { loadAllTasks.remove(future); if (t != null) { logger.warning("Problem in loadAll task", t); } }, CALLER_RUNS); } catch (Exception e) { if (completionListener != null) { completionListener.onException(e); } throw new CacheException(e); } } @SuppressWarnings("unchecked") protected T injectDependencies(Object obj) { ManagedContext managedContext = serializationService.getManagedContext(); return (T) managedContext.initialize(obj); } protected InvocationFuture invoke(Operation op, Data keyData, boolean completionOperation) { int partitionId = getPartitionId(keyData); return invoke(op, partitionId, completionOperation); } protected InvocationFuture removeAsyncInternal(K key, V oldValue, boolean hasOldValue, boolean isGet, boolean withCompletionEvent) { ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue); CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, oldValue); } else { validateNotNull(key); CacheProxyUtil.validateConfiguredTypes(cacheConfig, key); } Data keyData = serializationService.toData(key); Data valueData = serializationService.toData(oldValue); Operation operation; if (isGet) { operation = operationProvider.createGetAndRemoveOperation(keyData, IGNORE_COMPLETION); } else { operation = operationProvider.createRemoveOperation(keyData, valueData, IGNORE_COMPLETION); } return invoke(operation, keyData, withCompletionEvent); } protected InvocationFuture replaceAsyncInternal(K key, V oldValue, V newValue, ExpiryPolicy expiryPolicy, boolean hasOldValue, boolean isGet, boolean withCompletionEvent) { ensureOpen(); if (hasOldValue) { validateNotNull(key, oldValue, newValue); CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, oldValue, newValue); } else { validateNotNull(key, newValue); CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, newValue); } Data keyData = serializationService.toData(key); Data oldValueData = serializationService.toData(oldValue); Data newValueData = serializationService.toData(newValue); Operation operation; if (isGet) { operation = operationProvider.createGetAndReplaceOperation(keyData, newValueData, expiryPolicy, IGNORE_COMPLETION); } else { operation = operationProvider.createReplaceOperation(keyData, oldValueData, newValueData, expiryPolicy, IGNORE_COMPLETION); } return invoke(operation, keyData, withCompletionEvent); } protected InvocationFuture putAsyncInternal(K key, V value, ExpiryPolicy expiryPolicy, boolean isGet, boolean withCompletionEvent) { ensureOpen(); validateNotNull(key, value); CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, value); Data keyData = serializationService.toData(key); Data valueData = serializationService.toData(value); Operation op = operationProvider.createPutOperation(keyData, valueData, expiryPolicy, isGet, IGNORE_COMPLETION); return invoke(op, keyData, withCompletionEvent); } protected InvocationFuture putIfAbsentAsyncInternal(K key, V value, ExpiryPolicy expiryPolicy, boolean withCompletionEvent) { ensureOpen(); validateNotNull(key, value); CacheProxyUtil.validateConfiguredTypes(cacheConfig, key, value); Data keyData = serializationService.toData(key); Data valueData = serializationService.toData(value); Operation operation = operationProvider.createPutIfAbsentOperation(keyData, valueData, expiryPolicy, IGNORE_COMPLETION); return invoke(operation, keyData, withCompletionEvent); } protected void clearInternal() { try { OperationService operationService = getNodeEngine().getOperationService(); OperationFactory operationFactory = operationProvider.createClearOperationFactory(); Map results = operationService.invokeOnAllPartitions(getServiceName(), operationFactory); for (Object result : results.values()) { if (result != null && result instanceof CacheClearResponse clearResponse) { Object response = clearResponse.getResponse(); if (response instanceof Throwable throwable) { throw throwable; } } } } catch (Throwable t) { throw rethrowAllowedTypeFirst(t, CacheException.class); } } protected void removeAllInternal(Set keys) { Set keysData = null; if (keys != null) { keysData = createHashSet(keys.size()); for (K key : keys) { validateNotNull(key); keysData.add(serializationService.toData(key)); } } int partitionCount = getNodeEngine().getPartitionService().getPartitionCount(); Integer completionId = listenerCompleter.registerCompletionLatch(partitionCount); OperationService operationService = getNodeEngine().getOperationService(); OperationFactory operationFactory = operationProvider.createRemoveAllOperationFactory(keysData, completionId); try { Map results = operationService.invokeOnAllPartitions(getServiceName(), operationFactory); int completionCount = 0; for (Object result : results.values()) { if (result != null && result instanceof CacheClearResponse clearResponse) { Object response = clearResponse.getResponse(); if (response instanceof Boolean) { completionCount++; } if (response instanceof Throwable throwable) { throw throwable; } } } listenerCompleter.waitCompletionLatch(completionId, partitionCount - completionCount); } catch (Throwable t) { listenerCompleter.deregisterCompletionLatch(completionId); throw rethrowAllowedTypeFirst(t, CacheException.class); } } protected void addListenerLocally(UUID regId, CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { listenerCompleter.putListenerIfAbsent(cacheEntryListenerConfiguration, regId); } protected void removeListenerLocally(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { listenerCompleter.removeListener(cacheEntryListenerConfiguration); } protected UUID getListenerIdLocal(CacheEntryListenerConfiguration cacheEntryListenerConfiguration) { return listenerCompleter.getListenerId(cacheEntryListenerConfiguration); } protected T invokeInternal(Data keyData, EntryProcessor entryProcessor, Object... arguments) throws EntryProcessorException { Integer completionId = listenerCompleter.registerCompletionLatch(1); Operation op = operationProvider.createEntryProcessorOperation(keyData, completionId, entryProcessor, arguments); try { OperationService operationService = getNodeEngine().getOperationService(); int partitionId = getPartitionId(keyData); InvocationFuture future = operationService.invokeOnPartition(getServiceName(), op, partitionId); T safely = future.joinInternal(); listenerCompleter.waitCompletionLatch(completionId); return safely; } catch (CacheException ce) { listenerCompleter.deregisterCompletionLatch(completionId); throw ce; } catch (Exception e) { listenerCompleter.deregisterCompletionLatch(completionId); throw new EntryProcessorException(e); } } protected void updateCacheListenerConfigOnOtherNodes(CacheEntryListenerConfiguration cacheEntryListenerConfiguration, boolean isRegister) { OperationService operationService = getNodeEngine().getOperationService(); Collection members = getNodeEngine().getClusterService().getMembers(); for (Member member : members) { if (!member.localMember()) { Operation op = new CacheListenerRegistrationOperation(getDistributedObjectName(), cacheEntryListenerConfiguration, isRegister); operationService.invokeOnTarget(CacheService.SERVICE_NAME, op, member.getAddress()); } } } protected List[] groupDataToPartitions(Collection keys, int partitionCount) { List[] keysPerPartition = new ArrayList[partitionCount]; for (K key: keys) { validateNotNull(key); Data dataKey = serializationService.toData(key); int partitionId = partitionService.getPartitionId(dataKey); List partition = keysPerPartition[partitionId]; if (partition == null) { partition = new ArrayList<>(); keysPerPartition[partitionId] = partition; } partition.add(dataKey); } return keysPerPartition; } protected void putToAllPartitionsAndWaitForCompletion(List>[] entriesPerPartition, ExpiryPolicy expiryPolicy) throws Exception { List futures = new ArrayList<>(entriesPerPartition.length); for (int partitionId = 0; partitionId < entriesPerPartition.length; partitionId++) { List> entries = entriesPerPartition[partitionId]; if (entries != null) { // TODO: if there is a single entry, we could make use of a put operation since that is a bit cheaper Operation operation = operationProvider.createPutAllOperation(entries, expiryPolicy, partitionId); Future future = invoke(operation, partitionId, true); futures.add(future); } } Throwable error = null; for (Future future : futures) { try { future.get(); } catch (Throwable t) { logger.finest("Error occurred while putting entries as batch!", t); if (error == null) { error = t; } } } if (error != null) { /* * There maybe multiple exceptions but we throw only the first one. * There are some ideas to throw all exceptions to caller but all of them have drawbacks: * - `Thread::addSuppressed` can be used to add other exceptions to the first one * but it is available since JDK 7. * - `Thread::initCause` can be used but this is wrong as semantic * since the other exceptions are not cause of the first one. * - We may wrap all exceptions in our custom exception (such as `MultipleCacheException`) * but in this case caller may wait different exception type and this idea causes problem. * For example see this TCK test: * `org.jsr107.tck.integration.CacheWriterTest::shouldWriteThoughUsingPutAll_partialSuccess` * In this test exception is thrown at `CacheWriter` and caller side expects this exception. * So as a result, we only throw the first exception and others are suppressed by only logging. */ throw rethrow(error); } } protected void setTTLAllPartitionsAndWaitForCompletion(List[] keysPerPartition, Data expiryPolicy) { List futures = new ArrayList<>(keysPerPartition.length); for (int partitionId = 0; partitionId < keysPerPartition.length; partitionId++) { List keys = keysPerPartition[partitionId]; if (keys != null) { Operation operation = operationProvider.createSetExpiryPolicyOperation(keys, expiryPolicy); futures.add(invoke(operation, partitionId, true)); } } List throwables = FutureUtil.waitUntilAllResponded(futures); if (!throwables.isEmpty()) { throw rethrow(throwables.get(0)); } } protected PartitionIdSet getPartitionsForKeys(Set keys) { IPartitionService partitionService = getNodeEngine().getPartitionService(); int partitions = partitionService.getPartitionCount(); PartitionIdSet partitionIds = new PartitionIdSet(partitions); Iterator iterator = keys.iterator(); int addedPartitions = 0; while (iterator.hasNext() && addedPartitions < partitions) { Data key = iterator.next(); if (partitionIds.add(partitionService.getPartitionId(key))) { addedPartitions++; } } return partitionIds; } private void deregisterAllCacheEntryListener(Collection listenerRegistrations) { ICacheService service = getService(); for (UUID regId : listenerRegistrations) { service.deregisterListener(nameWithPrefix, regId); } } @SuppressWarnings("unchecked") private T initializeListener(ListenerConfig listenerConfig) { T listener = null; if (listenerConfig.getImplementation() != null) { listener = (T) listenerConfig.getImplementation(); } else if (listenerConfig.getClassName() != null) { try { ClassLoader loader = NamespaceUtil.getClassLoaderForNamespace(getNodeEngine(), cacheConfig.getUserCodeNamespace()); listener = ClassLoaderUtil.newInstance(loader, listenerConfig.getClassName()); } catch (Exception e) { throw ExceptionUtil.rethrow(e); } } listener = injectDependencies(listener); return listener; } private void close0(boolean destroy) { if (!isClosed.compareAndSet(false, true)) { return; } Exception caughtException = null; for (Future f : loadAllTasks) { try { f.get(TIMEOUT, TimeUnit.SECONDS); } catch (Exception e) { if (caughtException == null) { caughtException = e; } getNodeEngine().getLogger(getClass()).warning("Problem while waiting for loadAll tasks to complete", e); } } loadAllTasks.clear(); closeListeners(); if (!destroy) { // when cache is being destroyed, the CacheManager is still required for cleanup and reset in postDestroy // when cache is being closed, the CacheManager is reset now resetCacheManager(); } if (caughtException != null) { throw new CacheException("Problem while waiting for loadAll tasks to complete", caughtException); } } private void closeListeners() { deregisterAllCacheEntryListener(listenerCompleter.getListenersIds(true)); deregisterAllCacheEntryListener(listenerCompleter.getListenersIds(false)); listenerCompleter.clearListeners(); } private InvocationFuture invoke(Operation op, int partitionId, boolean completionOperation) { Integer completionId = null; if (completionOperation) { completionId = listenerCompleter.registerCompletionLatch(1); if (op instanceof MutableOperation operation) { operation.setCompletionId(completionId); } } try { InvocationFuture future = getNodeEngine().getOperationService() .invokeOnPartition(getServiceName(), op, partitionId); if (completionOperation) { listenerCompleter.waitCompletionLatch(completionId); } return future; } catch (Throwable e) { if (e instanceof IllegalStateException) { close(); } throw rethrowAllowedTypeFirst(e, CacheException.class); } finally { if (completionOperation) { listenerCompleter.deregisterCompletionLatch(completionId); } } } }