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

org.hibernate.cache.infinispan.access.VersionedCallInterceptor Maven / Gradle / Ivy

Go to download

Integration for Infinispan into Hibernate as a second-level caching service

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.access;

import org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion;
import org.hibernate.cache.infinispan.util.VersionedEntry;
import org.infinispan.AdvancedCache;
import org.infinispan.commands.read.SizeCommand;
import org.infinispan.commands.write.PutKeyValueCommand;
import org.infinispan.commons.util.CloseableIterable;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.entries.MVCCEntry;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.filter.NullValueConverter;
import org.infinispan.interceptors.CallInterceptor;
import org.infinispan.metadata.EmbeddedMetadata;
import org.infinispan.metadata.Metadata;

import java.util.Comparator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Note that this does not implement all commands, only those appropriate for {@link TombstoneAccessDelegate}
 * and {@link org.hibernate.cache.infinispan.impl.BaseTransactionalDataRegion}
 *
 * The behaviour here also breaks notifications, which are not used for 2LC caches.
 *
 * @author Radim Vansa <[email protected]>
 */
public class VersionedCallInterceptor extends CallInterceptor {
	private final Comparator versionComparator;
	private final Metadata expiringMetadata;
	private AdvancedCache cache;
	private Metadata defaultMetadata;

	public VersionedCallInterceptor(BaseTransactionalDataRegion region, Comparator versionComparator) {
		this.versionComparator = versionComparator;
		expiringMetadata = new EmbeddedMetadata.Builder().lifespan(region.getTombstoneExpiration(), TimeUnit.MILLISECONDS).build();
	}

	@Inject
	public void injectDependencies(AdvancedCache cache) {
		this.cache = cache;
	}

	@Start
	public void start() {
		defaultMetadata = new EmbeddedMetadata.Builder()
			.lifespan(cacheConfiguration.expiration().lifespan())
			.maxIdle(cacheConfiguration.expiration().maxIdle()).build();
	}

	@Override
	public Object visitPutKeyValueCommand(InvocationContext ctx, PutKeyValueCommand command) throws Throwable {
		MVCCEntry e = (MVCCEntry) ctx.lookupEntry(command.getKey());
		if (e == null) {
			return null;
		}

		Object oldValue = e.getValue();
		Object oldVersion = null;
		long oldTimestamp = Long.MIN_VALUE;
		if (oldValue instanceof VersionedEntry) {
			oldVersion = ((VersionedEntry) oldValue).getVersion();
			oldTimestamp = ((VersionedEntry) oldValue).getTimestamp();
			oldValue = ((VersionedEntry) oldValue).getValue();
		}
		else if (oldValue instanceof org.hibernate.cache.spi.entry.CacheEntry) {
			oldVersion = ((org.hibernate.cache.spi.entry.CacheEntry) oldValue).getVersion();
		}

		Object newValue = command.getValue();
		Object newVersion;
		long newTimestamp;
		Object actualNewValue = newValue;
		boolean isRemoval = false;
		if (newValue instanceof VersionedEntry) {
			VersionedEntry ve = (VersionedEntry) newValue;
			newVersion = ve.getVersion();
			newTimestamp = ve.getTimestamp();
			if (ve.getValue() == null) {
				isRemoval = true;
			}
			else if (ve.getValue() instanceof org.hibernate.cache.spi.entry.CacheEntry) {
				actualNewValue = ve.getValue();
			}
		}
		else {
			throw new IllegalArgumentException(String.valueOf(newValue));
		}

		if (newVersion == null) {
			// eviction or post-commit removal: we'll store it with given timestamp
			setValue(e, newValue, expiringMetadata);
			return null;
		}
		if (oldVersion == null) {
			assert oldValue == null || oldTimestamp != Long.MIN_VALUE;
			if (newTimestamp <= oldTimestamp) {
				// either putFromLoad or regular update/insert - in either case this update might come
				// when it was evicted/region-invalidated. In both cases, with old timestamp we'll leave
				// the invalid value
				assert oldValue == null;
			}
			else {
				setValue(e, actualNewValue, defaultMetadata);
			}
			return null;
		}
		int compareResult = versionComparator.compare(newVersion, oldVersion);
		if (isRemoval && compareResult >= 0) {
			setValue(e, actualNewValue, expiringMetadata);
		}
		else if (compareResult > 0) {
			setValue(e, actualNewValue, defaultMetadata);
		}
		return null;
	}

	private Object setValue(MVCCEntry e, Object value, Metadata metadata) {
		if (e.isRemoved()) {
			e.setRemoved(false);
			e.setCreated(true);
			e.setValid(true);
		}
		else {
			e.setChanged(true);
		}
		e.setMetadata(metadata);
		return e.setValue(value);
	}

	@Override
	public Object visitSizeCommand(InvocationContext ctx, SizeCommand command) throws Throwable {
		Set flags = command.getFlags();
		int size = 0;
		AdvancedCache decoratedCache = cache.getAdvancedCache();
		if (flags != null) {
			decoratedCache = decoratedCache.withFlags(flags.toArray(new Flag[flags.size()]));
		}
		// In non-transactional caches we don't care about context
		CloseableIterable> iterable = decoratedCache
				.filterEntries(VersionedEntry.EXCLUDE_EMPTY_EXTRACT_VALUE).converter(NullValueConverter.getInstance());
		try {
			for (CacheEntry entry : iterable) {
				if (size++ == Integer.MAX_VALUE) {
					return Integer.MAX_VALUE;
				}
			}
		}
		finally {
			iterable.close();
		}
		return size;
	}
}