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

org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion Maven / Gradle / Ivy

There is a newer version: 5.6.15.Final
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or .
 */
package org.hibernate.cache.infinispan.impl;

import org.hibernate.cache.infinispan.InfinispanRegionFactory;
import org.hibernate.cache.infinispan.access.AccessDelegate;
import org.hibernate.cache.infinispan.access.LockingInterceptor;
import org.hibernate.cache.infinispan.access.NonStrictAccessDelegate;
import org.hibernate.cache.infinispan.access.NonTxInvalidationCacheAccessDelegate;
import org.hibernate.cache.infinispan.access.PutFromLoadValidator;
import org.hibernate.cache.infinispan.access.TombstoneAccessDelegate;
import org.hibernate.cache.infinispan.access.TombstoneCallInterceptor;
import org.hibernate.cache.infinispan.access.TxInvalidationCacheAccessDelegate;
import org.hibernate.cache.infinispan.access.UnorderedDistributionInterceptor;
import org.hibernate.cache.infinispan.access.VersionedCallInterceptor;
import org.hibernate.cache.infinispan.util.Caches;
import org.hibernate.cache.infinispan.util.FutureUpdate;
import org.hibernate.cache.infinispan.util.InfinispanMessageLogger;
import org.hibernate.cache.infinispan.util.Tombstone;
import org.hibernate.cache.infinispan.util.VersionedEntry;
import org.hibernate.cache.spi.CacheDataDescription;
import org.hibernate.cache.spi.CacheKeysFactory;
import org.hibernate.cache.spi.TransactionalDataRegion;

import org.hibernate.cache.spi.access.AccessType;
import org.infinispan.AdvancedCache;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.expiration.ExpirationManager;
import org.infinispan.expiration.impl.ClusterExpirationManager;
import org.infinispan.expiration.impl.ExpirationManagerImpl;
import org.infinispan.filter.KeyValueFilter;
import org.infinispan.interceptors.CallInterceptor;
import org.infinispan.interceptors.EntryWrappingInterceptor;
import org.infinispan.interceptors.base.CommandInterceptor;
import org.infinispan.interceptors.distribution.NonTxDistributionInterceptor;
import org.infinispan.interceptors.locking.NonTransactionalLockingInterceptor;

import javax.transaction.TransactionManager;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Support for Inifinispan {@link org.hibernate.cache.spi.TransactionalDataRegion} implementors.
 *
 * @author Chris Bredesen
 * @author Galder Zamarreño
 * @since 3.5
 */
public abstract class BaseTransactionalDataRegion
		extends BaseRegion implements TransactionalDataRegion {
	private static final InfinispanMessageLogger log = InfinispanMessageLogger.Provider.getLog( BaseTransactionalDataRegion.class );
	private final CacheDataDescription metadata;
	private final CacheKeysFactory cacheKeysFactory;
	private final boolean requiresTransaction;

	private long tombstoneExpiration;
	private PutFromLoadValidator validator;

	private AccessType accessType;
	private Strategy strategy;

	protected enum Strategy {
		NONE, VALIDATION, TOMBSTONES, VERSIONED_ENTRIES
	}

	/**
	 * Base transactional region constructor
	 *  @param cache instance to store transactional data
	 * @param name of the transactional region
	 * @param transactionManager
	 * @param metadata for the transactional region
	 * @param factory for the transactional region
	 * @param cacheKeysFactory factory for cache keys
	 */
	public BaseTransactionalDataRegion(
			AdvancedCache cache, String name, TransactionManager transactionManager,
			CacheDataDescription metadata, InfinispanRegionFactory factory, CacheKeysFactory cacheKeysFactory) {
		super( cache, name, transactionManager, factory);
		this.metadata = metadata;
		this.cacheKeysFactory = cacheKeysFactory;

		Configuration configuration = cache.getCacheConfiguration();
		requiresTransaction = configuration.transaction().transactionMode().isTransactional()
				&& !configuration.transaction().autoCommit();
		tombstoneExpiration = factory.getPendingPutsCacheConfiguration().expiration().maxIdle();
		if (!isRegionAccessStrategyEnabled()) {
			strategy = Strategy.NONE;
		}
	}

	/**
	 * @return True if this region is accessed through RegionAccessStrategy, false if it is accessed directly.
    */
	protected boolean isRegionAccessStrategyEnabled() {
		return true;
	}

	@Override
	public CacheDataDescription getCacheDataDescription() {
		return metadata;
	}

	public CacheKeysFactory getCacheKeysFactory() {
		return cacheKeysFactory;
	}

	protected synchronized AccessDelegate createAccessDelegate(AccessType accessType) {
		if (accessType == null) {
			throw new IllegalArgumentException();
		}
		if (this.accessType != null && !this.accessType.equals(accessType)) {
			throw new IllegalStateException("This region was already set up for " + this.accessType + ", cannot use using " + accessType);
		}
		this.accessType = accessType;

		CacheMode cacheMode = cache.getCacheConfiguration().clustering().cacheMode();
		if (accessType == AccessType.NONSTRICT_READ_WRITE) {
			prepareForVersionedEntries();
			return new NonStrictAccessDelegate(this);
		}
		if (cacheMode.isDistributed() || cacheMode.isReplicated()) {
			prepareForTombstones();
			return new TombstoneAccessDelegate(this);
		}
		else {
			prepareForValidation();
			if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) {
				return new TxInvalidationCacheAccessDelegate(this, validator);
			}
			else {
				return new NonTxInvalidationCacheAccessDelegate(this, validator);
			}
		}
	}

	protected void prepareForValidation() {
		if (strategy != null) {
			assert strategy == Strategy.VALIDATION;
			return;
		}
		validator = new PutFromLoadValidator(cache, factory);
		strategy = Strategy.VALIDATION;
	}

	protected void prepareForVersionedEntries() {
		if (strategy != null) {
			assert strategy == Strategy.VERSIONED_ENTRIES;
			return;
		}

		replaceCommonInterceptors();
		replaceExpirationManager();

		cache.removeInterceptor(CallInterceptor.class);
		VersionedCallInterceptor tombstoneCallInterceptor = new VersionedCallInterceptor(this, metadata.getVersionComparator());
		cache.getComponentRegistry().registerComponent(tombstoneCallInterceptor, VersionedCallInterceptor.class);
		List interceptorChain = cache.getInterceptorChain();
		cache.addInterceptor(tombstoneCallInterceptor, interceptorChain.size());

		strategy = Strategy.VERSIONED_ENTRIES;
	}

	private void prepareForTombstones() {
		if (strategy != null) {
			assert strategy == Strategy.TOMBSTONES;
			return;
		}
		Configuration configuration = cache.getCacheConfiguration();
		if (configuration.eviction().maxEntries() >= 0) {
			log.evictionWithTombstones();
		}

		replaceCommonInterceptors();
		replaceExpirationManager();

		cache.removeInterceptor(CallInterceptor.class);
		TombstoneCallInterceptor tombstoneCallInterceptor = new TombstoneCallInterceptor(this);
		cache.getComponentRegistry().registerComponent(tombstoneCallInterceptor, TombstoneCallInterceptor.class);
		List interceptorChain = cache.getInterceptorChain();
		cache.addInterceptor(tombstoneCallInterceptor, interceptorChain.size());

		strategy = Strategy.TOMBSTONES;
	}

	private void replaceCommonInterceptors() {
		CacheMode cacheMode = cache.getCacheConfiguration().clustering().cacheMode();
		if (!cacheMode.isReplicated() && !cacheMode.isDistributed()) {
			return;
		}

		LockingInterceptor lockingInterceptor = new LockingInterceptor();
		cache.getComponentRegistry().registerComponent(lockingInterceptor, LockingInterceptor.class);
		if (!cache.addInterceptorBefore(lockingInterceptor, NonTransactionalLockingInterceptor.class)) {
			throw new IllegalStateException("Misconfigured cache, interceptor chain is " + cache.getInterceptorChain());
		}
		cache.removeInterceptor(NonTransactionalLockingInterceptor.class);

		UnorderedDistributionInterceptor distributionInterceptor = new UnorderedDistributionInterceptor();
		cache.getComponentRegistry().registerComponent(distributionInterceptor, UnorderedDistributionInterceptor.class);
		if (!cache.addInterceptorBefore(distributionInterceptor, NonTxDistributionInterceptor.class)) {
			throw new IllegalStateException("Misconfigured cache, interceptor chain is " + cache.getInterceptorChain());
		}
		cache.removeInterceptor(NonTxDistributionInterceptor.class);

		EntryWrappingInterceptor ewi = cache.getComponentRegistry().getComponent(EntryWrappingInterceptor.class);
		try {
			Field isUsingLockDelegation = EntryWrappingInterceptor.class.getDeclaredField("isUsingLockDelegation");
			isUsingLockDelegation.setAccessible(true);
			isUsingLockDelegation.set(ewi, false);
		}
		catch (NoSuchFieldException | IllegalAccessException e) {
			throw new IllegalStateException(e);
		}
	}

	private void replaceExpirationManager() {
		// ClusteredExpirationManager sends RemoteExpirationCommands to remote nodes which causes
		// undesired overhead. When get() triggers a RemoteExpirationCommand executed in async executor
		// this locks the entry for the duration of RPC, and putFromLoad with ZERO_LOCK_ACQUISITION_TIMEOUT
		// fails as it finds the entry being blocked.
		ExpirationManager expirationManager = cache.getComponentRegistry().getComponent(ExpirationManager.class);
		if ((expirationManager instanceof ClusterExpirationManager)) {
			// re-registering component does not stop the old one
			((ClusterExpirationManager) expirationManager).stop();
			cache.getComponentRegistry().registerComponent(new ExpirationManagerImpl<>(), ExpirationManager.class);
			cache.getComponentRegistry().rewire();
		}
		else if (expirationManager instanceof ExpirationManagerImpl) {
			// do nothing
		}
		else {
			throw new IllegalStateException("Expected clustered expiration manager, found " + expirationManager);
		}
	}

	public long getTombstoneExpiration() {
		return tombstoneExpiration;
	}

	public long getLastRegionInvalidation() {
		return lastRegionInvalidation;
	}

	@Override
	protected void runInvalidation(boolean inTransaction) {
		if (strategy == null) {
			throw new IllegalStateException("Strategy was not set");
		}
		switch (strategy) {
			case NONE:
			case VALIDATION:
				super.runInvalidation(inTransaction);
				return;
			case TOMBSTONES:
				removeEntries(inTransaction, Tombstone.EXCLUDE_TOMBSTONES);
				return;
			case VERSIONED_ENTRIES:
				removeEntries(inTransaction, VersionedEntry.EXCLUDE_EMPTY_EXTRACT_VALUE);
				return;
		}
	}

	private void removeEntries(boolean inTransaction, KeyValueFilter filter) {
		// If the transaction is required, we simply need it -> will create our own
		boolean startedTx = false;
		if ( !inTransaction && requiresTransaction) {
			try {
				tm.begin();
				startedTx = true;
			}
			catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		// We can never use cache.clear() since tombstones must be kept.
		try {
			AdvancedCache localCache = Caches.localCache(cache);
			CloseableIterator it = Caches.entrySet(localCache, Tombstone.EXCLUDE_TOMBSTONES).iterator();
			long now = nextTimestamp();
			try {
				while (it.hasNext()) {
					// Cannot use it.next(); it.remove() due to ISPN-5653
					CacheEntry entry = it.next();
					switch (strategy) {
						case TOMBSTONES:
							localCache.remove(entry.getKey(), entry.getValue());
							break;
						case VERSIONED_ENTRIES:
							localCache.put(entry.getKey(), new VersionedEntry(null, null, now), tombstoneExpiration, TimeUnit.MILLISECONDS);
							break;
					}
				}
			}
			finally {
				it.close();
			}
		}
		finally {
			if (startedTx) {
				try {
					tm.commit();
				}
				catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	@Override
	public Map toMap() {
		if (strategy == null) {
			throw new IllegalStateException("Strategy was not set");
		}
		switch (strategy) {
			case NONE:
			case VALIDATION:
				return super.toMap();
			case TOMBSTONES:
				return Caches.entrySet(Caches.localCache(cache), Tombstone.EXCLUDE_TOMBSTONES).toMap();
			case VERSIONED_ENTRIES:
				return Caches.entrySet(Caches.localCache(cache), VersionedEntry.EXCLUDE_EMPTY_EXTRACT_VALUE, VersionedEntry.EXCLUDE_EMPTY_EXTRACT_VALUE).toMap();
			default:
				throw new IllegalStateException(strategy.toString());
		}
	}

	@Override
	public boolean contains(Object key) {
		if (!checkValid()) {
			return false;
		}
		Object value = cache.get(key);
		if (value instanceof Tombstone) {
			return false;
		}
		if (value instanceof FutureUpdate) {
			return ((FutureUpdate) value).getValue() != null;
		}
		if (value instanceof VersionedEntry) {
			return ((VersionedEntry) value).getValue() != null;
		}
		return value != null;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy