/*
* INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa
* Copyright 2013 INESC-ID and/or its affiliates and other
* contributors as indicated by the @author tags. All rights reserved.
* See the copyright.txt in the distribution for a full listing of
* individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3.0 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.infinispan.container.gmu;
import org.infinispan.container.AbstractDataContainer;
import org.infinispan.container.DataContainer;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.container.entries.gmu.InternalGMUNullCacheEntry;
import org.infinispan.container.entries.gmu.InternalGMURemovedCacheEntry;
import org.infinispan.container.versioning.EntryVersion;
import org.infinispan.container.versioning.gmu.GMUCacheEntryVersion;
import org.infinispan.container.versioning.gmu.GMUReadVersion;
import org.infinispan.eviction.EvictionStrategy;
import org.infinispan.eviction.EvictionThreadPolicy;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.transaction.gmu.CommitLog;
import org.infinispan.util.Util;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import static org.infinispan.container.gmu.GMUEntryFactoryImpl.wrap;
import static org.infinispan.transaction.gmu.GMUHelper.convert;
import static org.infinispan.transaction.gmu.GMUHelper.toInternalGMUCacheEntry;
/**
* // TODO: Document this
*
* @author Pedro Ruivo
* @since 5.2
*/
public class GMUDataContainer extends AbstractDataContainer<GMUDataContainer.DataContainerVersionChain> {
private static final Log log = LogFactory.getLog(GMUDataContainer.class);
private CommitLog commitLog;
protected GMUDataContainer(int concurrencyLevel) {
super(concurrencyLevel);
}
protected GMUDataContainer(int concurrencyLevel, int maxEntries, EvictionStrategy strategy, EvictionThreadPolicy policy) {
super(concurrencyLevel, maxEntries, strategy, policy);
}
public static DataContainer boundedDataContainer(int concurrencyLevel, int maxEntries,
EvictionStrategy strategy, EvictionThreadPolicy policy) {
return new GMUDataContainer(concurrencyLevel, maxEntries, strategy, policy);
}
public static DataContainer unBoundedDataContainer(int concurrencyLevel) {
return new GMUDataContainer(concurrencyLevel);
}
@Inject
public void setCommitLog(CommitLog commitLog) {
this.commitLog = commitLog;
}
@Override
public InternalCacheEntry get(Object k, EntryVersion version) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.get(%s,%s)", k, version);
}
InternalCacheEntry entry = peek(k, version);
long now = System.currentTimeMillis();
if (entry.canExpire() && entry.isExpired(now)) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.get(%s,%s) => EXPIRED", k, version);
}
return new InternalGMUNullCacheEntry(toInternalGMUCacheEntry(entry));
}
entry.touch(now);
if (log.isTraceEnabled()) {
log.tracef("DataContainer.get(%s,%s) => %s", k, version, entry);
}
return entry;
}
@Override
public InternalCacheEntry peek(Object k, EntryVersion version) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.peek(%s,%s)", k, version);
}
DataContainerVersionChain chain = entries.get(k);
if (chain == null) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.peek(%s,%s) => NOT_FOUND", k, version);
}
return wrap(k, null, true, version, null, null);
}
VersionEntry<InternalCacheEntry> entry = chain.get(getReadVersion(version));
if (log.isTraceEnabled()) {
log.tracef("DataContainer.peek(%s,%s) => %s", k, version, entry);
}
EntryVersion creationVersion = entry.getEntry() == null ? null : entry.getEntry().getVersion();
return wrap(k, entry.getEntry(), entry.isMostRecent(), version, creationVersion, entry.getNextVersion());
}
@Override
public void put(Object k, Object v, EntryVersion version, long lifespan, long maxIdle) {
if (version == null) {
throw new IllegalArgumentException("Key cannot have null versions!");
}
if (log.isTraceEnabled()) {
log.tracef("DataContainer.put(%s,%s,%s,%s,%s)", k, v, version, lifespan, maxIdle);
}
GMUCacheEntryVersion cacheEntryVersion = assertGMUCacheEntryVersion(version);
DataContainerVersionChain chain = entries.get(k);
if (chain == null) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.put(%s,%s,%s,%s,%s), create new VersionChain", k, v, version, lifespan, maxIdle);
}
chain = new DataContainerVersionChain();
entries.put(k, chain);
}
if (log.isTraceEnabled()) {
log.tracef("DataContainer.put(%s,%s,%s,%s,%s), correct version is %s", k, v, version, lifespan, maxIdle, cacheEntryVersion);
}
chain.add(entryFactory.create(k, v, cacheEntryVersion, lifespan, maxIdle));
if (log.isTraceEnabled()) {
StringBuilder stringBuilder = new StringBuilder();
chain.chainToString(stringBuilder);
log.tracef("Updated chain is %s", stringBuilder);
}
}
@Override
public boolean containsKey(Object k, EntryVersion version) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.containsKey(%s,%s)", k, version);
}
VersionChain chain = entries.get(k);
boolean contains = chain != null && chain.contains(getReadVersion(version));
if (log.isTraceEnabled()) {
log.tracef("DataContainer.containsKey(%s,%s) => %s", k, version, contains);
}
return contains;
}
@Override
public InternalCacheEntry remove(Object k, EntryVersion version) {
if (version == null) {
throw new IllegalArgumentException("Key cannot have null version!");
}
if (log.isTraceEnabled()) {
log.tracef("DataContainer.remove(%s,%s)", k, version);
}
DataContainerVersionChain chain = entries.get(k);
if (chain == null) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.remove(%s,%s) => NOT_FOUND", k, version);
}
return wrap(k, null, true, null, null, null);
}
VersionEntry<InternalCacheEntry> entry = chain.remove(new InternalGMURemovedCacheEntry(k, assertGMUCacheEntryVersion(version)));
if (log.isTraceEnabled()) {
log.tracef("DataContainer.remove(%s,%s) => %s", k, version, entry);
}
return wrap(k, entry.getEntry(), entry.isMostRecent(), null, null, null);
}
@Override
public int size(EntryVersion version) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.size(%s)", version);
}
int size = 0;
for (VersionChain chain : entries.values()) {
if (chain.contains(getReadVersion(version))) {
size++;
}
}
if (log.isTraceEnabled()) {
log.tracef("DataContainer.size(%s) => %s", version, size);
}
return size;
}
@Override
public void clear() {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.clear()");
}
entries.clear();
}
@Override
public void clear(EntryVersion version) {
if (log.isTraceEnabled()) {
log.tracef("DataContainer.clear(%s)", version);
}
for (Object key : entries.keySet()) {
remove(key, version);
}
}
@Override
public void purgeExpired() {
long currentTimeMillis = System.currentTimeMillis();
if (log.isTraceEnabled()) {
log.tracef("DataContainer.purgeExpired(%s)", currentTimeMillis);
}
for (VersionChain chain : entries.values()) {
chain.purgeExpired(currentTimeMillis);
}
}
@Override
public final boolean dumpTo(String filePath) {
BufferedWriter bufferedWriter = Util.getBufferedWriter(filePath);
if (bufferedWriter == null) {
return false;
}
try {
for (Map.Entry<Object, DataContainerVersionChain> entry : entries.entrySet()) {
Util.safeWrite(bufferedWriter, entry.getKey());
Util.safeWrite(bufferedWriter, "=");
entry.getValue().dumpChain(bufferedWriter);
bufferedWriter.newLine();
bufferedWriter.flush();
}
return true;
} catch (IOException e) {
return false;
} finally {
Util.close(bufferedWriter);
}
}
@Override
public final void gc(EntryVersion minimumVersion) {
for (DataContainerVersionChain versionChain : entries.values()) {
versionChain.gc(minimumVersion);
}
}
public final VersionChain<?> getVersionChain(Object key) {
return entries.get(key);
}
public final String stateToString() {
StringBuilder stringBuilder = new StringBuilder(8132);
for (Map.Entry<Object, DataContainerVersionChain> entry : entries.entrySet()) {
stringBuilder.append(entry.getKey())
.append("=");
entry.getValue().chainToString(stringBuilder);
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
@Override
protected Map<Object, InternalCacheEntry> getCacheEntries(Map<Object, DataContainerVersionChain> evicted) {
Map<Object, InternalCacheEntry> evictedMap = new HashMap<Object, InternalCacheEntry>();
for (Map.Entry<Object, DataContainerVersionChain> entry : evicted.entrySet()) {
evictedMap.put(entry.getKey(), entry.getValue().get(null).getEntry());
}
return evictedMap;
}
@Override
protected InternalCacheEntry getCacheEntry(DataContainerVersionChain evicted) {
return evicted.get(null).getEntry();
}
@Override
protected InternalCacheEntry getCacheEntry(DataContainerVersionChain entry, EntryVersion version) {
return entry == null ? null : entry.get(version).getEntry();
}
@Override
protected EntryIterator createEntryIterator(EntryVersion version) {
return new GMUEntryIterator(version, entries.values().iterator());
}
private GMUCacheEntryVersion assertGMUCacheEntryVersion(EntryVersion entryVersion) {
return convert(entryVersion, GMUCacheEntryVersion.class);
}
private GMUReadVersion getReadVersion(EntryVersion entryVersion) {
GMUReadVersion gmuReadVersion = commitLog.getReadVersion(entryVersion);
if (log.isDebugEnabled()) {
log.debugf("getReadVersion(%s) ==> %s", entryVersion, gmuReadVersion);
}
return gmuReadVersion;
}
public static class DataContainerVersionChain extends VersionChain<InternalCacheEntry> {
@Override
protected VersionBody<InternalCacheEntry> newValue(InternalCacheEntry value) {
return new DataContainerVersionBody(value);
}
@Override
protected void writeValue(BufferedWriter writer, InternalCacheEntry value) throws IOException {
writer.write(String.valueOf(value.getValue()));
writer.write("=");
writer.write(String.valueOf(value.getVersion()));
}
}
public static class DataContainerVersionBody extends VersionBody<InternalCacheEntry> {
protected DataContainerVersionBody(InternalCacheEntry value) {
super(value);
}
@Override
public EntryVersion getVersion() {
return getValue().getVersion();
}
@Override
public boolean isOlder(VersionBody<InternalCacheEntry> otherBody) {
return isOlder(getValue().getVersion(), otherBody.getVersion());
}
@Override
public boolean isOlderOrEquals(EntryVersion entryVersion) {
return isOlderOrEquals(getValue().getVersion(), entryVersion);
}
@Override
public boolean isEqual(VersionBody<InternalCacheEntry> otherBody) {
return isEqual(getValue().getVersion(), otherBody.getVersion());
}
@Override
public boolean isRemove() {
return getValue().isRemoved();
}
@Override
public void reincarnate(VersionBody<InternalCacheEntry> other) {
throw new IllegalStateException("This cannot happen");
}
@Override
public VersionBody<InternalCacheEntry> gc(EntryVersion minVersion) {
if (isOlderOrEquals(getValue().getVersion(), minVersion)) {
VersionBody<InternalCacheEntry> previous = getPrevious();
//GC previous entries, removing all the references to the previous version entry
setPrevious(null);
return previous;
} else {
return getPrevious();
}
}
@Override
protected boolean isExpired(long now) {
InternalCacheEntry entry = getValue();
return entry != null && entry.canExpire() && entry.isExpired(now);
}
}
private class GMUEntryIterator extends EntryIterator {
private final EntryVersion version;
private final Iterator<DataContainerVersionChain> iterator;
private InternalCacheEntry next;
private GMUEntryIterator(EntryVersion version, Iterator<DataContainerVersionChain> iterator) {
this.version = version;
this.iterator = iterator;
findNext();
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public InternalCacheEntry next() {
if (next == null) {
throw new NoSuchElementException();
}
InternalCacheEntry toReturn = next;
findNext();
return toReturn;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void findNext() {
next = null;
while (iterator.hasNext()) {
DataContainerVersionChain chain = iterator.next();
next = chain.get(version).getEntry();
if (next != null) {
return;
}
}
}
}
}