/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.accumulo.core.iterators.system; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.PartialKey; import org.apache.accumulo.core.data.Range; import org.apache.accumulo.core.data.Value; import org.apache.accumulo.core.iterators.IteratorEnvironment; import org.apache.accumulo.core.iterators.SortedKeyValueIterator; /** * A SortedKeyValueIterator which presents a view over some section of data, regardless of whether or not it is backed by memory (InMemoryMap) or an RFile * (InMemoryMap that was minor compacted to a file). Clients reading from a table that has data in memory should not see interruption in their scan when that * data is minor compacted. This iterator is designed to manage this behind the scene. */ public class SourceSwitchingIterator implements InterruptibleIterator { public interface DataSource { boolean isCurrent(); DataSource getNewDataSource(); DataSource getDeepCopyDataSource(IteratorEnvironment env); SortedKeyValueIterator<Key,Value> iterator() throws IOException; void setInterruptFlag(AtomicBoolean flag); } private DataSource source; private SortedKeyValueIterator<Key,Value> iter; private Key key; private Value val; private Range range; private boolean inclusive; private Collection<ByteSequence> columnFamilies; private boolean onlySwitchAfterRow; // Synchronization on copies synchronizes operations across all deep copies of this instance. // // This implementation assumes that there is one thread reading data (a scan) from all deep copies // and that another thread may call switch at any point. A single scan may have multiple deep // copies of this iterator if other iterators above this one duplicate their source. For example, // if an IntersectingIterator over two columns was configured, `copies` would contain two SSIs // instead of just one SSI. The two instances in `copies` would both be at the same "level" // in the tree of iterators for the scan. If multiple instances of SSI are configure in the iterator // tree (e.g. priority 8 and priority 12), each instance would share their own `copies` e.g. // SSI@priority8:copies1[...], SSI@priority12:copies2[...] private final List<SourceSwitchingIterator> copies; private SourceSwitchingIterator(DataSource source, boolean onlySwitchAfterRow, List<SourceSwitchingIterator> copies) { this.source = source; this.onlySwitchAfterRow = onlySwitchAfterRow; this.copies = copies; copies.add(this); } public SourceSwitchingIterator(DataSource source, boolean onlySwitchAfterRow) { this(source, onlySwitchAfterRow, new ArrayList<SourceSwitchingIterator>()); } public SourceSwitchingIterator(DataSource source) { this(source, false); } @Override public SortedKeyValueIterator<Key,Value> deepCopy(IteratorEnvironment env) { synchronized (copies) { return new SourceSwitchingIterator(source.getDeepCopyDataSource(env), onlySwitchAfterRow, copies); } } @Override public Key getTopKey() { return key; } @Override public Value getTopValue() { return val; } @Override public boolean hasTop() { return key != null; } @Override public void init(SortedKeyValueIterator<Key,Value> source, Map<String,String> options, IteratorEnvironment env) throws IOException { throw new UnsupportedOperationException(); } @Override public void next() throws IOException { synchronized (copies) { readNext(false); } } private void readNext(boolean initialSeek) throws IOException { // check of initialSeek second is intentional so that it does not short // circuit the call to switchSource boolean seekNeeded = (!onlySwitchAfterRow && switchSource()) || initialSeek; if (seekNeeded) if (initialSeek) iter.seek(range, columnFamilies, inclusive); else iter.seek(new Range(key, false, range.getEndKey(), range.isEndKeyInclusive()), columnFamilies, inclusive); else { iter.next(); if (onlySwitchAfterRow && iter.hasTop() && !source.isCurrent() && !key.getRowData().equals(iter.getTopKey().getRowData())) { switchSource(); iter.seek(new Range(key.followingKey(PartialKey.ROW), true, range.getEndKey(), range.isEndKeyInclusive()), columnFamilies, inclusive); } } if (iter.hasTop()) { Key nextKey = iter.getTopKey(); Value nextVal = iter.getTopValue(); try { key = (Key) nextKey.clone(); } catch (CloneNotSupportedException e) { throw new IOException(e); } val = nextVal; } else { key = null; val = null; } } private boolean switchSource() throws IOException { if (!source.isCurrent()) { source = source.getNewDataSource(); iter = source.iterator(); return true; } return false; } @Override public void seek(Range range, Collection<ByteSequence> columnFamilies, boolean inclusive) throws IOException { synchronized (copies) { this.range = range; this.inclusive = inclusive; this.columnFamilies = columnFamilies; if (iter == null) iter = source.iterator(); readNext(true); } } private void _switchNow() throws IOException { if (onlySwitchAfterRow) throw new IllegalStateException("Can only switch on row boundries"); if (switchSource()) { if (key != null) { iter.seek(new Range(key, true, range.getEndKey(), range.isEndKeyInclusive()), columnFamilies, inclusive); } } } public void switchNow() throws IOException { synchronized (copies) { for (SourceSwitchingIterator ssi : copies) ssi._switchNow(); } } @Override public void setInterruptFlag(AtomicBoolean flag) { synchronized (copies) { if (copies.size() != 1) throw new IllegalStateException("setInterruptFlag() called after deep copies made " + copies.size()); if (iter != null) ((InterruptibleIterator) iter).setInterruptFlag(flag); source.setInterruptFlag(flag); } } }