accumulo-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ctubb...@apache.org
Subject [42/61] [abbrv] [partial] accumulo git commit: ACCUMULO-722 put trunk in my sandbox
Date Thu, 03 Mar 2016 22:00:07 GMT
http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MetadataLocationObtainer.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MetadataLocationObtainer.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MetadataLocationObtainer.java
new file mode 100644
index 0000000..5ec17cd
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MetadataLocationObtainer.java
@@ -0,0 +1,172 @@
+/*
+ * 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.client.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.impl.TabletLocator.TabletLocation;
+import org.apache.accumulo.core.client.impl.TabletLocatorImpl.TabletLocationObtainer;
+import org.apache.accumulo.core.client.impl.TabletServerBatchReaderIterator.ResultReceiver;
+import org.apache.accumulo.core.data.Column;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.KeyExtent;
+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.security.thrift.AuthInfo;
+import org.apache.accumulo.core.tabletserver.thrift.NotServingTabletException;
+import org.apache.accumulo.core.util.MetadataTable;
+import org.apache.accumulo.core.util.OpTimer;
+import org.apache.accumulo.core.util.TextUtil;
+import org.apache.hadoop.io.Text;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+public class MetadataLocationObtainer implements TabletLocationObtainer {
+  private static final Logger log = Logger.getLogger(MetadataLocationObtainer.class);
+  private AuthInfo credentials;
+  private SortedSet<Column> locCols;
+  private ArrayList<Column> columns;
+  private Instance instance;
+  
+  MetadataLocationObtainer(AuthInfo credentials, Instance instance) {
+    
+    this.instance = instance;
+    this.credentials = credentials;
+    
+    locCols = new TreeSet<Column>();
+    locCols.add(new Column(TextUtil.getBytes(Constants.METADATA_CURRENT_LOCATION_COLUMN_FAMILY), null, null));
+    locCols.add(Constants.METADATA_PREV_ROW_COLUMN.toColumn());
+    columns = new ArrayList<Column>(locCols);
+  }
+  
+  @Override
+  public List<TabletLocation> lookupTablet(TabletLocation src, Text row, Text stopRow, TabletLocator parent) throws AccumuloSecurityException,
+      AccumuloException {
+    
+    ArrayList<TabletLocation> list = new ArrayList<TabletLocation>();
+    
+    try {
+      OpTimer opTimer = null;
+      if (log.isTraceEnabled())
+        opTimer = new OpTimer(log, Level.TRACE).start("Looking up in " + src.tablet_extent.getTableId() + " row=" + TextUtil.truncate(row) + "  extent="
+            + src.tablet_extent + " tserver=" + src.tablet_location);
+      
+      Range range = new Range(row, true, stopRow, true);
+      
+      TreeMap<Key,Value> results = new TreeMap<Key,Value>();
+      
+      // System.out.println(range);
+      
+      boolean more = ThriftScanner.getBatchFromServer(credentials, range, src.tablet_extent, src.tablet_location, results, locCols, Constants.SCAN_BATCH_SIZE,
+          Constants.NO_AUTHS, false, instance.getConfiguration());
+      if (more && results.size() == 1) {
+        range = new Range(results.lastKey().followingKey(PartialKey.ROW_COLFAM_COLQUAL_COLVIS_TIME), true, new Key(stopRow).followingKey(PartialKey.ROW), false);
+        more = ThriftScanner.getBatchFromServer(credentials, range, src.tablet_extent, src.tablet_location, results, locCols, Constants.SCAN_BATCH_SIZE,
+            Constants.NO_AUTHS, false, instance.getConfiguration());
+      }
+      
+      if (opTimer != null)
+        opTimer.stop("Got " + results.size() + " results  from " + src.tablet_extent + " in %DURATION%");
+      
+      // System.out.println("results "+results.keySet());
+      
+      SortedMap<KeyExtent,Text> metadata = MetadataTable.getMetadataLocationEntries(results);
+      
+      for (Entry<KeyExtent,Text> entry : metadata.entrySet()) {
+        list.add(new TabletLocation(entry.getKey(), entry.getValue().toString()));
+      }
+      
+    } catch (AccumuloServerException ase) {
+      if (log.isTraceEnabled())
+        log.trace(src.tablet_extent.getTableId() + " lookup failed, " + src.tablet_location + " server side exception");
+      throw ase;
+    } catch (NotServingTabletException e) {
+      if (log.isTraceEnabled())
+        log.trace(src.tablet_extent.getTableId() + " lookup failed, " + src.tablet_location + " not serving " + src.tablet_extent);
+      parent.invalidateCache(src.tablet_extent);
+    } catch (AccumuloException e) {
+      if (log.isTraceEnabled())
+        log.trace(src.tablet_extent.getTableId() + " lookup failed", e);
+      parent.invalidateCache(src.tablet_location);
+    }
+    
+    return list;
+  }
+  
+  @Override
+  public List<TabletLocation> lookupTablets(String tserver, Map<KeyExtent,List<Range>> tabletsRanges, TabletLocator parent) throws AccumuloSecurityException,
+      AccumuloException {
+    
+    final TreeMap<Key,Value> results = new TreeMap<Key,Value>();
+    
+    ArrayList<TabletLocation> list = new ArrayList<TabletLocation>();
+    
+    ResultReceiver rr = new ResultReceiver() {
+      
+      @Override
+      public void receive(List<Entry<Key,Value>> entries) {
+        for (Entry<Key,Value> entry : entries) {
+          results.put(entry.getKey(), entry.getValue());
+        }
+      }
+    };
+    
+    ScannerOptions opts = new ScannerOptions();
+    opts.fetchedColumns = locCols;
+    
+    Map<KeyExtent,List<Range>> unscanned = new HashMap<KeyExtent,List<Range>>();
+    Map<KeyExtent,List<Range>> failures = new HashMap<KeyExtent,List<Range>>();
+    try {
+      TabletServerBatchReaderIterator.doLookup(tserver, tabletsRanges, failures, unscanned, rr, columns, credentials, opts, Constants.NO_AUTHS,
+          instance.getConfiguration());
+      if (failures.size() > 0) {
+        // invalidate extents in parents cache
+        if (log.isTraceEnabled())
+          log.trace("lookupTablets failed for " + failures.size() + " extents");
+        parent.invalidateCache(failures.keySet());
+      }
+    } catch (IOException e) {
+      log.trace("lookupTablets failed server=" + tserver, e);
+      parent.invalidateCache(tserver);
+    } catch (AccumuloServerException e) {
+      log.trace("lookupTablets failed server=" + tserver, e);
+      throw e;
+    }
+    
+    SortedMap<KeyExtent,Text> metadata = MetadataTable.getMetadataLocationEntries(results);
+    
+    for (Entry<KeyExtent,Text> entry : metadata.entrySet()) {
+      list.add(new TabletLocation(entry.getKey(), entry.getValue().toString()));
+    }
+    
+    return list;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MultiTableBatchWriterImpl.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MultiTableBatchWriterImpl.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MultiTableBatchWriterImpl.java
new file mode 100644
index 0000000..567e6bd
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/MultiTableBatchWriterImpl.java
@@ -0,0 +1,130 @@
+/*
+ * 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.client.impl;
+
+import java.util.HashMap;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.BatchWriter;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.MultiTableBatchWriter;
+import org.apache.accumulo.core.client.MutationsRejectedException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.TableOfflineException;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.master.state.tables.TableState;
+import org.apache.accumulo.core.security.thrift.AuthInfo;
+import org.apache.accumulo.core.util.ArgumentChecker;
+import org.apache.log4j.Logger;
+
+public class MultiTableBatchWriterImpl implements MultiTableBatchWriter {
+  static final Logger log = Logger.getLogger(MultiTableBatchWriterImpl.class);
+  private boolean closed;
+  
+  private class TableBatchWriter implements BatchWriter {
+    
+    private String table;
+    
+    TableBatchWriter(String table) {
+      this.table = table;
+    }
+    
+    @Override
+    public void addMutation(Mutation m) throws MutationsRejectedException {
+      ArgumentChecker.notNull(m);
+      bw.addMutation(table, m);
+    }
+    
+    @Override
+    public void addMutations(Iterable<Mutation> iterable) throws MutationsRejectedException {
+      bw.addMutation(table, iterable.iterator());
+    }
+    
+    @Override
+    public void close() {
+      throw new UnsupportedOperationException("Must close all tables, can not close an individual table");
+    }
+    
+    @Override
+    public void flush() {
+      throw new UnsupportedOperationException("Must flush all tables, can not flush an individual table");
+    }
+    
+  }
+  
+  private TabletServerBatchWriter bw;
+  private HashMap<String,BatchWriter> tableWriters;
+  private Instance instance;
+  
+  public MultiTableBatchWriterImpl(Instance instance, AuthInfo credentials, long maxMemory, long maxLatency, int maxWriteThreads) {
+    ArgumentChecker.notNull(instance, credentials);
+    this.instance = instance;
+    this.bw = new TabletServerBatchWriter(instance, credentials, maxMemory, maxLatency, maxWriteThreads);
+    tableWriters = new HashMap<String,BatchWriter>();
+    this.closed = false;
+  }
+  
+  public boolean isClosed() {
+    return this.closed;
+  }
+  
+  public void close() throws MutationsRejectedException {
+    bw.close();
+    this.closed = true;
+  }
+  
+  /**
+   * Warning: do not rely upon finalize to close this class. Finalize is not guaranteed to be called.
+   */
+  @Override
+  protected void finalize() {
+    if (!closed) {
+      log.warn(MultiTableBatchWriterImpl.class.getSimpleName() + " not shutdown; did you forget to call close()?");
+      try {
+        close();
+      } catch (MutationsRejectedException mre) {
+        log.error(MultiTableBatchWriterImpl.class.getSimpleName() + " internal error.", mre);
+        throw new RuntimeException("Exception when closing " + MultiTableBatchWriterImpl.class.getSimpleName(), mre);
+      }
+    }
+  }
+  
+  @Override
+  public synchronized BatchWriter getBatchWriter(String tableName) throws AccumuloException, AccumuloSecurityException, TableNotFoundException {
+    ArgumentChecker.notNull(tableName);
+    String tableId = Tables.getNameToIdMap(instance).get(tableName);
+    if (tableId == null)
+      throw new TableNotFoundException(tableId, tableName, null);
+    
+    if (Tables.getTableState(instance, tableId) == TableState.OFFLINE)
+      throw new TableOfflineException(instance, tableId);
+    
+    BatchWriter tbw = tableWriters.get(tableId);
+    if (tbw == null) {
+      tbw = new TableBatchWriter(tableId);
+      tableWriters.put(tableId, tbw);
+    }
+    return tbw;
+  }
+  
+  @Override
+  public void flush() throws MutationsRejectedException {
+    bw.flush();
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/OfflineScanner.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/OfflineScanner.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/OfflineScanner.java
new file mode 100644
index 0000000..c26024c
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/OfflineScanner.java
@@ -0,0 +1,409 @@
+/**
+ * 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.client.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.Connector;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.RowIterator;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.conf.AccumuloConfiguration;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.data.Column;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.KeyExtent;
+import org.apache.accumulo.core.data.KeyValue;
+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.file.FileOperations;
+import org.apache.accumulo.core.file.FileSKVIterator;
+import org.apache.accumulo.core.file.FileUtil;
+import org.apache.accumulo.core.iterators.IteratorEnvironment;
+import org.apache.accumulo.core.iterators.IteratorUtil;
+import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
+import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
+import org.apache.accumulo.core.iterators.system.ColumnFamilySkippingIterator;
+import org.apache.accumulo.core.iterators.system.ColumnQualifierFilter;
+import org.apache.accumulo.core.iterators.system.DeletingIterator;
+import org.apache.accumulo.core.iterators.system.MultiIterator;
+import org.apache.accumulo.core.iterators.system.VisibilityFilter;
+import org.apache.accumulo.core.master.state.tables.TableState;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.security.ColumnVisibility;
+import org.apache.accumulo.core.security.thrift.AuthInfo;
+import org.apache.accumulo.core.util.ArgumentChecker;
+import org.apache.accumulo.core.util.CachedConfiguration;
+import org.apache.accumulo.core.util.LocalityGroupUtil;
+import org.apache.accumulo.core.util.Pair;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.commons.lang.NotImplementedException;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.io.Text;
+
+class OfflineIterator implements Iterator<Entry<Key,Value>> {
+  
+  static class OfflineIteratorEnvironment implements IteratorEnvironment {
+    @Override
+    public SortedKeyValueIterator<Key,Value> reserveMapFileReader(String mapFileName) throws IOException {
+      throw new NotImplementedException();
+    }
+    
+    @Override
+    public AccumuloConfiguration getConfig() {
+      return AccumuloConfiguration.getDefaultConfiguration();
+    }
+    
+    @Override
+    public IteratorScope getIteratorScope() {
+      return IteratorScope.scan;
+    }
+    
+    @Override
+    public boolean isFullMajorCompaction() {
+      return false;
+    }
+    
+    private ArrayList<SortedKeyValueIterator<Key,Value>> topLevelIterators = new ArrayList<SortedKeyValueIterator<Key,Value>>();
+    
+    @Override
+    public void registerSideChannel(SortedKeyValueIterator<Key,Value> iter) {
+      topLevelIterators.add(iter);
+    }
+    
+    SortedKeyValueIterator<Key,Value> getTopLevelIterator(SortedKeyValueIterator<Key,Value> iter) {
+      if (topLevelIterators.isEmpty())
+        return iter;
+      ArrayList<SortedKeyValueIterator<Key,Value>> allIters = new ArrayList<SortedKeyValueIterator<Key,Value>>(topLevelIterators);
+      allIters.add(iter);
+      return new MultiIterator(allIters, false);
+    }
+  }
+
+  private SortedKeyValueIterator<Key,Value> iter;
+  private Range range;
+  private KeyExtent currentExtent;
+  private Connector conn;
+  private String tableId;
+  private Authorizations authorizations;
+  private Instance instance;
+  private ScannerOptions options;
+  private ArrayList<SortedKeyValueIterator<Key,Value>> readers;
+
+  /**
+   * @param offlineScanner
+   * @param instance
+   * @param credentials
+   * @param authorizations
+   * @param table
+   */
+  public OfflineIterator(ScannerOptions options, Instance instance, AuthInfo credentials, Authorizations authorizations, Text table, Range range) {
+    this.options = new ScannerOptions(options);
+    this.instance = instance;
+    this.range = range;
+    
+    if (this.options.fetchedColumns.size() > 0) {
+      this.range = range.bound(this.options.fetchedColumns.first(), this.options.fetchedColumns.last());
+    }
+
+    this.tableId = table.toString();
+    this.authorizations = authorizations;
+    this.readers = new ArrayList<SortedKeyValueIterator<Key,Value>>();
+    
+    try {
+      conn = instance.getConnector(credentials);
+      nextTablet();
+      
+      while (iter != null && !iter.hasTop())
+        nextTablet();
+
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public boolean hasNext() {
+    return iter != null && iter.hasTop();
+  }
+  
+  @Override
+  public Entry<Key,Value> next() {
+    try {
+      byte[] v = iter.getTopValue().get();
+      // copy just like tablet server does, do this before calling next
+      KeyValue ret = new KeyValue(new Key(iter.getTopKey()), Arrays.copyOf(v, v.length));
+
+      iter.next();
+      
+      while (iter != null && !iter.hasTop())
+        nextTablet();
+      
+      return ret;
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+  
+  /**
+   * @throws TableNotFoundException
+   * @throws IOException
+   * @throws AccumuloException
+   * 
+   */
+  private void nextTablet() throws TableNotFoundException, AccumuloException, IOException {
+    
+    Range nextRange = null;
+    
+    if (currentExtent == null) {
+      Text startRow;
+      
+      if (range.getStartKey() != null)
+        startRow = range.getStartKey().getRow();
+      else
+        startRow = new Text();
+      
+      nextRange = new Range(new KeyExtent(new Text(tableId), startRow, null).getMetadataEntry(), true, null, false);
+    } else {
+      
+      if (currentExtent.getEndRow() == null) {
+        iter = null;
+        return;
+      }
+
+      if (range.afterEndKey(new Key(currentExtent.getEndRow()).followingKey(PartialKey.ROW))) {
+        iter = null;
+        return;
+      }
+
+      nextRange = new Range(currentExtent.getMetadataEntry(), false, null, false);
+    }
+
+    List<String> relFiles = new ArrayList<String>();
+    
+    Pair<KeyExtent,String> eloc = getTabletFiles(nextRange, relFiles);
+
+    while (eloc.getSecond() != null) {
+      if (Tables.getTableState(instance, tableId) != TableState.OFFLINE) {
+        Tables.clearCache(instance);
+        if (Tables.getTableState(instance, tableId) != TableState.OFFLINE) {
+          throw new AccumuloException("Table is online " + tableId + " cannot scan tablet in offline mode " + eloc.getFirst());
+        }
+      }
+      
+      UtilWaitThread.sleep(250);
+      
+      eloc = getTabletFiles(nextRange, relFiles);
+    }
+    
+    KeyExtent extent = eloc.getFirst();
+    
+    if (!extent.getTableId().toString().equals(tableId)) {
+      throw new AccumuloException(" did not find tablets for table " + tableId + " " + extent);
+    }
+
+    if (currentExtent != null && !extent.isPreviousExtent(currentExtent))
+      throw new AccumuloException(" " + currentExtent + " is not previous extent " + extent);
+    
+    String tablesDir = Constants.getTablesDir(instance.getConfiguration());
+    List<String> absFiles = new ArrayList<String>();
+    for (String relPath : relFiles) {
+      if (relPath.startsWith(".."))
+        absFiles.add(tablesDir + relPath.substring(2));
+      else
+        absFiles.add(tablesDir + "/" + tableId + relPath);
+    }
+    
+    iter = createIterator(extent, absFiles);
+    iter.seek(range, LocalityGroupUtil.families(options.fetchedColumns), options.fetchedColumns.size() == 0 ? false : true);
+    currentExtent = extent;
+    
+  }
+  
+  private Pair<KeyExtent,String> getTabletFiles(Range nextRange, List<String> relFiles) throws TableNotFoundException {
+    Scanner scanner = conn.createScanner(Constants.METADATA_TABLE_NAME, Constants.NO_AUTHS);
+    scanner.setBatchSize(100);
+    scanner.setRange(nextRange);
+    
+    RowIterator rowIter = new RowIterator(scanner);
+    Iterator<Entry<Key,Value>> row = rowIter.next();
+    
+    KeyExtent extent = null;
+    String location = null;
+    
+    while (row.hasNext()) {
+      Entry<Key,Value> entry = row.next();
+      Key key = entry.getKey();
+
+      if (key.getColumnFamily().equals(Constants.METADATA_DATAFILE_COLUMN_FAMILY)) {
+        relFiles.add(key.getColumnQualifier().toString());
+      }
+      
+      if (key.getColumnFamily().equals(Constants.METADATA_CURRENT_LOCATION_COLUMN_FAMILY)
+          || key.getColumnFamily().equals(Constants.METADATA_FUTURE_LOCATION_COLUMN_FAMILY)) {
+        location = entry.getValue().toString();
+      }
+      
+      if (Constants.METADATA_PREV_ROW_COLUMN.hasColumns(key)) {
+        extent = new KeyExtent(key.getRow(), entry.getValue());
+      }
+
+    }
+    return new Pair<KeyExtent,String>(extent, location);
+  }
+
+  /**
+   * @param absFiles
+   * @return
+   * @throws AccumuloException
+   * @throws TableNotFoundException
+   * @throws IOException
+   */
+  private SortedKeyValueIterator<Key,Value> createIterator(KeyExtent extent, List<String> absFiles) throws TableNotFoundException, AccumuloException,
+      IOException {
+    
+    // TODO share code w/ tablet
+    AccumuloConfiguration acuTableConf = AccumuloConfiguration.getTableConfiguration(conn, tableId);
+    
+    Configuration conf = CachedConfiguration.getInstance();
+    
+    FileSystem fs = FileUtil.getFileSystem(conf, instance.getConfiguration());
+    
+    for (SortedKeyValueIterator<Key,Value> reader : readers) {
+      ((FileSKVIterator) reader).close();
+    }
+    
+    readers.clear();
+
+    // TODO need to close files
+    for (String file : absFiles) {
+      FileSKVIterator reader = FileOperations.getInstance().openReader(file, false, fs, conf, acuTableConf, null, null);
+      readers.add(reader);
+    }
+    
+    MultiIterator multiIter = new MultiIterator(readers, extent);
+    
+    OfflineIteratorEnvironment iterEnv = new OfflineIteratorEnvironment();
+    
+    DeletingIterator delIter = new DeletingIterator(multiIter, false);
+    
+    ColumnFamilySkippingIterator cfsi = new ColumnFamilySkippingIterator(delIter);
+    
+    ColumnQualifierFilter colFilter = new ColumnQualifierFilter(cfsi, new HashSet<Column>(options.fetchedColumns));
+    
+    byte[] defaultSecurityLabel;
+    
+    ColumnVisibility cv = new ColumnVisibility(acuTableConf.get(Property.TABLE_DEFAULT_SCANTIME_VISIBILITY));
+    defaultSecurityLabel = cv.getExpression();
+    
+    VisibilityFilter visFilter = new VisibilityFilter(colFilter, authorizations, defaultSecurityLabel);
+    
+    return iterEnv.getTopLevelIterator(IteratorUtil.loadIterators(IteratorScope.scan, visFilter, extent, acuTableConf, options.serverSideIteratorList,
+        options.serverSideIteratorOptions, iterEnv, false));
+  }
+
+  @Override
+  public void remove() {
+    throw new UnsupportedOperationException();
+  }
+  
+}
+
+/**
+ * 
+ */
+public class OfflineScanner extends ScannerOptions implements Scanner {
+  
+  private int batchSize;
+  private int timeOut;
+  private Range range;
+  
+  private Instance instance;
+  private AuthInfo credentials;
+  private Authorizations authorizations;
+  private Text tableId;
+  
+  public OfflineScanner(Instance instance, AuthInfo credentials, String tableId, Authorizations authorizations) {
+    ArgumentChecker.notNull(instance, credentials, tableId, authorizations);
+    this.instance = instance;
+    this.credentials = credentials;
+    this.tableId = new Text(tableId);
+    this.range = new Range((Key) null, (Key) null);
+
+    this.authorizations = authorizations;
+    
+    this.batchSize = Constants.SCAN_BATCH_SIZE;
+    this.timeOut = Integer.MAX_VALUE;
+  }
+
+  @Override
+  public void setTimeOut(int timeOut) {
+    this.timeOut = timeOut;
+  }
+  
+  @Override
+  public int getTimeOut() {
+    return timeOut;
+  }
+  
+  @Override
+  public void setRange(Range range) {
+    this.range = range;
+  }
+  
+  @Override
+  public Range getRange() {
+    return range;
+  }
+  
+  @Override
+  public void setBatchSize(int size) {
+    this.batchSize = size;
+  }
+  
+  @Override
+  public int getBatchSize() {
+    return batchSize;
+  }
+  
+  @Override
+  public void enableIsolation() {
+    
+  }
+  
+  @Override
+  public void disableIsolation() {
+    
+  }
+  
+  @Override
+  public Iterator<Entry<Key,Value>> iterator() {
+    return new OfflineIterator(this, instance, credentials, authorizations, tableId, range);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/RootTabletLocator.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/RootTabletLocator.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/RootTabletLocator.java
new file mode 100644
index 0000000..53f9999
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/RootTabletLocator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.client.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.data.KeyExtent;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.hadoop.io.Text;
+
+public class RootTabletLocator extends TabletLocator {
+  
+  private Instance instance;
+  
+  RootTabletLocator(Instance instance) {
+    this.instance = instance;
+  }
+  
+  @Override
+  public void binMutations(List<Mutation> mutations, Map<String,TabletServerMutations> binnedMutations, List<Mutation> failures) throws AccumuloException,
+      AccumuloSecurityException, TableNotFoundException {
+    throw new UnsupportedOperationException();
+  }
+  
+  @Override
+  public List<Range> binRanges(List<Range> ranges, Map<String,Map<KeyExtent,List<Range>>> binnedRanges) throws AccumuloException, AccumuloSecurityException,
+      TableNotFoundException {
+    
+    String rootTabletLocation = instance.getRootTabletLocation();
+    if (rootTabletLocation != null) {
+      for (Range range : ranges) {
+        TabletLocatorImpl.addRange(binnedRanges, rootTabletLocation, Constants.ROOT_TABLET_EXTENT, range);
+      }
+    }
+    return Collections.emptyList();
+  }
+  
+  @Override
+  public void invalidateCache(KeyExtent failedExtent) {}
+  
+  @Override
+  public void invalidateCache(Collection<KeyExtent> keySet) {}
+  
+  @Override
+  public void invalidateCache(String server) {}
+  
+  @Override
+  public void invalidateCache() {}
+  
+  @Override
+  public TabletLocation locateTablet(Text row, boolean skipRow, boolean retry) throws AccumuloException, AccumuloSecurityException, TableNotFoundException {
+    if (skipRow) {
+      row = new Text(row);
+      row.append(new byte[] {0}, 0, 1);
+    }
+    if (!Constants.ROOT_TABLET_EXTENT.contains(row)) {
+      throw new AccumuloException("Tried to locate row out side of root tablet " + row);
+    }
+    String location = instance.getRootTabletLocation();
+    // Always retry when finding the root tablet
+    while (retry && location == null) {
+      UtilWaitThread.sleep(500);
+      location = instance.getRootTabletLocation();
+    }
+    if (location != null)
+      return new TabletLocation(Constants.ROOT_TABLET_EXTENT, location);
+    return null;
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerImpl.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerImpl.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerImpl.java
new file mode 100644
index 0000000..be5f521
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerImpl.java
@@ -0,0 +1,115 @@
+/*
+ * 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.client.impl;
+
+/**
+ * provides scanner functionality
+ * 
+ * "Clients can iterate over multiple column families, and there are several 
+ * mechanisms for limiting the rows, columns, and timestamps traversed by a 
+ * scan. For example, we could restrict [a] scan ... to only produce anchors 
+ * whose columns match [a] regular expression ..., or to only produce 
+ * anchors whose timestamps fall within ten days of the current time."
+ * 
+ */
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.Scanner;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.security.thrift.AuthInfo;
+import org.apache.accumulo.core.util.ArgumentChecker;
+import org.apache.hadoop.io.Text;
+
+public class ScannerImpl extends ScannerOptions implements Scanner {
+  
+  // keep a list of columns over which to scan
+  // keep track of the last thing read
+  // hopefully, we can track all the state in the scanner on the client
+  // and just query for the next highest row from the tablet server
+  
+  private Instance instance;
+  private AuthInfo credentials;
+  private Authorizations authorizations;
+  private Text table;
+  
+  private int size;
+  private int timeOut;
+  
+  private Range range;
+  private boolean isolated = false;
+  
+  public ScannerImpl(Instance instance, AuthInfo credentials, String table, Authorizations authorizations) {
+    ArgumentChecker.notNull(instance, credentials, table, authorizations);
+    this.instance = instance;
+    this.credentials = credentials;
+    this.table = new Text(table);
+    this.range = new Range((Key) null, (Key) null);
+    this.authorizations = authorizations;
+    
+    this.size = Constants.SCAN_BATCH_SIZE;
+    this.timeOut = Integer.MAX_VALUE;
+  }
+  
+  @Override
+  public synchronized void setRange(Range range) {
+    ArgumentChecker.notNull(range);
+    this.range = range;
+  }
+  
+  @Override
+  public synchronized Range getRange() {
+    return range;
+  }
+  
+  @Override
+  public synchronized void setBatchSize(int size) {
+    if (size > 0)
+      this.size = size;
+    else
+      throw new IllegalArgumentException("size must be greater than zero");
+  }
+  
+  @Override
+  public synchronized int getBatchSize() {
+    return size;
+  }
+  
+  /**
+   * Returns an iterator over an accumulo table. This iterator uses the options that are currently set on the scanner for its lifetime. So setting options on a
+   * Scanner object will have no effect on existing iterators.
+   */
+  public synchronized Iterator<Entry<Key,Value>> iterator() {
+    return new ScannerIterator(instance, credentials, table, authorizations, range, size, timeOut, this, isolated);
+  }
+  
+  @Override
+  public synchronized void enableIsolation() {
+    this.isolated = true;
+  }
+  
+  @Override
+  public synchronized void disableIsolation() {
+    this.isolated = false;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerIterator.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerIterator.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerIterator.java
new file mode 100644
index 0000000..76fe0fe
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerIterator.java
@@ -0,0 +1,211 @@
+/*
+ * 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.client.impl;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.TableDeletedException;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.client.TableOfflineException;
+import org.apache.accumulo.core.client.impl.ThriftScanner.ScanState;
+import org.apache.accumulo.core.client.impl.ThriftScanner.ScanTimedOutException;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.KeyValue;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.security.Authorizations;
+import org.apache.accumulo.core.security.thrift.AuthInfo;
+import org.apache.accumulo.core.util.NamingThreadFactory;
+import org.apache.hadoop.io.Text;
+import org.apache.log4j.Logger;
+
+public class ScannerIterator implements Iterator<Entry<Key,Value>> {
+  
+  private static final Logger log = Logger.getLogger(ScannerIterator.class);
+  
+  // scanner options
+  private Text tableName;
+  private int timeOut;
+  
+  // scanner state
+  private Iterator<KeyValue> iter;
+  private ScanState scanState;
+  private AuthInfo credentials;
+  private Instance instance;
+  
+  private ScannerOptions options;
+  
+  private ArrayBlockingQueue<Object> synchQ;
+  
+  private boolean finished = false;
+  
+  private boolean readaheadInProgress;
+  private long batchCount = 0;
+  
+  private static final List<KeyValue> EMPTY_LIST = Collections.emptyList();
+  
+  private static ThreadPoolExecutor readaheadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3l, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
+      new NamingThreadFactory("Accumulo scanner read ahead thread"));
+
+  private class Reader implements Runnable {
+    
+    @Override
+    public void run() {
+      
+      try {
+        while (true) {
+          List<KeyValue> currentBatch = ThriftScanner.scan(instance, credentials, scanState, timeOut, instance.getConfiguration());
+          
+          if (currentBatch == null) {
+            synchQ.add(EMPTY_LIST);
+            return;
+          }
+          
+          if (currentBatch.size() == 0)
+            continue;
+          
+          synchQ.add(currentBatch);
+          return;
+        }
+      } catch (IsolationException e) {
+        synchQ.add(e);
+        log.trace(e, e);
+      } catch (ScanTimedOutException e) {
+        synchQ.add(e);
+        log.trace(e, e);
+      } catch (AccumuloException e) {
+        synchQ.add(e);
+        log.trace(e, e);
+      } catch (AccumuloSecurityException e) {
+        log.trace(e, e);
+        synchQ.add(e);
+      } catch (TableDeletedException e) {
+        log.trace(e, e);
+        synchQ.add(e);
+      } catch (TableOfflineException e) {
+        log.trace(e, e);
+        synchQ.add(e);
+      } catch (TableNotFoundException e) {
+        log.warn(e, e);
+        synchQ.add(e);
+      } catch (Exception e) {
+        log.error(e, e);
+        synchQ.add(e);
+      }
+    }
+    
+  }
+  
+  ScannerIterator(Instance instance, AuthInfo credentials, Text table, Authorizations authorizations, Range range, int size, int timeOut,
+      ScannerOptions options, boolean isolated) {
+    this.instance = instance;
+    this.tableName = new Text(table);
+    this.timeOut = timeOut;
+    this.credentials = credentials;
+    
+    this.options = new ScannerOptions(options);
+    
+    synchQ = new ArrayBlockingQueue<Object>(1);
+    
+    if (this.options.fetchedColumns.size() > 0) {
+      range = range.bound(this.options.fetchedColumns.first(), this.options.fetchedColumns.last());
+    }
+    
+    scanState = new ScanState(credentials, tableName, authorizations, new Range(range), options.fetchedColumns, size, options.serverSideIteratorList,
+        options.serverSideIteratorOptions, isolated);
+    readaheadInProgress = false;
+    iter = null;
+  }
+  
+  private void initiateReadAhead() {
+    readaheadInProgress = true;
+    readaheadPool.execute(new Reader());
+  }
+  
+  @SuppressWarnings("unchecked")
+  public boolean hasNext() {
+    if (finished)
+      return false;
+    
+    if (iter != null && iter.hasNext()) {
+      return true;
+    }
+    
+    // this is done in order to find see if there is another batch to get
+    
+    try {
+      if (!readaheadInProgress) {
+        // no read ahead run, fetch the next batch right now
+        new Reader().run();
+      }
+      
+      Object obj = synchQ.take();
+      
+      if (obj instanceof Exception) {
+        finished = true;
+        if (obj instanceof RuntimeException)
+          throw (RuntimeException) obj;
+        else
+          throw new RuntimeException((Exception) obj);
+      }
+      
+      List<KeyValue> currentBatch = (List<KeyValue>) obj;
+      
+      if (currentBatch.size() == 0) {
+        currentBatch = null;
+        finished = true;
+        return false;
+      }
+      iter = currentBatch.iterator();
+      batchCount++;
+      
+      if (batchCount > 3) {
+        // start a thread to read the next batch
+        initiateReadAhead();
+      }
+      
+    } catch (InterruptedException e1) {
+      throw new RuntimeException(e1);
+    }
+    
+    return true;
+  }
+  
+  public Entry<Key,Value> next() {
+    if (hasNext())
+      return iter.next();
+    throw new NoSuchElementException();
+  }
+  
+  // just here to satisfy the interface
+  // could make this actually delete things from the database
+  public void remove() {
+    throw new UnsupportedOperationException("remove is not supported in Scanner");
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerOptions.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerOptions.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerOptions.java
new file mode 100644
index 0000000..4527dc8
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ScannerOptions.java
@@ -0,0 +1,200 @@
+/*
+ * 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.client.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.accumulo.core.client.IteratorSetting;
+import org.apache.accumulo.core.client.ScannerBase;
+import org.apache.accumulo.core.data.Column;
+import org.apache.accumulo.core.data.Key;
+import org.apache.accumulo.core.data.Value;
+import org.apache.accumulo.core.data.thrift.IterInfo;
+import org.apache.accumulo.core.util.ArgumentChecker;
+import org.apache.accumulo.core.util.TextUtil;
+import org.apache.hadoop.io.Text;
+
+public class ScannerOptions implements ScannerBase {
+  
+  protected List<IterInfo> serverSideIteratorList = Collections.emptyList();
+  protected Map<String,Map<String,String>> serverSideIteratorOptions = Collections.emptyMap();
+  
+  protected SortedSet<Column> fetchedColumns = new TreeSet<Column>();
+  
+  protected int timeOut = Integer.MAX_VALUE;
+
+  private String regexIterName = null;
+  
+  protected ScannerOptions() {}
+  
+  public ScannerOptions(ScannerOptions so) {
+    setOptions(this, so);
+  }
+  
+  /**
+   * Adds server-side scan iterators.
+   * 
+   */
+  @Override
+  public synchronized void addScanIterator(IteratorSetting si) {
+    ArgumentChecker.notNull(si);
+    if (serverSideIteratorList.size() == 0)
+      serverSideIteratorList = new ArrayList<IterInfo>();
+    
+    for (IterInfo ii : serverSideIteratorList) {
+      if (ii.iterName.equals(si.getName()))
+        throw new IllegalArgumentException("Iterator name is already in use " + si.getName());
+      if (ii.getPriority() == si.getPriority())
+        throw new IllegalArgumentException("Iterator priority is already in use " + si.getPriority());
+    }
+    
+    serverSideIteratorList.add(new IterInfo(si.getPriority(), si.getIteratorClass(), si.getName()));
+    
+    if (serverSideIteratorOptions.size() == 0)
+      serverSideIteratorOptions = new HashMap<String,Map<String,String>>();
+    
+    Map<String,String> opts = serverSideIteratorOptions.get(si.getName());
+    
+    if (opts == null) {
+      opts = new HashMap<String,String>();
+      serverSideIteratorOptions.put(si.getName(), opts);
+    }
+    opts.putAll(si.getOptions());
+  }
+  
+  @Override
+  public synchronized void removeScanIterator(String iteratorName) {
+    ArgumentChecker.notNull(iteratorName);
+    // if no iterators are set, we don't have it, so it is already removed
+    if (serverSideIteratorList.size() == 0)
+      return;
+    
+    for (IterInfo ii : serverSideIteratorList) {
+      if (ii.iterName.equals(iteratorName)) {
+        serverSideIteratorList.remove(ii);
+        break;
+      }
+    }
+    
+    serverSideIteratorOptions.remove(iteratorName);
+  }
+    
+  /**
+   * Override any existing options on the given named iterator
+   */
+  @Override
+  public synchronized void updateScanIteratorOption(String iteratorName, String key, String value) {
+    ArgumentChecker.notNull(iteratorName, key, value);
+    if (serverSideIteratorOptions.size() == 0)
+      serverSideIteratorOptions = new HashMap<String,Map<String,String>>();
+    
+    Map<String,String> opts = serverSideIteratorOptions.get(iteratorName);
+    
+    if (opts == null) {
+      opts = new HashMap<String,String>();
+      serverSideIteratorOptions.put(iteratorName, opts);
+    }
+    opts.put(key, value);
+  }
+  
+  /**
+   * Limit a scan to the specified column family. This can limit which locality groups are read on the server side.
+   * 
+   * To fetch multiple column families call this function multiple times.
+   */
+  
+  @Override
+  public synchronized void fetchColumnFamily(Text col) {
+    ArgumentChecker.notNull(col);
+    Column c = new Column(TextUtil.getBytes(col), null, null);
+    fetchedColumns.add(c);
+  }
+  
+  @Override
+  public synchronized void fetchColumn(Text colFam, Text colQual) {
+    ArgumentChecker.notNull(colFam, colQual);
+    Column c = new Column(TextUtil.getBytes(colFam), TextUtil.getBytes(colQual), null);
+    fetchedColumns.add(c);
+  }
+  
+  public synchronized void fetchColumn(Column column) {
+    ArgumentChecker.notNull(column);
+    fetchedColumns.add(column);
+  }
+  
+  @Override
+  public synchronized void clearColumns() {
+    fetchedColumns.clear();
+  }
+  
+  public synchronized SortedSet<Column> getFetchedColumns() {
+    return fetchedColumns;
+  }
+  
+  /**
+   * Clears scan iterators prior to returning a scanner to the pool.
+   */
+  @Override
+  public synchronized void clearScanIterators() {
+    serverSideIteratorList = Collections.emptyList();
+    serverSideIteratorOptions = Collections.emptyMap();
+    regexIterName = null;
+  }
+  
+  protected static void setOptions(ScannerOptions dst, ScannerOptions src) {
+    synchronized (dst) {
+      synchronized (src) {
+        dst.regexIterName = src.regexIterName;
+        dst.fetchedColumns = new TreeSet<Column>(src.fetchedColumns);
+        dst.serverSideIteratorList = new ArrayList<IterInfo>(src.serverSideIteratorList);
+        
+        dst.serverSideIteratorOptions = new HashMap<String,Map<String,String>>();
+        Set<Entry<String,Map<String,String>>> es = src.serverSideIteratorOptions.entrySet();
+        for (Entry<String,Map<String,String>> entry : es)
+          dst.serverSideIteratorOptions.put(entry.getKey(), new HashMap<String,String>(entry.getValue()));
+      }
+    }
+  }
+  
+  @Override
+  public Iterator<Entry<Key,Value>> iterator() {
+    throw new UnsupportedOperationException();
+  }
+  
+  @Override
+  public void setTimeOut(int timeOut) {
+    if (timeOut <= 0) {
+      throw new IllegalArgumentException("TimeOut must be positive : " + timeOut);
+    }
+
+    this.timeOut = timeOut;
+  }
+  
+  @Override
+  public int getTimeOut() {
+    return timeOut;
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ServerClient.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ServerClient.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ServerClient.java
new file mode 100644
index 0000000..81ee594
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/ServerClient.java
@@ -0,0 +1,170 @@
+/*
+ * 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.client.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.impl.thrift.ClientService;
+import org.apache.accumulo.core.client.impl.thrift.ClientService.Client;
+import org.apache.accumulo.core.conf.Property;
+import org.apache.accumulo.core.security.thrift.ThriftSecurityException;
+import org.apache.accumulo.core.util.ArgumentChecker;
+import org.apache.accumulo.core.util.Pair;
+import org.apache.accumulo.core.util.ServerServices;
+import org.apache.accumulo.core.util.ServerServices.Service;
+import org.apache.accumulo.core.util.ThriftUtil;
+import org.apache.accumulo.core.util.UtilWaitThread;
+import org.apache.accumulo.core.zookeeper.ZooUtil;
+import org.apache.accumulo.fate.zookeeper.ZooCache;
+import org.apache.log4j.Logger;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+
+public class ServerClient {
+  private static final Logger log = Logger.getLogger(ServerClient.class);
+  private static final Map<String,ZooCache> zooCaches = new HashMap<String,ZooCache>();
+  
+  private synchronized static ZooCache getZooCache(Instance instance) {
+    ZooCache result = zooCaches.get(instance.getZooKeepers());
+    if (result == null) {
+      result = new ZooCache(instance.getZooKeepers(), instance.getZooKeepersSessionTimeOut(), null);
+      zooCaches.put(instance.getZooKeepers(), result);
+    }
+    return result;
+  }
+  
+  public static <T> T execute(Instance instance, ClientExecReturn<T,ClientService.Client> exec) throws AccumuloException, AccumuloSecurityException {
+    try {
+      return executeRaw(instance, exec);
+    } catch (ThriftSecurityException e) {
+      throw new AccumuloSecurityException(e.user, e.code, e);
+    } catch (AccumuloException e) {
+      throw e;
+    } catch (Exception e) {
+      throw new AccumuloException(e);
+    }
+  }
+  
+  public static void execute(Instance instance, ClientExec<ClientService.Client> exec) throws AccumuloException, AccumuloSecurityException {
+    try {
+      executeRaw(instance, exec);
+    } catch (ThriftSecurityException e) {
+      throw new AccumuloSecurityException(e.user, e.code, e);
+    } catch (AccumuloException e) {
+      throw e;
+    } catch (Exception e) {
+      throw new AccumuloException(e);
+    }
+  }
+  
+  public static <T> T executeRaw(Instance instance, ClientExecReturn<T,ClientService.Client> exec) throws Exception {
+    while (true) {
+      ClientService.Client client = null;
+      String server = null;
+      try {
+        Pair<String,Client> pair = ServerClient.getConnection(instance);
+        server = pair.getFirst();
+        client = pair.getSecond();
+        return exec.execute(client);
+      } catch (TTransportException tte) {
+        log.debug("ClientService request failed " + server + ", retrying ... ", tte);
+        UtilWaitThread.sleep(100);
+      } finally {
+        if (client != null)
+          ServerClient.close(client);
+      }
+    }
+  }
+  
+  public static void executeRaw(Instance instance, ClientExec<ClientService.Client> exec) throws Exception {
+    while (true) {
+      ClientService.Client client = null;
+      String server = null;
+      try {
+        Pair<String,Client> pair = ServerClient.getConnection(instance);
+        server = pair.getFirst();
+        client = pair.getSecond();
+        exec.execute(client);
+        break;
+      } catch (TTransportException tte) {
+        log.debug("ClientService request failed " + server + ", retrying ... ", tte);
+        UtilWaitThread.sleep(100);
+      } finally {
+        if (client != null)
+          ServerClient.close(client);
+      }
+    }
+  }
+  
+  static volatile boolean warnedAboutTServersBeingDown = false;
+
+  public static Pair<String,ClientService.Client> getConnection(Instance instance) throws TTransportException {
+    return getConnection(instance, true);
+  }
+  
+  public static Pair<String,ClientService.Client> getConnection(Instance instance, boolean preferCachedConnections) throws TTransportException {
+    ArgumentChecker.notNull(instance);
+    // create list of servers
+    ArrayList<ThriftTransportKey> servers = new ArrayList<ThriftTransportKey>();
+    
+    // add tservers
+    
+    ZooCache zc = getZooCache(instance);
+    
+    for (String tserver : zc.getChildren(ZooUtil.getRoot(instance) + Constants.ZTSERVERS)) {
+      String path = ZooUtil.getRoot(instance) + Constants.ZTSERVERS + "/" + tserver;
+      byte[] data = ZooUtil.getLockData(zc, path);
+      if (data != null && !new String(data).equals("master"))
+        servers.add(new ThriftTransportKey(new ServerServices(new String(data)).getAddressString(Service.TSERV_CLIENT), instance.getConfiguration().getPort(
+            Property.TSERV_CLIENTPORT), instance.getConfiguration().getTimeInMillis(Property.GENERAL_RPC_TIMEOUT)));
+    }
+    
+    boolean opened = false;
+    try {
+      Pair<String,TTransport> pair = ThriftTransportPool.getInstance().getAnyTransport(servers, preferCachedConnections);
+      ClientService.Client client = ThriftUtil.createClient(new ClientService.Client.Factory(), pair.getSecond());
+      opened = true;
+      warnedAboutTServersBeingDown = false;
+      return new Pair<String,ClientService.Client>(pair.getFirst(), client);
+    } finally {
+      if (!opened) {
+        if (!warnedAboutTServersBeingDown) {
+          if (servers.isEmpty()) {
+            log.warn("There are no tablet servers: check that zookeeper and accumulo are running.");
+          } else {
+            log.warn("Failed to find an available server in the list of servers: " + servers);
+          }
+          warnedAboutTServersBeingDown = true;
+        }
+      }
+    }
+  }
+  
+  public static void close(ClientService.Client client) {
+    if (client != null && client.getInputProtocol() != null && client.getInputProtocol().getTransport() != null) {
+      ThriftTransportPool.getInstance().returnTransport(client.getInputProtocol().getTransport());
+    } else {
+      log.debug("Attempt to close null connection to a server", new Exception());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/Tables.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/Tables.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/Tables.java
new file mode 100644
index 0000000..71518c5
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/Tables.java
@@ -0,0 +1,114 @@
+/*
+ * 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.client.impl;
+
+import java.security.SecurityPermission;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.master.state.tables.TableState;
+import org.apache.accumulo.core.zookeeper.ZooUtil;
+import org.apache.accumulo.fate.zookeeper.ZooCache;
+
+public class Tables {
+  private static SecurityPermission TABLES_PERMISSION = new SecurityPermission("tablesPermission");
+  
+  private static ZooCache getZooCache(Instance instance) {
+    SecurityManager sm = System.getSecurityManager();
+    if (sm != null) {
+      sm.checkPermission(TABLES_PERMISSION);
+    }
+    return ZooCache.getInstance(instance.getZooKeepers(), instance.getZooKeepersSessionTimeOut());
+  }
+  
+  private static SortedMap<String,String> getMap(Instance instance, boolean nameAsKey) {
+    ZooCache zc = getZooCache(instance);
+    
+    List<String> tableIds = zc.getChildren(ZooUtil.getRoot(instance) + Constants.ZTABLES);
+    
+    TreeMap<String,String> tableMap = new TreeMap<String,String>();
+    
+    for (String tableId : tableIds) {
+      byte[] tblPath = zc.get(ZooUtil.getRoot(instance) + Constants.ZTABLES + "/" + tableId + Constants.ZTABLE_NAME);
+      if (tblPath != null) {
+        if (nameAsKey)
+          tableMap.put(new String(tblPath), tableId);
+        else
+          tableMap.put(tableId, new String(tblPath));
+      }
+    }
+    
+    return tableMap;
+  }
+  
+  public static String getTableId(Instance instance, String tableName) throws TableNotFoundException {
+    String tableId = getNameToIdMap(instance).get(tableName);
+    if (tableId == null)
+      throw new TableNotFoundException(tableId, tableName, null);
+    return tableId;
+  }
+  
+  public static String getTableName(Instance instance, String tableId) throws TableNotFoundException {
+    String tableName = getIdToNameMap(instance).get(tableId);
+    if (tableName == null)
+      throw new TableNotFoundException(tableId, tableName, null);
+    return tableName;
+  }
+  
+  public static SortedMap<String,String> getNameToIdMap(Instance instance) {
+    return getMap(instance, true);
+  }
+  
+  public static SortedMap<String,String> getIdToNameMap(Instance instance) {
+    return getMap(instance, false);
+  }
+  
+  public static boolean exists(Instance instance, String tableId) {
+    ZooCache zc = getZooCache(instance);
+    List<String> tableIds = zc.getChildren(ZooUtil.getRoot(instance) + Constants.ZTABLES);
+    return tableIds.contains(tableId);
+  }
+  
+  public static void clearCache(Instance instance) {
+    getZooCache(instance).clear(ZooUtil.getRoot(instance) + Constants.ZTABLES);
+  }
+  
+  public static String getPrintableTableNameFromId(Map<String,String> tidToNameMap, String tableId) {
+    String tableName = tidToNameMap.get(tableId);
+    return tableName == null ? "(ID:" + tableId + ")" : tableName;
+  }
+  
+  public static String getPrintableTableIdFromName(Map<String,String> nameToIdMap, String tableName) {
+    String tableId = nameToIdMap.get(tableName);
+    return tableId == null ? "(NAME:" + tableName + ")" : tableId;
+  }
+  
+  public static TableState getTableState(Instance instance, String tableId) {
+    String statePath = ZooUtil.getRoot(instance) + Constants.ZTABLES + "/" + tableId + Constants.ZTABLE_STATE;
+    ZooCache zc = getZooCache(instance);
+    byte[] state = zc.get(statePath);
+    if (state == null)
+      return TableState.UNKNOWN;
+    
+    return TableState.valueOf(new String(state));
+  }
+}

http://git-wip-us.apache.org/repos/asf/accumulo/blob/7bdbfccb/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/TabletLocator.java
----------------------------------------------------------------------
diff --git a/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/TabletLocator.java b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/TabletLocator.java
new file mode 100644
index 0000000..e65172b
--- /dev/null
+++ b/1.5/core/src/main/java/org/apache/accumulo/core/client/impl/TabletLocator.java
@@ -0,0 +1,206 @@
+/*
+ * 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.client.impl;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.accumulo.core.Constants;
+import org.apache.accumulo.core.client.AccumuloException;
+import org.apache.accumulo.core.client.AccumuloSecurityException;
+import org.apache.accumulo.core.client.Instance;
+import org.apache.accumulo.core.client.TableNotFoundException;
+import org.apache.accumulo.core.data.KeyExtent;
+import org.apache.accumulo.core.data.Mutation;
+import org.apache.accumulo.core.data.Range;
+import org.apache.accumulo.core.security.thrift.AuthInfo;
+import org.apache.accumulo.core.util.ArgumentChecker;
+import org.apache.hadoop.io.Text;
+
+public abstract class TabletLocator {
+  
+  public abstract TabletLocation locateTablet(Text row, boolean skipRow, boolean retry) throws AccumuloException, AccumuloSecurityException,
+      TableNotFoundException;
+  
+  public abstract void binMutations(List<Mutation> mutations, Map<String,TabletServerMutations> binnedMutations, List<Mutation> failures)
+      throws AccumuloException, AccumuloSecurityException, TableNotFoundException;
+  
+  public abstract List<Range> binRanges(List<Range> ranges, Map<String,Map<KeyExtent,List<Range>>> binnedRanges) throws AccumuloException,
+      AccumuloSecurityException, TableNotFoundException;
+  
+  public abstract void invalidateCache(KeyExtent failedExtent);
+  
+  public abstract void invalidateCache(Collection<KeyExtent> keySet);
+  
+  /**
+   * Invalidate entire cache
+   */
+  public abstract void invalidateCache();
+  
+  /**
+   * Invalidate all metadata entries that point to server
+   */
+  public abstract void invalidateCache(String server);
+  
+  private static class LocatorKey {
+    String instanceId;
+    Text tableName;
+    
+    LocatorKey(String instanceId, Text table) {
+      this.instanceId = instanceId;
+      this.tableName = table;
+    }
+    
+    @Override
+    public int hashCode() {
+      return instanceId.hashCode() + tableName.hashCode();
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof LocatorKey)
+        return equals((LocatorKey) o);
+      return false;
+    }
+    
+    public boolean equals(LocatorKey lk) {
+      return instanceId.equals(lk.instanceId) && tableName.equals(lk.tableName);
+    }
+    
+  }
+  
+  private static HashMap<LocatorKey,TabletLocator> locators = new HashMap<LocatorKey,TabletLocator>();
+  
+  private static final Text ROOT_TABLET_MDE = KeyExtent.getMetadataEntry(new Text(Constants.METADATA_TABLE_ID), null);
+  
+  public static synchronized TabletLocator getInstance(Instance instance, AuthInfo credentials, Text tableId) {
+    LocatorKey key = new LocatorKey(instance.getInstanceID(), tableId);
+    
+    TabletLocator tl = locators.get(key);
+    
+    if (tl == null) {
+      MetadataLocationObtainer mlo = new MetadataLocationObtainer(credentials, instance);
+      
+      if (tableId.toString().equals(Constants.METADATA_TABLE_ID)) {
+        RootTabletLocator rootTabletLocator = new RootTabletLocator(instance);
+        tl = new TabletLocatorImpl(new Text(Constants.METADATA_TABLE_ID), rootTabletLocator, mlo) {
+          public TabletLocation _locateTablet(Text row, boolean skipRow, boolean retry, boolean lock) throws AccumuloException, AccumuloSecurityException,
+              TableNotFoundException {
+            // add a special case for the root tablet itself to the cache of information in the root tablet
+            int comparison_result = row.compareTo(ROOT_TABLET_MDE);
+            
+            if ((skipRow && comparison_result < 0) || (!skipRow && comparison_result <= 0)) {
+              return parent.locateTablet(row, skipRow, retry);
+            }
+            
+            return super._locateTablet(row, skipRow, retry, lock);
+          }
+        };
+      } else {
+        TabletLocator rootTabletCache = getInstance(instance, credentials, new Text(Constants.METADATA_TABLE_ID));
+        tl = new TabletLocatorImpl(tableId, rootTabletCache, mlo);
+      }
+      
+      locators.put(key, tl);
+    }
+    
+    return tl;
+  }
+  
+  public static class TabletLocation implements Comparable<TabletLocation> {
+    private static WeakHashMap<String,WeakReference<String>> tabletLocs = new WeakHashMap<String,WeakReference<String>>();
+    
+    private static String dedupeLocation(String tabletLoc) {
+      synchronized (tabletLocs) {
+        WeakReference<String> lref = tabletLocs.get(tabletLoc);
+        if (lref != null) {
+          String loc = lref.get();
+          if (loc != null) {
+            return loc;
+          }
+        }
+        
+        tabletLoc = new String(tabletLoc);
+        tabletLocs.put(tabletLoc, new WeakReference<String>(tabletLoc));
+        return tabletLoc;
+      }
+    }
+    
+    public final KeyExtent tablet_extent;
+    public final String tablet_location;
+    
+    public TabletLocation(KeyExtent tablet_extent, String tablet_location) {
+      ArgumentChecker.notNull(tablet_extent, tablet_location);
+      this.tablet_extent = tablet_extent;
+      this.tablet_location = dedupeLocation(tablet_location);
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof TabletLocation) {
+        TabletLocation otl = (TabletLocation) o;
+        return tablet_extent.equals(otl.tablet_extent) && tablet_location.equals(otl.tablet_location);
+      }
+      return false;
+    }
+    
+    @Override
+    public int hashCode() {
+      throw new UnsupportedOperationException("hashcode is not implemented for class " + this.getClass().toString());
+    }
+    
+    @Override
+    public String toString() {
+      return "(" + tablet_extent + "," + tablet_location + ")";
+    }
+    
+    @Override
+    public int compareTo(TabletLocation o) {
+      int result = tablet_extent.compareTo(o.tablet_extent);
+      if (result == 0)
+        result = tablet_location.compareTo(o.tablet_location);
+      return result;
+    }
+  }
+  
+  public static class TabletServerMutations {
+    private Map<KeyExtent,List<Mutation>> mutations;
+    
+    public TabletServerMutations() {
+      mutations = new HashMap<KeyExtent,List<Mutation>>();
+    }
+    
+    public void addMutation(KeyExtent ke, Mutation m) {
+      List<Mutation> mutList = mutations.get(ke);
+      if (mutList == null) {
+        mutList = new ArrayList<Mutation>();
+        mutations.put(ke, mutList);
+      }
+      
+      mutList.add(m);
+    }
+    
+    public Map<KeyExtent,List<Mutation>> getMutations() {
+      return mutations;
+    }
+  }
+}


Mime
View raw message