com.hazelcast.cache.impl.AbstractClusterWideIterator Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.cache.impl;
import com.hazelcast.cache.ICache;
import com.hazelcast.nio.serialization.Data;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.cache.Cache;
/**
* {@link AbstractClusterWideIterator} provides the core iterator functionality shared by its descendants.
*
*
Hazelcast cluster is made of partitions which holds a slice of all clusters data. Partition count
* never increase or decrease in a cluster. In order to implement an iterator over a partitioned data, we use
* the following parameters.
*
* - To iterate over partitioned data, we use partitionId as the first parameter of this iterator.
* - Each partition may have a lot of entries, so we use a second parameter to track the iteration of the
* partition.
*
*
*
* Iteration steps:
*
* - fetching fixed sized of keys from the current partition defined by partitionId.
* - iteration on fetched keys.
* - get value of each key with {@link #next()} when method is called.
* - when fetched keys are all used by calling {@link #next()}, more keys are fetched from the cluster.
*
* This implementation iterates over partitions and for each partition it iterates over the internal map using the
* internal table index of the map {@link com.hazelcast.util.SampleableConcurrentHashMap}.
*
*
*
Fetching data from cluster:
* Fetching is getting a fixed size of keys from the internal table of records of a partition defined by
* partitionId. Table index is also provided as a table index locator. Fetch response is the keys and
* last table index. The last table index is included in the result to be used in the next fetch.
*
*
*
Notes:
*
* - Iterator fetches keys in batch with a fixed size that is configurable.
* - Fetched keys are cached in the iterator to be used in each iteration step.
* - {@link #hasNext()} may return true for a key already removed.
* - {@link #hasNext()} only return false when all known keys are fetched and iterated.
* - {@link #next()} may return null although cache never has null value. This may happen when, for example,
* someone removes the entry after the current thread has checked with {@link #hasNext()}.
* - This implementation does not affected by value updates as each value is got from the cluster
* when {@link #next()} called.
*
*
*
* @param the type of key.
* @param the type of value.
* @see com.hazelcast.cache.impl.CacheRecordStore#fetchKeys(int tableIndex, int size)
* @see com.hazelcast.cache.impl.ClusterWideIterator
* @see CacheKeyIterationResult
*/
public abstract class AbstractClusterWideIterator
implements Iterator> {
protected static final int DEFAULT_FETCH_SIZE = 100;
protected ICache cache;
protected List result;
protected final int partitionCount;
protected int partitionIndex = -1;
/**
* The table is segment table of hash map, which is an array that stores the actual records.
* This field is used to mark where the latest entry is read.
*
* The iteration will start from highest index available to the table.
* It will be converted to array size on the server side.
*/
protected int lastTableIndex = Integer.MAX_VALUE;
protected final int fetchSize;
protected boolean prefetchValues;
protected int index;
protected int currentIndex = -1;
public AbstractClusterWideIterator(ICache cache, int partitionCount, int fetchSize, boolean prefetchValues) {
this.cache = cache;
this.partitionCount = partitionCount;
this.fetchSize = fetchSize;
this.prefetchValues = prefetchValues;
}
@Override
public boolean hasNext() {
ensureOpen();
if (result != null && index < result.size()) {
return true;
}
return advance();
}
@Override
public Cache.Entry next() {
while (hasNext()) {
currentIndex = index;
index++;
final Data keyData = getKey(currentIndex);
final K key = toObject(keyData);
final V value = getValue(currentIndex, key);
// Value might be removed or evicted
if (value != null) {
return new CacheEntry(key, value);
}
}
throw new NoSuchElementException();
}
@Override
public void remove() {
ensureOpen();
if (result == null || currentIndex < 0) {
throw new IllegalStateException("Iterator.next() must be called before remove()!");
}
Data keyData = getKey(currentIndex);
final K key = toObject(keyData);
cache.remove(key);
currentIndex = -1;
}
protected boolean advance() {
while (partitionIndex < getPartitionCount()) {
if (result == null || result.size() < fetchSize || lastTableIndex < 0) {
partitionIndex++;
lastTableIndex = Integer.MAX_VALUE;
result = null;
if (partitionIndex == getPartitionCount()) {
return false;
}
}
result = fetch();
if (result != null && result.size() > 0) {
index = 0;
return true;
}
}
return false;
}
private Data getKey(int index) {
if (result != null) {
if (prefetchValues) {
Map.Entry entry = (Map.Entry) result.get(index);
return entry.getKey();
} else {
return (Data) result.get(index);
}
}
return null;
}
private V getValue(int index, K key) {
if (result != null) {
if (prefetchValues) {
Map.Entry entry = (Map.Entry) result.get(index);
return (V) toObject(entry.getValue());
} else {
return cache.get(key);
}
}
return null;
}
protected void ensureOpen() {
if (cache.isClosed()) {
throw new IllegalStateException("Cache operations can not be performed. The cache closed");
}
}
protected void setLastTableIndex(List response, int lastTableIndex) {
if (response != null && response.size() > 0) {
this.lastTableIndex = lastTableIndex;
}
}
protected int getPartitionCount() {
return partitionCount;
}
protected abstract List fetch();
protected abstract Data toData(Object obj);
protected abstract T toObject(Object data);
}