/*
* 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.jcache.access;
import java.io.Serializable;
import java.util.Comparator;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.jcache.JCacheMessageLogger;
import org.hibernate.cache.jcache.JCacheTransactionalDataRegion;
import org.hibernate.cache.spi.access.SoftLock;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.jboss.logging.Logger;
/**
* @author Alex Snaps
*/
abstract class AbstractReadWriteRegionAccessStrategy<R extends JCacheTransactionalDataRegion> {
private static final JCacheMessageLogger LOG = Logger.getMessageLogger(
JCacheMessageLogger.class,
AbstractReadWriteRegionAccessStrategy.class.getName()
);
protected final R region;
protected final Comparator versionComparator;
private final UUID uuid = UUID.randomUUID();
private final AtomicLong nextLockId = new AtomicLong();
private final AtomicLong nextItemId = new AtomicLong();
public AbstractReadWriteRegionAccessStrategy(R region) {
this.versionComparator = region.getCacheDataDescription().getVersionComparator();
this.region = region;
}
public R getRegion() {
return region;
}
public Object get(SharedSessionContractImplementor session, Object key, long txTimestamp) throws CacheException {
final Lockable item = (Lockable) region.get( key );
final boolean readable = item != null && item.isReadable( txTimestamp );
if ( readable ) {
return item.getValue();
}
else {
return null;
}
}
public boolean putFromLoad(SharedSessionContractImplementor session, Object key, Object value, long txTimestamp, Object version) throws CacheException {
while (true) {
Lockable item = (Lockable) region.get( key );
if (item == null) {
/*
* If the item is null due a softlock being evicted... then this
* is wrong, the in-doubt soft-lock could get replaced with the
* old value. All that can be done from a JCache perspective is
* to log a warning.
*/
if (region.putIfAbsent( key, new Item( value, version, txTimestamp, nextItemId() ))) {
return true;
}
}
else if (item.isWriteable( txTimestamp, version, versionComparator )) {
if (region.replace( key, item, new Item( value, version, txTimestamp, nextItemId() ))) {
return true;
}
}
else {
return false;
}
}
}
public boolean putFromLoad(SharedSessionContractImplementor session, Object key, Object value, long txTimestamp, Object version, boolean minimalPutOverride)
throws CacheException {
return putFromLoad( session, key, value, txTimestamp, version );
}
public SoftLock lockItem(SharedSessionContractImplementor session, Object key, Object version) throws CacheException {
long timeout = region.nextTimestamp() + region.getTimeout();
while (true) {
Lockable item = (Lockable) region.get( key );
if ( item == null ) {
/*
* What happens here if a previous soft-lock was evicted to make
* this null.
*/
Lock lock = new Lock(timeout, uuid, nextLockId(), version);
if (region.putIfAbsent( key, lock )) {
return lock;
}
}
else {
Lock lock = item.lock( timeout, uuid, nextLockId() );
if (region.replace(key, item, lock)) {
return lock;
}
}
}
}
public void unlockItem(SharedSessionContractImplementor session, Object key, SoftLock lock) throws CacheException {
while (true) {
Lockable item = (Lockable) region.get( key );
if (item != null && item.isUnlockable( lock )) {
if (region.replace(key, item, ((Lock) item ).unlock(region.nextTimestamp()))) {
return;
}
}
else {
handleMissingLock( key, item );
return;
}
}
}
public void remove(SharedSessionContractImplementor session, Object key) throws CacheException {
//this access strategy is asynchronous
}
public void removeAll() throws CacheException {
region.clear();
}
public void evict(Object key) throws CacheException {
region.remove( key );
}
public void evictAll() throws CacheException {
region.clear();
}
public SoftLock lockRegion() throws CacheException {
return null;
}
public void unlockRegion(SoftLock lock) throws CacheException {
region.clear();
}
private long nextLockId() {
return nextLockId.getAndIncrement();
}
protected long nextItemId() {
return nextItemId.getAndIncrement();
}
protected void handleMissingLock(Object key, Lockable lock) {
LOG.missingLock( region, key, lock );
long ts = region.nextTimestamp() + region.getTimeout();
// create new lock that times out immediately
Lock newLock = new Lock( ts, uuid, nextLockId.getAndIncrement(), null ).unlock( ts );
region.put( key, newLock );
}
/**
* Interface type implemented by all wrapper objects in the cache.
*/
protected static interface Lockable {
/**
* Returns <code>true</code> if the enclosed value can be read by a transaction started at the given time.
*/
public boolean isReadable(long txTimestamp);
/**
* Returns <code>true</code> if the enclosed value can be replaced with one of the given version by a
* transaction started at the given time.
*/
public boolean isWriteable(long txTimestamp, Object version, Comparator versionComparator);
/**
* Returns the enclosed value.
*/
public Object getValue();
/**
* Returns <code>true</code> if the given lock can be unlocked using the given SoftLock instance as a handle.
*/
public boolean isUnlockable(SoftLock lock);
/**
* Locks this entry, stamping it with the UUID and lockId given, with the lock timeout occuring at the specified
* time. The returned Lock object can be used to unlock the entry in the future.
*/
public Lock lock(long timeout, UUID uuid, long lockId);
}
/**
* Wrapper type representing unlocked items.
*/
protected static final class Item implements Serializable, Lockable {
private static final long serialVersionUID = 1L;
private final Object value;
private final Object version;
private final long timestamp;
private final long itemId;
/**
* Creates an unlocked item wrapping the given value with a version and creation timestamp.
*/
Item(Object value, Object version, long timestamp, long itemId) {
this.value = value;
this.version = version;
this.timestamp = timestamp;
this.itemId = itemId;
}
@Override
public boolean isReadable(long txTimestamp) {
return txTimestamp > timestamp;
}
@Override
@SuppressWarnings("unchecked")
public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
return version != null && versionComparator.compare( version, newVersion ) < 0;
}
@Override
public Object getValue() {
return value;
}
@Override
public boolean isUnlockable(SoftLock lock) {
return false;
}
@Override
public Lock lock(long timeout, UUID uuid, long lockId) {
return new Lock( timeout, uuid, lockId, version );
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
else if (obj instanceof Item) {
return itemId == ((Item) obj).itemId;
}
else {
return false;
}
}
@Override
public int hashCode() {
return Long.hashCode( itemId );
}
@Override
public String toString() {
return "Item{" +
"value=" + value +
", version=" + version +
", timestamp=" + timestamp +
'}';
}
}
/**
* Wrapper type representing locked items.
*/
public static final class Lock implements Serializable, Lockable, SoftLock {
private static final long serialVersionUID = 2L;
private final UUID sourceUuid;
private final long lockId;
private final Object version;
private final long timeout;
private final boolean concurrent;
private final int multiplicity;
private final long unlockTimestamp;
/**
* Creates a locked item with the given identifiers and object version.
*/
public Lock(long timeout, UUID sourceUuid, long lockId, Object version) {
this(timeout, sourceUuid, lockId, version, 0, 1, false);
}
private Lock(long timeout, UUID sourceUuid, long lockId, Object version,
long unlockTimestamp, int multiplicity, boolean concurrent) {
this.sourceUuid = sourceUuid;
this.lockId = lockId;
this.version = version;
this.timeout = timeout;
this.unlockTimestamp = unlockTimestamp;
this.multiplicity = multiplicity;
this.concurrent = concurrent;
}
@Override
public boolean isReadable(long txTimestamp) {
return false;
}
@Override
@SuppressWarnings({ "SimplifiableIfStatement", "unchecked" })
public boolean isWriteable(long txTimestamp, Object newVersion, Comparator versionComparator) {
if ( txTimestamp > timeout ) {
// if timedout then allow write
return true;
}
if ( multiplicity > 0 ) {
// if still locked then disallow write
return false;
}
return version == null
? txTimestamp > unlockTimestamp
: versionComparator.compare( version, newVersion ) < 0;
}
@Override
public Object getValue() {
return null;
}
@Override
public boolean isUnlockable(SoftLock lock) {
if ( lock == this ) {
return true;
}
else if ( lock instanceof Lock ) {
return (lockId == ((Lock) lock).lockId) && sourceUuid.equals(((Lock) lock).sourceUuid);
}
else {
return false;
}
}
@Override
@SuppressWarnings("SimplifiableIfStatement")
public boolean equals(Object o) {
if ( o == this ) {
return true;
}
else if ( o instanceof Lock ) {
return (lockId == ((Lock)o ).lockId) && sourceUuid.equals( ( (Lock) o ).sourceUuid )
&& (multiplicity == ((Lock) o).multiplicity);
}
else {
return false;
}
}
@Override
public int hashCode() {
final int hash = ( sourceUuid != null ? sourceUuid.hashCode() : 0 );
return hash ^ Long.hashCode( lockId );
}
/**
* Returns true if this Lock has been concurrently locked by more than one transaction.
*/
public boolean wasLockedConcurrently() {
return concurrent;
}
@Override
public Lock lock(long timeout, UUID uuid, long lockId) {
return new Lock( timeout, this.sourceUuid, this.lockId, this.version,
0, this.multiplicity + 1, true );
}
/**
* Unlocks this Lock, and timestamps the unlock event.
*/
public Lock unlock(long timestamp) {
if (multiplicity == 1) {
return new Lock(timeout, sourceUuid, lockId, version,
timestamp, 0, concurrent );
}
else {
return new Lock(timeout, sourceUuid, lockId, version,
0, multiplicity - 1, concurrent );
}
}
@Override
public String toString() {
return "Lock Source-UUID:" + sourceUuid + " Lock-ID:" + lockId;
}
}
}