/* * 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 <http://www.gnu.org/licenses/lgpl-2.1.html>. */ 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 <rvansa@redhat.com> */ public class VersionedCallInterceptor extends CallInterceptor { private final Comparator<Object> versionComparator; private final Metadata expiringMetadata; private AdvancedCache cache; private Metadata defaultMetadata; public VersionedCallInterceptor(BaseTransactionalDataRegion region, Comparator<Object> 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<Flag> 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<CacheEntry<Object, Void>> iterable = decoratedCache .filterEntries(VersionedEntry.EXCLUDE_EMPTY_EXTRACT_VALUE).converter(NullValueConverter.getInstance()); try { for (CacheEntry<Object, Void> entry : iterable) { if (size++ == Integer.MAX_VALUE) { return Integer.MAX_VALUE; } } } finally { iterable.close(); } return size; } }