package org.infinispan.container.entries;
import static org.infinispan.commons.util.Util.toStr;
import static org.infinispan.container.entries.ReadCommittedEntry.Flags.CHANGED;
import static org.infinispan.container.entries.ReadCommittedEntry.Flags.CREATED;
import static org.infinispan.container.entries.ReadCommittedEntry.Flags.EVICTED;
import static org.infinispan.container.entries.ReadCommittedEntry.Flags.EXPIRED;
import static org.infinispan.container.entries.ReadCommittedEntry.Flags.REMOVED;
import static org.infinispan.container.entries.ReadCommittedEntry.Flags.VALID;
import org.infinispan.atomic.impl.AtomicHashMap;
import org.infinispan.commons.util.Util;
import org.infinispan.container.DataContainer;
import org.infinispan.metadata.Metadata;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* A wrapper around a cached entry that encapsulates read committed semantics when writes are initiated, committed or
* rolled back.
*
* @author Manik Surtani (<a href="mailto:manik@jboss.org">manik@jboss.org</a>)
* @since 4.0
*/
public class ReadCommittedEntry implements MVCCEntry {
private static final Log log = LogFactory.getLog(ReadCommittedEntry.class);
private static final boolean trace = log.isTraceEnabled();
protected Object key;
protected Object value;
protected long created, lastUsed;
protected byte flags = 0;
protected Metadata metadata;
public ReadCommittedEntry(Object key, Object value, Metadata metadata) {
setValid(true);
this.key = key;
this.value = value;
this.metadata = metadata;
}
// if this or any MVCC entry implementation ever needs to store a boolean, always use a flag instead. This is far
// more space-efficient. Note that this value will be stored in a byte, which means up to 8 flags can be stored in
// a single byte. Always start shifting with 0, the last shift cannot be greater than 7.
protected enum Flags {
CHANGED(1),
CREATED(1 << 1),
REMOVED(1 << 2),
VALID(1 << 3),
EVICTED(1 << 4),
EXPIRED(1 << 5),
SKIP_LOOKUP(1 << 6),
READ(1 << 7),
;
final byte mask;
Flags(int mask) {
this.mask = (byte) mask;
}
}
/**
* Tests whether a flag is set.
*
* @param flag flag to test
* @return true if set, false otherwise.
*/
final boolean isFlagSet(Flags flag) {
return (flags & flag.mask) != 0;
}
/**
* Utility method that sets the value of the given flag to true.
*
* @param flag flag to set
*/
protected final void setFlag(Flags flag) {
flags |= flag.mask;
}
/**
* Utility method that sets the value of the flag to false.
*
* @param flag flag to unset
*/
private void unsetFlag(Flags flag) {
flags &= ~flag.mask;
}
@Override
public final long getLifespan() {
return metadata.lifespan();
}
@Override
public final long getMaxIdle() {
return metadata.maxIdle();
}
@Override
public final Object getKey() {
return key;
}
@Override
public final Object getValue() {
return value;
}
@Override
public boolean isNull() {
return value == null;
}
@Override
public final void commit(DataContainer container, Metadata providedMetadata) {
// TODO: No tombstones for now!! I'll only need them for an eventually consistent cache
// only do stuff if there are changes.
if (isChanged()) {
if (trace)
log.tracef("Updating entry (key=%s removed=%s valid=%s changed=%s created=%s value=%s metadata=%s, providedMetadata=%s)",
toStr(getKey()), isRemoved(), isValid(), isChanged(), isCreated(), toStr(value), getMetadata(), providedMetadata);
// Ugh!
if (value instanceof AtomicHashMap) {
AtomicHashMap<?, ?> ahm = (AtomicHashMap<?, ?>) value;
// Removing commit() call should not be an issue.
// If marshalling is needed (clustering, or cache store), calling
// delta() will clear the delta, avoiding leaking values in delta.
// For local caches, using atomic hash maps does not make sense,
// so leaking delta values is not so important.
if (isRemoved() && !isEvicted()) ahm.markRemoved(true);
}
if (isEvicted()) {
container.evict(key);
} else if (isRemoved()) {
container.remove(key);
} else if (value != null) {
// Can't just rely on the entry's metadata because it could have
// been modified by the interceptor chain (i.e. new version
// generated if none provided by the user)
container.put(key, value, providedMetadata == null ? metadata : providedMetadata);
}
}
}
@Override
public final boolean isChanged() {
return isFlagSet(CHANGED);
}
@Override
public final void setChanged(boolean changed) {
setFlag(changed, CHANGED);
}
@Override
public void setSkipLookup(boolean skipLookup) {
//no-op
}
@Override
public boolean skipLookup() {
//in read committed, it can read from the data container / remote source multiple times.
return false;
}
@Override
public long getCreated() {
return created;
}
@Override
public long getLastUsed() {
return lastUsed;
}
@Override
public Object setValue(Object value) {
Object prev = this.value;
this.value = value;
return prev;
}
@Override
public boolean isValid() {
return isFlagSet(VALID);
}
@Override
public final void setValid(boolean valid) {
setFlag(valid, VALID);
}
@Override
public Metadata getMetadata() {
return metadata;
}
@Override
public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}
@Override
public final boolean isCreated() {
return isFlagSet(CREATED);
}
@Override
public final void setCreated(boolean created) {
setFlag(created, CREATED);
}
@Override
public boolean isRemoved() {
return isFlagSet(REMOVED);
}
@Override
public boolean isEvicted() {
return isFlagSet(EVICTED);
}
@Override
public boolean isExpired() {
return isFlagSet(EXPIRED);
}
@Override
public void resetCurrentValue() {
// noop, the entry is removed from context
}
@Override
public void updatePreviousValue() {
// noop, the previous value is not stored
}
@Override
public final void setRemoved(boolean removed) {
setFlag(removed, REMOVED);
}
@Override
public void setEvicted(boolean evicted) {
setFlag(evicted, EVICTED);
}
@Override
public void setExpired(boolean expired) {
setFlag(expired, EXPIRED);
}
@Override
@Deprecated
public boolean isLoaded() {
return false;
}
@Override
@Deprecated
public void setLoaded(boolean loaded) {
}
final void setFlag(boolean enable, Flags flag) {
if (enable)
setFlag(flag);
else
unsetFlag(flag);
}
@Override
public ReadCommittedEntry clone() {
try {
return (ReadCommittedEntry) super.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
@Override
public void setCreated(long created) {
this.created = created;
}
@Override
public void setLastUsed(long lastUsed) {
this.lastUsed = lastUsed;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + Util.hexIdHashCode(this) + "){" +
"key=" + toStr(key) +
", value=" + toStr(value) +
", isCreated=" + isCreated() +
", isChanged=" + isChanged() +
", isRemoved=" + isRemoved() +
", isValid=" + isValid() +
", isExpired=" + isExpired() +
", skipLookup=" + skipLookup() +
", metadata=" + metadata +
'}';
}
}